DMA - Memory to Memory transfer
Objective
- Copy data from one memory block to another memory block using DMA controller
- Use implemented driver to compare the performance between DMA copy and CPU copy
Part 0: Read the DMA chapter
The first step is to familiarize yourself with the DMA peripheral. Read the LPC user manual multiple times before you start the assignment.
Part 1: Basic DMA driver
In this portion of the lab, you will implement GPDMA driver by choosing one of the DMA channels (0-7, preferably 7) for performing memory-to-memory copy between 2 arrays. You will be modifying DMA registers available in lpc40xx.h
#include <stdbool.h>
#include "lpc40xx.h"
#include "lpc_peripherals.h"
typedef enum {
DMA__CHANNEL_0 = 0,
DMA__CHANNEL_1,
DMA__CHANNEL_2,
DMA__CHANNEL_3,
DMA__CHANNEL_4,
DMA__CHANNEL_5,
DMA__CHANNEL_6,
DMA__CHANNEL_7,
} dma__channel_e;
LPC_GPDMACH_TypeDef *dma_channels[] = {LPC_GPDMACH0, LPC_GPDMACH1, LPC_GPDMACH2, LPC_GPDMACH3,
LPC_GPDMACH4, LPC_GPDMACH5, LPC_GPDMACH6, LPC_GPDMACH7};
void dma__initialize(void) {
// 1. Power on GPDMA peripheral; @see "lpc_peripherals.h"
lpc_peripheral__turn_on_power_to(...); // Fill in the arguments
}
void dma__copy(dma__channel_e channel, const void *dest, void *source, uint32_t size_in_bytes);
// 2. Enable GPDMA - Set the Enable bit in Config register
// 3. Clear any pending interrupts on the channel to be used by writing to the IntTCClear
// and IntErrClear register. The previous channel operation might have left interrupt active
// 4. Write the source address into the CSrcAddr register.
dma_channels[channel]->CSrcAddr = (uint32_t)source;
// 5. Write the destination address into the DestAddr register.
// 6. Write the control information into the Control register in one instruction
// transfer size [11:0]
// source burst size [13:12]
// dest burst size [15:14]
// source transfer width [20:18]
// destination transfer width [23:21]
// source address increment [26]
// destination address increment [27]
// TCI enable
// Enable channel by setting enable bit for the channels CConfig register
// IMPORTANT:
// Poll for DMA completion here
}
bool check_memory_match(const void *src_addr, const void *dest_addr, size_t size) {
// Write code to check the data of source and destination arrays
// Hint: You may use memcmp()
}
int main(void) {
const uint32_t array_len = _____; //specify a length (< 2^12)
// Be aware that you only have 64K of RAM where stack memory begins
// You can make these 'static uint32_t' or make them global to not use stack memory
uint32_t src_array[array_len];
uint32_t dest_array[array_len];
// Initialize the source array items to some random numbers
// Choose a free DMA channel with the priority needed.
// DMA channel 0 has the highest priority and
// DMA channel 7 the lowest priority
dma__initialize();
dma__copy(...); // fill in the arguments
const bool memory_matches = check_memory_match(...); // fill in the arguments
while (true) {
}
return 1; // main() shall never return
}
Part 2: Compare Performance of various methods
Reuse code from Part 1. Reference the code below to measure the time taken for DMA copy, programmatic copy and standard library function memcpy()
#include <string.h>
// Declare source and destination memory for the lab
static uint8_t memory_array_source[4096];
static uint8_t memory_array_destination[4096];
// Re-initialize the memory so they do not match
static void reset_memory(void) {
for (size_t i = 0; i < sizeof(memory_array_source); i++) {
memory_array_source[i] = i;
memory_array_destination[i] = i + 1;
}
}
// Copy memory using standard library memcpy()
static uint32_t memory_copy__memcpy(void) {
const uint32_t start_time_us = sys_time__get_uptime_us();
{
memcpy(...); // TODO: Fill in the parameters
}
const uint32_t end_time_us = sys_time__get_uptime_us();
return (end_time_us - start_time_us);
}
// Copy memory using the DMA
static uint32_t memory_copy__dma(void) {
const uint32_t start_time_us = sys_time__get_uptime_us();
{
dma__copy(...); // TODO: Fill in the parameters
}
const uint32_t end_time_us = sys_time__get_uptime_us();
return (end_time_us - start_time_us);
}
static uint32_t memory_copy__loop(void) {
const uint32_t start_time_us = sys_time__get_uptime_us();
{
// TODO: Use a for loop
}
const uint32_t end_time_us = sys_time__get_uptime_us();
return (end_time_us - start_time_us);
}
int main()
{
dma__initialize(...);
// Test 1:
reset_memory();
const uint32_t dma_us = memory_copy__dma();
if (!check_memory_match(memory_array_source, memory_array_destination, sizeof(memory_array_source)) {
puts("ERROR: Memory copy did not work");
}
// TODO: Reset memory, then perform memory copy, and check if memory matches
// Test 2:
const uint32_t loop_us = memory_copy__loop();
// Test 3:
const uint32_t memcpy_us = memory_copy__memcpy();
printf("DMA copy completed in: %lu microsec\n", dma_us);
printf("For loop completed in: %lu microsec\n", loop_us);
printf("memcpy completed in : %lu microsec\n", memcpy_us);
while (true) {
}
return 1; // main() shall never return
}
Requirements
-
Part 1 and Part 2 must be completed and fully functional.
- You are encouraged to ask questions for any line of code that is not well understood (or magical).
- Try changing source burst size and destination burst size bits to understand performance improvements
- Note the time taken in each case and turn in the screenshots
- Should be able to make it work for any DMA channels(0-7) or array size
- We may ask you to change the channel or array length and then recompile and re-flash your board to and prove it works
What to turn in:
- Turn in the screenshots of terminal output for different burst size
Extra Credit - Make program to choose DMA channels using onboard buttons and run DMA copy process