In the previous article, we examined how to improve the efficiency of an analog to digital converter (ADC) driver by converting from a polled driver to an interrupt driven driver. Using interrupts allows the application code to continue executing without having to wait on the driver to complete its operation. This can dramatically improve the application’s real-time performance. Despite these improvements, developers can still take their driver to the next level by uses a direct memory access (DMA) controller. In this article, we are going to explore how we can use DMA to further improve performance.
Why use the DMA Controller?
The DMA controller is a hardware peripheral that can be programmed to move data around a microcontroller with minimal CPU usage. DMA controllers are broken up into channels, each of which can be programmed to handle a desired data transfer. The DMA controller can transfer data around the microcontroller from one memory location to another almost anywhere within the memory map (depending of course how the MPU is configured). To give you an idea of how data can be moved, the following configurations are commonly used in embedded applications:
- Memory location to memory location (For example, from one RAM location to another)
- Memory location to peripheral (For example, into UART, SPI, Ethernet, etc)
- Peripheral to memory location
I mention that the DMA uses minimal CPU usage because the CPU does need to setup and configure the DMA controller. Depending on the setup, the CPU may also need to process DMA interrupts occasionally. Otherwise, the CPU is completely hands-off. (I also mention it because a lot of literature says that the DMA doesn’t require the CPU at all which isn’t 100% accurate.)
A CPU that doesn’t have to be actively involved in the transfer can also be used in several different ways. First, it could be used to continue executing code while the data transfer is taking place. This allows more to get done in less time. Second, the CPU could be put into a low power state. For a typical battery-operated device, the CPU is usually one of the largest energy consumers in the system. Leveraging the DMA controller can allow the CPU to be placed into a low power state which can dramatically decrease the system’s energy consumption.
ADC Strategies for using DMA
How the DMA will be used with an analog driver will depend on the end application. DMA is best utilized when the application requires a continuous stream of data to be collected. For example, a great application to use a DMA channel with is an audio application. A microphone can be sampled automatically by the ADC which then kicks off a DMA transfer to move the data into a circular buffer. Once the buffer is filled, a DMA complete interrupt would fire, allowing the CPU to come and process the data or do whatever it is that needs to be done.
One very important point to recognize is that the DMA controller has a limited number of channels. For example, a resource constrained microcontroller may only have three DMA channels whereas a feature rich microcontroller may have a dozen or more. Channel availability makes it absolutely critical that developers carefully decide when and where to use their DMA channels the most effectively.
One strategy that we can use when designing our DMA driver is to continuously trigger. In this case, we may have several options on how we store our data. First, we could create two separate buffers. Once the first buffer is filled, the DMA channel starts to fill the second while the processor processes the first buffer. Once completed, the processor waits for the second buffer to fill and then processes that buffer while the first one is filled by the DMA channel. This is often referred to as a ping-pong buffer. In some microcontrollers, the DMA controller can fire an interrupt when a buffer is half filled and when it is completely filled. This allows us to create a ping-pong buffer in a contiguous buffer which can dramatically simplify the buffer design and minimize CPU interactions with the DMA controller!
A second strategy is using the DMA controller in a one-shot mode. In this case, the CPU or a background timer may kick-off an ADC sample which then triggers the DMA controller to transfer the results. In this case a ping-point buffer could be used, but a circular buffer could also be an option.
Configuring DMA for ADCs
The details on how to integrate DMA into an ADC driver will vary dramatically based on the processor that is being used. In this article, I thought I would demonstrate how easy this is becoming using toolchains provided by the microcontroller vendor. As an example, I will use ST Microelectronics STM32CubeMx tool which is designed to ease the effort required to configure hardware features and peripherals.
Within the toolchain, developers can create a new project for their microcontroller which then provides them with some menus to configure the device. Under the analog tab, under the ADC peripheral, developers can select the ADC channels that they want to convert in addition to navigating through the parameter settings to configure the analog channel for speed, resolution and other typical parameters that are associated with an ADC.
The settings that we are most interested in are the DMA settings. From directly in the toolchain, we can add a DMA request which by default sets the data transfer direction to “Peripheral to Memory” along with setting the memory to automatically increment after each transfer. Developers can even change the mode settings from normal to Circular if they so desire. An example configuration can be seen in the screenshot below.
(Source: Jacob Beningo)
I think it’s also important to note that the STM32F091 processor I selected has two DMA controllers available, one with seven channels and the other with five. On top of that, when it came to mapping peripherals, only certain channels could be mapped to certain peripherals. For example, the ADC peripheral could only be mapped to the following:
- DMA1 Channel 1
- DMA1 Channel 2
- DMA2 Channel 5
In an application, again we must be careful how we map our DMA controller to ensure we have enough channels. (Although this part has quite a few options for being a more resource constrained device.)
From within the toolchain, a developer can then save their project and generate their code which puts in place all the hooks to use the DMA controller to pull ADC data into their application. Based on the application, it may be necessary to add user code to the DMA interrupts or further enhance the generated code to meet the desired application.
This is just one example, but there are plenty more from other vendors as well. (For full disclosure, I chose this part and manufacturer for this article because I’m currently working on a design project with this part and the tools and code are the freshest in my mind at the moment.)
As we have discussed in this article, developers can use a DMA controller with their ADC in order to efficiently transfer the converted ADC data from the ADC peripheral into memory. This has the benefit of further streamlining the ADC driver to make it more efficiency. While this can be extremely helpful in applications that require acquiring analog data in a continuous and rapid rate, developers do need to balance their use of DMA channels as they are often limited. This is becoming less of an issue with modern processors but can still be an issue with teams working with legacy microcontrollers.
Jacob Beningo is an embedded software consultant, advisor and educator who currently works with clients in more than a dozen countries to dramatically transform their software, systems and processes. Feel free to contact him at firstname.lastname@example.org, at his website www.beningo.com, and sign-up for his monthly Embedded Bytes Newsletter.