How to reduce CPU load by tuning I2S DMA buffers
Posted: Tue Dec 05, 2023 2:15 pm
Hello,
I'm building a polyphonic synth and am hitting CPU constraints. I haven't dug into optimizing my DSP yet as I expected to be able to increase my buffer size to avoid the problem, given that my current buffers sizes are giving me really good latency (better than I need, in fact - I have probably 6ms of latency headroom). I'm using DMA buffer settings of count 2, size 32, and doing my DSP in a loop with buffer size 32 samples. I'm able to do 6 voices with my DSP but when I try to do 7, the audio breaks up.
I've read enough that I thought that just increasing the buffer size would solve this problem - if I double the buffers, to say 10 and 32, what I'm seeing is that the CPU is never blocked by i2s_write, the watchdog timer complains and I'm getting a different periodic audio breakup (though it does sound different from the buffer too low issue). I haven't performance profiled my code yet, but I sort of expected that just increasing the DMA buffer sizes would "work". I also tried adjusting my own loop buffer size - BUFFER_LENGTH - but it didn't really seem o make much of a difference - given that the watchdog timer isn't firing, it seems like I'm spending 100% of my time in the loop and am never blocked on i2s_write.
Here's the important part of my code:
What's the best way to approach this problem? Thanks!
I'm building a polyphonic synth and am hitting CPU constraints. I haven't dug into optimizing my DSP yet as I expected to be able to increase my buffer size to avoid the problem, given that my current buffers sizes are giving me really good latency (better than I need, in fact - I have probably 6ms of latency headroom). I'm using DMA buffer settings of count 2, size 32, and doing my DSP in a loop with buffer size 32 samples. I'm able to do 6 voices with my DSP but when I try to do 7, the audio breaks up.
I've read enough that I thought that just increasing the buffer size would solve this problem - if I double the buffers, to say 10 and 32, what I'm seeing is that the CPU is never blocked by i2s_write, the watchdog timer complains and I'm getting a different periodic audio breakup (though it does sound different from the buffer too low issue). I haven't performance profiled my code yet, but I sort of expected that just increasing the DMA buffer sizes would "work". I also tried adjusting my own loop buffer size - BUFFER_LENGTH - but it didn't really seem o make much of a difference - given that the watchdog timer isn't firing, it seems like I'm spending 100% of my time in the loop and am never blocked on i2s_write.
Here's the important part of my code:
- #define BUFFER_LENGTH (32)
- #define NUM_VOICES (8)
- void init_i2s() {
- i2s_port_t i2s_port = (i2s_port_t)0;
- static const i2s_config_t i2s_config = {
- .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
- .sample_rate = 44100,
- .bits_per_sample = (i2s_bits_per_sample_t)16,
- .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
- .communication_format = I2S_COMM_FORMAT_STAND_MSB,
- .intr_alloc_flags = 0, // default interrupt priority
- .dma_buf_count = 2,
- .dma_buf_len = 32,
- .use_apll = true};
- i2s_driver_install(i2s_port, &i2s_config, 0, NULL);
- static const i2s_pin_config_t pin_config = {.bck_io_num = GPIO_NUM_4,
- .ws_io_num = GPIO_NUM_5,
- .data_out_num = GPIO_NUM_18,
- .data_in_num = I2S_PIN_NO_CHANGE};
- i2s_set_pin(i2s_port, &pin_config);
- }
- void i2s_task(void *data) {
- while (1) {
- int16_t buffer[BUFFER_LENGTH];
- for (unsigned int j = 0; j < BUFFER_LENGTH; j += 2) {
- int16_t summed = 0;
- for (unsigned int k = 0; k < NUM_VOICES; k++) {
- int16_t current_sample = voices[k].Process();
- summed += (current_sample / 5);
- }
- buffer[j] = summed;
- buffer[j + 1] = summed;
- }
- size_t i2s_bytes_write = 0;
- i2s_write((i2s_port_t)0, buffer, sizeof(buffer), &i2s_bytes_write, 100);
- }
- }
- extern "C" void app_main() {
- init_i2s();
- xTaskCreatePinnedToCore(i2s_task, "i2s_task", 4096, NULL, 19, NULL, 1);
- }