NCS: Writing I2S data to SD card

Hi everybody,

I'm using the nRF Connect SDK v2.4.99 and an nRF5340 DK to write data received via I2S to the SD Card. 

My application is based on the SDK's I2S echo sample. Both, the I2S sample and the code for writing to the SD Card are working independently. However, when I combine both into one application, my I2S code fails after very short time with the error message: "Failed to allocate next RX buffer: -12" ("i2s_nrfx.c" in line 205).

I was doing some debugging but I couldn't figure out the problem. I'm posting my main.c below. Since I do not want to use any I2S TX device or buffer, I removed all corresponding code from the I2S echo sample. I would appreciate if anybody could point me towards the bug in my code.

#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/i2s.h>
#include <zephyr/drivers/gpio.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/storage/disk_access.h>
#include <zephyr/logging/log.h>
#include <zephyr/fs/fs.h>
#include <ff.h>
#include <stdio.h>

LOG_MODULE_REGISTER(main);

/*------------------------------------
 *------------I2S SETTINGS------------
 *------------------------------------
 */ 
#if DT_NODE_EXISTS(DT_NODELABEL(i2s_rxtx))
#define I2S_RX_NODE  DT_NODELABEL(i2s_rxtx)
#define I2S_TX_NODE  I2S_RX_NODE
#else
#define I2S_RX_NODE  DT_NODELABEL(i2s_rx)
// #define I2S_TX_NODE  DT_NODELABEL(i2s_tx)
#endif

#define SAMPLE_FREQUENCY    44100
#define SAMPLE_BIT_WIDTH    32
#define BYTES_PER_SAMPLE    sizeof(int32_t) //4bytes
#define NUMBER_OF_CHANNELS  1
#define SAMPLES_PER_BLOCK   ((SAMPLE_FREQUENCY / 4) * NUMBER_OF_CHANNELS) 
#define INITIAL_BLOCKS      2
#define TIMEOUT 1

#define BLOCK_SIZE  (BYTES_PER_SAMPLE * SAMPLES_PER_BLOCK)
#define BLOCK_COUNT (INITIAL_BLOCKS + 2)
K_MEM_SLAB_DEFINE_STATIC(mem_slab, BLOCK_SIZE, BLOCK_COUNT, 4);

/*----------------------------------------
 *----------BUTTONS/ISR CONFIG------------
 *----------------------------------------
 */ 
#define SW1_NODE        DT_ALIAS(sw1)
#if DT_NODE_HAS_STATUS(SW1_NODE, okay)
static struct gpio_dt_spec sw1_spec = GPIO_DT_SPEC_GET(SW1_NODE, gpios);
#endif


static K_SEM_DEFINE(toggle_transfer, 1, 1); //Sera la variable que activara o no el semaforo, segun el boton sw1


#if DT_NODE_HAS_STATUS(SW1_NODE, okay)
static void sw1_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	k_sem_give(&toggle_transfer); //Si se apreta el boton, la variable "se activa"
}
#endif

static bool init_interruptions(void)
{
	int ret;

#if DT_NODE_HAS_STATUS(SW1_NODE, okay)
	static struct gpio_callback sw1_cb_data;

	if (!device_is_ready(sw1_spec.port)) {
		printk("%s is not ready\n", sw1_spec.port->name);
		return false;
	}

	ret = gpio_pin_configure_dt(&sw1_spec, GPIO_INPUT);
	if (ret < 0) {
		printk("Failed to configure %s pin %d: %d\n",
		       sw1_spec.port->name, sw1_spec.pin, ret);
		return false;
	}

	ret = gpio_pin_interrupt_configure_dt(&sw1_spec, GPIO_INT_EDGE_TO_ACTIVE);
	if (ret < 0) {
		printk("Failed to configure interrupt on %s pin %d: %d\n",
		       sw1_spec.port->name, sw1_spec.pin, ret);
		return false;
	}

	gpio_init_callback(&sw1_cb_data, sw1_handler, BIT(sw1_spec.pin));
	gpio_add_callback(sw1_spec.port, &sw1_cb_data);
	printk("Press \"%s\" to stop/restart I2S streams\n", sw1_spec.port->name);
#endif
	(void)ret;
	return true;
}

/* ----------------------------------------------------------------------------
* ----------------------------------- SD CARD ---------------------------------
* -----------------------------------------------------------------------------
*/
#define DISK_DRIVE_NAME "SD"
#define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":"
char file_name[16];
static const char *disk_mount_pt = DISK_MOUNT_PT;

static FATFS fat_fs;
static struct fs_mount_t mp = {
	.type = FS_FATFS,
	.fs_data = &fat_fs,
};

bool SD_iniciar(void){
	static const char *disk_pdrv = DISK_DRIVE_NAME;
		uint64_t memory_size_mb;
		uint32_t block_count;
		uint32_t block_size;

		if (disk_access_init(disk_pdrv) != 0) {
			LOG_ERR("Storage init ERROR!");
			return false;
		}

		if (disk_access_ioctl(disk_pdrv, DISK_IOCTL_GET_SECTOR_COUNT, &block_count)) {
			LOG_ERR("Unable to get sector count");
			return false;
		}
		LOG_INF("Block count %u", block_count);

		if (disk_access_ioctl(disk_pdrv,
			DISK_IOCTL_GET_SECTOR_SIZE, &block_size)) {
			LOG_ERR("Unable to get sector size");
			return false;
		}
		printk("Sector size %u\n", block_size);

		memory_size_mb = (uint64_t)block_count * block_size;
		printk("Memory Size(MB) %u\n", (uint32_t)(memory_size_mb >> 20));
		return true;
}

int SD_lsdir(const char *path){
//Pre: Absolute path to list
//Post: -5 on error, listed entries on success. Retorna int num de wavs

	int ret;
	struct fs_dir_t dir;
	static struct fs_dirent entry;
	int count = 0;
	uint8_t exst_wavs = 0;

	fs_dir_t_init(&dir);

	/* Verify fs_opendir() */
	ret = fs_opendir(&dir, path);
	if (ret) {
		printk("Error opening dir %s [%d]\n", path, ret);
		return ret;
	}

	printk("\nListing dir %s ...\n", path);
	while(true) {
		/* Verify fs_readdir() */
		ret = fs_readdir(&dir, &entry);
		/* entry.name[0] == 0 means end-of-dir */
		if (ret || entry.name[0] == 0) {
			break;
		}
		if (entry.type == FS_DIR_ENTRY_DIR) {
			printk("[DIR ] %s\n", entry.name);
		} else {
			printk("[FILE] %s (size = %zu)\n",
				entry.name, entry.size);
		}
		if(strstr(entry.name,".WAV") != NULL) exst_wavs++;
		count++;
	}
	printk("Hay %d archivos .WAV", exst_wavs);
	printk(" en un total de %d archivos.\n", count);
	/* Verify fs_closedir() */
	fs_closedir(&dir);
	if (ret == 0) {
		ret = exst_wavs;
	}
	return ret;
}

int SD_WriteTest(void)
{
	//#define BUF_SIZE 16128
	#define BUF_SIZE 100 //Para ASCII
	int16_t writebuff[BUF_SIZE];
	int ret;
	struct fs_file_t file;
	fs_file_t_init(&file);
    for (int i = 0; i < BUF_SIZE - 1; i++)
    {
        writebuff[i] = 'A' + (i % 26); // Escribe el abecedario
    }
	writebuff[BUF_SIZE - 1] = '\0'; // El texto debe tener terminación nula
	LOG_INF("Opening file path");
	ret = fs_open(&file, "/SD:/HOLA.txt", FS_O_CREATE | FS_O_WRITE);
	if (ret) {
		LOG_ERR("Error opening file [%d]", ret);
		return 0;
	}
	LOG_INF("Done opening file path");
	if (fs_write(&file, writebuff, BUF_SIZE*sizeof(int16_t)) != BUF_SIZE*sizeof(int16_t))
	{
		LOG_ERR("failed to write buff");
		fs_close(&file);
		return 0;
	}
	LOG_INF("Done writing");
	fs_close(&file);
	return 1;
}

static void SD_ejecutar(void){
		if(SD_iniciar()){
			mp.mnt_point = disk_mount_pt;
			int ret = fs_mount(&mp);
			if (ret == FR_OK) {
				printk("Disk mounted.\n");
				if (SD_lsdir(disk_mount_pt) == 0) {
					printk("Creando test files.\n");
					ret = SD_WriteTest();
					if (!ret){
						LOG_ERR("failed to write");
					}
					printk("Showing again...\n");
					SD_lsdir(disk_mount_pt);
				}
			}	
		else {
			printk("Error mounting disk.\n");
		}
	}
}


/* ----------------------------------------------------------------------------
* --------------------------------- I2S AUDIO ---------------------------------
* -----------------------------------------------------------------------------
*/
static bool configure_streams(const struct device *i2s_dev_rx,
			    //   const struct device *i2s_dev_tx,
			      const struct i2s_config *config)
{
	int ret;

	ret = i2s_configure(i2s_dev_rx, I2S_DIR_RX, config);
	if (ret < 0) {
		printk("Failed to configure RX stream: %d\n", ret);
		return false;
	}

	return true;
}

static bool prepare_transfer(const struct device *i2s_dev_rx, const struct device *i2s_dev_tx)
{
	int ret;

	for (int i = 0; i < INITIAL_BLOCKS; ++i) {
		void *mem_block;

		ret = k_mem_slab_alloc(&mem_slab, &mem_block, K_NO_WAIT);
		if (ret < 0) {
			printk("Failed to allocate TX block %d: %d\n", i, ret);
			return false;
		}
		memset(mem_block, 0, BLOCK_SIZE);
		ret = i2s_write(i2s_dev_tx, mem_block, BLOCK_SIZE);
		if (ret < 0) {
			printk("Failed to write block %d: %d\n", i, ret);
			return false;
		}
	}

	return true;
}

static bool trigger_command(const struct device *i2s_dev_rx, enum i2s_trigger_cmd cmd)
{
	int ret;

	ret = i2s_trigger(i2s_dev_rx, I2S_DIR_RX, cmd);
	if (ret < 0) {
		printk("Failed to trigger command %d on RX: %d\n", cmd, ret);
		return false;
	}

	return true;
}

/* ----------------------------------------------------------------------------
* ----------------------------------- MAIN ------------------------------------
* -----------------------------------------------------------------------------
*/
int main(void)
{
	const struct device *const i2s_dev_rx = DEVICE_DT_GET(I2S_RX_NODE);
	// const struct device *const i2s_dev_tx = DEVICE_DT_GET(I2S_TX_NODE);
	struct i2s_config config;
	printk("SD_I2S program\n");
	SD_ejecutar();
	if (!device_is_ready(i2s_dev_rx)) {
		printk("%s is not ready\n", i2s_dev_rx->name);
		return 0;
	}

	config.word_size = SAMPLE_BIT_WIDTH;
	config.channels = NUMBER_OF_CHANNELS;
	config.format = I2S_FMT_DATA_FORMAT_I2S;
	config.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER;
	config.frame_clk_freq = SAMPLE_FREQUENCY;
	config.mem_slab = &mem_slab;
	config.block_size = BLOCK_SIZE;
	config.timeout = TIMEOUT; 
	if (!configure_streams(i2s_dev_rx, &config)) {
		return 0;
	}
	int i = 1; //Para la generacion de archivos
	struct fs_file_t file;
	for (;;) {
		k_sem_take(&toggle_transfer, K_FOREVER); //Solo avanza si la variable toggle se activa (cuando se toca el boton)
		fs_file_t_init(&file);
		LOG_INF("Opening file path");
		char filename[30];
		sprintf(&filename, "/SD:/audio%d.txt", i); //Para que sea nombre archivo iterativo
		int ret = fs_open(&file, filename, FS_O_CREATE | FS_O_WRITE);
		if (ret) {
			LOG_ERR("Error opening file [%d]", ret);
			return 0;
		}

		printk("Streams started\n");
		if (!init_interruptions()) {
			return 0;
		}

		if (!trigger_command(i2s_dev_rx, I2S_TRIGGER_START)) {
			return 0;
		}
		while (k_sem_take(&toggle_transfer, K_NO_WAIT) != 0) { //Solo en el caso que se active la variable sale del bucle, PERO NO ESPERA a que sea activado
			void *mem_block;
			size_t block_size;
			ret = i2s_read(i2s_dev_rx, &mem_block, &block_size);
			if (ret < 0) {
				printk("Failed to read data: %d\n", ret);
				return 0;
			}

			ret = fs_write(&file, mem_block, block_size);
			if (ret < 0) {
				printk("Failed to write data in sd card: %d\n", ret);
				break;
			}
		}
		if (!trigger_command(i2s_dev_rx, I2S_TRIGGER_DROP)) {
			return 0;
		}
		printk("Stream stopped\n");

		printk("File named \"audio%d.txt\" successfully created\n", i);		
		fs_close(&file);
		i++;//contador
	}
}

  • Hi,

    I understand the problem now. I've been taking a look on what you suggested but I don't know how to free the memory if it's not with the i2s_write(). How can I do it?

    Thank you

  • Comment out the memory allocation code aswell if you are commenting out i2s_write

  • Hi,

    I commented the parts you told me but many errors appeared, this is the output:

    *** Booting Zephyr OS build v3.3.99-ncs1 ***
    SD_I2S program
    [00:00:00.510,681] <inf> main: Block count 62333952
    Sector size 512
    Memory Size(MB) 30436
    Disk mounted.
    
    Listing dir /SD: ...
    [DIR ] SYSTEM~1
    [FILE] AUDIO1.WAV (size = 196652)
    1 AUDIOxx.WAV files were found out of 1 files.
    [00:00:00.516,693] <inf> i2s_nrfx: I2S MCK frequency: 2822400, actual PCM rate: 44100
    Press "gpio@842500" to start/stop I2S streams
    [00:00:04.388,763] <inf> main: Opening file path
    New file created: /SD:/audio2.wav
    [00:00:04.399,963] <err> os: ***** MPU FAULT *****
    [00:00:04.399,963] <err> os:   Data Access Violation
    [00:00:04.399,993] <err> os:   MMFAR Address: 0x0
    [00:00:04.399,993] <err> os: r0/a1:  0x00000000  r1/a2:  0x00000000  r2/a3:  0x00008000
    [00:00:04.400,024] <err> os: r3/a4:  0x00000000 r12/ip:  0x00000000 r14/lr:  0x0000e487
    [00:00:04.400,024] <err> os:  xpsr:  0x29000000
    [00:00:04.400,024] <err> os: Faulting instruction address (r15/pc): 0x0000fe06
    [00:00:04.400,054] <err> os: >>> ZEPHYR FATAL ERROR 19: Unknown error on CPU 0
    [00:00:04.400,085] <err> os: Current thread: 0x20000a78 (unknown)
    [00:00:04.469,970] <err> os: Halting system

    The changes I made were:

    -Commenting the i2s_write from the main

    -Commenting part of the prepare transfer function:

    static bool prepare_transfer(const struct device *i2s_dev_tx)
    {
    	int ret;
    	for (int i = 0; i < INITIAL_BLOCKS; ++i) {
    		void *mem_block;
    
    		/*ret = k_mem_slab_alloc(&mem_slab, &mem_block, K_NO_WAIT);
    		if (ret < 0) {
    			printk("Failed to allocate TX block %d: %d\n", i, ret);
    			return false;
    		}*/
    		memset(mem_block, 0, BLOCK_SIZE);
    		/*ret = i2s_write(i2s_dev_tx, mem_block, BLOCK_SIZE);
    		if (ret < 0) {
    			printk("Failed to write block %d: %d\n", i, ret);
    			return false;
    		}*/
    	}
    	return true;
    }

    I also tried to simply erase the prepare function call in the main and appeared "No TX buffer available" error message.

    Listing dir /SD: ...
    [DIR ] SYSTEM~1
    [FILE] AUDIO1.WAV (size = 196652)
    [FILE] AUDIO2.WAV (size = 0)
    2 AUDIOxx.WAV files were found out of 2 files.
    [00:00:00.514,373] <inf> i2s_nrfx: I2S MCK frequency: 2822400, actual PCM rate: 44100
    Press "gpio@842500" to start/stop I2S streams
    [00:00:03.988,006] <inf> main: Opening file path
    New file created: /SD:/audio3.wav
    WAV header set, starting streams
    [00:00:03.999,908] <err> i2s_nrfx: No TX buffer available
    Failed to read data: -11
    [00:00:05.999,664] <inf> main: Streams stopped

    Did I left something else in the code to comment?

    Thanks,
    Agustín

  • You seem to doing a memset (memory erase) on an unallocated memory block. Probably that is the cause? comment memset(mem_block... as well.

    If that does not solve the issue, then we need to debug the MPU_FAULT. 

    The log of the hardfault does not show which thread caused this. Can you recompile your application with CONFIG_THREAD_NAME=y added in your prj.conf?

  • Hi,

    I commented that line as well, I guess we are talking about the prepare function right? If so, then the whole function is already commented, I ended up deleting the call of the function. The "<err> i2s_nrfx: No TX buffer available    Failed to read data: -11" error still persists. 

    I added the config_thread_name in my prj.conf and built it again, but there's no change in the serial monitor...

Related