MODDMA

Table of Contents

    Import libraryMODDMA

    MODDMA GPDMA Controller New features: transfer pins to memory buffer under periodic timer control and send double buffers to DAC

    MODDMA takes the NXP MCU SW Application Team's standard library for the GPDMA and adapts it as a specific implementation for the Mbed using standard Mbed libraries where approipate. The original library was straight C where as MODDMA presents the GPDMA controller as a C++ object using multiple C++ classes as configuration. MODDMA also presents the API in a similar way to the standard Mbed libraries so Mbed community members and users will be familiar with "dropping in" a library and writing code to use it in a consistent "Mbed way".

    The LPC17xx GPDMA

    The GPDMA is presented at the hardware level as a set of registers that allow, at the basic level, the configuration of source, destination, length of data, bus width. Other settings are available but the main function is to take a block of data from one place and move it to another place without a large amount of intervention from the host CPU. Source and destination can be memory or peripheral registers and the GPDMA offers up to eight prioritised channels.

    The MODDMA implementation

    MODDMA takes the original NXP library concept of a controller that manages the hardware registers interface and a set of one or more (up to 8) DMA configurations. Each configuration specifies the source and destination types, length and addresses which the controller uses to manage the transfer. MODDMA extends this into an object based system where MODDMA is a single instance object that manages these configurations. Additionally, MODDMA adds to both the controller and each configuration features that Mbed users will be familiar, like attaching callbacks to let the user's program know when certain events occur.

    Each configuration is an object of type MODDMA_Config and the user creates these as required and passes them to the MODDMA manager.

    Additionally, MODDMA can be used by other libraries to add DMA functionality where it's needed. An example of this is MODSERIAL. If this library is compiled with MODDMA then additional methods are made available native to MODSERIAL for sending large block buffers out of a serial port/uart.

    Getting started

    To use the MODDMA library first create a new project and then import the library into your project in the usual way (there's a link at the beginning of this article to import into your compiler). Once imported we need to create an instance of the controller. Your program should only ever have one controller defined. If you try to create a second controller while one exists the Mbed will display the "blue LEds of death", report the error to <stdout> and halt. So only create one instance. If you start a new project then adding teh controller looks like this:-

    #include "mbed.h"
    #include "MODDMA.h"
    
    DigitalOut myled(LED1);
    Serial pc(USBTX, USBRX);
    MODDMA dma;
    
    int main() {
        pc.baud(115200);
        pc.printf("Hello World\r\n");
        
        while(1) {
            myled = 1;
            wait(0.2);
            myled = 0;
            wait(0.2);
        }
    }
    

    Notice we added #include "MODDMA.h" and then defined an instance of the controller, called "dma" with MODDMA dma; We now have in place the main GPDMA controller. Also created here was the Serial pc(USBTX, USBRX); which we will use for debugging and made it say "Hello World" to check things are working as expected so far. The baud rate was set to 115200 which you should set to match your setup.

    Now we have a controller in place lets look at first creating a simple configuration. In this example we will move a short block of memory from one place to another.

    #include "mbed.h"
    #include "MODDMA.h"
    
    DigitalOut myled(LED1);
    Serial pc(USBTX, USBRX);
    MODDMA dma;
    
    int main() {
        pc.baud(115200);
        pc.printf("Hello World\r\n");
        
        // Create a source buffer we are going to move.
        char src[] = "TEST TEST TEST";
        
        // Create a buffer for the destination to copy to.
        char dst[sizeof(src)];
        
        // Create a MODDMA configuration object.
        MODDMA_Config *config = new MODDMA_Config;
        
        // Setup that configuration
        config
         ->channelNum    ( MODDMA::Channel_0 )
         ->srcMemAddr    ( (uint32_t) &src )
         ->dstMemAddr    ( (uint32_t) &dst )
         ->transferSize  ( sizeof(src) )
         ->transferType  ( MODDMA::m2m )     
        ; // config end
    
        // Pass the configuration to the controller
        dma.Setup( config );
        
        // Tell the controller to perform the DMA operation
        // defined by that configuration.
        dma.Enable ( config );
        
        wait(1);
        
        pc.printf("%s\r\n", dst);
        
        while(1) {
            myled = 1;
            wait(0.2);
            myled = 0;
            wait(0.2);
        }
    }
    

    Running this will produce:-

    Quote:

    Hello World

    TEST TEST TEST

    Now, you will have noticed that in that example we had to wait() before calling the pc.printf() so as to make sure the DMA copy was complete. Given that the main advanyage of DMA is that transfer happen without CPU intervention it seems daft that we should wait around for it to complete. The CPU can be doing other things while the transfer is in progress. This is where callbacks come in to play. MODDMA provides standard callbacks for events that the GPDMA system generate as a result of DMA operations. So now we will modify the above program to add a callback that lets us know when a transfer is complete. Here's the code:-

    #include "mbed.h"
    #include "MODDMA.h"
    
    DigitalOut myled(LED1);
    DigitalOut led2(LED2);
    Serial pc(USBTX, USBRX);
    MODDMA dma;
    
    int transferState = 0;
    
    void myCallback(void) {
        if (transferState == 0) {
            led2 = 1;
            transferState = 1;
        }
    }
    
    int main() {
        pc.baud(115200);
        pc.printf("Hello World\r\n");
        
        // Create a source buffer we are going to move.
        char src[] = "TEST TEST TEST";
        
        // Create a buffer for the destination to copy to.
        char dst[sizeof(src)];
        
        // Create a MODDMA configuration object.
        MODDMA_Config *config = new MODDMA_Config;
        
        // Setup that configuration
        config
         ->channelNum    ( MODDMA::Channel_0 )
         ->srcMemAddr    ( (uint32_t) &src )
         ->dstMemAddr    ( (uint32_t) &dst )
         ->transferSize  ( sizeof(src) )
         ->transferType  ( MODDMA::m2m )
         ->attach_tc     ( &myCallback )
        ; // config end
    
        // Pass the configuration to the controller
        dma.Setup( config );
        
        // Tell the controller to perform the DMA operation
        // defined by that configuration.
        dma.Enable ( config );
        
        while(1) {
            if (transferState == 1) {
                transferState = 2;
                pc.printf("%s\r\n", dst);
            }
            myled = 1;
            wait(0.2);
            myled = 0;
            wait(0.2);
        }
    }
    

    So what's new here? First, we added a new LED2 definition, then we added a global variable called transferState = 0, we added a function called myCallback() which when called changes the transfer state and switches on LED2 so we can see the function was called.

    Now, to make use of this function we added ->attach_tc(&myCallback) to the configuration. The "tc" in attach_tc means "terminal count". So, when the transfer reaches the terminal count as defined by ->transferSize(sizeof(src)) the callback is triggered.

    Finally, in the while(1) we tested the global variable transferState to see when it changed from 0 to 1 indicating the transfer was complete at which point we print out the destination buffer to show it was copied. Also, see we set transferState to 2. This prevents the while(1) loop continually printing out the destination buffer.

    The above example showed how to move a block of memory from one place to another. The GPDMA is capable of moving to peripherals also. To see an example of this the example1.cpp shows how to move a memory block directly to a Uart.

    Additionally, if you are writing a library you can make use of MODDMA by compiling along side. An example of this is MODSERIAL. To see how this is done see the cookbook page MODSERIAL using MODDMA. In this example, when MODSERIAL is compiled alongside MODDMA, MODSERIAL creates a new method called dmaSend() which takes a character buffer and directly sends it out of MODSERIAL's Uart without having to even think about how MODDMA is actually doing it for you.