Timing-sensitive peripheral access - trying to start two peripherals with deterministic delay
Posted: Tue Mar 01, 2022 8:39 pm
Hi,
I am using a device which has a serial data input (uses a shift register internally), and a latch signal. I am using the ESP32 SPI controller to transmit the data and clock to the device. The latch signal is required to go high during the final 3 clock pulses of the SPI transmission (the width of the pulse is used by the device to communicate different commands, so it must be exactly 3 clock pulses). Therefore I cannot use the normal CS signal to control the latch.
My current design for controlling this is to use the LEDC controller to generate the latch pulse at the right time, and start the SPI transmission and LEDC timer one line after another in the code. It doesn't matter that the SPI and LEDC don't start at exactly the right time, because I can time how long the time takes in-between lines of code and include that as an offset in the timing setup of the LEDC controller. The transmission starts within an interrupt handler, with priority level 3.
However, there has to be a deterministic time between starting the SPI transmission and starting the LEDC timer. I hoped to achieve this by putting these lines within a critical section so that no higher-priority interrupts on this core would happen in-between the execution of these two lines - but it isn't working! For some transmissions, the LEDC starts on-time, but the SPI starts a little later than usual, so the latch pulse (caused by LEDC) is too early.
I have managed to reduce the variation in timing between starting the SPI and LEDC, by putting the interrupt handler and all data accessed into IRAM / DRAM. I guess the flash and its cache were causing some of the occasional delays - perhaps from an interrupt running on the other core.
There is still a small extra delay to starting the SPI sometimes. At the same time, I have another FreeRTOS task running which triggers an ADC capture from the other core every 10ms, and I think this is what is causing the small delay sometimes. If I stop this task, or if I pin it to the same core as the SPI/LEDC, I get rock-solid timing consistently.
My guess with what is happening here is that starting the ADC capture and starting the SPI both involve setting peripheral registers, and they cannot happen at exactly the same time, so one has to wait until the other one is set successfully.
My question is will this be the case with any peripheral register access, or just certain peripherals (is it anything special about shared a shared bus between ADC / SPI / LEDC, perhaps?). I guess there is one APB bus which all peripherals share, because I cannot see any distinction further than this in the reference manual?
If it is any peripherals, I don't think I can control the system enough to have all the peripheral access happen from one core (to protect the critical timing between SPI and LEDC), so I may have to stall core 1 while I start SPI and LEDC on core 0, to make sure the timing is deterministic.
I am also curious if this small delay would only happen if the peripheral registers are accessed from both cores at the same time, or if the same effect could happen from the DMA accessing registers at the same time as a core?
Thanks for your support!
Andy
I am using a device which has a serial data input (uses a shift register internally), and a latch signal. I am using the ESP32 SPI controller to transmit the data and clock to the device. The latch signal is required to go high during the final 3 clock pulses of the SPI transmission (the width of the pulse is used by the device to communicate different commands, so it must be exactly 3 clock pulses). Therefore I cannot use the normal CS signal to control the latch.
My current design for controlling this is to use the LEDC controller to generate the latch pulse at the right time, and start the SPI transmission and LEDC timer one line after another in the code. It doesn't matter that the SPI and LEDC don't start at exactly the right time, because I can time how long the time takes in-between lines of code and include that as an offset in the timing setup of the LEDC controller. The transmission starts within an interrupt handler, with priority level 3.
However, there has to be a deterministic time between starting the SPI transmission and starting the LEDC timer. I hoped to achieve this by putting these lines within a critical section so that no higher-priority interrupts on this core would happen in-between the execution of these two lines - but it isn't working! For some transmissions, the LEDC starts on-time, but the SPI starts a little later than usual, so the latch pulse (caused by LEDC) is too early.
I have managed to reduce the variation in timing between starting the SPI and LEDC, by putting the interrupt handler and all data accessed into IRAM / DRAM. I guess the flash and its cache were causing some of the occasional delays - perhaps from an interrupt running on the other core.
There is still a small extra delay to starting the SPI sometimes. At the same time, I have another FreeRTOS task running which triggers an ADC capture from the other core every 10ms, and I think this is what is causing the small delay sometimes. If I stop this task, or if I pin it to the same core as the SPI/LEDC, I get rock-solid timing consistently.
My guess with what is happening here is that starting the ADC capture and starting the SPI both involve setting peripheral registers, and they cannot happen at exactly the same time, so one has to wait until the other one is set successfully.
My question is will this be the case with any peripheral register access, or just certain peripherals (is it anything special about shared a shared bus between ADC / SPI / LEDC, perhaps?). I guess there is one APB bus which all peripherals share, because I cannot see any distinction further than this in the reference manual?
If it is any peripherals, I don't think I can control the system enough to have all the peripheral access happen from one core (to protect the critical timing between SPI and LEDC), so I may have to stall core 1 while I start SPI and LEDC on core 0, to make sure the timing is deterministic.
I am also curious if this small delay would only happen if the peripheral registers are accessed from both cores at the same time, or if the same effect could happen from the DMA accessing registers at the same time as a core?
Thanks for your support!
Andy