Tuesday, May 13, 2014

Simple PIC32 DMA example

I've been testing the PIC32 DMA recently. The PIC32 has a DMA (direct memory access) module that allows the data transfer in the PIC32 without CPU intervention during data transfer - thus freeing up CPU to perform other tasks while the data is transferred).

First, I did a simple test to just see if it works (I've also done some DMA timing testing - I'll talk about them soon in another article). The first test was to just use the DMA module to transfer data from a constant array (from FLASH) to PORTA (LATA for output). This would be visually seen as LEDs connected to PORTA blinking.

The code is:

//================================

/***************************************************
 * Programmer: Syed Tahmid Mahbub
 * Target PIC: PIC32MX250F128B
 *
 * Date: 05/08/2014 - 05/10/2014
 *
 * Program to test DMA operation
 * DMA will transfer const (from FLASH) to PORTA
 ***************************************************/

#include "plib.h"           // peripheral library
#include "settings.h"       // configuration bits settings

const unsigned char portOut[] = {0x05, 0x0A};

unsigned int sourceAddr;
unsigned int destinationAddr;

void Initialize(void){

    SYSTEMConfigPerformance(8000000); // Running at 8MHz for now
    ANSELA = 0; ANSELB = 0;
    TRISA = 0; TRISB = 0;

    ///// Virtual to physical memory

    /* using a pointer returns virtual memory address:
     *      eg. const unsigned int* src = (void*) &LATA;
     * returns 0xBF886030 -> agrees with datasheet
     *
     * reference manual says that to convert this to physical
     *      address, AND it with 0x1FFFFFFF and get the
     *      corresponding physical address
     */

    ///// Set source and destination addresses
   
    sourceAddr = (unsigned int) &portOut & 0x1FFFFFFF;      // Physical address of portOut
    destinationAddr = (unsigned int) &LATA & 0x1FFFFFFF;    // Physical address of LATA

    ///// Initialize dma first
    DMACON = 0x8000;            // dma module on
    DCRCCON = 0;                // crc module off
    DCH0INT = 0;                // interrupts disabled
    DCH0SSA = sourceAddr;       // source start address
    DCH0DSA = destinationAddr;  // destination start address
    DCH0SSIZ = 2;               // source size - 2 bytes
    DCH0DSIZ = 1;               // destination size - 1 byte
    DCH0CSIZ = 1;               // cell size - 1 bytes
    DCH0ECON = 0x1310;          // dma transfer triggered by interrupt 19: Timer 4

    ///// Initialize timer 4 now - timer 4 interrupt triggers dma transfer
    PR4 = 3124;         // 100 milliseconds
    T4CON = 0x70;       // prescaler 1:256, timer currently off
    // timer 4 interrupt request triggers dma

    ///// Enable dma channel
    DCH0CON = 0x93;     // channel enabled, always on, priority 3
}


void main(void){

    Initialize();
    T4CONSET = 0x8000; // turn on timer 4
    while (1){
       
    }

}

//================================

The DMA module allows data transfer from a source to a destination without CPU intervention during data transfer. The data transfer can be triggered by any interrupt request within the PIC. An interesting point of note is that the DMA module maintains its own flags for detecting interrupt requests for data transfer start/abort requests. This is completely independent of the INT interrupt controller enable and flag settings/configuration.

Points of note on the DMA:

  • Data is transferred from the source register to the destination register upon a transfer request.
  • The source size and destination size registers define the size of the source and destination to which you want to transfer data. For example, LATA is a 32-bit register (typical for the PIC32). However, since I'm only planning to transfer data to the lowest byte in LATA (see code above), my destination size is one byte.
  • The cell size describes the number of bytes to transfer upon one DMA transfer request. For example, in my code I was transferring just one byte every DMA request (either 0x05 or 0x0A) - so the cell size is 1 byte.
  • The DMA transfer is complete when either a block transfer occurs or you abort the transfer. A block transfer occurs when the number of bytes equal to the size of the larger of the source and destination register sizes is transferred. So, in my code, a block transfer is when both byte 0x05 and 0x0A are transferred. So, that's two DMA cell transfers upon two interrupt requests (only one cell transfer occurs upon one interrupt request).
  • The source and destination addresses are PHYSICAL ADDRESSES, NOT VIRTUAL ADDRESSES used by the MIPS core. The reference manual mentions the easy translation between physical and virtual addresses: (Physical address) = (Virutal address) & 0x1FFFFFFF. Remember that when you point to a register address (eg &LATA), that returns the virtual address and not the physical address.
  • The DMA transfer is triggered by an interrupt request (IRQ). So, in the DMA event control register, you must specify which IRQ is to trigger the data transfer. To ensure that this IRQ triggers a DMA transfer, the SIRQEN (Channel Start IRQ Enable bit) bit (bit 4 of the DCHxECON register) must be set. Additionally, when you specify the IRQ in the event control register, make sure you use the IRQ # and not the vector #.
  • The maximum source size, destination size and cell size are all 65,535 bytes (wow!). You can transfer that many bytes on an event. 
For further details on the DMA controller, go through the reference manual.

For information on using the DMA plib (peripheral library) functions, see: http://people.ece.cornell.edu/land/courses/ece4760/PIC32/



5 comments:

  1. Can you give me the configuration bits you used to run the program, i.e your settings.h?

    ReplyDelete
  2. What should you change in the code if you want transfer the source data to another buffer[0] instead of Port A?
    uint_16t mybuffer[2];
    destinationAddr = (unsigned int) & mybuffer[0];
    I tried something like this but mybuffer is always empty.

    ReplyDelete
  3. What should you change in the code if you want transfer the source data to another buffer[0] instead of Port A?
    uint_16t mybuffer[2];
    destinationAddr = (unsigned int) & mybuffer[0];
    I tried something like this but mybuffer is always empty.

    ReplyDelete
    Replies
    1. Unknown: Seems you forgot to convert to a physical address... You need to and your buffer with 0x1fffffff.
      I'm a bit late.. but :)

      Delete
  4. This comment has been removed by the author.

    ReplyDelete