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

 

 


Back to top