Pause SPI clock momentarily while using DMA
Posted: Mon Nov 04, 2024 3:53 am
I'm trying to use the ESP32's SPI peripheral to drive a chain of LED matrices dynamically.
The LED matrices have a shift register for the vertical and horizontal control lines, so the cycle is basically:
1. Shift out (row length/2) bits on QIO0 and QIO1 with the bitmap data, while also in QIO2 setting the bits at positions (bit_num % row_number) to 1 to enable the correct row.
2. Stop the clock and momentarily change the LATCH pin state from 1 to 0 then back.
3. Continue with the next row
What I currently do on every frame (at roughly 60 Hz) is queue 17 transactions by DMA (one for each row + one dummy so that the bottom row isn't brighter than the rest due to waiting for the next frame to come). Then in the `post_cb` ISR I do the LATCH change.
The final code is here inside the project: https://github.com/vladkorotnev/plasma- ... i_k875.cpp
However, because of the many other tasks running on the ESP, the timing of this process drifts significantly. Eventually some rows start blinking brighter than the others at random because there is some task that has to be taken care of between the post_cb happens and the next transaction starts.
So I thought to use low-level SPI and circular linked lists similar to that one VGA output project, to never have to bother with the callback and enqueueing the transactions again. For that I would wire the LATCH pin to the currently unused QIO3, and clear that bit in the data array between frames when I need to latch the row.
However even before implementing the circular linked list solution, just trying this approach with the normal SPI and the whole screen in a single transaction, resulted in "noise" next to the active pixels. You can see those are not perfect diagonal lines but have some "jitter".
This seems to be because of the clock signal being still active while the LATCH change is happening with this approach — because the shift register continues to shift even though there is no data being fed and latch is active.
So my question is, can I pause the clock of the SPI easily for just one bus cycle?
P.S. Now that I think of it, maybe a good solution would be to use the clock-on-CS configuration, and send that one single bus cycle to a different dummy device, so that the shift register doesn't see the clock signal. But I'm not sure if it's possible to do multiple devices in a single transaction, neither whether that is doable with the clock-on-CS flag set.
The LED matrices have a shift register for the vertical and horizontal control lines, so the cycle is basically:
1. Shift out (row length/2) bits on QIO0 and QIO1 with the bitmap data, while also in QIO2 setting the bits at positions (bit_num % row_number) to 1 to enable the correct row.
2. Stop the clock and momentarily change the LATCH pin state from 1 to 0 then back.
3. Continue with the next row
What I currently do on every frame (at roughly 60 Hz) is queue 17 transactions by DMA (one for each row + one dummy so that the bottom row isn't brighter than the rest due to waiting for the next frame to come). Then in the `post_cb` ISR I do the LATCH change.
The final code is here inside the project: https://github.com/vladkorotnev/plasma- ... i_k875.cpp
However, because of the many other tasks running on the ESP, the timing of this process drifts significantly. Eventually some rows start blinking brighter than the others at random because there is some task that has to be taken care of between the post_cb happens and the next transaction starts.
So I thought to use low-level SPI and circular linked lists similar to that one VGA output project, to never have to bother with the callback and enqueueing the transactions again. For that I would wire the LATCH pin to the currently unused QIO3, and clear that bit in the data array between frames when I need to latch the row.
However even before implementing the circular linked list solution, just trying this approach with the normal SPI and the whole screen in a single transaction, resulted in "noise" next to the active pixels. You can see those are not perfect diagonal lines but have some "jitter".
This seems to be because of the clock signal being still active while the LATCH change is happening with this approach — because the shift register continues to shift even though there is no data being fed and latch is active.
So my question is, can I pause the clock of the SPI easily for just one bus cycle?
P.S. Now that I think of it, maybe a good solution would be to use the clock-on-CS configuration, and send that one single bus cycle to a different dummy device, so that the shift register doesn't see the clock signal. But I'm not sure if it's possible to do multiple devices in a single transaction, neither whether that is doable with the clock-on-CS flag set.