I'm working on a device that measures some analog sensors and then updates an LCD display in real-time with a STM32F103. The display is driven through SPI at high speed (36MHz), which generates a fair amount of noise. Board layout moderates the SPI noise, but doesn't eliminate it entirely, so I need to find a way to ensure that the SPI lines are quiet while the ADC is reading data.
The ADC readings have to be accurately timed in order to do some frequency domain analysis. The ADC sample rate is a manageable 10kHz, and with only 4 channels to worry about, there's plenty of time to do other things between samples.
I've got things working at the moment by driving the ADC from a GPT callback and then synchronously updating the display after a sufficient number of samples have been read. The ADC code is interrupt driven and broadcasts an event to trigger a separate display thread. Unfortunately, the signal being measured can go down to 4Hz meaning the device has to wait without updating the display for a long time in order to capture an entire cycle.
What I'd really like is to be able to interleave periodic ADC readings (short bursts) with SPI activity (comparatively longer). Conceptually, this would be like disabling the SPI peripheral at the beginning of each sample and re-enabling it at the end. However, my understanding is that the SPI hardware will finish whatever it's sending if it's disabled part-way through, potentially corrupting the initial reading(s) for that data sample. I'm also not sure how the SPI and it's DMA channel will respond to be started and stopped that way.
Another implementation strategy would be to essentially pull the display updating code *into* the ADC callback so that the SPI activity always happens during the "dead time" between ADC readings. That's going to make for some very unwieldy interrupt code and I'm hoping that isn't the best solution.
Have other people dealt with this kind of problem before? If so, how did you solve it?
Locking out SPI while the ADC collects data
Moderators: RoccoMarco, lbednarz, tfAteba
- Giovanni
- Site Admin
- Posts: 14891
- Joined: Wed May 27, 2009 8:48 am
- Has thanked: 1202 times
- Been thanked: 996 times
Re: Locking out SPI while the ADC collects data
One possible solution would be to activate the SPI transfer from the ADC callback and start a new ADC conversion from the SPI callback, this way the two peripherals activity would never overlap.
If you don't want the complexity of callbacks you may use a dedicated thread and use the synchronous variants of ADC and SPI APIs.
Giovanni
If you don't want the complexity of callbacks you may use a dedicated thread and use the synchronous variants of ADC and SPI APIs.
Giovanni
Re: Locking out SPI while the ADC collects data
I think what you've described is pretty close to what I've got now. I.e., the code is arranged so that the ADC code only runs between display updates. It works, but it makes a mess of any frequency analysis of the ADC data because it starts and stops at effectively random intervals. What I hoping to achieve is regular ADC samples at 10kHz (thus making frequency domain analysis easy) with the display SPI activity sandwiched between the samples.
The tricky part is that one display update spans about 100 ADC sample times.
What I'm experimenting with now is disabling the SPI DMA before each ADC sample and then re-enabling it if necessary afterwards. Hopefully, this will allow the current SPI transaction to complete normally and then gracefully stall. I can pad the ADC instructions with a couple of dummy acquisitions to last long enough to skip the SPI transaction already underway. Unfortunately, I haven't actually gotten SPI to start up again (i.e., the board locks up each time), but I have hope (and a large pot of coffee).
It seems there are two places to disable SPI DMA activity. One is in SPI_CR2 with TXDMAEN/RXDMAEN, and the other is the EN bit of the DMA_CCRx. I'm working on the DMA_CCRx version now since setting the EN bit should restart the SPI peripheral after the ADC is done. My reading of the datasheet is that clearing the ?XDMAEN bits will stop future DMA but setting them won't actually start up the SPI where it left off.
This is STM32 specific stuff, well below the level of ChibiOS, but I'd be grateful for any advice...
The tricky part is that one display update spans about 100 ADC sample times.
What I'm experimenting with now is disabling the SPI DMA before each ADC sample and then re-enabling it if necessary afterwards. Hopefully, this will allow the current SPI transaction to complete normally and then gracefully stall. I can pad the ADC instructions with a couple of dummy acquisitions to last long enough to skip the SPI transaction already underway. Unfortunately, I haven't actually gotten SPI to start up again (i.e., the board locks up each time), but I have hope (and a large pot of coffee).
It seems there are two places to disable SPI DMA activity. One is in SPI_CR2 with TXDMAEN/RXDMAEN, and the other is the EN bit of the DMA_CCRx. I'm working on the DMA_CCRx version now since setting the EN bit should restart the SPI peripheral after the ADC is done. My reading of the datasheet is that clearing the ?XDMAEN bits will stop future DMA but setting them won't actually start up the SPI where it left off.
This is STM32 specific stuff, well below the level of ChibiOS, but I'd be grateful for any advice...
Re: Locking out SPI while the ADC collects data
Here's an illustration of the problem...

The display is showing a 10 millivolt peak-to-peak square wave as sampled by the ADC. The high and low levels are a little crunchy, but I think that's a limit of my signal generator (10mV is a pretty small signal).
The problem shows up as the large downward spikes that seem to be about 20mV from where the actual signal is. These spikes are crosstalk coming from the digital SPI signals for updating the display. There's probably some inductive coupling between the fast square waves used by the SPI interface and the analog lines which (unfortunately) have to run right underneath the display. There may also be some noise on the power rails, but I've separated VDDA/VSSA from the digital side, so hopefully that's minimized.
20mV may not sound like a lot, but that's nearly 5 bits of error from a 12-bit ADC at 3.3V. If I can prevent the SPI from blasting out this noise while the ADC is collecting data, those spikes will go away.

The display is showing a 10 millivolt peak-to-peak square wave as sampled by the ADC. The high and low levels are a little crunchy, but I think that's a limit of my signal generator (10mV is a pretty small signal).
The problem shows up as the large downward spikes that seem to be about 20mV from where the actual signal is. These spikes are crosstalk coming from the digital SPI signals for updating the display. There's probably some inductive coupling between the fast square waves used by the SPI interface and the analog lines which (unfortunately) have to run right underneath the display. There may also be some noise on the power rails, but I've separated VDDA/VSSA from the digital side, so hopefully that's minimized.
20mV may not sound like a lot, but that's nearly 5 bits of error from a 12-bit ADC at 3.3V. If I can prevent the SPI from blasting out this noise while the ADC is collecting data, those spikes will go away.
- Giovanni
- Site Admin
- Posts: 14891
- Joined: Wed May 27, 2009 8:48 am
- Has thanked: 1202 times
- Been thanked: 996 times
Re: Locking out SPI while the ADC collects data
Hi,
You may try to slow down the SPI and program the SPI I/O to a slower setting, there are 4 possible speed setting on the F4. It is possible this would reduce the noise. The PAL driver has the macros to be used for speed settings, OR them with the I/O mode.
Giovanni
You may try to slow down the SPI and program the SPI I/O to a slower setting, there are 4 possible speed setting on the F4. It is possible this would reduce the noise. The PAL driver has the macros to be used for speed settings, OR them with the I/O mode.
Giovanni
Re: Locking out SPI while the ADC collects data
Giovanni wrote:You may try to slow down the SPI and program the SPI I/O to a slower setting, there are 4 possible speed setting on the F4. It is possible this would reduce the noise. The PAL driver has the macros to be used for speed settings, OR them with the I/O mode.
I might end up going that way as a last resort. But it sure seems like I should be able to prevent the SPI and ADC from overlapping. Here's the scope showing the time when the ADC is active:

That's about 13usec of "quiet" with 137usec left over for SPI (the base sample rate is ~6kHz). That's gotta be possible, right?
I tried hacking around with this yesterday (a fine way to spend Christmas
Code: Select all
void NoiseInterlock::begin_quiet() {
if (spi.dmatx->channel->CNDTR > 0) {
spi.dmatx->channel->CCR &= ~STM32_DMA_CR_EN;
spi.dmarx->channel->CCR &= ~STM32_DMA_CR_EN;
}
}
void NoiseInterlock::end_quiet() {
if (spi.dmatx->channel->CNDTR) {
size_t completed = tx_len - spi.dmatx->channel->CNDTR;
spi.dmatx->channel->CMAR += completed;
tx_len -= completed;
}
if (spi.dmarx->channel->CNDTR) {
size_t completed = rx_len - spi.dmarx->channel->CNDTR;
spi.dmarx->channel->CMAR += completed;
rx_len -= completed;
}
spi.dmatx->channel->CCR |= STM32_DMA_CR_EN;
spi.dmarx->channel->CCR |= STM32_DMA_CR_EN;
}
The idea is that the ADC readings are bracketed by begin_quiet() and end_quiet(). I was hoping that simply re-enabling the dma channels would allow start the transfer up again (after adjusting CMAR to account for data already transferred).