This example project uses the PIC32CM PL10 Curiosity Nano board to show how to configure the DMAC to perform a data transfer from a source memory location to a destination memory data location. Pressing SW0 once will start a 16-bit beat transfer and display the measured transfer cycle time in the terminal. Pressing it again will start a 32-bit beat transfer, and still display the transfer cycle time. Pressing SW0 for the third time will trigger a validation where the data in the source memory location is compared to the data in the destination memory locations. If the data matches, the validation is successful and a message is again displayed in the terminal. After the data validation, the LED0 is turned on and the program will stop unless the RESET button is pressed, restarting the program from the beginning.
- MPLAB® Tools for Visual Studio Code® 1.2.3
- MPLAB® XC32 v5.00
- Microchip PIC32CM-PL10 Device Support v1.2.329
- USB-C® to USB 2.0 cable
The DMAC facilitates data transfers without intervention from the CPU. This peripheral helps to maximize system efficiency by enabling higher data transfer rates and freeing up the CPU time by using multiple dedicated hardware channels to directly move data from one memory location to another, or to a peripheral, and even from one peripheral to another. This is especially helpful for time- and resource-sensitive operations required in communications and analog applications.
The process of transferring data via the DMA is called a transaction. Transactions can be split into smaller data transfers. A beat transfer is the unit of data that can be accessed by the DMAC for transfer. The size of each beat unit can be configured to be 8-bit, 16-bit, or 32-bit. A block transfer is the amount of data that can be transferred by the DMAC without releasing the data bus, and its size can range from 1K to 64K beats. A transaction can consist of linking multiple block transfers until the data transfer is completed.
In this example, the application will first copy data stored in a source buffer to a destination buffer first by using a 16-bit beat size. The DMAC is then reconfigured to use a 32-bit beat size and the same operation is performed to see the performance difference.
Performing data transfers through the DMAC starts with configuring the DMA channel parameters before the DMAC peripheral is even enabled. Using the DMA Configurator of MPLAB® Code Configurator (MCC) Harmony, the following parameters can be configured:
- The DMA channel to be used
- The trigger for a data transfer to begin
- The action to perform when a trigger is received, whether it is to transfer a beat of data, a block of data, or complete a whole transaction.
- Whether the source and destination memory addresses are automatically incremented after every transfer
- The size of each access of the data bus i.e., the size of each beat unit indicated by the Beat Size bits (
BTCTRL.BEATSIZE)
Figure 1. Block Transfer Control Register
For this example, the DMA configuration is seen here.
Once the DMAC channel is configured as shown above, a data transfer is triggered through the software by calling the DMAC_ChannelTransfer function. This function takes the channel number, the memory addresses of the source and destination buffers, and the total size of the data to be transferred in bytes and initiates a data transfer. An interrupt may be enabled to signal the completion of a data transfer. Once a transfer is completed, a simple check to see if the transfer was successful is to compare the data between the source and destination buffers. A match indicates that all the data have been copied correctly.
Figure 2. DMAC Memory Data Transfer Flowchart
- Open VS Code. If not already in the system, download and install the latest version of VS Code and the MPLAB Tools for VS Code extension.
- If not already on your system, download and install the XC32 C-Compiler version 5.00 or newer.
- Clone or download this project from GitHub to your local machine.
- If downloaded as a
.zipfile, extract the files as close as possible to the root directory. This helps avoid long filename issues and potential errors in VS Code before moving to the next step. - In VS Code, go to File>Open Folder, navigate to the location where the project was extracted and select the
pic32cmpl10-cnano-dma-memory-vscode-harmony. Click the Select Folder button to open the project and the MPLAB extension will automatically detect it as an MPLAB project.
Figure 3. Open Folder Window
- Connect the Debug USB port of the PIC32CM PL10 Curiosity Nano to a PC using the USB-C® to USB 2.0 cable.
- Click the MPLAB button in the left sidebar. Confirm that the PIC32CM PL10 Curiosity Nano board is detected and the project folder is open active (see the figure below).
Figure 4. MPLAB Tools
- Initiate the build by clicking the hammer icon in the top-left corner. Wait for the process to finish and ensure the build is successful.
Figure 5. Build Project
Figure 6. Build Successful
- Open Data Visualizer. Press
Ctrl + Shift + Pon the keyboard and typeMPLAB: Data Visualizer. Find the Serial COM port of the PIC32CM PL10. Click the gear icon to check that the serial port settings match the SERCOM USART settings here. Click the Display as text in the terminal button to view the terminal output.
Figure 7. Location of Display as Text Button
-
After successful build, upload the program to the device. Press
Ctrl + Shift + Pon the keyboard and typeMPLAB: Program Device, click enter or select it from the list. -
Select
default.hex. -
Select the
PIC32CM PL10 Curiosity Nanodebug tool. -
Wait for the process to finish. Verify that the program is running.
Figure 8. Program Complete
- The result displays as follows:
Figure 9. Initial State While Waiting for Button Press
- Follow the application instructions to view the transfer cycle times for each DMA configuration and the result of the data transfer validation. Then, LED0 will light up and the program will stop.
Figure 10. Final State Demo
This example project already comes preconfigured to work out of the box. To view or verify the configuration of the individual components of the application, the example project can be opened by launching MCC and opening the MCC configuration.
Figure 11. Project Components
Figure 12. DMA Configuration
In the Project Graph tab, click the SERCOM1 block to open the SERCOM1 USART Configuration window and verify that the SERCOM is configured as below.
Figure 13. SERCOM USART Configuration
Click the System block in the Project Graph tab to enable SysTick, a system timer integrated within Arm® Cortex®-M.
Figure 14. SysTick Configuration
Click the EIC block in the Project Graph tab to view how SW0 is configured.
Figure 15. EIC Configuration
Open the Pin Configurator tool from the Plugins section in the Project Graph tab to verify that the following pins are configured as expected in the Pin Settings table.
Figure 16. PIC32CM PL10 Pin Config
The application source code is located in the main.c file.
#include <stddef.h> // Defines NULL
#include <stdbool.h> // Defines true
#include <stdlib.h> // Defines EXIT_FAILURE
#include <string.h> // Defines strncmp
#include <stdio.h>
#include "definitions.h" // SYS function prototypes
/* Macro definitions */
#define LED_On LED0_Clear
#define LED_Off LED0_Set
#define TRANSFER_SIZE 1024
char srcBuffer[TRANSFER_SIZE] = {};
char dstBuffer1[TRANSFER_SIZE] = {};
char dstBuffer2[TRANSFER_SIZE] = {};
volatile bool completeStatus = false;
static volatile bool errorStatus = false;
volatile uint8_t transfersDone = 0;
volatile uint32_t timeStamp = 0;
uint32_t transferCyclesBeatSize32 = 0, transferCyclesBeatSize16 = 0;int main(void) {
uint32_t i = 0;
uint32_t channelSettings32Bit = 0;
/* Initializes all modules. */
SYS_Initialize(NULL);
EIC_CallbackRegister(EIC_PIN_8, SW0_callback, 0);
LED_Off();
/* Builds the srcBuffer. */
while (i < TRANSFER_SIZE) {
srcBuffer[i] = (uint8_t) i;
i++;
}
printf("\n\r-----------------------------------------------------------------");
printf("\n\r\t\t DMAC Memory Transfer DEMO\t\t\t");
printf("\n\r-----------------------------------------------------------------");
printf("\n\r Press SW0 once to initiate a data transfer with a 16-bit beat size.");
printf("\n\r Press it again to initiate a data transfer with a 32-bit beat size.");
printf("\n\r Press it again to validate the data transferred.");
printf("\n\r-----------------------------------------------------------------");
printf("\n\r");
/* Registers a callback with the DMAC PLIB to get a 'transfer' complete notification and error
* events. */
DMAC_ChannelCallbackRegister(DMAC_CHANNEL_0, DMA_Callback, 0);
//channelSettings16Bit = DMAC_ChannelSettingsGet(DMAC_CHANNEL_0);
while (transfersDone < 4) {
switch (transfersDone) {
case 1:
{
if (completeStatus == false) {
SYSTICK_TimerStart();
timeStamp = SYSTICK_TimerCounterGet();
DMAC_ChannelTransfer(DMAC_CHANNEL_0, &srcBuffer, &dstBuffer1, TRANSFER_SIZE);
while (completeStatus == false);
transferCyclesBeatSize16 = timeStamp;
printf("\n\r Number of transfer cycles with a beat size of 16. --> %lu", transferCyclesBeatSize16);
}
break;
}
case 2:
{
if (completeStatus == true) {
completeStatus = false;
channelSettings32Bit = DMAC_ChannelSettingsGet(DMAC_CHANNEL_0);
channelSettings32Bit = (channelSettings32Bit & ~DMAC_BTCTRL_BEATSIZE_Msk) | DMAC_BTCTRL_BEATSIZE(DMAC_BTCTRL_BEATSIZE_WORD_Val);
DMAC_ChannelSettingsSet(DMAC_CHANNEL_0, channelSettings32Bit);
SYSTICK_TimerStart();
timeStamp = SYSTICK_TimerCounterGet();
DMAC_ChannelTransfer(DMAC_CHANNEL_0, &srcBuffer, &dstBuffer2, TRANSFER_SIZE);
while (completeStatus == false);
transferCyclesBeatSize32 = timeStamp;
completeStatus = false;
printf("\n\r Number of transfer cycles with a beat size of 32. --> %lu", transferCyclesBeatSize32);
}
break;
}
case 3:
{
if ((strncmp(srcBuffer, dstBuffer1, TRANSFER_SIZE) == 0) && (strncmp(srcBuffer, dstBuffer2, TRANSFER_SIZE) == 0)) {
/* Successfully transferred the data using DMAC. */
printf("\n\r");
printf("\n\r DMAC memory transfer successful with Data Match.\n\r");
}
else {
/* Data transfers done, but data mismatch occurred */
printf("\n\r");
printf("\n\r DMAC memory transfer successful with Data Mismatch. !!!\n\r");
}
transfersDone++;
break;
}
default:
{
break;
}
}
}
LED_On();
printf("\n\r-----------------------------------------------------------------");
while (true) {
;
}
/* Execution will not arrive at this stage during normal operation */
return ( EXIT_FAILURE);
}void DMA_Callback(DMAC_TRANSFER_EVENT status, uintptr_t context) {
timeStamp = timeStamp - SYSTICK_TimerCounterGet();
if (status == DMAC_TRANSFER_EVENT_COMPLETE) {
completeStatus = true;
} else {
errorStatus = true;
transfersDone--;
}
}void SW0_callback() {
transfersDone++;
}The DMAC peripheral optimizes the system performance by directly moving data between memory locations without CPU intervention. The flexibility and ease of configuring the peripheral with MCC Harmony make it a handy tool in ensuring applications run smoothly and efficiently with minimal development time.
For more ways to use the DMAC peripheral, check out the other DMAC example projects:
















