How to interface an external 18-bit ADC over SPI with DMA on a STM32 - Page 1
Forum section: electronics / general

author: sled
author_rank: Contributor
timestamp: August 24, 2017, 11:19:42 am
content: Hey there,I'm currently investigating how to efficiently connect an 18bit ADC (AD7691) to an STM32L432KC microcontroller. The ADC uses SPI to transmit the data, where the ADC is the slave and the STM32 is the master. Preferrably I'd like to use DMA to keep the load low, so reading the ADC in an interrupt is not an option.Unfortunately I haven't found much information about how to setup DMA with SPI frames larger than 16bit. I think the biggest problem is that I only get one busy indicator (interrupt) before the transmission of the 18bits (see attached timing diagram), so I can't trigger two 9bit DMA transfers from the SPI peripheral easily.Could you give me any pointers how to read out the ADC efficiently maybe someone has done this before?

author: JPortici
author_rank: Super Contributor
timestamp: August 24, 2017, 01:03:11 pm
content: manual says spi can do 16 bit only so you are still screwed...i don't know if you can do something like this, i did it on PIC32/dsPICs. It relies on the SPI to have a transmit fifo buffer.let's say you do two 16 bit transferts to have the result alinged by 32 bit.-Output compare peripheral generates CS signal and triggers one DMA, this DMA will transfer "zero" or whatever random data TWO times, so the SPI peripheral will generate a total of 32 clock pulses-Other DMA is triggered by SPI end of transfert, takes the data from SPI in and puts it in ramthis however will use both the DMA peripherals on your MCU

author: andersm
author_rank: Super Contributor
timestamp: August 24, 2017, 01:53:42 pm
content: Can't you just set up a DMA transfer for 3*8 bits, or 2*16 bits? From the datasheet page you posted it doesn't look like the ADC minds seeing a few extra serial clocks.

author: sled
author_rank: Contributor
timestamp: August 24, 2017, 04:10:51 pm
content: Maybe I need to give a broader overviewMy goal is to have a sample buffer of let's say 100 18-bit samples, when the buffer is full I want to get an interrupt to process the samples. While the buffer is filling up I want to do other tasks, therefore I use DMA. I can configure the SPI peripheral to transfer 9bits, so per sample I need to trigger two SPI transfers. In the "buffer full" interrupt I can stitch those samples together. At the core of this is a timer which pulses the ADC's conversion pin to initiate a sample at a fixed rate.All I can think of is to construct a timer tree to get the attached timing diagram, not sure if this is the best solution to the problemIf there's a STM32 timer expert here, I'm glad for any input on how to create such a timing.

author: andyturk
author_rank: Frequent Contributor
timestamp: August 24, 2017, 06:32:14 pm
content: Quote from: sled on August 24, 2017, 04:10:51 pmI've been working with a LTC ADC that works similarly, although that one has a separate BUSY signal. Your part uses the SDO pin for that, which might make things more complicated. If you're going to use the 7691 BUSY signal, you'll want an interrupt for that. But you also need that pin to be configured (on STM32) as an AFx for your SPI peripheral. Will EXTI let you interrupt on a pin that's configured for an alternate function?Maybe better to just measure the conversion time experimentally, and use a timer to give you reasonable margin.I don't think you need to "trigger two SPI DMA transfers". You'll only need one DMA transfer operation that grabs both 9-bit words. I.e., the DMA count will be 2 (which hardly seems worth the trouble of using DMA, IMO).It'd be cleaner to use a timer to generate the overall sample timing and then a state machine that wakes up on the SPI's SR.RXNE interrupt to glue your two 9-bit words together and store them somewhere.I think a single advanced timer will suffice. You can use one output channel to generate the conversion edge, another channel to wait for the conversion (your tCONV) and throw an interrupt that you can use to initiate the SPI transfer. The STM32 advanced timers have this thing called a "repetition counter" that you can use to generate 100 cycles automatically with precise timing and then generate a different interrupt when it's done. The repetition counter is only 8-bits wide, so you can do at most 256 conversions in one go.This approach gets pretty heavily into the STM32 timer details (which can be frustrating), but it may be worth it for your application.

author: hans
author_rank: Super Contributor
timestamp: August 24, 2017, 07:58:58 pm
content: Have you tried perhaps that a DMA burst read will work?Although I expect it will probably generate an underrun event in the SPI peripheral.Also, handling interrupts at 250ksps is no joy. I couldn't manage to do so reliably with a 500ksps 16-bit on a 168MHz STM32, because entering/leaving ISR takes considerable amount of time, and then there is also the time it takes to complete a transfer despite very high SPI bitrates.

author: sled
author_rank: Contributor
timestamp: August 24, 2017, 09:34:55 pm
content: Quote from: andyturk on August 24, 2017, 06:32:14 pmMy plan is to fill a sample buffer with ~100 samples with the DMA and stitch the 9bit samples together in the "buffer full" interrupt.Quote from: andyturk on August 24, 2017, 06:32:14 pmI think this goes in the right direction, but I want to avoid the interrupt... It's possible to trigger a SPI DMA transfer from a timer channel though - maybe that could be an option. If I use double buffering I could run this pattern forever without a problem... I'm just not sure whether one timer can do all this tasks at once, i.e one channel for the conversion pulse, one channel for the first SPI transfer and another channel for the second transfer.

author: andyturk
author_rank: Frequent Contributor
timestamp: August 25, 2017, 03:29:09 am
content: I'm not sure if you can kick off a SPI transaction directly from a timer. Some of the STM32 timers have TRGO (trigger out) functionality, but those are usually hooked up to ADCs, DACs and other timers.If you're desperate to avoid interrupts, you can also "bit bang" SPI via DMA, one of TIM1/TIM8 and a GPIO port. Use one of the timer output channels as your clock signal, and then use DMA to read one bit from a GPIO port on whichever edge you need (using another of the timer channels with the phase you need). It's a pain in the neck, but works reasonably well if you have a dozen SPI ADCs to read simultaneously.The next trick is to stitch the bit stream to actual integers. In your case, that'd be something like 18 x 100 16-bit words (if you read and store the whole port).

author: sled
author_rank: Contributor
timestamp: August 25, 2017, 10:44:28 am
content: I think I found a possible solutionI digged into the 1000+ pagereference manualand found this about the SPI peripheral on page 1135:QuoteThis sounds very promising but I need to test this whether the ADC minds seeing the SPI clock signal constantly

author: dgtl
author_rank: Regular Contributor
timestamp: August 25, 2017, 12:44:27 pm
content: The DMA is nothing special. It just reads/writes the peripheral data registers the same way the CPU does (look at the block diagram, the DMA master interface connects to the bus matrix the same way as the CPU). The only addition is a pair of ready signals coming directly from peripheral to DMA controller, that trigger the DMA controller to do its work.You have to look at the SPI DMA as two separate DMAs:1) RX: SPI notifies that there is RX data at its DR. DMA comes and fetches it.2) TX: SPI notifies that there is room in its TX DR, DMA comes and fills it. The SPI starts sending AND receiving if there is something to send.For the RX side, you just have to set up one large DMA transfer for the whole buffer. As something arrives in RX data buffer, it gets DMAd to buffer.For the TX side, you need to figure out some way to write the 19 bits of 1-s to DR. As the DR is 16-bit, this implies 2 transfers. At least the DR has FIFO behind it, so you can just dumbly write 2 words and thats it. These 2 words can be at a location in RAM and a circular dma could just copy them.The difficult part is how to perform 2 DMA writes on one request and then wait for another request. As the 2 transfers fit into 32 bits, it could be possible to have the DMA read one 32-bit word and write 2 16-bit words out. As the DMA request clocks the "peripheral" side of dma (one request = one transfer in peripheral side, either read or write), it could be possible to swap the "peripheral" and "memory" sides (swap the pointers and direction), perhaps the DMA is willing to do this one memory read from the request and then 2 writes to actual SPI DR?The DMA channel you are using does not have to be triggered by the same peripheral. For example, I've used a UART RX DMA request to trigger to copy a value from timer data register to RAM for timestamping purposes. The difficult part comes is in the receiving of the busy signal from ADC. If it were a separate busy pin, it would be much simpler. Now you need to filter out the received data pulses. Perhaps wiring the ADC SDO in parallel to a timer input and using the one pulse mode of timer to trigger a DMA and then wait a bit to avoid retriggering while the SPI is working.

author: sled
author_rank: Contributor
timestamp: August 25, 2017, 01:02:40 pm
content: Quote from: dgtl on August 25, 2017, 12:44:27 pmThe STM32L4 series supports variable SPI transfer sizes, such as 9bit, so it's possible to write a 16bit value into the register and only clock out only 9bits. Do I really need the busy indicator or could I just use a timer with the maximum conversion time to initiate the transfer? My idea is to have a timer with two compare outputs, one after the maximum conversion time to transfer the first 9bits, and then a second channel which follows after the maximum conversion time + 9 SPI clock cycles to start the second transfer of 9bits.I think the RXONLY master mode could be the optimal solution, as it generates a continous SPI clock. The datasheet is however a bit unclear... It says the RX FIFO can't be used in RXONLY mode so I'm not sure whether the RXNE (fifo not empty) event is triggered, which is needed by the DMA. I plan to do a mock setup on the weekend using two microcontrollers, one simulating the ADC and the other reading from it.

author: andyturk
author_rank: Frequent Contributor
timestamp: August 25, 2017, 02:35:19 pm
content: Quote from: sled on August 25, 2017, 01:02:40 pmIt would have to generate RXNE, right? How else would you know to read the DR?Another possibility is to run the SPI in RXONLY slave mode and use a timer (or two) to generate the SPI clock and conversion signals. That would change the pin mapping and you'd have to physically connected the timer output to the SPI clock input.

author: sled
author_rank: Contributor
timestamp: August 25, 2017, 02:59:43 pm
content: Quote from: andyturk on August 25, 2017, 02:35:19 pmThe timer to pulse the SPI clock is certainly an option, layoutwise I'm free. I plan to test it this weekend with another MCU simulating the ADC, before ordering the part at 25$ a pieceThe RXNE thing is a bit weird, here are some excerpts from the data sheet:QuoteWhich means, there is no FIFO for RXONLY mode. Further, the datasheet says the RXNE event is dependent on the FIFO.QuoteTo use the DMA I need the RXNE flag though:QuoteThis makes me wonder whether DMA with RXONLY is even an option

author: dgtl
author_rank: Regular Contributor
timestamp: August 25, 2017, 06:22:49 pm
content: Quote from: sled on August 25, 2017, 01:02:40 pmCorrect. The register is connected to 16-bit APB bus, so the writes are always 8-bit or 16-bit. For the 18-bit data, you have to use 2x9-bit transfers, so 16-bit writes with unused bits at the top.Quote from: sled on August 25, 2017, 01:02:40 pmAs the conversion is triggered from uc (CONV input) and is not free running, you can do without the busy indicator.If you are using the SPI CS as the CONV input, you have to write both words so closely that the CS would not switch inbetween. Alternatively, another timer compare output can create the CONV pulse as well. This seems the best option, use one timers one compare output as CONV pulse and another for triggering DMA. It might work with one 32-bit reversed DMA write; if not you need 2 DMAs to write the upper and lower 9 bits. It would be better to use the FIFO and write both as closely as possible to each other.Quote from: sled on August 25, 2017, 01:02:40 pmWhy do you need the continuous clock? It makes things much more harder. The ADC is using its internal clock, the datasheet says it does not need clocking except for reading out data. For continuous clock it is possible to mess up the word boundaries of both sides. Use just regular SPI. If MOSI is not needed, don't pinmux it. The SPI will transmit and receive 9 words every time you write the DR, the written data may be thrown away. You can set up a circular DMA to write data from one memory address over and over to the DR, on every DMA request one word is written to DR.

author: sled
author_rank: Contributor
timestamp: August 25, 2017, 08:12:50 pm
content: Quote from: dgtl on August 25, 2017, 06:22:49 pmAs far as I understand in the "receive only" mode the master just "listens" for any incoming bits on the MISO. The ADC does not care whether it sees a SPI clock all the time, when conversion is done it will clock out the 18bits when it sees a SPI clock. The clock does not need to be turned off between conversions, at least the datasheet suggests that. Question is whether the DMA works with the receive only mode.I'd be interested to know why you would think that the continous SPI clock will mess up the word boundaries, what's the difference between seeing a continous SPI clock vs suddenly turning on the SPI clock for a single transfer (from the ADC's perspective).I'm going to try both methods (timer triggered transfers, continous SPI receive only) this weekend

author: mark03
author_rank: Frequent Contributor
timestamp: August 28, 2017, 05:31:20 am
content: The 16-bit SPI limitation is a perennial pet peeve. ST can stuff an entire USB implementation in there but they still can't find the transistors for a slightly longer shift register(That, and their decision to make the DFSDM data path only 32 bits wide, severely hindering the usefulness of their shiny new peripheral.)Anyway, one more option you might consider is running the MCU SPI in slave mode. You can use the timer repetition counter to generate N pulses (in your case 24) at the desired clock frequency every time it sees a rising or falling edge on one of the timer input pins. That timer input gets driven by your ADC ready signal, and the timer output drives the SPI clock for both the MCU and the ADC. I did this mainly because of the higher speed limit (40 MHz) in slave mode on the STM32L4.

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: August 28, 2017, 05:58:14 pm
content: Quote from: mark03 on August 28, 2017, 05:31:20 amYes, this is a very annoying limitation. It's bitten me many times over the years, and I've had to implement some nasty workarounds.Recently I've started to use Infineon XMC4000 parts and these don't have as many petty limitations on the peripherals. The SPI peripheral can handle up to 63 bit frame sizes, and even more with a little extra work in the driver. Ditto for the UARTs, although I've never needed a UART with a 63 bit data frame, it's nice to know it can handle it if I need it.