Zephyr I2S

I have the following example running on an nrf52 dongle:

https://docs.zephyrproject.org/latest/samples/drivers/i2s/echo/README.html

However, when I add the following code

#define I2S_LEDS_FRAME_WORD_SIZE 35

uint32_t m_i2s_led_buffer_tx[I2S_LEDS_FRAME_WORD_SIZE];
memset(m_i2s_led_buffer_tx, 0, sizeof(m_i2s_led_buffer_tx));

int ret;
const struct device *const i2s_dev_tx = DEVICE_DT_GET(I2S_TX_NODE);
ret = i2s_write(i2s_dev_tx, m_i2s_led_buffer_tx, I2S_LEDS_FRAME_WORD_SIZE + 1);

I get the following error:

[00:00:15.802,490] <inf> i2s_nrfx: I2S MCK frequency: 3200000, actual PCM rate: 100000
Streams started
[00:00:15.916,259] <err> i2s_nrfx: This device can only write blocks of 41664 bytes
[00:00:15.967,010] <err> i2s_nrfx: This device can only write blocks of 41664 bytes
[00:00:16.014,465] <err> i2s_nrfx: Next buffers not supplied on time
[00:00:16.017,761] <err> i2s_nrfx: Cannot write in state: 4
[00:00:16.068,389] <err> i2s_nrfx: Cannot write in state: 4
[00:00:16.119,018] <err> i2s_nrfx: Cannot write in state: 4
[00:00:16.169,708] <err> i2s_nrfx: Cannot write in state: 4
[00:00:16.220,336] <err> i2s_nrfx: Cannot write in state: 4
[00:00:16.270,965] <err> i2s_nrfx: Cannot write in state: 4
[00:00:16.321,594] <err> i2s_nrfx: Cannot write in state: 4

It seems like it might be because I'm not using the memory properly? The debugger values for the example mem_block is different than the array m_i2s_led_buffer_tx I'm trying to write to the I2S memory

Any tips on what I'm doing wrong?

Parents
  • Adding some more information here:

    I’m bastardizing the interface because we’re driving a LED controller with very strict timing requirements. This is something I’ve seen quite a few people do online (e..g https://github.com/cheehieu/nrf52-sk6812-led-driver). I am having a block I’ve posted about on the DevZone if you’re interested/experienced: https://devzone.nordicsemi.com/f/nordic-q-a/104790/zephyr-i2s. Just for reference, here are the timing requirements, in case there is indeed a better interface to use:
  • Josh, I am not experienced in this but I am trying to understand the error messages you are getting. Attempting to replicate it. How are you running the I2S sample on a nRF52840 dongle? Atleast the documentation says that you need a coded shield for it. 

    parksj10 said:
    I’m bastardizing the interface because we’re driving a LED controller with very strict timing requirements.

    Do not know how to test that at my end though :( 

  • Thanks for the consideration Susheel!

    As far as I understand, the codec is unnecessary unless you're trying to (re)encode audio data. I'm simply creating a byte stream I'm trying to pipe over the I2S bus (because the bus has the correct timing requirements for the LED driver). 

    As for testing, I think Oscilloscope is how I'm planning on testing signal fidelity once I get it to run (I.e. no need for LEDs).

    For replication, the only changes I've made to the project are in the overlay file and main.c. Here's the current code:

    nrf52840dongle_nrf52840.overlay

    &pinctrl {
        i2s0_default_alt: i2s0_default_alt {
            group1 {
                psels = <NRF_PSEL(I2S_SCK_M, 1, 10)>,
                        <NRF_PSEL(I2S_LRCK_M, 1, 12)>,
                        <NRF_PSEL(I2S_SDOUT, 1, 11)>,
                        <NRF_PSEL(I2S_SDIN, 1, 14)>,
                        <NRF_PSEL(I2S_MCK, 1,15)>;
            };
        };
    };
    
    i2s_rxtx: &i2s0 {
        status = "okay";
        pinctrl-0 = <&i2s0_default_alt>;
        pinctrl-names = "default";
    };

    main.c

    // #include <zephyr/kernel.h>
    // #include <zephyr/drivers/gpio.h>
    // #include <zephyr/device.h>
    
    // /* 1000 msec = 1 sec */
    // #define SLEEP_TIME_MS 1000
    
    // /* The devicetree node identifier for the "led0" alias. */
    // #define LED0_NODE DT_ALIAS(led0)
    
    // // test GPIO voltage pin
    // #define VOLT_PIN 15
    
    // /*
    //  * A build error on this line means your board is unsupported.
    //  * See the sample documentation for information on how to fix this.
    //  */
    // static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
    
    // int main(void)
    // {
    // 	// // get the GPIO device
    // 	// const struct device *dev = device_get_binding("GPIO_0");
    // 	// // configure the LED pin as output
    // 	// gpio_pin_configure(dev, VOLT_PIN, GPIO_OUTPUT_ACTIVE);
    
    // 	int ret;
    // 	int loop_number = 0;
    // 	int blink_number = 0;
    
    // 	if (!gpio_is_ready_dt(&led))
    // 	{
    // 		loop_number++;
    // 		return 0;
    // 	}
    
    // 	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
    // 	if (ret < 0)
    // 	{
    // 		return 0;
    // 	}
    
    // 	while (1)
    // 	{
    // 		ret = gpio_pin_toggle_dt(&led);
    // 		if (ret < 0)
    // 		{
    // 			return 0;
    // 		}
    // 		printk("Blink number %d\n", blink_number);
    // 		blink_number++;
    // 		k_msleep(SLEEP_TIME_MS);
    // 	}
    // 	return 0;
    // }
    
    /*
     * Copyright (c) 2021 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr/kernel.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/drivers/i2s.h>
    #include <zephyr/drivers/gpio.h>
    #include <string.h>
    
    #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 104167 // 3.333333 MHz / 2 channels / 16 bits per sample
    #define SAMPLE_BIT_WIDTH 16
    #define BYTES_PER_SAMPLE sizeof(int16_t)
    #define NUMBER_OF_CHANNELS 2
    /* Such block length provides an echo with the delay of 100 ms. */
    #define SAMPLES_PER_BLOCK ((SAMPLE_FREQUENCY / 10) * NUMBER_OF_CHANNELS)
    #define INITIAL_BLOCKS 2
    #define TIMEOUT 1000
    
    #define NUM_LEDS 9
    #define DATA_BYTES_PER_LED 3 // 24-bit GRB data structure
    #define LEDS_DATA_BYTE_SIZE NUM_LEDS *DATA_BYTES_PER_LED
    
    #define I2S_BITS_PER_DATA_BIT 4
    #define I2S_SK6812_ZERO 0x8							   // 0b'1000 (0.3us; 0.9us)
    #define I2S_SK6812_ONE 0xC							   // 0b'1100 (0.6us; 0.6us)
    #define I2S_WS2812B_ZERO 0x8						   // 0b'1000 (0.4us; 0.85us)
    #define I2S_WS2812B_ONE 0xE							   // 0b'1110 (0.8us; 0.45us)
    #define I2S_BYTES_PER_RESET 256 / 8					   // reset_pulse / period = 80us / 0.3125us = 256 bits
    #define BYTES_TO_WORDS(n_bytes) (((n_bytes) + 3) >> 2) // (>> 2 === divide by 4... and + 3 to round up bytes)
    #define I2S_LEDS_WORD_SIZE BYTES_TO_WORDS(LEDS_DATA_BYTE_SIZE *I2S_BITS_PER_DATA_BIT)
    #define I2S_RESET_WORD_SIZE BYTES_TO_WORDS(I2S_BYTES_PER_RESET) // 8 words
    #define I2S_LEDS_FRAME_WORD_SIZE I2S_LEDS_WORD_SIZE + I2S_RESET_WORD_SIZE
    
    typedef struct
    {
    	uint8_t g;
    	uint8_t r;
    	uint8_t b;
    } sk6812_led_t;
    
    sk6812_led_t m_led_buffer_tx[NUM_LEDS];
    uint32_t m_i2s_led_buffer_tx[I2S_LEDS_FRAME_WORD_SIZE];
    
    /**
     * @brief Clears the LED and I2S data buffers
     */
    nrfx_err_t sk6812_i2s_init_mem()
    {
    	// TODO: dynamically allocate memory when needed instead of using global arrays
    	//    // Init memory for LED data
    	//    if (p_led_buffer_tx) free(p_led_buffer_tx);
    	//    p_led_buffer_tx = (sk6812_led_t *)malloc(LEDS_DATA_BYTE_SIZE);
    	//    if (p_led_buffer_tx) memset(p_led_buffer_tx, 0, LEDS_DATA_BYTE_SIZE);
    	//    else return NRF_ERROR_NO_MEM;
    	//
    	//    // Init memory for I2S data
    	//    if (p_i2s_led_buffer_tx) free(p_i2s_led_buffer_tx);
    	//    p_i2s_led_buffer_tx = (uint32_t *)malloc(I2S_LEDS_WORD_SIZE * 4);
    	//    if (p_i2s_led_buffer_tx) memset(p_i2s_led_buffer_tx, 0, I2S_LEDS_WORD_SIZE * 4);
    	//    else return NRF_ERROR_NO_MEM;
    
    	// Reset data buffers
    	memset(m_led_buffer_tx, 0, sizeof(m_led_buffer_tx));
    	memset(m_i2s_led_buffer_tx, 0, sizeof(m_i2s_led_buffer_tx));
    
    	return NRFX_SUCCESS;
    }
    
    /**
     * @brief Convert a byte of LED data to a word of I2S data
     * @brief 1 data bit   <--> 4 I2S bits
     * @brief 1 data byte  <--> 1 I2S word
     */
    uint32_t convert_byte_to_i2s_bits(uint8_t data_byte)
    {
    	uint32_t data_bits = 0;
    
    	// Set data_bits based on MSB, then left-shift data_byte
    	for (int ii = 0; ii < 8; ii++)
    	{
    		// Extract the current most significant bit (MSB) of data_byte
    		uint8_t msb = (data_byte & 0x80);
    
    		// Determine the appropriate I2S pattern based on the MSB
    		uint32_t i2s_pattern = (msb ? I2S_SK6812_ONE : I2S_SK6812_ZERO);
    
    		// Calculate the shift amount to align the pattern with the current bit position
    		int shift_amount = (8 - 1 - ii) * 4;
    
    		// Left-shift the I2S pattern by the calculated amount
    		i2s_pattern = i2s_pattern << shift_amount;
    
    		// Merge the shifted pattern with data_bits
    		data_bits |= i2s_pattern;
    
    		// prepare for reading next bit
    		data_byte = data_byte << 1;
    	}
    	return data_bits;
    }
    
    /**
     * @brief Sets I2S data from converted LED data
     */
    void set_i2s_led_data()
    {
    	uint16_t jj = 0;
    	for (uint16_t ii = 0; ii < NUM_LEDS; ii++)
    	{
    		m_i2s_led_buffer_tx[jj] = convert_byte_to_i2s_bits(m_led_buffer_tx[ii].g);
    		m_i2s_led_buffer_tx[jj + 1] = convert_byte_to_i2s_bits(m_led_buffer_tx[ii].r);
    		m_i2s_led_buffer_tx[jj + 2] = convert_byte_to_i2s_bits(m_led_buffer_tx[ii].b);
    		jj += 3;
    	}
    }
    
    // TODO: need to change this nrfx code
    /**
     * @brief Initializes I2S and starts the data transfer
     * @brief Assumes data buffers have already been set
     */
    void send_i2s_led_data()
    {
    	int ret;
    	const struct device *const i2s_dev_tx = DEVICE_DT_GET(I2S_TX_NODE);
    	ret = i2s_write(i2s_dev_tx, m_i2s_led_buffer_tx, I2S_LEDS_FRAME_WORD_SIZE + 1);
    
    	// // Configure the I2S module and map IO pins
    	// uint32_t err_code = sk6812_i2s_init();
    
    	// if (ret < 0)
    	// {
    	// 	printk("Failed to write data: %d\n", ret);
    	// 	return 0;
    	// }
    
    	// // Prevent starting a new data transfer if I2S already initialized
    	// if (err_code == NRFX_SUCCESS)
    	// {
    	// 	// Configure TX data buffer
    	// 	nrfx_i2s_buffers_t const initial_buffers = {
    	// 		.p_tx_buffer = m_i2s_led_buffer_tx,
    	// 		.p_rx_buffer = NULL};
    
    	// 	Enable the I2S module and start data streaming
    	// 	err_code = nrf_drv_i2s_start(&initial_buffers, I2S_LEDS_FRAME_WORD_SIZE + 1, 0);
    	// 	APP_ERROR_CHECK(err_code);
    	// }
    	// else
    	// {
    	// 	// Reset to all I2S_SK6812_ZERO's
    	// 	memset(m_i2s_led_buffer_tx, 0x88, I2S_LEDS_WORD_SIZE * 4);
    	// }
    }
    
    /**
     * @brief Set an RGB LED color data in the global sk6812_led_t array
     */
    void set_led_pixel_RGB(uint16_t pos, uint8_t r, uint8_t g, uint8_t b)
    {
    	m_led_buffer_tx[pos].r = r;
    	m_led_buffer_tx[pos].g = g;
    	m_led_buffer_tx[pos].b = b;
    }
    
    #define SW0_NODE DT_ALIAS(sw0)
    #if DT_NODE_HAS_STATUS(SW0_NODE, okay)
    static struct gpio_dt_spec sw0_spec = GPIO_DT_SPEC_GET(SW0_NODE, gpios);
    #endif
    
    #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
    
    #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);
    
    static int16_t echo_block[SAMPLES_PER_BLOCK];
    static volatile bool echo_enabled = true;
    static K_SEM_DEFINE(toggle_transfer, 1, 1);
    
    #if DT_NODE_HAS_STATUS(SW0_NODE, okay)
    static void sw0_handler(const struct device *dev, struct gpio_callback *cb,
    						uint32_t pins)
    {
    	bool enable = !echo_enabled;
    
    	echo_enabled = enable;
    	printk("Echo %sabled\n", (enable ? "en" : "dis"));
    }
    #endif
    
    #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);
    }
    #endif
    
    static bool init_buttons(void)
    {
    	int ret;
    
    #if DT_NODE_HAS_STATUS(SW0_NODE, okay)
    	static struct gpio_callback sw0_cb_data;
    
    	if (!device_is_ready(sw0_spec.port))
    	{
    		printk("%s is not ready\n", sw0_spec.port->name);
    		return false;
    	}
    
    	ret = gpio_pin_configure_dt(&sw0_spec, GPIO_INPUT);
    	if (ret < 0)
    	{
    		printk("Failed to configure %s pin %d: %d\n",
    			   sw0_spec.port->name, sw0_spec.pin, ret);
    		return false;
    	}
    
    	ret = gpio_pin_interrupt_configure_dt(&sw0_spec,
    										  GPIO_INT_EDGE_TO_ACTIVE);
    	if (ret < 0)
    	{
    		printk("Failed to configure interrupt on %s pin %d: %d\n",
    			   sw0_spec.port->name, sw0_spec.pin, ret);
    		return false;
    	}
    
    	gpio_init_callback(&sw0_cb_data, sw0_handler, BIT(sw0_spec.pin));
    	gpio_add_callback(sw0_spec.port, &sw0_cb_data);
    	printk("Press \"%s\" to toggle the echo effect\n", sw0_spec.port->name);
    #endif
    
    #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;
    }
    
    static void process_block_data(void *mem_block, uint32_t number_of_samples)
    {
    	static bool clear_echo_block;
    
    	if (echo_enabled)
    	{
    		for (int i = 0; i < number_of_samples; ++i)
    		{
    			int16_t *sample = &((int16_t *)mem_block)[i];
    			*sample += echo_block[i];
    			echo_block[i] = (*sample) / 2;
    		}
    
    		clear_echo_block = true;
    	}
    	else if (clear_echo_block)
    	{
    		clear_echo_block = false;
    		memset(echo_block, 0, sizeof(echo_block));
    	}
    }
    
    static bool configure_streams(const struct device *i2s_dev_rx,
    							  const struct device *i2s_dev_tx,
    							  const struct i2s_config *config)
    {
    	int ret;
    
    	if (i2s_dev_rx == i2s_dev_tx)
    	{
    		ret = i2s_configure(i2s_dev_rx, I2S_DIR_BOTH, config);
    		if (ret == 0)
    		{
    			return true;
    		}
    		/* -ENOSYS means that the RX and TX streams need to be
    		 * configured separately.
    		 */
    		if (ret != -ENOSYS)
    		{
    			printk("Failed to configure streams: %d\n", ret);
    			return false;
    		}
    	}
    
    	ret = i2s_configure(i2s_dev_rx, I2S_DIR_RX, config);
    	if (ret < 0)
    	{
    		printk("Failed to configure RX stream: %d\n", ret);
    		return false;
    	}
    
    	ret = i2s_configure(i2s_dev_tx, I2S_DIR_TX, config);
    	if (ret < 0)
    	{
    		printk("Failed to configure TX 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,
    							const struct device *i2s_dev_tx,
    							enum i2s_trigger_cmd cmd)
    {
    	int ret;
    
    	if (i2s_dev_rx == i2s_dev_tx)
    	{
    		ret = i2s_trigger(i2s_dev_rx, I2S_DIR_BOTH, cmd);
    		if (ret == 0)
    		{
    			return true;
    		}
    		/* -ENOSYS means that commands for the RX and TX streams need
    		 * to be triggered separately.
    		 */
    		if (ret != -ENOSYS)
    		{
    			printk("Failed to trigger command %d: %d\n", cmd, ret);
    			return false;
    		}
    	}
    
    	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;
    	}
    
    	ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, cmd);
    	if (ret < 0)
    	{
    		printk("Failed to trigger command %d on TX: %d\n", cmd, ret);
    		return false;
    	}
    
    	return true;
    }
    
    int main(void)
    {
    	// TODO: put the IO voltage up to 3.3V
    	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("I2S echo sample\n");
    
    	if (!init_buttons())
    	{
    		return 0;
    	}
    
    	if (!device_is_ready(i2s_dev_rx))
    	{
    		printk("%s is not ready\n", i2s_dev_rx->name);
    		return 0;
    	}
    
    	if (i2s_dev_rx != i2s_dev_tx && !device_is_ready(i2s_dev_tx))
    	{
    		printk("%s is not ready\n", i2s_dev_tx->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, i2s_dev_tx, &config))
    	{
    		return 0;
    	}
    
    	while (true)
    	{
    		k_sem_take(&toggle_transfer, K_FOREVER);
    
    		if (!prepare_transfer(i2s_dev_rx, i2s_dev_tx))
    		{
    			return 0;
    		}
    
    		if (!trigger_command(i2s_dev_rx, i2s_dev_tx,
    							 I2S_TRIGGER_START))
    		{
    			return 0;
    		}
    
    		printk("Streams started\n");
    
    		while (k_sem_take(&toggle_transfer, K_NO_WAIT) != 0)
    		{
    			void *mem_block;
    			uint32_t block_size;
    			int ret;
    
    			// TODO remove block of reading (only writing)
    			ret = i2s_read(i2s_dev_rx, &mem_block, &block_size);
    			if (ret < 0)
    			{
    				printk("Failed to read data: %d\n", ret);
    				break;
    			}
    
    			// TODO remove block of reading (only writing)
    			process_block_data(mem_block, SAMPLES_PER_BLOCK);
    
    			// ret = i2s_write(i2s_dev_tx, mem_block, block_size);
    			// ret = i2s_write(i2s_dev_tx, m_i2s_led_buffer_tx, I2S_LEDS_FRAME_WORD_SIZE + 1);
    			color_wipe(255, 0, 0, 50); // Red
    			if (ret < 0)
    			{
    				printk("Failed to write data: %d\n", ret);
    				break;
    			}
    		}
    
    		if (!trigger_command(i2s_dev_rx, i2s_dev_tx,
    							 I2S_TRIGGER_DROP))
    		{
    			return 0;
    		}
    
    		printk("Streams stopped\n");
    	}
    }
    
    // TODO move to another file, these are the color animations
    /**
     * @brief Ported Adafruit NeoPixel paint animation functions
     */
    // Fill the dots one after the other with a color
    void color_wipe(uint8_t r, uint8_t g, uint8_t b, uint8_t ms_delay)
    {
    	sk6812_i2s_init_mem();
    
    	for (uint16_t ii = 0; ii < NUM_LEDS; ii++)
    	{
    		set_led_pixel_RGB(ii, r, g, b);
    		set_i2s_led_data();
    		send_i2s_led_data();
    		k_msleep(ms_delay);
    	}
    }
    
    // Input a value 0 to 255 to get a color value
    sk6812_led_t wheel(uint8_t wheel_pos)
    {
    	sk6812_led_t color;
    	wheel_pos = 255 - wheel_pos;
    
    	if (wheel_pos < 85)
    	{
    		color.r = 255 - wheel_pos * 3;
    		color.g = 0;
    		color.b = wheel_pos * 3;
    		return color;
    	}
    
    	if (wheel_pos < 170)
    	{
    		wheel_pos -= 85;
    		color.r = 0;
    		color.g = wheel_pos * 3;
    		color.b = 255 - wheel_pos * 3;
    		return color;
    	}
    
    	wheel_pos -= 170;
    	color.r = wheel_pos * 3;
    	color.g = 255 - wheel_pos * 3;
    	color.b = 0;
    	return color;
    }
    
    void rainbow(uint16_t ms_delay)
    {
    	uint16_t ii, jj;
    	sk6812_led_t color;
    
    	sk6812_i2s_init_mem();
    	for (jj = 0; jj < NUM_LEDS; jj++)
    	{
    		for (ii = 0; ii < NUM_LEDS; ii++)
    		{
    			color = wheel(((ii * 256 / NUM_LEDS) + jj) & 255);
    			set_led_pixel_RGB(ii, color.r, color.g, color.b);
    		}
    		set_i2s_led_data();
    		send_i2s_led_data();
    		k_msleep(ms_delay);
    	}
    }
    
    // Slightly different, this makes the rainbow equally distributed throughout
    void rainbow_cycle(uint16_t ms_delay)
    {
    	uint16_t ii, jj;
    	sk6812_led_t color;
    
    	sk6812_i2s_init_mem();
    	for (jj = 0; jj < 256 * 3; jj++)
    	{ // 3 cycles of all colors on wheel
    		for (ii = 0; ii < NUM_LEDS; ii++)
    		{
    			color = wheel(((ii * 256 / NUM_LEDS) + jj) & 255);
    			set_led_pixel_RGB(ii, color.r, color.g, color.b);
    		}
    		set_i2s_led_data();
    		send_i2s_led_data();
    		nrf_delay_ms(ms_delay);
    	}
    }
    
    // Theater-style crawling lights.
    void theater_chase(uint8_t r, uint8_t g, uint8_t b, uint16_t ms_delay)
    {
    	sk6812_i2s_init_mem();
    	for (int jj = 0; jj < 10; jj++)
    	{ // do 10 cycles of chasing
    		for (int qq = 0; qq < 3; qq++)
    		{
    			for (uint16_t ii = 0; ii < NUM_LEDS; ii = ii + 3)
    			{
    				set_led_pixel_RGB(ii + qq, r, g, b); // turn every third pixel on
    			}
    			set_i2s_led_data();
    			send_i2s_led_data();
    
    			nrf_delay_ms(ms_delay);
    
    			for (uint16_t ii = 0; ii < NUM_LEDS; ii = ii + 3)
    			{
    				set_led_pixel_RGB(ii + qq, 0, 0, 0); // turn every third pixel off
    			}
    		}
    	}
    }
    
    // Theater-style crawling lights with rainbow effect
    void theater_chase_rainbow(uint16_t ms_delay)
    {
    	sk6812_led_t color;
    	sk6812_i2s_init_mem();
    	for (int jj = 0; jj < 256; jj++)
    	{ // cycle all 256 colors in the wheel
    		for (int qq = 0; qq < 3; qq++)
    		{
    			for (uint16_t ii = 0; ii < NUM_LEDS; ii = ii + 3)
    			{
    				color = wheel((ii + jj) % 255);
    				set_led_pixel_RGB(ii + qq, color.r, color.g, color.b); // turn every third pixel on
    			}
    			set_i2s_led_data();
    			send_i2s_led_data();
    
    			nrf_delay_ms(ms_delay);
    
    			for (uint16_t ii = 0; ii < NUM_LEDS; ii = ii + 3)
    			{
    				set_led_pixel_RGB(ii + qq, 0, 0, 0); // turn every third pixel off
    			}
    		}
    	}
    }

Reply
  • Thanks for the consideration Susheel!

    As far as I understand, the codec is unnecessary unless you're trying to (re)encode audio data. I'm simply creating a byte stream I'm trying to pipe over the I2S bus (because the bus has the correct timing requirements for the LED driver). 

    As for testing, I think Oscilloscope is how I'm planning on testing signal fidelity once I get it to run (I.e. no need for LEDs).

    For replication, the only changes I've made to the project are in the overlay file and main.c. Here's the current code:

    nrf52840dongle_nrf52840.overlay

    &pinctrl {
        i2s0_default_alt: i2s0_default_alt {
            group1 {
                psels = <NRF_PSEL(I2S_SCK_M, 1, 10)>,
                        <NRF_PSEL(I2S_LRCK_M, 1, 12)>,
                        <NRF_PSEL(I2S_SDOUT, 1, 11)>,
                        <NRF_PSEL(I2S_SDIN, 1, 14)>,
                        <NRF_PSEL(I2S_MCK, 1,15)>;
            };
        };
    };
    
    i2s_rxtx: &i2s0 {
        status = "okay";
        pinctrl-0 = <&i2s0_default_alt>;
        pinctrl-names = "default";
    };

    main.c

    // #include <zephyr/kernel.h>
    // #include <zephyr/drivers/gpio.h>
    // #include <zephyr/device.h>
    
    // /* 1000 msec = 1 sec */
    // #define SLEEP_TIME_MS 1000
    
    // /* The devicetree node identifier for the "led0" alias. */
    // #define LED0_NODE DT_ALIAS(led0)
    
    // // test GPIO voltage pin
    // #define VOLT_PIN 15
    
    // /*
    //  * A build error on this line means your board is unsupported.
    //  * See the sample documentation for information on how to fix this.
    //  */
    // static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
    
    // int main(void)
    // {
    // 	// // get the GPIO device
    // 	// const struct device *dev = device_get_binding("GPIO_0");
    // 	// // configure the LED pin as output
    // 	// gpio_pin_configure(dev, VOLT_PIN, GPIO_OUTPUT_ACTIVE);
    
    // 	int ret;
    // 	int loop_number = 0;
    // 	int blink_number = 0;
    
    // 	if (!gpio_is_ready_dt(&led))
    // 	{
    // 		loop_number++;
    // 		return 0;
    // 	}
    
    // 	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
    // 	if (ret < 0)
    // 	{
    // 		return 0;
    // 	}
    
    // 	while (1)
    // 	{
    // 		ret = gpio_pin_toggle_dt(&led);
    // 		if (ret < 0)
    // 		{
    // 			return 0;
    // 		}
    // 		printk("Blink number %d\n", blink_number);
    // 		blink_number++;
    // 		k_msleep(SLEEP_TIME_MS);
    // 	}
    // 	return 0;
    // }
    
    /*
     * Copyright (c) 2021 Nordic Semiconductor ASA
     *
     * SPDX-License-Identifier: Apache-2.0
     */
    
    #include <zephyr/kernel.h>
    #include <zephyr/sys/printk.h>
    #include <zephyr/drivers/i2s.h>
    #include <zephyr/drivers/gpio.h>
    #include <string.h>
    
    #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 104167 // 3.333333 MHz / 2 channels / 16 bits per sample
    #define SAMPLE_BIT_WIDTH 16
    #define BYTES_PER_SAMPLE sizeof(int16_t)
    #define NUMBER_OF_CHANNELS 2
    /* Such block length provides an echo with the delay of 100 ms. */
    #define SAMPLES_PER_BLOCK ((SAMPLE_FREQUENCY / 10) * NUMBER_OF_CHANNELS)
    #define INITIAL_BLOCKS 2
    #define TIMEOUT 1000
    
    #define NUM_LEDS 9
    #define DATA_BYTES_PER_LED 3 // 24-bit GRB data structure
    #define LEDS_DATA_BYTE_SIZE NUM_LEDS *DATA_BYTES_PER_LED
    
    #define I2S_BITS_PER_DATA_BIT 4
    #define I2S_SK6812_ZERO 0x8							   // 0b'1000 (0.3us; 0.9us)
    #define I2S_SK6812_ONE 0xC							   // 0b'1100 (0.6us; 0.6us)
    #define I2S_WS2812B_ZERO 0x8						   // 0b'1000 (0.4us; 0.85us)
    #define I2S_WS2812B_ONE 0xE							   // 0b'1110 (0.8us; 0.45us)
    #define I2S_BYTES_PER_RESET 256 / 8					   // reset_pulse / period = 80us / 0.3125us = 256 bits
    #define BYTES_TO_WORDS(n_bytes) (((n_bytes) + 3) >> 2) // (>> 2 === divide by 4... and + 3 to round up bytes)
    #define I2S_LEDS_WORD_SIZE BYTES_TO_WORDS(LEDS_DATA_BYTE_SIZE *I2S_BITS_PER_DATA_BIT)
    #define I2S_RESET_WORD_SIZE BYTES_TO_WORDS(I2S_BYTES_PER_RESET) // 8 words
    #define I2S_LEDS_FRAME_WORD_SIZE I2S_LEDS_WORD_SIZE + I2S_RESET_WORD_SIZE
    
    typedef struct
    {
    	uint8_t g;
    	uint8_t r;
    	uint8_t b;
    } sk6812_led_t;
    
    sk6812_led_t m_led_buffer_tx[NUM_LEDS];
    uint32_t m_i2s_led_buffer_tx[I2S_LEDS_FRAME_WORD_SIZE];
    
    /**
     * @brief Clears the LED and I2S data buffers
     */
    nrfx_err_t sk6812_i2s_init_mem()
    {
    	// TODO: dynamically allocate memory when needed instead of using global arrays
    	//    // Init memory for LED data
    	//    if (p_led_buffer_tx) free(p_led_buffer_tx);
    	//    p_led_buffer_tx = (sk6812_led_t *)malloc(LEDS_DATA_BYTE_SIZE);
    	//    if (p_led_buffer_tx) memset(p_led_buffer_tx, 0, LEDS_DATA_BYTE_SIZE);
    	//    else return NRF_ERROR_NO_MEM;
    	//
    	//    // Init memory for I2S data
    	//    if (p_i2s_led_buffer_tx) free(p_i2s_led_buffer_tx);
    	//    p_i2s_led_buffer_tx = (uint32_t *)malloc(I2S_LEDS_WORD_SIZE * 4);
    	//    if (p_i2s_led_buffer_tx) memset(p_i2s_led_buffer_tx, 0, I2S_LEDS_WORD_SIZE * 4);
    	//    else return NRF_ERROR_NO_MEM;
    
    	// Reset data buffers
    	memset(m_led_buffer_tx, 0, sizeof(m_led_buffer_tx));
    	memset(m_i2s_led_buffer_tx, 0, sizeof(m_i2s_led_buffer_tx));
    
    	return NRFX_SUCCESS;
    }
    
    /**
     * @brief Convert a byte of LED data to a word of I2S data
     * @brief 1 data bit   <--> 4 I2S bits
     * @brief 1 data byte  <--> 1 I2S word
     */
    uint32_t convert_byte_to_i2s_bits(uint8_t data_byte)
    {
    	uint32_t data_bits = 0;
    
    	// Set data_bits based on MSB, then left-shift data_byte
    	for (int ii = 0; ii < 8; ii++)
    	{
    		// Extract the current most significant bit (MSB) of data_byte
    		uint8_t msb = (data_byte & 0x80);
    
    		// Determine the appropriate I2S pattern based on the MSB
    		uint32_t i2s_pattern = (msb ? I2S_SK6812_ONE : I2S_SK6812_ZERO);
    
    		// Calculate the shift amount to align the pattern with the current bit position
    		int shift_amount = (8 - 1 - ii) * 4;
    
    		// Left-shift the I2S pattern by the calculated amount
    		i2s_pattern = i2s_pattern << shift_amount;
    
    		// Merge the shifted pattern with data_bits
    		data_bits |= i2s_pattern;
    
    		// prepare for reading next bit
    		data_byte = data_byte << 1;
    	}
    	return data_bits;
    }
    
    /**
     * @brief Sets I2S data from converted LED data
     */
    void set_i2s_led_data()
    {
    	uint16_t jj = 0;
    	for (uint16_t ii = 0; ii < NUM_LEDS; ii++)
    	{
    		m_i2s_led_buffer_tx[jj] = convert_byte_to_i2s_bits(m_led_buffer_tx[ii].g);
    		m_i2s_led_buffer_tx[jj + 1] = convert_byte_to_i2s_bits(m_led_buffer_tx[ii].r);
    		m_i2s_led_buffer_tx[jj + 2] = convert_byte_to_i2s_bits(m_led_buffer_tx[ii].b);
    		jj += 3;
    	}
    }
    
    // TODO: need to change this nrfx code
    /**
     * @brief Initializes I2S and starts the data transfer
     * @brief Assumes data buffers have already been set
     */
    void send_i2s_led_data()
    {
    	int ret;
    	const struct device *const i2s_dev_tx = DEVICE_DT_GET(I2S_TX_NODE);
    	ret = i2s_write(i2s_dev_tx, m_i2s_led_buffer_tx, I2S_LEDS_FRAME_WORD_SIZE + 1);
    
    	// // Configure the I2S module and map IO pins
    	// uint32_t err_code = sk6812_i2s_init();
    
    	// if (ret < 0)
    	// {
    	// 	printk("Failed to write data: %d\n", ret);
    	// 	return 0;
    	// }
    
    	// // Prevent starting a new data transfer if I2S already initialized
    	// if (err_code == NRFX_SUCCESS)
    	// {
    	// 	// Configure TX data buffer
    	// 	nrfx_i2s_buffers_t const initial_buffers = {
    	// 		.p_tx_buffer = m_i2s_led_buffer_tx,
    	// 		.p_rx_buffer = NULL};
    
    	// 	Enable the I2S module and start data streaming
    	// 	err_code = nrf_drv_i2s_start(&initial_buffers, I2S_LEDS_FRAME_WORD_SIZE + 1, 0);
    	// 	APP_ERROR_CHECK(err_code);
    	// }
    	// else
    	// {
    	// 	// Reset to all I2S_SK6812_ZERO's
    	// 	memset(m_i2s_led_buffer_tx, 0x88, I2S_LEDS_WORD_SIZE * 4);
    	// }
    }
    
    /**
     * @brief Set an RGB LED color data in the global sk6812_led_t array
     */
    void set_led_pixel_RGB(uint16_t pos, uint8_t r, uint8_t g, uint8_t b)
    {
    	m_led_buffer_tx[pos].r = r;
    	m_led_buffer_tx[pos].g = g;
    	m_led_buffer_tx[pos].b = b;
    }
    
    #define SW0_NODE DT_ALIAS(sw0)
    #if DT_NODE_HAS_STATUS(SW0_NODE, okay)
    static struct gpio_dt_spec sw0_spec = GPIO_DT_SPEC_GET(SW0_NODE, gpios);
    #endif
    
    #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
    
    #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);
    
    static int16_t echo_block[SAMPLES_PER_BLOCK];
    static volatile bool echo_enabled = true;
    static K_SEM_DEFINE(toggle_transfer, 1, 1);
    
    #if DT_NODE_HAS_STATUS(SW0_NODE, okay)
    static void sw0_handler(const struct device *dev, struct gpio_callback *cb,
    						uint32_t pins)
    {
    	bool enable = !echo_enabled;
    
    	echo_enabled = enable;
    	printk("Echo %sabled\n", (enable ? "en" : "dis"));
    }
    #endif
    
    #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);
    }
    #endif
    
    static bool init_buttons(void)
    {
    	int ret;
    
    #if DT_NODE_HAS_STATUS(SW0_NODE, okay)
    	static struct gpio_callback sw0_cb_data;
    
    	if (!device_is_ready(sw0_spec.port))
    	{
    		printk("%s is not ready\n", sw0_spec.port->name);
    		return false;
    	}
    
    	ret = gpio_pin_configure_dt(&sw0_spec, GPIO_INPUT);
    	if (ret < 0)
    	{
    		printk("Failed to configure %s pin %d: %d\n",
    			   sw0_spec.port->name, sw0_spec.pin, ret);
    		return false;
    	}
    
    	ret = gpio_pin_interrupt_configure_dt(&sw0_spec,
    										  GPIO_INT_EDGE_TO_ACTIVE);
    	if (ret < 0)
    	{
    		printk("Failed to configure interrupt on %s pin %d: %d\n",
    			   sw0_spec.port->name, sw0_spec.pin, ret);
    		return false;
    	}
    
    	gpio_init_callback(&sw0_cb_data, sw0_handler, BIT(sw0_spec.pin));
    	gpio_add_callback(sw0_spec.port, &sw0_cb_data);
    	printk("Press \"%s\" to toggle the echo effect\n", sw0_spec.port->name);
    #endif
    
    #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;
    }
    
    static void process_block_data(void *mem_block, uint32_t number_of_samples)
    {
    	static bool clear_echo_block;
    
    	if (echo_enabled)
    	{
    		for (int i = 0; i < number_of_samples; ++i)
    		{
    			int16_t *sample = &((int16_t *)mem_block)[i];
    			*sample += echo_block[i];
    			echo_block[i] = (*sample) / 2;
    		}
    
    		clear_echo_block = true;
    	}
    	else if (clear_echo_block)
    	{
    		clear_echo_block = false;
    		memset(echo_block, 0, sizeof(echo_block));
    	}
    }
    
    static bool configure_streams(const struct device *i2s_dev_rx,
    							  const struct device *i2s_dev_tx,
    							  const struct i2s_config *config)
    {
    	int ret;
    
    	if (i2s_dev_rx == i2s_dev_tx)
    	{
    		ret = i2s_configure(i2s_dev_rx, I2S_DIR_BOTH, config);
    		if (ret == 0)
    		{
    			return true;
    		}
    		/* -ENOSYS means that the RX and TX streams need to be
    		 * configured separately.
    		 */
    		if (ret != -ENOSYS)
    		{
    			printk("Failed to configure streams: %d\n", ret);
    			return false;
    		}
    	}
    
    	ret = i2s_configure(i2s_dev_rx, I2S_DIR_RX, config);
    	if (ret < 0)
    	{
    		printk("Failed to configure RX stream: %d\n", ret);
    		return false;
    	}
    
    	ret = i2s_configure(i2s_dev_tx, I2S_DIR_TX, config);
    	if (ret < 0)
    	{
    		printk("Failed to configure TX 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,
    							const struct device *i2s_dev_tx,
    							enum i2s_trigger_cmd cmd)
    {
    	int ret;
    
    	if (i2s_dev_rx == i2s_dev_tx)
    	{
    		ret = i2s_trigger(i2s_dev_rx, I2S_DIR_BOTH, cmd);
    		if (ret == 0)
    		{
    			return true;
    		}
    		/* -ENOSYS means that commands for the RX and TX streams need
    		 * to be triggered separately.
    		 */
    		if (ret != -ENOSYS)
    		{
    			printk("Failed to trigger command %d: %d\n", cmd, ret);
    			return false;
    		}
    	}
    
    	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;
    	}
    
    	ret = i2s_trigger(i2s_dev_tx, I2S_DIR_TX, cmd);
    	if (ret < 0)
    	{
    		printk("Failed to trigger command %d on TX: %d\n", cmd, ret);
    		return false;
    	}
    
    	return true;
    }
    
    int main(void)
    {
    	// TODO: put the IO voltage up to 3.3V
    	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("I2S echo sample\n");
    
    	if (!init_buttons())
    	{
    		return 0;
    	}
    
    	if (!device_is_ready(i2s_dev_rx))
    	{
    		printk("%s is not ready\n", i2s_dev_rx->name);
    		return 0;
    	}
    
    	if (i2s_dev_rx != i2s_dev_tx && !device_is_ready(i2s_dev_tx))
    	{
    		printk("%s is not ready\n", i2s_dev_tx->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, i2s_dev_tx, &config))
    	{
    		return 0;
    	}
    
    	while (true)
    	{
    		k_sem_take(&toggle_transfer, K_FOREVER);
    
    		if (!prepare_transfer(i2s_dev_rx, i2s_dev_tx))
    		{
    			return 0;
    		}
    
    		if (!trigger_command(i2s_dev_rx, i2s_dev_tx,
    							 I2S_TRIGGER_START))
    		{
    			return 0;
    		}
    
    		printk("Streams started\n");
    
    		while (k_sem_take(&toggle_transfer, K_NO_WAIT) != 0)
    		{
    			void *mem_block;
    			uint32_t block_size;
    			int ret;
    
    			// TODO remove block of reading (only writing)
    			ret = i2s_read(i2s_dev_rx, &mem_block, &block_size);
    			if (ret < 0)
    			{
    				printk("Failed to read data: %d\n", ret);
    				break;
    			}
    
    			// TODO remove block of reading (only writing)
    			process_block_data(mem_block, SAMPLES_PER_BLOCK);
    
    			// ret = i2s_write(i2s_dev_tx, mem_block, block_size);
    			// ret = i2s_write(i2s_dev_tx, m_i2s_led_buffer_tx, I2S_LEDS_FRAME_WORD_SIZE + 1);
    			color_wipe(255, 0, 0, 50); // Red
    			if (ret < 0)
    			{
    				printk("Failed to write data: %d\n", ret);
    				break;
    			}
    		}
    
    		if (!trigger_command(i2s_dev_rx, i2s_dev_tx,
    							 I2S_TRIGGER_DROP))
    		{
    			return 0;
    		}
    
    		printk("Streams stopped\n");
    	}
    }
    
    // TODO move to another file, these are the color animations
    /**
     * @brief Ported Adafruit NeoPixel paint animation functions
     */
    // Fill the dots one after the other with a color
    void color_wipe(uint8_t r, uint8_t g, uint8_t b, uint8_t ms_delay)
    {
    	sk6812_i2s_init_mem();
    
    	for (uint16_t ii = 0; ii < NUM_LEDS; ii++)
    	{
    		set_led_pixel_RGB(ii, r, g, b);
    		set_i2s_led_data();
    		send_i2s_led_data();
    		k_msleep(ms_delay);
    	}
    }
    
    // Input a value 0 to 255 to get a color value
    sk6812_led_t wheel(uint8_t wheel_pos)
    {
    	sk6812_led_t color;
    	wheel_pos = 255 - wheel_pos;
    
    	if (wheel_pos < 85)
    	{
    		color.r = 255 - wheel_pos * 3;
    		color.g = 0;
    		color.b = wheel_pos * 3;
    		return color;
    	}
    
    	if (wheel_pos < 170)
    	{
    		wheel_pos -= 85;
    		color.r = 0;
    		color.g = wheel_pos * 3;
    		color.b = 255 - wheel_pos * 3;
    		return color;
    	}
    
    	wheel_pos -= 170;
    	color.r = wheel_pos * 3;
    	color.g = 255 - wheel_pos * 3;
    	color.b = 0;
    	return color;
    }
    
    void rainbow(uint16_t ms_delay)
    {
    	uint16_t ii, jj;
    	sk6812_led_t color;
    
    	sk6812_i2s_init_mem();
    	for (jj = 0; jj < NUM_LEDS; jj++)
    	{
    		for (ii = 0; ii < NUM_LEDS; ii++)
    		{
    			color = wheel(((ii * 256 / NUM_LEDS) + jj) & 255);
    			set_led_pixel_RGB(ii, color.r, color.g, color.b);
    		}
    		set_i2s_led_data();
    		send_i2s_led_data();
    		k_msleep(ms_delay);
    	}
    }
    
    // Slightly different, this makes the rainbow equally distributed throughout
    void rainbow_cycle(uint16_t ms_delay)
    {
    	uint16_t ii, jj;
    	sk6812_led_t color;
    
    	sk6812_i2s_init_mem();
    	for (jj = 0; jj < 256 * 3; jj++)
    	{ // 3 cycles of all colors on wheel
    		for (ii = 0; ii < NUM_LEDS; ii++)
    		{
    			color = wheel(((ii * 256 / NUM_LEDS) + jj) & 255);
    			set_led_pixel_RGB(ii, color.r, color.g, color.b);
    		}
    		set_i2s_led_data();
    		send_i2s_led_data();
    		nrf_delay_ms(ms_delay);
    	}
    }
    
    // Theater-style crawling lights.
    void theater_chase(uint8_t r, uint8_t g, uint8_t b, uint16_t ms_delay)
    {
    	sk6812_i2s_init_mem();
    	for (int jj = 0; jj < 10; jj++)
    	{ // do 10 cycles of chasing
    		for (int qq = 0; qq < 3; qq++)
    		{
    			for (uint16_t ii = 0; ii < NUM_LEDS; ii = ii + 3)
    			{
    				set_led_pixel_RGB(ii + qq, r, g, b); // turn every third pixel on
    			}
    			set_i2s_led_data();
    			send_i2s_led_data();
    
    			nrf_delay_ms(ms_delay);
    
    			for (uint16_t ii = 0; ii < NUM_LEDS; ii = ii + 3)
    			{
    				set_led_pixel_RGB(ii + qq, 0, 0, 0); // turn every third pixel off
    			}
    		}
    	}
    }
    
    // Theater-style crawling lights with rainbow effect
    void theater_chase_rainbow(uint16_t ms_delay)
    {
    	sk6812_led_t color;
    	sk6812_i2s_init_mem();
    	for (int jj = 0; jj < 256; jj++)
    	{ // cycle all 256 colors in the wheel
    		for (int qq = 0; qq < 3; qq++)
    		{
    			for (uint16_t ii = 0; ii < NUM_LEDS; ii = ii + 3)
    			{
    				color = wheel((ii + jj) % 255);
    				set_led_pixel_RGB(ii + qq, color.r, color.g, color.b); // turn every third pixel on
    			}
    			set_i2s_led_data();
    			send_i2s_led_data();
    
    			nrf_delay_ms(ms_delay);
    
    			for (uint16_t ii = 0; ii < NUM_LEDS; ii = ii + 3)
    			{
    				set_led_pixel_RGB(ii + qq, 0, 0, 0); // turn every third pixel off
    			}
    		}
    	}
    }

Children
  • I see, 

    I think it would have been better to use PWN or SPI for controlling the LED strip but I2S can also work. A similar effort using another driver which uses PWM can be found here.

    In your case, I think the huge memory being allocated in the global static region could be the issue.

    About the error you are getting, it seems like the buffer m_i2s_led_buffer_tx is allocated in the memory slab,  and it seems like K_MEM_SLAB_DEFINE_STATIC creates (K_MEM_SLAB_DEFINE) with only the max space of 41664 and you are using more than the allocated space in the memory slab.

Related