Tuesday, January 25, 2011

DMA in LPC17xx

Direct Memory Access is a feature which allows the peripherals to access the memory directly or to transfer data to other peripherals without the help of CPU. So whenever we are using DMA, we can be doing computations on the CPU while the data transfer is taking place. Effectively the microcontroller is multitasking.

LPC17xx has a General Purpose DMA (GPDMA) which has 8 channels of which each can perform unidirectional data transfer. All types of data transfers like memory-memory, memory-peripheral, peripheral-peripheral are supported. And we can prioritize the DMA transfers as we wish. It can also perform 8-bit, 16-bit and 32-bit wide transactions. If we want to transfer a stream of data that is not stored in contiguous manner, then we can use the scatter and gather feature of the DMA which uses linked list. There are many many more features available with the GPDMA out of which I will be explaining some basic features in this post. I will talk about other advanced features as and when it is required.

The essential things that you need to do while configuring the DMA are
  1. Select the channel to be used for data transfer based on the priority requirement (DMA channels have fixed priorities. So we should choose the channel of required priority for our use.)
  2. Set the bus transfer width (8-bit, 16-bit or 32-bit)
  3. Set the source and destination
  4. Set the DMA request signal (The signal that will start the DMA transfer)
  5. Set the endian behaviour (Little endian or Big endian)
  6. Set the burst size (Number of bytes that need to be transferred continuously in one DMA transaction)
  7. Set the type of transfer (Mem-Mem, Mem-Peripheral or Peripheral-Peripheral)
Now let us go step by step and setup the GPDMA to perform a simple task of toggling the LED. Here lets store a series of alternating 1's and 0's in an array and then transfer it to the GPIO port for which the LED is connected through channel 0 of the DMA.

Setup of the timer which generates the DMA request signal

    LPC_SC->PCONP |= 1 << 2; // Power up
    LPC_SC->PCLKSEL0 |= 0x01 << 4; // CCLK
    LPC_TIM1->MR0 = 1 << 25;
    LPC_TIM1->MCR = 1 << 1; // reset on Match Compare 0

Now lets configure the DMA.
1. Power up the GPDMA (LPC17xx manual table 46, pg 64)

    LPC_SC->PCONP |= 1 << 29;

There is no need to setup the clock for DMA as we were doing for other peripherals. The system clock will be directly given to the DMA.

2. Enable the GPDMA (LPC17xx manual table 557, pg 599)

    LPC_GPDMA->DMACConfig |= 1 << 0;

If you are using big endian mode you need to set the bit 1 of DMACConfig register.

3. Set the Match Compare 0 (MAT1.0 signal) as the DMA request signal

    LPC_GPDMA->DMACSync &= ~(1 << 10); // use MAT1.0 for Sync (LPC17xx manual table 558, pg 599)

    // The above line is actually not needed as DMACSync register will be zero on reset.

    LPC_SC->DMAREQSEL |= 1 << 2; // Timer1 Match Compare 0 as DMA request (LPC17xx manual table 559, pg 600)

4. Clear the Interrupt Terminal Count Request and Interrupt Error Status register. (Writing 1 to the clear registers will clear the entry in the main register. i.e writing 0xff to DMACIntErrClr register will clear the DMACIntErrStat register)

    LPC_GPDMA->DMACIntErrClr |= 0xff; // (LPC17xx manual table 549, pg 596)

    LPC_GPDMA->DMACIntTCClear |= 0xff; // (LPC17xx manual table 547, pg 595)

5. Set the source and destination addresses (LPC17xx manual table 560 and 561, pg 601)

    LPC_GPDMACH0->DMACCDestAddr = (uint32_t) &(LPC_GPIO1->FIOPIN3); // LED is connected to P1.29

    LPC_GPDMACH0->DMACCSrcAddr = (uint32_t) &data[0]; // data[] is the array where I have stored alternating 1's and 0's

6. Clear the Linked List as we are not using it. (LPC17xx manual table 562, pg 602)

    LPC_GPDMACH0->DMACCLLI = 0;

7. Set the burst transfer size, source burst and destination burst size, source and destination width, source increment, destination increment (LPC17xx manual table 563, pg 603)

In our case, let the transfer size be 200 bytes, source burst and destination burst sizes are 1, source and destination width are 8-bits and we need to do source increment and there is no need for destination increment.

    LPC_GPDMACH0->DMACCControl = 200 | ( 1 << 26 );

8. Set the type of transfer as Memory to Peripheral and the destination request line as MAT1.0 (LPC17xx manual table 564, pg 605)

    LPC_GPDMACH0->DMACCConfig = ( 10 << 6 ) | ( 1 << 11); // 10 corresponds to MAT1.0 and it is selected as the destination request peripheral (LPC17xx manual table 543, pg 592)

9. Enable the channel (LPC17xx manual table 564, pg 605)

    LPC_GPDMACH0->DMACCConfig |= 1; //enable ch0

You can setup an interrupt for DMA transfer completion so that after the DMA transfer is complete and interrupt request is generated and in the interrupt service routine you need to disable the channel by writing

    LPC_GPDMACH0->DMACCConfig = 0; // stop ch0 dma

and then repeat all the steps of configuration to setup another transaction.

The compiled code which toggles the LED connected to pin P1.29 with the help of DMA is here.

Lot more details about DMA will be discussed later as and when required. I do not know what I would be doing without DMA because I need it for almost all purposes in the Game Console project. It is being used to put out Luminance and Chrominance signals. It will be used to put out audio, SD card interface, etc etc. May be at some time I will run out of channels!!

30 comments:

Anonymous said...

I am trying to do a simple RAM-to-RAM DMA in the NGX Blueboard and cannot seem to make it go. I have it down to just 120 lines of code - half of them are to startup and stabilize the clock. Would you be willing to look at my file to see what I am doing wrong? Thanks! Terry Hansen, Colorado Springs

Thejasvi said...

@ Terry
Sure. Post your code here or put it in some file sharing site and send the link. I will have a look at it.

@ All
If any of you have problems with signing in while posting the comments please inform me. I will try to rectify it. And I appreciate you not to post comments as anonymous.

Anonymous said...

Hi, i have some question about this elaboration. Can you write more about setting DestPeripheral? Documentation is not clear for me about this and i cant understand when i have to set DestPeripheral and when SrcPeripheral. Can you give me a simple example to explain this issue?

Thejasvi said...

@^^

For every DMA transfer you need to specify the source and the destination. That is what you are doing by setting the DMACCDestAddr and DMACCSrcAddr registers.

In the example given in the tutorial I am transferring huge amount of data from memory to GPIO port. So I have set the source address as the address of the array that is storing the data in memory and destination address as the GPIO port address (FIOPIN3).

You will have to set the source and destination addresses before you start the DMA.

See the entire code I have posted, you might understand it there. If you still have problems repost your question, I will try my best to explain it to you.

Anonymous said...

Hi, we don't understand each other, i mean, what you are doing in 8 point, here:

LPC_GPDMACH0->DMACCConfig = ( 10 << 6 ) | ( 1 << 11)

(1 << 11) - you are setting type of transfer and this is clear for me, but i don't understand this (10 << 6), why you are setting the timer1 address here? I think it should be (10 << 1) becouse the timer1 generate request so it is source request peripheral rather destination request peripheral, am i right?

Thejasvi said...

Here I am doing transfer from memory to peripheral and it is the peripheral (not memory) which should have a request generator. Memory will not have a request generator. Since the peripheral is the destination, I am setting timer1 as the destination request peripheral.

To be more clear, in DMA always the peripheral generates the request and then memory just sends or receives data through the DMA controller. So in this case the destination should generate the request.

Benjamin said...

Thanks for posting these great tutorials on your blog. Keep up the good work!

Anonymous said...

Hi again :) I have one more questions, how should i know which type of transfer i should choose?

Thejasvi said...

@^^

Isnt it obvious??

If you are doing transfer from memory to peripheral you will choose that.

Or if you need to collect some data that is coming as input to one of your peripheral, you will do peripheral to memory transfer.

Or if you need to transfer blocks of data between different memory blocks you will do memory to memory transfer.

Or if you need to just get input from a peripheral and output it to another peripheral you will choose peripheral to peripheral transfer.

Anonymous said...

Hi, it is obvious if i copy for example
- from spp rx| adc | uart rx to memory (peripheral to memory)
- from memory to spp tx| adc | uart tx (memory to peripheral)
- from spp rx| adc | uart rx to spp tx| adc | uart tx (peripheral to peripheral)

but it isnt obviuos which type of transfer i have to use if i use timer to generate request. In this case you do copy from memory to memory (external port is also memory) where timer generate request and this is not obvious for me.

Thejasvi said...

Well now I understand your question. As you said memory to GPIO (which is again in the memory address space) is a memory to memory transfer. But for memory to memory transfers DMA will simply dump all the data without the need of any request.

In this code I wanted a set of burst transfers to occur at specific periods of time and not all at once. Therefore I selected the transfer type as memory to peripheral and simply selected timer1 as request generator so that I can control the timing by changing the compare values of the timer. So this is a pseudo memory to peripheral transfer done so just to get some timing.

The thing you need to understand here is that the destination and the request generator need not be same. But usually in memory to peripheral transfers you will select the same peripheral as the request generator.

Hope it helps.

Anonymous said...

alright, I understand almost everything but i have one last questions, would this program be working correctly if i will use peripheral to memory transfer instead of memory to peripheral?

use this instruction
LPC_GPDMACH0->DMACCConfig = ( 10 << 1 ) | ( 2 << 11)
instead of this one
LPC_GPDMACH0->DMACCConfig = ( 10 << 6 ) | ( 1 << 11)
?

Anonymous said...

I would like to ask if this program will be working correctly if I will use peripheral to memory transfer instead of memory to peripheral?

use this instruction
LPC_GPDMACH0->DMACCConfig = ( 10 << 1 ) | ( 2 << 11)
instead of this one
LPC_GPDMACH0->DMACCConfig = ( 10 << 6 ) | ( 1 << 11)
?

ps. please remove my previous post...

Thejasvi said...

Well I think 90% it will work. As long as you specify your source and destination the DMA will transfer data between them only and it does not depend on the type of transfer. But now it will wait for the source request to arrive before making a transfer.

For the remaining 10%, I do not know how the DMA uses the AHB and APB in LPC1768. If it tries to get the data from APB thinking that the source is a peripheral then there will be a problem.

Zdrav said...

3. Set the Match Compare 0 (MAT1.0 signal) as the DMA request signal

LPC_GPDMA->DMACSync |= 1 << 10; // use MAT1.0 for Sync (LPC17xx manual table 558, pg 599)

Just for the record. Writing bit hi in DMACSync disables the synchronization logic.
Did you mention any difference with synchronization disabled and enabled? I'm trying to figure out the impact of this setting on the DMA.

Thejasvi said...

Actually you are right. I do not know why I have done that. I should not have put that line in there. Basically that line is disabling the synchronization of DMA w.r.t the request signal from the timer. I should have left it as zero. Thanks for pointing it out.

Anonymous said...

Hello! Thanks to share your projects. Do you have any doc about ethernet with LPC1768? I tried to compile the example (http://coocox.org/EXAMPLE/NXP_LPC1766_GCC.htm) from Coocox but I got a lot of errors with CoIDE 1.6. Thanks a lot.
Wagner

Thejasvi .M.V said...

Wagner,

I was using some ethernet libraries provided for mbed. But I do not know any general library that works on LPC 176x other than mbed. Sorry.

But the site that you downloaded the library from, mentions that it is written for LPC 1766 and not 1768. Make sure that it is not using any features specific to 1766 that are not there in 1768.

dinesh kumar. R said...

hi @thejasvi.

my setup is like, i have to do complete transfer of 256 bytes each of data is of size 1 byte. i don't have all the data ready made, but i will get next byte within some time after making transferring 1st byte. i configured timer as DMA request.

Now, my doubt is that once it gets the DMA request from timer. will this makes complete 256 bytes transfer or it waits for timer request after completing each byte transfer?

Thejasvi said...

See the explanation in Step 7. For your case, you will need to configure the transfer size as 256 and the source and destination burst size as 1. So it will be

LPC_GPDMACH0->DMACCControl = 256 | ( 1 << 26 );

If you configure it like this, the DMA will transfer only one byte per timer DMA request until 256 bytes are transferred.

dinesh kumar.R said...

Hi thejasvi,

DMAREQSEL definiton is missing in lpc17xx.h, instead it is present in uart structure in lpc17xx.h. how to make use of that register, is the address defined is right? how to change it or else we can use the same as it is mentioned in the uart struct.

Venkatesh said...

Hi all,
I am working the USB DEVICE controller in LPC17XX.Here i Need to transfer the data from SRAM to Host device through USB device controller(planning to DMA in USB).
I was looking into to USBMEM code of LPC17XX sample code.
so can any one help regarding......

1.How data will be transferred to HOST.
2.As Get Descriptor cmd will send by HOST.Where it will fetch the info. from USB device configuration(EP_RAM -contrl endpoint0 or ANY of DESC(device/config)?

Thanks,
Venkatesh.

dinesh kumar.R said...

sorry,
In keil examples, they had given one header file, in that it was not ter.

but now i am using lpc17xx.h from the internet. Its fine now.

DMAREQSEL is present in LPC_SC.

Thejasvi said...

@ Venkatesh

At this point of time, I know very little about the USB. So please wait for someone else to answer your question or post the same question in some other forum. Sorry.

venkatesh said...

Now I am trying GPDMA in LPC1768, i am trying for GPIO to memory transfer (SRAM).
I thought like it is memory to memory transfer as mentioned in the user manual. And wrote code for 256 bytes of transfer with timer DMA request.

configuration:
transfer length - 256 bytes.
source width & destination width - 1 byte.
source & destination burst size - 1 byte.
destination address auto increment.
DMA request for each timer match.

DMA transfer is occurring but timer match is not recognized by the controller.

between two cycle's it is transferring 11 bytes but 1 byte is expected.

is timer request applicable for M2M transfer??

Thejasvi said...

DMA does not use timer match for memory to memory transfers. You can set the destination as a peripheral even though GPIO is in memory address space. This will then be a pseudo mem to peripheral transfer and this uses the timer match.

Please see the above discussions in the comments for more information.

dinesh kumar.R said...

hi @thejasvi,

suppose if i am using both PWM and timer and configured timer for DMA request purpose. microcontroller has 2 different counter 's for PWM and timer? or use the same counter for both. if so, how to make it synchronise.

I configured PWM & also timer. starting PWM first & timer in the next instruction. for each pwm cycle, it ll fetch a byte of data. but when i tried to change(increase) timer match value for DMA request, it is transferring the same value twice. if i try to increase the timer value, it is not transferring the data. actually missing some data.

i am confused, how this dma request selection of timer will work.

Thanks & regards,
Dinesh.

Thejasvi said...

The PWMs in LPC17xx have dedicated timers and will not use the general purpose timers.

It is very difficult to exactly synchronize them. If you are using the same clock frequency for both the timer and the pwm, then try to decrease (not increase) the match value. Because, here the PWM is ahead and thus the timer should count less number of cycles to be synchronized with the PWM. (I know it is counter-intuitive, but if you think, that is how it is).

If you need to synchronize them exactly, you can write a code very carefully in assembly language that can synchronize them. But for this you should choose instructions that take fixed number of cycles, and also you need to modify the value you store into the timer such that it takes care of the number of cycles elapsed between when you started the PWM timer and when you will start the TIMER0 or other timer.

The basic idea is this. You will configure the PWM as usual, but you will enable/start the PWM using an assembly instruction. Then you will load a suitable value to the timer using assembly instructions and start it (like if the instructions between when you started the PWM and when you actually start the TIMER takes 5 cycles to complete, and the timer is running at the same frequency as of the ARM core, then you will load the TIMER with a value 6. 6 because the timer starts counting from the next clock cycle when start it). But if you are using a pre-scaled clock for the timer, then you need to pre-scale the value that you load into the timer. Also you will have to use "nop"s to get the total number of cycles to an LCM of the time duration of the core clock and the timer clock.

If you turn the PWM off and turn it on again, then you will have to do this procedure again.

dinesh kumar.R said...

thanks @thejasvi, still confused and using by tuning the timer value. i will try in assembly language.

Thanks & regards,
Dinesh.

Leonardo Garberoglio said...

I need to use SPI with DMA and timer1. I want to send 256bytes, 10useg after Match Register 0 of timer 1.

How do I configure DMAREQSEL and destination request peripheral?

I try different way but no one works fine...