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: Basic DMA driver

In this portion of the lab, you will implement GPDMA driver by choosing one of the DMA channels (0-7, preferrably 7) for performing memory-memory copy between 2 arrays. You will be modifying DMA registers available in LPC40xx.h

#include "board_io.h"
#include "delay.h"
#include "gpio.h"
#include "lpc40xx.h"
#include "lpc_peripherals.h"
#include "sys_time.h"
#include "uart_printf.h"
#include <stdio.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(dma__channel_e channel, uint32_t *src, uint32_t *dest, uint32_t length) {
  // 1. Power on GPDMA peripheral
  lpc_peripheral__turn_on_power_to(...); //Fill in the arguments
  
  // 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)src;
  
  // 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
}

void dma__enable_channel(dma__channel_e channel) {
  // Enable channel by setting enable bit for the channels CConfig register
}

void dma__verify_memory_data(uint32_t *src_addr, uint32_t *dest_addr, uint32_t size) {
  //Write code to check the data of source and destination arrays
  //Print Error message if contents don't match at any index
  
  printf("DMA copy verified!!! No errors\n");
}

int main(void) {
  uint32_t array_len = _____; //specify a length (< 2^12)
  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(....); //Fill in the arguments
  dma__enable_channel(...); //fill in the arguments
  
  delay__ms(100);
  
  dma__verify_memory_data(...); //fill in the arguments

  while (true) {
  }

  return 1; // main() shall never return
}

 


Part 1: Processor copy Vs DMA copy

Reuse Part0 code. Add below additional code to measure the time taken for DMA copy process compared to regular copy process

//Add below variables to compute the time difference
uint32_t dma_copy_start_time = 0;
uint32_t dma_copy_end_time = 0;
uint32_t processor_copy_start_time = 0;
uint32_t processor_copy_end_time = 0;

void dma__irq_handler(void) {
  const gpio_s led1 = board_io__get_led1();
  //Check IntTCStat register to see if an interrupt occurred
  //If Yes, then do the following
  gpio__reset(led1); //LED to indicate interrupt was triggered after DMA copy process
  dma_copy_end_time = sys_time__get_uptime_us();
      
  //Clear the interrupts by writing to IntTCClear register
}

//In DMA Initialize function do the following:
// 1. Replace NVIC_EnableIRQ() function with below function
    lpc_peripheral__enable_interrupt(LPC_PERIPHERAL__GPDMA, dma__irq_handler);

// 2. Enable ITC bit in CConfig register of the selected DMA channel

//In main function add the below code before enabling DMA
int main()
{
  ...
  ...
  dma_copy_start_time = sys_time__get_uptime_us();
  dma__enable_channel(...);
  delay__ms(100);
  
  dma__verify_memory_data(...);

  processor_copy_start_time = sys_time__get_uptime_us();
  for (i = 0; i < array_len; i++) {
    dest_array[i] = src_array[i];
  }
  processor_copy_end_time = sys_time__get_uptime_us();
  printf("DMA copy completed in %lu microsec\n", (dma_copy_end_time - dma_copy_start_time));
  printf("Processor copy completed in %lu microsec\n", (processor_copy_end_time - processor_copy_start_time));

  while (true) {
  }
  return 1; // main() shall never return
}
Requirements
  • Part0 and Part1 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

Do not write long printf statements inside dma__irq_handler ISR.

 What to turn in:

  • Copy all relevant source files into a .pdf file and upload
  • Turn in the screenshots of terminal output for different burst sizes

Extra Credit - Make program to choose DMA channels using onboard buttons and run DMA copy process

 

 


Back to top