Hi ! Its me again.
I'm using zephyr 3.5.0 via zmk.
I'm trying to play a 60 Hz uncompressed PCM file from a wav embedded in flash memory.
I'm noticing some inconsistent output on I2s. I would expect similar output every time I run this.
I'm using salae to capture/analyze the I2S, then a python script to plot the wave form and generate the FFT
- sometimes playback is flawless
- sometimes I get 1 cycle of the sine wave and then it stops
- sometimes i get most of the sine wave perfect, followed by nonsense.
When the waveform is clean, I get a perfect FFT with a peak at 60 Hz.
When the waveform isn't perfect, it looks like this

Here is where it goes from good to bad

Also noticing a clear gap in the data

This is my first time using the zephyr i2s API. I've been successful on other platforms and toolchains.
There's a few obvious issues with my code.
1- I'd like to signal the timer to stop from the timer interrupt -> k_work -> fill buf, when the play back reaches EOF.
That didn't work right for me so I stuck with an arbitrary loop counter that bails out and stops the timer.
2- I occasionally see the following error, does that mean I should retry that block? If so - immediate or next timer?
For the following waveform, I see 3 of these-
<err> audio: i2s_write: error fffffff5
On clean wave forms, I do not see this error.
3 - the wave file is 3 seconds long. I would not expect to see anything past 300000 msec
4 - am I using the i2s API properly? 20 blocks in the slab are allocated one at a time
Here is my source file -
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/i2s.h>
#include <zephyr/audio/codec.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/iterable_sections.h>
#include "wavheader.h"
#include "audio.h"
LOG_MODULE_REGISTER(audio, CONFIG_AUDIO_CODEC_LOG_LEVEL);
static const struct device *i2s_dev;
static int16_t *tx_block;
static struct k_work audio_work;
static int16_t *asset;
static uint32_t file_pos = 0;
static uint32_t asset_size = 0;
#define SAMPLE_NO 64
#define NUM_BLOCKS 20
#define BLOCK_SIZE (2 * sizeof(uint16_t) * SAMPLE_NO)
#ifdef CONFIG_NOCACHE_MEMORY
#define MEM_SLAB_CACHE_ATTR __nocache
#else
#define MEM_SLAB_CACHE_ATTR
#endif /* CONFIG_NOCACHE_MEMORY */
static char MEM_SLAB_CACHE_ATTR __aligned(WB_UP(32))
_k_mem_slab_buf_tx_0_mem_slab[(NUM_BLOCKS)*WB_UP(BLOCK_SIZE)];
static STRUCT_SECTION_ITERABLE(k_mem_slab, tx_0_mem_slab) = Z_MEM_SLAB_INITIALIZER(
tx_0_mem_slab, _k_mem_slab_buf_tx_0_mem_slab, WB_UP(BLOCK_SIZE), NUM_BLOCKS);
static void i2s_timer_handler(struct k_timer *timer_id);
K_TIMER_DEFINE(i2s_timer, i2s_timer_handler, NULL);
static void fill_buf(int att) {
for (int i = 0; i < SAMPLE_NO; i += 1) {
if (file_pos + i > asset_size) {
tx_block[2 * i] = 0;
tx_block[2 * i + 1] = 0;
if (k_timer_remaining_get(&i2s_timer) > 0) {
k_timer_stop(&i2s_timer);
}
} else {
tx_block[2 * i] = (asset[file_pos + i] >> att);
tx_block[2 * i + 1] = (asset[file_pos + i] >> att);
}
}
file_pos += SAMPLE_NO;
}
static void audio_work_handler(struct k_work *work) {
int ret;
ret = k_mem_slab_alloc(&tx_0_mem_slab, (void *)&tx_block, K_FOREVER);
if (ret != 0) {
LOG_ERR("k_mem_slab_alloc: error %08x", ret);
return;
}
fill_buf(0);
ret = i2s_write(i2s_dev, tx_block, BLOCK_SIZE);
if (ret != 0) {
LOG_ERR("i2s_write: error %08x", ret);
k_mem_slab_free(&tx_0_mem_slab, (void *)tx_block);
}
}
static void i2s_timer_handler(struct k_timer *timer_id) { k_work_submit(&audio_work); }
static WavFileHeader_t *audio_file_init(const int16_t *asset_in, uint32_t asset_size_in) {
asset_size = asset_size_in;
LOG_INF("sizeof(WavFileHeader_t): %d", sizeof(WavFileHeader_t));
LOG_INF("asset loc: %p size: %d", (void *)asset_in, asset_size);
WavFileHeader_t *wavheader = (WavFileHeader_t *)asset_in;
LOG_INF("PcmFlags: %d Channels: %d", wavheader->PcmFlags, wavheader->Channels);
LOG_INF("BitDepth: %d SampleRate: %d", wavheader->BitDepth, wavheader->SampleRate);
// LOG_INF("FileFormat: %s BlockAlign: %d", wavheader->FileFormat, wavheader->BlockAlign);
LOG_INF("\n");
asset = asset_in + sizeof(WavFileHeader_t) / 2;
return wavheader;
}
static void audio_i2s_init(const struct device *dev, WavFileHeader_t *wavheader) {
i2s_dev = dev;
struct i2s_config i2s_cfg;
int ret;
i2s_cfg.word_size = 16U;
i2s_cfg.channels = 2U;
i2s_cfg.format = I2S_FMT_DATA_FORMAT_I2S;
i2s_cfg.frame_clk_freq = wavheader->SampleRate;
i2s_cfg.block_size = BLOCK_SIZE;
i2s_cfg.timeout = 2000;
i2s_cfg.options = I2S_OPT_FRAME_CLK_MASTER | I2S_OPT_BIT_CLK_MASTER;
i2s_cfg.mem_slab = &tx_0_mem_slab;
ret = i2s_configure(i2s_dev, I2S_DIR_TX, &i2s_cfg);
if (ret < 0) {
LOG_ERR("Failed to configure I2S stream\n");
return ret;
}
}
int audio_play(const struct device *dev, const int16_t *asset_in, uint32_t asset_size_in) {
WavFileHeader_t *wavheader = audio_file_init(asset_in, asset_size_in);
audio_i2s_init(dev, wavheader);
file_pos = 0;
k_work_init(&audio_work, audio_work_handler);
k_timer_start(&i2s_timer, K_USEC(5), K_USEC(5));
audio_work_handler(NULL); // prime the i2s buffer
/* Trigger the I2S transmission */
int ret = i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_START);
if (ret < 0) {
LOG_ERR("Could not trigger I2S tx\n");
return ret;
}
// TODO replace with DONE flag from timer handler and ditch loop
// had issues prototyping this, stashed away for now.
uint32_t cnt = 0x800;
while (1) {
k_sleep(K_MSEC(1));
if (--cnt == 0) {
if (k_timer_remaining_get(&i2s_timer) > 0) {
k_timer_stop(&i2s_timer);
}
break;
}
}
/* Drain TX queue */
// ret = i2s_trigger(i2s_dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
// if (ret < 0) {
// LOG_ERR("Could not trigger I2S tx\n");
// return ret;
// }
LOG_DBG("All I2S blocks written\n");
return 0;
}