DMA represents the fastest and easiest way of transferring data between memory and the peripherals.

The STM32F401RE has two DMA-controllers that connects the different peripherals and the memory. In the datasheet we see that DMA2 can connect to the ADC, and that DMA1 can connect to SPI or UART.

That’s incredibly lucky because it means we only need to configure DMA1 and DMA2 once.

It’s also in the datasheet we find information about which DMA-streams serve which peripherals.

DMA2 and ADC
The DMA is set up to transfer the value in the ADC data-register to an array. Since the ADC is 12-bit we need a datasize of 16-bit (ie. halfword) array for the data. The data is right-aligned, so a value like 0x2FF from the ADC is stored in memory as 0x02FF.

For every new conversion the value in the ADC data-register is sent to the next address in the buffer:
pixel_1 -> buffer[0]
pixel_2 -> buffer[1]
pixel_3694 -> buffer[3693]

Once the buffer is full the next value it receives from the ADC goes to buffer[0], because DMA2 is set to circular mode. However DMA2 also generates a transfer-complete interrupt which disables TIM4 – the timer used to trigger the ADC – and TIM4 is not enabled before the TCD1304 outputs its pixels again.

This way each pixel-value always ends up at the same address in the buffer.

DMA1 and SPI
DMA1 is set up in circular mode since the pixel-values are always stored the same place. Because we need to both send and receive at the same time, two DMA-streams are configured.

The data to be transmitted is 16-bit but the raspberry pi’s SPI-controller only supports 8-bit communication, so we tell DMA1 that the datasize is 8 bit (ie. byte). This is not a problem as long as we remember to say that the size of the array is twice as big as before (7388 instead of 3694).

The above causes a small problem related to endianness. It’s explained in the SPI-section.

When buffer is full a transfer-complete interrupt checks the data and sets the new integration time accordingly.

In the case of the UART firmware, communication is of course done over UART. The highest reliable baudrate I’ve been able to achieve with the nucleo’s ST-link is 115.5 kbps. This is slow enough that the DMA-controller isn’t actually needed, but the DMA adds a lot of convenience.

UART reception is setup with DMA1 with a circular buffer of 12 bytes. Because the buffer is circular the nucleo listens continuously on UART2, and an interrupt is triggered everytime the buffer-adress is reset. The interrupt includes a small delay to make sure that all 12 bytes are received before using the data.¹

UART transmission is also handled with DMA, but the transmission is started only if there’s data to send and the pc is ready.

Suggested reading:

Application note AN4031 from ST.

  1. It seems that the ST-link is active on UART2 on power-up, so DMA1 doesn’t always start at buffer[0]. To compensate for this, the received data is sorted, but because UART is slow, the interrupt must wait a short while before sorting to avoid putting old data into the newly sorted array.