DMA module reference#include <dma.h>Makefile :
dma is already included by default
The DMA (Direct Memory Access) controller is a peripheral dedicated to moving data between RAM and peripherals. This is useful to automatically send or receive a buffer of data to or from a peripheral, without CPU intervention, kind of like a background task.
Indeed, most communication peripherals (such as USART, I2C or SPI) work one byte at a time :
This is where the DMA comes in handy. It is internally connected to the receiving/sending registers and the "data received" and "data sent" signals in every peripheral, and is smart enough to copy a new byte every time the signal rises. All you need to do is set up a new DMA channel with a peripheral name, a transfer direction (RAM -> peripheral or peripheral -> RAM), and a buffer address and size allocated in RAM. Once started, it will wait and copy data every time the peripheral is ready, until the buffer is empty (when sending) or full (when receiving). You can check at any time if the transfer is finished using isFinished(), or set up an interrupt using enableInterrupt().
device, see the Device enum in the header below for the list of available devices. size refer to the amount of bytes to be transfered at a time (it depends on the size of the register in the peripheral), and can be BYTE, HALFWORD (2 bytes) or WORD (4 bytes). address and length refer to the buffer in memory where data is copied to/from, which are null by default and usually specified in startChannel() or setupChannel(). If ring is enabled, the address will be reset as soon as the transfer is complete and another transfer will start with the same buffer.
RELOAD_EMPTY, TRANSFER_FINISHED or TRANSFER_ERROR.
startChannel() function below.
setupChannel() must have been called beforehand to configure the channel.
setupChannel() and startChannel() functions above.
startChannel with a buffer AreloadChannelreloadChannel with the buffer A when it is done processing ittrue if the current transfer is finished. This is equivalent to checking if the counter has reach 0.
true if the reload buffer is empty. If one was set with reloadChannel(), this means that the transfer has finished and the reload buffer is now in use. The channel can be reloaded again with reloadChannel().
The DMA controller is pretty straightforward and doesn't allow much customization.
#ifndef _DMA_H_
#define _DMA_H_
#include <stdint.h>
#include "error.h"
// Direct Memory Access
// This module is able to automatically copy data between RAM and peripherals,
// without CPU intervention
namespace DMA {
// Peripheral memory space base address
const uint32_t BASE = 0x400A2000;
const uint32_t CHANNEL_REG_SIZE = 0x40;
// Registers addresses
const uint32_t OFFSET_MAR = 0x000; // Memory Address Register
const uint32_t OFFSET_PSR = 0x004; // Peripheral Select Register
const uint32_t OFFSET_TCR = 0x008; // Transfer Counter Register
const uint32_t OFFSET_MARR = 0x00C; // Memory Address Reload Register
const uint32_t OFFSET_TCRR = 0x010; // Transfer Counter Reload Register
const uint32_t OFFSET_CR = 0x014; // Control Register
const uint32_t OFFSET_MR = 0x018; // Mode Register
const uint32_t OFFSET_SR = 0x01C; // Status Register
const uint32_t OFFSET_IER = 0x020; // Interrupt Enable Register
const uint32_t OFFSET_IDR = 0x024; // Interrupt Disable Register
const uint32_t OFFSET_IMR = 0x028; // Interrupt Mask Register
const uint32_t OFFSET_ISR = 0x02C; // Interrupt Status Register
// Subregisters
const uint8_t MR_SIZE = 0;
const uint8_t MR_ETRIG = 2;
const uint8_t MR_RING = 3;
const uint8_t CR_TEN = 0;
const uint8_t CR_TDIS = 1;
const uint8_t ISR_RCZ = 0;
const uint8_t ISR_TRC = 1;
const uint8_t ISR_TERR = 2;
const uint8_t SR_TEN = 0;
// Error codes
const Error::Code ERR_NO_CHANNEL_AVAILABLE = 0x0001;
const Error::Code ERR_CHANNEL_NOT_INITIALIZED = 0x0002;
// Size constants
enum class Size {
BYTE,
HALFWORD,
WORD
};
// Interrupts
const int N_INTERRUPTS = 3;
enum class Interrupt {
RELOAD_EMPTY,
TRANSFER_FINISHED,
TRANSFER_ERROR
};
// Device constants
enum class Device {
USART0_RX = 0,
USART1_RX = 1,
USART2_RX = 2,
USART3_RX = 3,
SPI_RX = 4,
I2C0_M_RX = 5,
I2C1_M_RX = 6,
I2C2_M_RX = 7,
I2C3_M_RX = 8,
I2C0_S_RX = 9,
I2C1_S_RX = 10,
USART0_TX = 18,
USART1_TX = 19,
USART2_TX = 20,
USART3_TX = 21,
SPI_TX = 22,
I2C0_M_TX = 23,
I2C1_M_TX = 24,
I2C2_M_TX = 25,
I2C3_M_TX = 26,
I2C0_S_TX = 27,
I2C1_S_TX = 28,
DAC = 35
};
struct ChannelConfig {
bool started;
bool interruptsEnabled;
};
const int N_CHANNELS_MAX = 16;
// Module API
int newChannel(Device device, Size size, uint32_t address=0x00000000, uint16_t length=0, bool ring=false);
int setupChannel(int channel, Device device, Size size, uint32_t address=0x00000000, uint16_t length=0, bool ring=false);
void enableInterrupt(int channel, void (*handler)(), Interrupt interrupt=Interrupt::TRANSFER_FINISHED);
void disableInterrupt(int channel, Interrupt interrupt=Interrupt::TRANSFER_FINISHED);
void setupChannel(int channel, uint32_t address, uint16_t length);
void startChannel(int channel);
void startChannel(int channel, uint32_t address, uint16_t length);
void reloadChannel(int channel, uint32_t address, uint16_t length);
void stopChannel(int channel);
int getCounter(int channel);
bool isEnabled(int channel);
bool isFinished(int channel);
bool isReloadEmpty(int channel);
void enableRing(int channel);
void disableRing(int channel);
}
#endif
#include "dma.h"
#include "core.h"
#include "error.h"
#include "pm.h"
namespace DMA {
// Current number of channels
int _nChannels = 0;
ChannelConfig _channels[N_CHANNELS_MAX];
// Interrupt handlers
extern uint8_t INTERRUPT_PRIORITY;
uint32_t _interruptHandlers[N_CHANNELS_MAX][N_INTERRUPTS];
const int _interruptBits[N_INTERRUPTS] = {ISR_RCZ, ISR_TRC, ISR_TERR};
void interruptHandlerWrapper();
int newChannel(Device device, Size size, uint32_t address, uint16_t length, bool ring) {
// Check that there is an available channel
if (_nChannels == N_CHANNELS_MAX) {
Error::happened(Error::Module::DMA, ERR_NO_CHANNEL_AVAILABLE, Error::Severity::CRITICAL);
return -1;
}
// Channel number : take the last channel
int n = _nChannels;
_nChannels++;
// Setup the chosen channel
setupChannel(n, device, size, address, length, ring);
return n;
}
// Create a channel or reuse an existing one
int setupChannel(int channel, Device device, Size size, uint32_t address, uint16_t length, bool ring) {
// If the given channel is negative, create a new one
if (channel < 0) {
return newChannel(device, size, address, length, ring);
}
const uint32_t REG_BASE = BASE + channel * CHANNEL_REG_SIZE;
// Make sure the clock for the PDCA (Peripheral DMA Controller) is enabled
PM::enablePeripheralClock(PM::CLK_DMA);
// Set up the channel
(*(volatile uint32_t*)(REG_BASE + OFFSET_PSR)) = static_cast<int>(device); // Peripheral select
(*(volatile uint32_t*)(REG_BASE + OFFSET_MAR)) = address; // Buffer memory address
(*(volatile uint32_t*)(REG_BASE + OFFSET_TCR)) = length; // Buffer length
(*(volatile uint32_t*)(REG_BASE + OFFSET_MARR)) = 0; // Buffer memory address (reload value)
(*(volatile uint32_t*)(REG_BASE + OFFSET_TCRR)) = 0; // Buffer length (reload value)
(*(volatile uint32_t*)(REG_BASE + OFFSET_MR)) = (static_cast<int>(size) & 0b11) << MR_SIZE; // Buffer unit size (byte, half-word or word)
_channels[channel].started = false;
_channels[channel].interruptsEnabled = false;
// Enable the ring buffer
if (ring) {
(*(volatile uint32_t*)(REG_BASE + OFFSET_MARR)) = address;
(*(volatile uint32_t*)(REG_BASE + OFFSET_TCRR)) = length;
(*(volatile uint32_t*)(REG_BASE + OFFSET_MR)) |= 1 << MR_RING;
}
// Enable transfer
(*(volatile uint32_t*)(REG_BASE + OFFSET_CR)) = 1;
return channel;
}
void enableInterrupt(int channel, void (*handler)(), Interrupt interrupt) {
// Save the user handler
_interruptHandlers[channel][static_cast<int>(interrupt)] = (uint32_t)handler;
// IER (Interrupt Enable Register) : enable the requested interrupt
(*(volatile uint32_t*)(BASE + channel * CHANNEL_REG_SIZE + OFFSET_IER))
= 1 << _interruptBits[static_cast<int>(interrupt)];
// Enable the interrupt in the NVIC
Core::Interrupt interruptChannel = static_cast<Core::Interrupt>(static_cast<int>(Core::Interrupt::DMA0) + channel);
Core::setInterruptHandler(interruptChannel, interruptHandlerWrapper);
Core::enableInterrupt(interruptChannel, INTERRUPT_PRIORITY);
_channels[channel].interruptsEnabled = true;
}
void disableInterrupt(int channel, Interrupt interrupt) {
// IDR (Interrupt Disable Register) : disable the requested interrupt
(*(volatile uint32_t*)(BASE + channel * CHANNEL_REG_SIZE + OFFSET_IDR))
= 1 << _interruptBits[static_cast<int>(interrupt)];
// If no interrupt is enabled anymore, disable the channel interrupt at the Core level
if ((*(volatile uint32_t*)(BASE + channel * CHANNEL_REG_SIZE + OFFSET_IMR)) == 0) {
Core::disableInterrupt(static_cast<Core::Interrupt>(static_cast<int>(Core::Interrupt::DMA0) + channel));
_channels[channel].interruptsEnabled = false;
}
}
void setupChannel(int channel, uint32_t address, uint16_t length) {
// Check that this channel exists
if (channel >= _nChannels) {
Error::happened(Error::Module::DMA, ERR_CHANNEL_NOT_INITIALIZED, Error::Severity::CRITICAL);
return;
}
const uint32_t REG_BASE = BASE + channel * CHANNEL_REG_SIZE;
// Empty TCR and disable the transfer
(*(volatile uint32_t*)(REG_BASE + OFFSET_TCR)) = 0;
(*(volatile uint32_t*)(REG_BASE + OFFSET_CR)) = 1 << CR_TDIS; // Disable transfer
// Configure this channel
(*(volatile uint32_t*)(REG_BASE + OFFSET_MAR)) = address; // Buffer memory address
(*(volatile uint32_t*)(REG_BASE + OFFSET_TCR)) = length; // Buffer length
}
void startChannel(int channel) {
// Check that this channel exists
if (channel >= _nChannels) {
Error::happened(Error::Module::DMA, ERR_CHANNEL_NOT_INITIALIZED, Error::Severity::CRITICAL);
return;
}
// Enable this channel
_channels[channel].started = true;
const uint32_t REG_BASE = BASE + channel * CHANNEL_REG_SIZE;
(*(volatile uint32_t*)(REG_BASE + OFFSET_CR)) = 1 << CR_TEN; // Enable transfer
// Reenable interrupts if necessary
if (_channels[channel].interruptsEnabled) {
Core::Interrupt interruptChannel = static_cast<Core::Interrupt>(static_cast<int>(Core::Interrupt::DMA0) + channel);
Core::enableInterrupt(interruptChannel, INTERRUPT_PRIORITY);
}
}
void startChannel(int channel, uint32_t address, uint16_t length) {
setupChannel(channel, address, length);
startChannel(channel);
}
void reloadChannel(int channel, uint32_t address, uint16_t length) {
// Check that this channel exists
if (channel >= _nChannels) {
Error::happened(Error::Module::DMA, ERR_CHANNEL_NOT_INITIALIZED, Error::Severity::CRITICAL);
return;
}
// Reload this channel
const uint32_t REG_BASE = BASE + channel * CHANNEL_REG_SIZE;
(*(volatile uint32_t*)(REG_BASE + OFFSET_MARR)) = address; // Buffer memory address
(*(volatile uint32_t*)(REG_BASE + OFFSET_TCRR)) = length; // Buffer length
// Reenable interrupts if necessary
if (_channels[channel].interruptsEnabled) {
Core::Interrupt interruptChannel = static_cast<Core::Interrupt>(static_cast<int>(Core::Interrupt::DMA0) + channel);
Core::enableInterrupt(interruptChannel, INTERRUPT_PRIORITY);
}
}
void stopChannel(int channel) {
// Check that this channel exists
if (channel >= _nChannels) {
Error::happened(Error::Module::DMA, ERR_CHANNEL_NOT_INITIALIZED, Error::Severity::CRITICAL);
return;
}
const uint32_t REG_BASE = BASE + channel * CHANNEL_REG_SIZE;
// Disable the channel interrupt line, it will be reenabled when the channel is started again
if (_channels[channel].interruptsEnabled) {
Core::Interrupt interruptChannel = static_cast<Core::Interrupt>(static_cast<int>(Core::Interrupt::DMA0) + channel);
Core::disableInterrupt(interruptChannel);
}
// Disable transfer and empty TCR
_channels[channel].started = false;
(*(volatile uint32_t*)(REG_BASE + OFFSET_CR)) = 1 << CR_TDIS;
(*(volatile uint32_t*)(REG_BASE + OFFSET_TCR)) = 0;
}
int getCounter(int channel) {
// TCR : Transfer Counter Register
return (*(volatile uint32_t*)(BASE + channel * CHANNEL_REG_SIZE + OFFSET_TCR));
}
bool isEnabled(int channel) {
// SR : Status Register
return (*(volatile uint32_t*)(BASE + channel * CHANNEL_REG_SIZE + OFFSET_SR)) & (1 << SR_TEN);
}
bool isFinished(int channel) {
// TCR : Transfer Counter Register
return (*(volatile uint32_t*)(BASE + channel * CHANNEL_REG_SIZE + OFFSET_TCR)) == 0;
}
bool isReloadEmpty(int channel) {
// TCR : Transfer Counter Reload Register
return (*(volatile uint32_t*)(BASE + channel * CHANNEL_REG_SIZE + OFFSET_TCRR)) == 0;
}
void enableRing(int channel) {
// MR : set the RING bit to keep reloading the channel with the same buffer
(*(volatile uint32_t*)(BASE + channel * CHANNEL_REG_SIZE + OFFSET_MR)) |= 1 << MR_RING;
}
void disableRing(int channel) {
// MR : reset the RING bit
(*(volatile uint32_t*)(BASE + channel * CHANNEL_REG_SIZE + OFFSET_MR)) &= ~(uint32_t)(1 << MR_RING);
}
void interruptHandlerWrapper() {
// Get the channel number through the current interrupt number
int channel = static_cast<int>(Core::currentInterrupt()) - static_cast<int>(Core::Interrupt::DMA0);
const uint32_t REG_BASE = BASE + channel * CHANNEL_REG_SIZE;
// Call the user handler of every interrupt that is enabled and pending
for (int i = 0; i < N_INTERRUPTS; i++) {
if ((*(volatile uint32_t*)(REG_BASE + OFFSET_IMR)) & (1 << _interruptBits[i]) // Interrupt is enabled
&& (*(volatile uint32_t*)(REG_BASE + OFFSET_ISR)) & (1 << _interruptBits[i])) { // Interrupt is pending
void (*handler)() = (void (*)())_interruptHandlers[channel][i];
if (handler != nullptr) {
handler();
}
// These interrupts are level-sensitive, they are cleared when their sources are resolved (e.g.
// the registers are written with non-zero values)
}
}
}
}