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
	}
}

Parents
  • I am not able to replicate the issue. Seems like the application is merged OK but I cannot test the functionality of the I2S. Can you give me more details of the SD you are using and the pin connections you have to it?

  • Hi, I am working with him.

    The SD card we are using is SanDisk EDGE 16GB class 10. The connections are:
    SCK->  P1.15
    MISO->P1.14
    MOSI->P1.13
    CS->    P1.12

    Although the problem doesn't appear to be the SD card, since in another code (same card, same pin connections) we could save data in the SD card without any problem.

    Here is the overlay file:

    //I2S
    &pinctrl {
    	i2s0_default_alt: i2s0_default_alt {
    		group1 {
    			psels = <NRF_PSEL(I2S_SCK_M, 0, 7)>,
    				<NRF_PSEL(I2S_LRCK_M, 0, 12)>,
    				<NRF_PSEL(I2S_SDIN, 1, 06)>;
    				//<NRF_PSEL(I2S_SDOUT, 1, 07)>;
    		};
    	};
    };
    
    
    &clock {
    	hfclkaudio-frequency = <11289600>;
    };
    
    i2s_rx: &i2s0 {
    	status = "okay";
    	pinctrl-0 = <&i2s0_default_alt>;
    	pinctrl-names = "default";
    	clock-source = "ACLK";
    };
    
    //SD CARD
    &spi4 {
    	status = "okay";
    	cs-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
    //     sck-pin = <17>;
    //     mosi-pin = <13>;
    //     miso-pin = <14>;
    	sdhc0: sdhc@0 {
    			compatible = "zephyr,sdhc-spi-slot";
    			reg = <0>;
    			status = "okay";
    			mmc{
    				compatible = "zephyr,sdmmc-disk";
    				status = "okay";
    			};
    			label = "SDHC0";
    			spi-max-frequency = <25000000>;
    	};
    };
    /* Because FAT FS needs at least 64kiB partition and default
    * storage_partition is 32kiB for that board, we need to reorgatnize
    * partitions to get at least 64KiB.
    * This overlay removes image slot partitions and strips each of 64kiB,
    * and removes the storage partition to add the additional 2*64kiB to
    * it.
    */
    /delete-node/ &slot0_partition;
    /delete-node/ &slot1_partition;
    /delete-node/ &storage_partition;
    
    &flash0 {
    
    	partitions {
    		compatible = "fixed-partitions";
    		#address-cells = <1>;
    		#size-cells = <1>;
    
    		slot0_partition: partition@c000 {
    			reg = <0x0000C000 0x00066000>;
    		};
    		slot1_partition: partition@72000 {
    			reg = <0x00072000 0x00066000>;
    		};
    
    		storage_partition: partition@d8000 {
    			label = "storage";
    			reg = <0x000d8000 0x00028000>;
    		};
    	};
    };
    
    / {
    	msc_disk0 {
    		status="okay";
    		compatible = "zephyr,flash-disk";
    		partition = <&storage_partition>;
    		disk-name = "SD";
    		/* cache-size == page erase size */
    		cache-size = <4096>;
    	};
    };

  • Hi,
    No my SPI setup is different in the nrf5340.

    SCK-1.15

    MISO-1.14

    MOSI-1.13

    CS-1.12

    I have only defined CS in the overlay file, since the rest were already predefined in the nrf5340_cpuapp_common-pinctrl.dtsi file. Here is how my pinout looks like:

    -

    And this is how I defined the SPI4 in the overlay file.

    &spi4 {
    	status = "okay";
    	cs-gpios = <&gpio1 12 GPIO_ACTIVE_LOW>;
    	sdhc0: sdhc@0 {
    			compatible = "zephyr,sdhc-spi-slot";
    			reg = <0>;
    			status = "okay";
    			mmc{
    				compatible = "zephyr,sdmmc-disk";
    				status = "okay";
    			};
    			label = "SDHC0";
    			spi-max-frequency = <25000000>;
    	};
    };

    I hope I answered your question. 

    Agus

  • Agus, I used your project with the pin numbers you mentioned. Now I have the same devicetree

    And I am able to run your project without any issue when I connect it to a normal SD card.

    Its been running for 30 minutes now without any error.

  • Hi,
    I am facing same issue i am using nrf9160 as a slave device for i2s interface and master sampling frequency in 96khz and word size is 24bit,

    when i try to write all the incoming data into sd card card.i am getting the above error and that mean i am dropping sample.

    i already raised a private ticket and i m still waiting for answer. devzone.nordicsemi.com/.../311176

  • Hi,

    Good to hear that. For me also works as well.

    The problem appears when I try to delete all what is related to the function i2s_write() in my code, the error "Failed to allocate next RX buffer: -12" pops-up. Is there a way to get rid off them since I don't have to use this function for my application?

    Agus

  • Depends on how you are moving I2s_write function. If you are just commenting out i2s_write in the prepare_transfer, then you can see that you are allocating the memory in prepare_transfer->k_mem_slab_alloc which is not freed until the write is cleared. So over time the memory slab is going to be full because you are not freeing the memory anywhere.

Reply Children
  • 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