Blink with DMA

Now that i explained the Tiva DMA let's try to do something with it. This is a very simple code to blink 8 LEDs. It's not exactly something that the DMA is needed but it's just to look into the API before doing something more complexThis example was made using IAR Workbech free version with 32Kb code limit.

Defines and needed global arrays:

This is to chose wich GPIO you want to use to blink

#define GPIO_BASE_OUTPUT1 GPIO_PORTN_BASE #define GPIO_PERIPH_OUTPUT1 SYSCTL_PERIPH_GPION

Now this part is important. The DMA configuration is saved in a array wich needs to be 1024 in size. It can be smaller depending on how you use the DMA but let's keep it like that. This has options for various compilers. You just need 1 array, all the DMA chanels are controled from here.

#if defined(ewarm)#pragma data_alignment=1024uint8_t DMAcontroltable[1024];#elif defined(ccs)#pragma DATA_ALIGN(DMAcontroltable, 1024)uint8_t DMAcontroltable[1024];#elseuint8_t DMAcontroltable[1024] __attribute__ ((aligned(1024)));#endif

This is the array that has the pins state, for now it's simple with just size 2 having 0xFF and 0x00 saved in it. You can control each individual pin by changing the data. 0xFF means all are on and 0x01 means that only pin 0 is on.

static uint8_t OutputState[2];

Of course we start by seting up the frequency and save it in a global variable:

g_ui32SysClock = MAP_SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480), 120000000);

void InitTimer()

Now let's set up the Timer for the triggering. We set up Timer3A as a periodic timer with a load value that is equivalent to 1 second with the MCU at 120Mhz

SysCtlPeripheralDisable(SYSCTL_PERIPH_TIMER3); SysCtlPeripheralReset(SYSCTL_PERIPH_TIMER3); SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER3); SysCtlDelay(10); TimerConfigure(TIMER3_BASE, TIMER_CFG_PERIODIC); TimerLoadSet(TIMER3_BASE, TIMER_A, 120000000-1);

Here is the DMA part:

TimerIntClear(TIMER3_BASE,TIMER_TIMA_DMA); TimerIntRegister(TIMER3_BASE,TIMER_A,TimerInt); TimerIntEnable(TIMER3_BASE,TIMER_TIMA_DMA); TimerDMAEventSet(TIMER3_BASE,TIMER_DMA_TIMEOUT_A);

We setup a interrupt for the DMA done interrupt and register the handler. We use the TimerDMAEventSet to set the timer to trigger the DMA.

void InitGPIO()

This is pretty simple, just setup the GPIO pins to be all output

SysCtlPeripheralDisable(GPIO_PERIPH_OUTPUT1); SysCtlPeripheralReset(GPIO_PERIPH_OUTPUT1); SysCtlPeripheralEnable(GPIO_PERIPH_OUTPUT1); SysCtlDelay(10); GPIOPinTypeGPIOOutput(GPIO_BASE_OUTPUT1, 0xFF); GPIOPinWrite(GPIO_BASE_OUTPUT1,0xFF,0x0);

void InitDMA()

SysCtlPeripheralDisable(SYSCTL_PERIPH_UDMA); SysCtlPeripheralReset(SYSCTL_PERIPH_UDMA); SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); SysCtlDelay(10); uDMAEnable(); uDMAControlBaseSet(DMAcontroltable); /* * This is for seting up the GPIO_BASE_OUTPUT1 with CH2 TimerA */ uDMAChannelAssign(UDMA_CH2_TIMER3A); uDMAChannelAttributeDisable(UDMA_CH2_TIMER3A, UDMA_ATTR_ALTSELECT | UDMA_ATTR_USEBURST | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK); uDMAChannelControlSet(UDMA_CH2_TIMER3A | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_1); uDMAChannelTransferSet(UDMA_CH2_TIMER3A | UDMA_PRI_SELECT, UDMA_MODE_BASIC, OutputState, (void *)(GPIO_BASE_OUTPUT1 + 0x3FC),

2);/* * End of CH2 */ //Enable the DMA chanels uDMAChannelEnable(UDMA_CH2_TIMER3A);

So let me explain better what can be more confusing:

  • UDMAChannelAssign is very important, as you saw each DMA chanel has alot of triggers so you need this to decide wich one, in this case it's UDMA_CH2_TIMER3A

  • uDMAControlBaseSet(DMAcontroltable); sets the DMA array to be DMAcontroltable.

  • uDMAChannelAttributeDisable is to prevent any atributes being set, so we always disable them all in the begining to have the DMA in a known state.

  • uDMAChannelControlSet sets the source and destination address incrementation as well the total transfer size and each transfer size per trigger. Since we always want to transfer to the GPIO we have a destination incrementation to UDMA_DST_INC_NONE . Our array with the data of the pins state needs to increment and being (and having to be) 8bits we need to increment 8 bits each transfer (UDMA_SRC_INC_8 ), this alows to cycle the array values. UDMA_SIZE_8 means that the DMA transfer 8 bits each time, and ARB_SIZE_1 it means it does 1 transfer per trigger

  • UDMAChanelTransferSet sets up the transfer mode (UDMA_MODE_BASIC), the source address (OutputState),the destination address (void *)(GPIO_BASE_OUTPUT1 + 0x3FC), and the total transfer size of 2.

void TimerInt()

Now, the interrupt, what is it for. You probably noticed that this code will only toggle the LED once. That what the interrupt is for, keep it going. You could use a array of 1024 for the GPIO output state and set a total transfer size also to 1024, but you would also need this interrupt if you wanted even more than 1024 toggles, and also, using a array of only 2 consumes less RAM.

To keep the GPIO toggling you just need to do again transfer set and enable the DMA chanel again and will keep going since the timer is still going in the background.

This is to reset the DMA to just use 2 bytes of memory to control the pins */ TimerIntClear(TIMER3_BASE,TIMER_TIMA_DMA); uDMAChannelTransferSet(UDMA_CH2_TIMER3A | UDMA_PRI_SELECT, UDMA_MODE_BASIC, OutputState, (void *)(GPIO_BASE_OUTPUT1 + 0x3FC),

2); uDMAChannelEnable(UDMA_CH2_TIMER3A);

With this application there's no problem using this method but for example in my RGB matrix, timing is very important and even a fast interrupt would not be enough and you would have to use other DMA Transfer Mode.

And here is the entire code put together, i hope i have helped anyone reading this tutorial.

void InituDMA(void);void InitGPIO(void);void InitTimer(void);void setup();void loop(void); void TimerInt(void);#include <stdint.h>#include <stdbool.h>#include "stdlib.h"#include "inc/hw_ints.h"#include "inc/hw_memmap.h"#include "inc/hw_uart.h"#include "inc/hw_gpio.h"#include "inc/hw_types.h"#include "driverlib/interrupt.c"#include "driverlib/sysctl.c"#include "driverlib/timer.c"#include "driverlib/udma.c"#include "driverlib/gpio.c"#include "driverlib/interrupt.h"#include "driverlib/pin_map.h"#include "driverlib/rom.h"#include "driverlib/rom_map.h"#include "driverlib/sysctl.h"#include "driverlib/uart.h"#include "driverlib/udma.h"#include <string.h>/* Here you can define what GPIO output you want to use */#define GPIO_BASE_OUTPUT1 GPIO_PORTN_BASE #define GPIO_PERIPH_OUTPUT1 SYSCTL_PERIPH_GPION //Saves the system clkvolatile uint32_t g_ui32SysClock;//Array to save the GPIO statesstatic uint8_t OutputState[2];//*****************************************************************************//// The control table used by the uDMA controller. This table must be aligned// to a 1024 byte boundary.////*****************************************************************************#if defined(ewarm)#pragma data_alignment=1024uint8_t DMAcontroltable[1024];#elif defined(ccs)#pragma DATA_ALIGN(DMAcontroltable, 1024)uint8_t DMAcontroltable[1024];#elseuint8_t DMAcontroltable[1024] __attribute__ ((aligned(1024)));#endif/* After 2 transfers the DMA is done and calls this interrupt This is to reset the DMA and re-enable it */voidTimerInt(void){ TimerIntClear(TIMER3_BASE,TIMER_TIMA_DMA); //Set again the same source address and destination uDMAChannelTransferSet(UDMA_CH2_TIMER3A | UDMA_PRI_SELECT, UDMA_MODE_BASIC, OutputState, (void *)(GPIO_BASE_OUTPUT1 + 0x3FC), 2); //Always needed since after it's done the DMA is disabled when in basic mode uDMAChannelEnable(UDMA_CH2_TIMER3A); }/* Function to setup the DMA */void InituDMA(){ //Just disable to be able to reset the peripheral state SysCtlPeripheralDisable(SYSCTL_PERIPH_UDMA); SysCtlPeripheralReset(SYSCTL_PERIPH_UDMA); SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA); SysCtlDelay(10); uDMAEnable(); uDMAControlBaseSet(DMAcontroltable); /* * This is for seting up the GPIO_BASE_OUTPUT1 with CH2 TimerA */ //Set the channel trigger to be Timer3A uDMAChannelAssign(UDMA_CH2_TIMER3A); //Disable all the atributes in case any was set uDMAChannelAttributeDisable(UDMA_CH2_TIMER3A, UDMA_ATTR_ALTSELECT | UDMA_ATTR_USEBURST | UDMA_ATTR_HIGH_PRIORITY | UDMA_ATTR_REQMASK); /* This sets up the item size to 8bits, source increment to 8bits and destination increment to none and arbitration size to 1 */ uDMAChannelControlSet(UDMA_CH2_TIMER3A | UDMA_PRI_SELECT, UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_1); /* This will setup the transfer mode to basic, source address to the array we want and destination address to the GPIO state we chosed. It also sets the total transfer size to 2. */ uDMAChannelTransferSet(UDMA_CH2_TIMER3A | UDMA_PRI_SELECT, UDMA_MODE_BASIC, OutputState, (void *)(GPIO_BASE_OUTPUT1 + 0x3FC), 2); //Enable the DMA chanel uDMAChannelEnable(UDMA_CH2_TIMER3A);}/* This is to set all the pins of the GPIO chosen to output */void InitGPIO(){ SysCtlPeripheralDisable(GPIO_PERIPH_OUTPUT1); SysCtlPeripheralReset(GPIO_PERIPH_OUTPUT1); SysCtlPeripheralEnable(GPIO_PERIPH_OUTPUT1); SysCtlDelay(10); GPIOPinTypeGPIOOutput(GPIO_BASE_OUTPUT1, 0xFF); GPIOPinWrite(GPIO_BASE_OUTPUT1,0xFF,0x0);}/* Function to setup the timer to count down periodic with 1 second period. It also enables the DMA trigger and the event to timeout (counter reach 0) */void InitTimer(){ SysCtlPeripheralDisable(SYSCTL_PERIPH_TIMER3); SysCtlPeripheralReset(SYSCTL_PERIPH_TIMER3); SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER3); SysCtlDelay(10); TimerConfigure(TIMER3_BASE, TIMER_CFG_PERIODIC); TimerLoadSet(TIMER3_BASE, TIMER_A, 120000000-1); TimerIntClear(TIMER3_BASE,TIMER_TIMA_DMA); TimerIntRegister(TIMER3_BASE,TIMER_A,TimerInt); TimerIntEnable(TIMER3_BASE,TIMER_TIMA_DMA); TimerDMAEventSet(TIMER3_BASE,TIMER_DMA_TIMEOUT_A);}void main(){ //Set CLK to 120Mhz g_ui32SysClock = MAP_SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480), 120000000); InitTimer(); InitGPIO(); InituDMA(); /* Sets the pins state all to 0 first and all to 1 in second. You can control individual pins in each bit of each array member. You can make the array bigger if you want but remember to set the DMA total transfer size too */ OutputState[0]=0x00; OutputState[1]=0xFF; //Enable the timer to start counting TimerEnable(TIMER3_BASE,TIMER_A); //Infinite loop and just watch the pins toggle while(1){ }}