Reduce time between two consecutive SPI transfers

wjxway
Posts: 15
Joined: Wed Aug 05, 2020 2:52 am

Re: Reduce time between two consecutive SPI transfers

Postby wjxway » Mon Aug 10, 2020 2:55 pm

ESP_Sprite wrote:
Wed Aug 05, 2020 3:02 pm
You may want to set USE_APLL to 1 as well as use a larger dma_buf_len... could be that the issue is that the ISR gets called too often or that the I2S driver has issues generating the frequencies you need.
Hi, my oscilloscope has arrived and I managed to get I2S interface running. Well, the good news is, I2S does enable continuous output and the output waveform is suitable for controlling DAC, however, I only managed to get the I2S clock up to 20MHz, which is a bit slower than I've anticipated.

Are there any method by which I can speed up the I2S clock (BCK) to 40MHz? my current code is like:

Code: Select all

#include "Arduino.h"
#include "driver/i2s.h" 

static const i2s_port_t i2s_num = I2S_NUM_0;

static const i2s_config_t i2s_config ={
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
    .sample_rate = 500000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = 1024,
    .use_apll=0,
    .tx_desc_auto_clear= true,
    .fixed_mclk=40000000
};

static const i2s_pin_config_t pin_config ={
    .bck_io_num = 27,
    .ws_io_num = 26,
    .data_out_num = 25,
    .data_in_num = I2S_PIN_NO_CHANGE
};

void setup() {
    Serial.begin(115200);
    i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
    i2s_set_pin(i2s_num, &pin_config);
}
void loop()
{
    static uint16_t Value16Bit[18]={65535,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9,0};

    size_t BytesWritten;

    unsigned long tt=micros();
    unsigned long count=0;
    while (micros()-tt<1000000)
    {
        i2s_write(i2s_num, Value16Bit, 36, &BytesWritten, portMAX_DELAY);
        count+=9;
    }
    Serial.println(count);
}
There is a related post here https://esp32.com/viewtopic.php?f=18&t=14185, but I can still only achieve 20MHz by tweaking this code.

Thanks a lot!

wjxway
Posts: 15
Joined: Wed Aug 05, 2020 2:52 am

Re: Reduce time between two consecutive SPI transfers

Postby wjxway » Mon Aug 10, 2020 3:50 pm

Whooo, I have managed to get it working at BCK@40MHz, that is 1.25M@16bits transfers/sec!
That is when I'm using a DAC with SPI interface, if someone is using a DAC with I2S interface, the number could ramp up to 2.5M@16bits transfers/sec!

The trick is just as described in https://esp32.com/viewtopic.php?f=18&t=14185. to customize the frequency of I2S MCLK and BCK by manipulating registers directly.

the code (with annotation) is as follows:

Code: Select all

#include "Arduino.h"
#include "driver/i2s.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/rtc.h"
#include "soc/syscon_reg.h"
#include "soc/rtc_cntl_struct.h"

// I2S settings
// dual channel, 16bits, MSB format, do not use APLL for maximum speed, other values are irrelavent
// APLL provides better timing accuracy, but can achieve 35MHz BCK at max, while not using APLL can reach 40MHz
static const i2s_port_t i2s_num = I2S_NUM_0;
static const i2s_config_t i2s_config ={
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
    .sample_rate = 500000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = 1024,
    .use_apll=0,
    .tx_desc_auto_clear= true,
    .fixed_mclk=40000000
};

// I2S pin config
static const i2s_pin_config_t pin_config ={
    .bck_io_num = 27,
    .ws_io_num = 26,
    .data_out_num = 25,
    .data_in_num = I2S_PIN_NO_CHANGE
};

// setup
void setup() {
    Serial.begin(115200);

    // I2S init
    i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
    i2s_set_pin(i2s_num, &pin_config);

    // change clock rate
    // apll_freq or PLL_D2_freq -> I2S mclk_freq -> I2S bck_freq

    // 35MHz APLL code, use when use_apll=1
    // set apll freq
    // apll_freq = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536)/((o_div + 2) * 2)
    // Note that the denominator xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536) should be between 350~500MHz
    // by setting sdm2=10, sdm0=sdm1=odiv=0, we achieve apll_freq@140MHz
    // rtc_clk_apll_enable(1, 0, 0, 10, 0);
    
    // set mclk_freq
    // mclk_freq = apll_freq / (N + b/a)
    // N -> clkm_div_num, N>=2
    // by setting N=2, b=0, a=1, we achieve mclk freq@80MHz
    I2S0.clkm_conf.clkm_div_num = 2;
    I2S0.clkm_conf.clkm_div_b = 0;
    I2S0.clkm_conf.clkm_div_a = 1;
    I2S0.clkm_conf.clk_en = 1;
    I2S0.clkm_conf.clka_en = 1;

    // set bck_freq
    // bck_freq=mclk_freq/div, div>=2
    // by setting div=2, we achieve bck freq@35MHz
    I2S0.sample_rate_conf.tx_bck_div_num = 2;
    I2S0.sample_rate_conf.rx_bck_div_num = 2;
}

void loop()
{
    static uint16_t Value16Bit[18]={65535,0,2,0,3,0,4,0,5,0,6,0,7,0,8,0,9,0};
    size_t BytesWritten;
    unsigned long tt=micros();
    unsigned long count=0;
    while (micros()-tt<1000000)
    {
        i2s_write(i2s_num, Value16Bit, 36, &BytesWritten, portMAX_DELAY);
        count+=9;
    }
    Serial.println(count);
}
Hooray! The only thing that's not satisfying enough is I2S protocol clock out data at the rising edge of BCK, but my DAC requires data to clock out at the descending edge, are there any software-based solution to this problem? (I can solve it by adding a negative gate, but software-based solutions are surely more convienient!

ESP_Sprite
Posts: 9757
Joined: Thu Nov 26, 2015 4:08 am

Re: Reduce time between two consecutive SPI transfers

Postby ESP_Sprite » Tue Aug 11, 2020 10:45 am

You can invert any GPIO pin in the ESP32. You should be able to do this; replace nn with the GPIO number:

Code: Select all

WRITE_PERI_REG(GPIO_FUNCnn_OUT_SEL_CFG_REG, READ_PERI_REG(GPIO_FUNCnn_OUT_SEL_CFG_REG) | GPIO_FUNCnn_OUT_INV_SEL);

wjxway
Posts: 15
Joined: Wed Aug 05, 2020 2:52 am

Re: Reduce time between two consecutive SPI transfers

Postby wjxway » Tue Aug 11, 2020 4:30 pm

@ESP_Sprite Thanks for your reply, now the polarity is correct and ready to go. There's just one issue left regarding timing.

I'm transferring nine 16bits data at a time, and I would like it to happen immediately after I execute the code (or after a fixed, short time delay). If no data is feed in, then output 0 will be fine.

However, seemingly some sort of buffered output mechanism will store all the data I feed in and then output them in a chunk, it also generate some sort of artifacts as well. Here are two photos of the output, where the green line is the data output signal.

Image

Image

Image

In the first figure, somewhere around the center, there is a transfer with only six 16bits data, I don't know where it comes from. (settings: I2S buffer@36bits*128, delay between consecutive transfers @ 50us)
In the second figure, it seems that I2S is buffering all the data and transfer them at the same time. (settings: I2S buffer@1024bits*8, delay between consecutive transfers @ 50us)
In the third figure, the previous two problems is combined...(settings: I2S buffer@1024bits*8, delay between consecutive transfers @ 5us)

In all scenarios I have tested, time between two consecutive transfers isn't stable.

Because I2S is for audio applications, so I guess there should be some way to control the output time more precisely? It would be of much help if you could generously give some hint!

Thanks!

PeterR
Posts: 621
Joined: Mon Jun 04, 2018 2:47 pm

Re: Reduce time between two consecutive SPI transfers

Postby PeterR » Tue Aug 11, 2020 7:29 pm

Maybe your ISR is being mugged by another ISR? Working out of IRAM/cache will also hurt.
I have seen similar with big 2mS hits on I2C transactions to the extant that someone starting producing I2C corruptions. My guess was that Ethernet caused the ISR/bus block but not sure if device or ESP caused the corruption (datasheet for both says cool).
I must take a keener interest in ESP32 bus architecture sometime such that I may better understand these issues. My (uneducated) guess is that above & beyond ISR latency we may have several transactions locked onto a single bus?
Hopefully I will be shot down but RAM DMA to peripheral bus etc.... I just do not know how many channels & how that works.
& I also believe that IDF CAN should be fixed.

wjxway
Posts: 15
Joined: Wed Aug 05, 2020 2:52 am

Re: Reduce time between two consecutive SPI transfers

Postby wjxway » Wed Aug 12, 2020 7:57 am

PeterR wrote:
Tue Aug 11, 2020 7:29 pm
Maybe your ISR is being mugged by another ISR? Working out of IRAM/cache will also hurt.
I have seen similar with big 2mS hits on I2C transactions to the extant that someone starting producing I2C corruptions. My guess was that Ethernet caused the ISR/bus block but not sure if device or ESP caused the corruption (datasheet for both says cool).
I must take a keener interest in ESP32 bus architecture sometime such that I may better understand these issues. My (uneducated) guess is that above & beyond ISR latency we may have several transactions locked onto a single bus?
Hopefully I will be shot down but RAM DMA to peripheral bus etc.... I just do not know how many channels & how that works.
Ah, some background might help the diagnostics. I encounter similar problems in both my testing environment and my real application.
In my test environment, only serial port and I2S is running on core 0, nothing else. and Serial port is opened once in a second, which cannot explain the 2ms latency which appears much more frequently than that.
In my real application, Serial, I2S (I2S0), one SPI (HSPI), one I2C are running. Serial, I2C and SPI transactions are processed by core 0 and core 1 is dedicated to I2S transfers. I've tried to turn off SPI and I2C transactions, but it didn't help.

wjxway
Posts: 15
Joined: Wed Aug 05, 2020 2:52 am

Re: Reduce time between two consecutive SPI transfers

Postby wjxway » Wed Aug 12, 2020 8:03 am

PeterR wrote:
Tue Aug 11, 2020 7:29 pm
Maybe your ISR is being mugged by another ISR? Working out of IRAM/cache will also hurt.
I have seen similar with big 2mS hits on I2C transactions to the extant that someone starting producing I2C corruptions. My guess was that Ethernet caused the ISR/bus block but not sure if device or ESP caused the corruption (datasheet for both says cool).
I must take a keener interest in ESP32 bus architecture sometime such that I may better understand these issues. My (uneducated) guess is that above & beyond ISR latency we may have several transactions locked onto a single bus?
Hopefully I will be shot down but RAM DMA to peripheral bus etc.... I just do not know how many channels & how that works.
I think I didn't explicitly mess up with IRAM and Cache, but I don't know whether any library I've used (all from the official ESP32_Arduino project) would do so. My program does not require any interrupts, and I didn't explicitly set up any. I2S is set up in Master+TX mode, which I believe would not involve interrupts as well?

Who is online

Users browsing this forum: joe_fang_iD, Majestic-12 [Bot] and 290 guests