Triggering DMA from timers (or GPIO) on the SAM E70
The SAM E70 is still a powerful MCU despite being released around 2015. It has a 300 MHz Cortex-M7, 150 MHz bus system and high-speed USB. High-speed USB (not the same thing as USB 2.0) is pretty rare in Cortex-M devices. It is usually the domain of much more powerful Cortex-A chips. When developing a data acquisition system on the SAM E70 I was surprised that there is no obvious way to trigger a DMA transfer from a timer!
Triggering DMA from a timer is a must if you have to do repetitive tasks with minimal jitter. For example triggering an external sensor or reading from an ADC. Of course you could do that in a timer interrupt handler. However, that would put load on the CPU which is undesirable when done at hundreds of kHz. Even with the interrupt handler running at the highest priority (and nesting in other handlers) there would be some jitter depending on the bus state, cache state, interrupt tail chaining etc.
Modern MCUs like the EFM32 (or even XMEGA) have a facility to connect various peripheral events to each other. Timers and DMA are an obvious combination. However, the SAM E70 lacks such features. The only DMA trigger signal I could find in the datasheet that had anything to do with timers is timer capture. Timer capture is only available on some GPIOs and needs an external signal so how can you use it to generate periodic DMA transfers? By outputting PWM on one pin and looping it back to the capture pin!
Overview
SAM E70 timers have multiple channels so one timer is enough. One timer channel is configured to output a PWM signal at the desired frequency. Second timer channel is configured in input capture mode. Pins PA0 and PA15 are connected on the PCB. The DMA reacts to the capture event. Easy. 🙂
In case the transfer should be started by an independent external signal (like an IRQ from a sensor) only the timer capture channel is needed.
Let's dive into the details...
Timer configuration
Timers in the SAM E70 are mostly the same. All channels are similar and behave pretty independently. Any combination of timers and channels should be okay. Of course the appropriate TIOxx timer signals must be available on outside pins.
The first step is to enable clocks for the the timer channels:
1 2 |
|
Pin muxing
Timer TIOxx pins have to be connected to the pins. In my case they were available on PA0 and PA15 but any other option could also work. If you are using libraries from Atmel START the code looks like:
1 2 3 4 |
|
PWM channel
This is the easiest part. Five lines of code and there should be a square wave on the output pin.
I picked MCK (150 MHz bus clock) divided by 32 as the timer clock. Any other source can also be used.
tick_rate_Hz
is of course the PWM frequency in Hz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Capture channel
Capture channel configuration is also easy. Timer channel clock can be anything but if it is slower than the PWM channel there will be a slight delay in the input capture. There should be no extra jitter as both channels operate from the same synchronous MCK clock. If you use totally different clock sources for the two channels there will be some jitter due to synchronization.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
DMA configuration
SAM E70 has a single DMA controller called XDMAC
with multiple channels. Every channel can do a different operation
but because there is only one controller all operations will be done sequentially anyway. There are various
schemes to switch between active channels like round robin or fixed priorities. Every channel can also
generate an interrupt when the operation is done.
DMA descriptors
DMA controllers come in all shapes and sizes but in every case the configuration must contain at least the same basic elements:
- source address
- destination address
- size of burst (byte/word/half word)
- number of transfers
- trigger signal and flow control by a peripheral
Controllers in smaller chips only use registers to configure the transfers. The ones in larger chips use descriptors to specify the operations. In the SAM E70 descriptors are fetched from regular RAM. From the C point of view they can be treated just like regular structs with a special format that is understood by the DMA hardware. There are several kinds of descriptors supported by this DMA controller. I found type 2 the most useful.
I could not find any predefined descriptor structs in Atmel's headers so I made my own type 2 descriptor struct that looks like this:
1 2 3 4 5 6 7 |
|
A killer feature is the ability to link descriptors together to execute a sequence of independent
transfers. Descriptor can hold the address of the next descriptor to start (the NDA
field).
It is important to discern between:
- DMA channel
- DMA descriptor
- DMA trigger signal
Example
Transfer a chunk of 16 bytes from RAM to the UART at 100 Hz.
There are two independent operations here so there must be at least two descriptors.
- Operation 1 is the (abstract) timer running at 100 Hz.
- Operation 2 is the transfer of 16 bytes from RAM to the UART but only after the timer delivers an event.
Because these two operations must happen in sequence they have to use the same channel. The first operation waits for the timer trigger signal. The second operation waits for UART TX data register empty trigger signal. There are two operations that use different source/destination addresses and different trigger signals so there have to be two linked descriptors. If the operation is supposed to run continuously the last descriptor must link back to the first descriptor.
Timer capture descriptor
I discovered that the timer in capture mode can generate continuous DMA trigger signals only when the captured value
is actually read from the timer. It is not as simple as just using the XDMAC_CC_PERID_TC0
signal for any DMA
channel. The DMA needs to do a dummy read from the timer. This means that there is a need for an extra
descriptor that will only do a read from the timer to any location in RAM.
This is the descriptor that reads timer capture value. Again, I was not able to find any #defines
for
UBC register so I had to write raw bits based on the datasheet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Even though the descriptor does not change it can not have the const
attribute because it must
be located in RAM for fast access. Accessing it from flash would be much slower.
Another important thing in the SAM E70 DMA is the AHB port number. There are two bus ports and they have to be carefully selected depending on source/destination.
This is the exact table from the datasheet. You can see that some combinations can work with any port.
Starting the transfer
I declared a convenient constant that is used whenever the first descriptor is configured in the controller. In a heavily loaded system (or when power consumption is critical) it may be beneficial to skip some operations. For example: skip reloading the source address when reading data from a peripheral to two independent buffers in RAM. In my case I want to reload all settings when changing descriptors.
1 2 3 4 5 |
|
Starting is as simple as telling the controller where the descriptor is, its characteristics, and setting the channel enable bit.
1 2 3 4 5 6 |
|
Adding a second descriptor
To get anything useful done you can create your own descriptor (for example doing a UART transfer or another operation)
and link it to dma_timer_capture_desc
. For continuous operation the NDA
field in dma_timer_capture_desc
should point to the second descriptor and the NDA
field of the second
descriptor should point back to dma_timer_capture_desc
. 🙂
Diagrams were made with Ditaa.