|
|
View previous topic :: View next topic |
Author |
Message |
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
PIC24 DMA help needed please [SOLVED] |
Posted: Fri Feb 14, 2014 7:57 am |
|
|
DEVICE: PIC24HJ128GP306
COMPILER: 4.141
Has anyone successfully been able to use DMA on the PIC24 series? It seems very simple but doesn't seem to work.
In my main .h file, I have this:
Code: |
#BANK_DMA unsigned char sHello[8] = "\n\rHello"; |
Then, when the PIC initializes in the main() function, I simply call this:
Code: |
setup_dma( 0, DMA_OUT_UART2, DMA_BYTE );
enable_interrupts( INT_DMA0 );
fprintf( UART2, "\n\rDMA INITIALIZED" ); |
UART2 in this case is a serial console where I print messages and this is also where the "Hello" message will be displayed through DMA transfer.
Then I also have this interrupt routine enable for DMA:
Code: |
#INT_DMA0
void INT_DMA_CH0( void )
{
fprintf( UART2, "\n\rDMA ISR Complete" );
} |
I also have an external interrupt #INT_CNI that gets fired through a signal change and this is the code:
Code: |
#INT_CNI
void EXT_INT( void )
{
dma_start( 0, DMA_ONE_SHOT, &g_sHello[0] );
} |
What I expect to happen is when the PIC initializes, my serial console tied to UART2 will display <DMA INITIALIZED> and when I trigger the external interrupt, I will get <Hello> followed by <DMA ISR Complete>.... but that's not what's happening.
As soon as the PIC initializes, I get <DMA INITIALIZED> immediately followed by <DMA ISR Complete> and <Hello> never gets printed.
Any suggestions would be greatly appreciated.
Thanks!
Ben
Last edited by benoitstjean on Mon Feb 17, 2014 1:56 pm; edited 1 time in total |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1349
|
|
Posted: Fri Feb 14, 2014 6:18 pm |
|
|
You don't show enabling the CNI interrupt. On that note, you specify the CNI interrupt, but call it the EXT interrupt. Those are two different interrupts. Which one are you really wanting? CNI triggers on both edges (rising and falling) and EXT only triggers on one edge (rising by default, falling if configured to do so) |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
|
Posted: Fri Feb 14, 2014 8:50 pm |
|
|
Hello Jeremiah,
Thanks for your answer. For the "external interrupt", it's just an interrupt on change, not an external interrupt. But regardless, the point with this topic is to transfer data using DMA when the interrupt "INT_CNI" changes state so whether it is ext_int or int_cni, it's just an external interrupt and when it changes state, I want to transfer data through DMA.
Thanks again,
Benoit |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1349
|
|
Posted: Sat Feb 15, 2014 1:11 pm |
|
|
You didn't address my very first comment. Did you enable the CNI interrupt? Your posted code doesn't show. This is why the forum rules specify that you provide us with a small completely compilable program that illustrates the problem. Without that, we are kinda shooting in the dark. You might have enabled the interrupt, but there's no way to tell if that is the issue or if it is deeper.
EDIT: The reason I asked the question on which interrupt you really wanted is because if you meant to use the EXT and enabled the CNI (or vice versa), it wouldn't work.
I realize these seem like simple questions, but without any other info to go on, we have to start simple. |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
|
Posted: Sat Feb 15, 2014 2:41 pm |
|
|
Hello Jeremiah,
Yes, the CNI interrupt is enabled but never mind, I got it to work.
Too bad CCS doesn't have good documentation but I have to say, their support is better that lots of other companies. I sent them an email and they responded and it had a solution.
So when I called dma_start, I had to add an extra flag:
dma_start( 0, DMA_ONE_SHOT | DMA_FORCE_NOW, &g_sHello[0] );
I tried both flags individually and was getting some weird results but never thought of bit-wise OR'in both flags together. When I have the complete solution working, I will post all the code here... but in the mean time, this is the email from CCS related to my original post (I used what I posted here as an email to CCS):
*****************************
The only thing you should need to get it to work is to change your dma_start() function call to the following:
dma_start(0, DMA_ONE_SHOT | DMA_FORCE_NOW, &g_sHello[0]);
The DMA_FORCE_NOW option causes the transfer to happen immediately, the way you were calling it causes the DMA to wait until the peripheral request it. You can get the UART2 peripheral to request it by doing a fputc() or a fprintf().
CCS Support
*****************************
Thanks again for your help.
Benoit |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
|
Posted: Sat Feb 15, 2014 3:29 pm |
|
|
Actually Jeremiah (or anyone else), if you've had DMA working, is there a way I can pass in DMA a block of variying size or does it need to be of fixed size?
The reason I am asking is because the function I use to transmit the packet over DMA has the first parameter as a pointer to an unsigned character string and another parameter as an unsigned int that is the length of the data to transmit.
That string can be anywhere between 6 bytes and 133 bytes and the string is not made-up only of printable characters, it can be any value from 0-255.
In my main():
setup_dma( 0, DMA_OUT_UART1, DMA_BYTE );
enable_interrupts( INT_DMA0 );
g_bCanDma = true;
In my DMA interrupt:
#INT_DMA0
void INT_DMA_CH0( void )
{
g_bCanDma = true;
}
Then wherever the code needs to send data using DMA is below.
I thought the this would work but the outcome is different than what I expected:
void MyFunction( unsigned char* Data, unsigned int packetLength)
{
while(g_bCanDma == false);
g_bCanDma = false;
#BANK_DMA unsigned char* dmaPacket;
dmaPacket = malloc( packetLength );
memset( dmaPacket, NULL, packetLength );
memcpy( dmaPacket, Data, packetLength );
dma_start( 0, DMA_ONE_SHOT | DMA_FORCE_NOW, &dmaPacket[0]);
free( dmaPacket );
}
If I explicitely specify the size of dmaPacket when creating it like this:
#BANK_DMA unsigned char dmaPacket[20];
then when dma_start is called, 20 characters will be transmitted. If there are less than 20 characters in the buffer, then the remaining characters will be NULL (0x00). If there are more than 20, it is truncated to 20.
Two interesting thing I noticed are first, with the way I am currently doing it (the code posted above using the malloc), it seems that the DMA transfers a buffer of 2048 bytes eventhough the malloc function created a buffer of 6 bytes. Second, the buffer is empty.
As soon as I specify the size of the buffer e.g. [20] and remove the malloc, then the data contains what I sent (but gets truncated/filled with nulls if not the exact size).
Any ideas?
Thanks,
Benoit |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Sun Feb 16, 2014 2:05 am |
|
|
I'd say the way to do what you want, is to use the CCS functions to setup the transfer for the largest size required, then directly access the DMA0CNT register, and just change this to contain the number of bytes required for the transfer, before sending. Remember to use #word, and that the transfer is one greater than the number in the register. So
#BANK_DMA unsigned char dmaPacket[133];
Once, in your initialisation,
Then when you are about to send, do the memcopy of packetLength bytes, then do:
#word DMA0CNT=getenv("SFR:DMA0CNT")
DMA0CNT=packetLength-1;
and start the transmission.
Best Wishes |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
|
Posted: Sun Feb 16, 2014 7:15 am |
|
|
Hello Ttelmah,
Thanks for the info. Unless I'm doing something wrong, it doesn't seem to work. Here's the code:
/* New code */
#word DMA0CNT = getenv( "SFR:DMA0CNT" );
DMA0CNT = packetLength - 1;
/* Print length on UART 2 monitor */
fprintf( MONITOR_SERIAL, "\n\rL: %04X (%02X)", DMA0CNT, packetLength );
/* Existing code */
dma_start( 0, DMA_ONE_SHOT | DMA_FORCE_NOW, &dmaPacket[0] );
When it runs, this is what gets printed (because of that fprintf):
L: 0005 (06)
So the value of 0005 is DMA0CNT and the value of 6 is packetLength (not -1).
What I see on my Logic Analyzer output is my message of let's say, 6 bytes, followed by 127 NULL characters.
I'll try more stuff but so far, that's what happens.
EDIT: I've never used the direct register access like you showed using the getenv function. So my question is if I "get" the value of the DMA0CNT register, once I change this value using <DMA0CNT = packetLength - 1;>, shouldn't I re-assign the new value to the register?
EDIT 2: So from what I can see, I guess I am already assigning the value to DMA0CNT because it's already *that* register because I used the #word thingy. I've also modified the code slightly at the fprintf level:
#word DMA0CNT = getenv( "SFR:DMA0CNT" );
fprintf( MONITOR_SERIAL, "\n\rDMA0CNT: %04X", DMA0CNT );
DMA0CNT = packetLength - 1;
fprintf( MONITOR_SERIAL, "\n\rNew DMA0CNT: %04X (%02X)", DMA0CNT, packetLength );
dma_start( 0, DMA_ONE_SHOT | DMA_FORCE_NOW, &dmaPacket[0] );
So what this does it that it prints the DMA0CNT count before it gets modified and the value does change - here's the output:
DMA0CNT: 0084 (hex, 132 in decimal)
New DMA0CNT: 0005 (06)
But again, I have 133 bytes of data transferred.... The first 6 bytes are the correct ones, then I have 127 NULL characters.
Let me know if you think of anything else.
Thanks again,
Benoit |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1349
|
|
Posted: Sun Feb 16, 2014 9:19 am |
|
|
At that point, I would make two suggestions:
1. Print out the contents of all the DMA registers and see if any bits look suspect. Not a lot of people use DMA compared to other peripherals, so it is possible there is a bug in the setup.
2. Make a small program that only has the DMA related stuff that generates the same issue. Make sure it is small and compilable. This is good for three reasons: a) Sometimes while doing this, you catch your own mistakes, b) We can take a better look at the setup and execution and maybe notice something like commands being out of order or missing a parameter, and c) If all else fails, you'll have something you can send to CCS support so they can troubleshoot it more.
Sorry I'm not much helpful than that. Still mulling over possible reasons for the issue. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Sun Feb 16, 2014 12:32 pm |
|
|
Unfortunately, it sounds as if the CCS DMA functions reset the values each time they are used, rather than setting them when configured.
The #word statement (you only need this once), declares a variable that is located at the defined address (the hardware DMA0CNT register), that you can then write to/read from like any other register. |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
|
Posted: Sun Feb 16, 2014 7:54 pm |
|
|
Hi Ttelmah,
Now that I understand about the part with the direct register access, I'll try to do the setup everytime I need to send the data and see what happens and post the results here.
I did open a ticket with Microchip and I'm also waiting on a reply from CCS.
Thanks again,
Benoit
EDIT: So using Ttelmah's idea of accessing the PIC registers directly with the DMA transfer partially working (that's when I have a fixed 133-byte buffer, fill-it with 6 bytes and the DMA transfer sends 6 bytes + 127 NULL bytes for a total of 133 bytes), when I call dma_start, this is what the registers look like just after the call to dma_start (using a printf):
code:
dma_start( 0, DMA_ONE_SHOT | DMA_FORCE_NOW, &dmaPacket[0] );
#word DMA0CNT = getenv( "SFR:DMA0CNT" );
#word DMA0PAD = getenv( "SFR:DMA0PAD" );
#word DMA0REQ = getenv( "SFR:DMA0REQ" );
#word DMA0STA = getenv( "SFR:DMA0STA" );
#word DMA0STB = getenv( "SFR:DMA0STB" );
#word DMA0CON = getenv( "SFR:DMA0CON" );
fprintf( MONITOR_SERIAL, "\n\rDMA0CNT: %04X", DMA0CNT );
fprintf( MONITOR_SERIAL, "\n\rDMA0PAD: %04X", DMA0PAD );
fprintf( MONITOR_SERIAL, "\n\rDMA0REQ: %04X", DMA0REQ );
fprintf( MONITOR_SERIAL, "\n\rDMA0STA: %04X (%04X)", DMA0STA, &dmaPacket[0] );
fprintf( MONITOR_SERIAL, "\n\rDMA0STB: %04X", DMA0STB );
fprintf( MONITOR_SERIAL, "\n\rDMA0CON: %04X", DMA0CON );
fprintf output:
DMA0CNT: 0x0084
DMA0PAD: 0x0224
DMA0REQ: 0x000C
DMA0STA: 0x0039 (0x4000)
DMA0STB: 0x004F
DMA0CON: 0xE001
Above, I presume that the address of 0x0039 for DMA0STA is the offset from 0x4000 and is **somehow** calculated by the dma_start() function... but that's what I'm trying to figure-out: how does dma_start() calculate the offset?
Here, the value of 0x4000 to me seems "fine" since it is the start address of the DMA ram... but how does the dma_start function calculate an offset of 0x0039? I've also seen in other instances that the address was like 0x002F. I also noticed that although I am not using DMA0STB, when I print this address, it is 0x16 bytes greater than DMA0STB.
At the moment, I am simply trying to emulate what dma_start() does by using the direct register access... I have all the numbers correct but I have no idea how to get the right offset like dma_start has.
So I guess my question would be: how do I calculate the offset for DMA0STA given that #BANK_DMA assigns address 0x4000 to element 0 of my dmaPacket buffer?
Any idea?
Thanks again,
Ben |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Mon Feb 17, 2014 1:24 am |
|
|
OK.
I'd still let CCS set things up. Trying to calculate addresses is hard. Look at DS70223B section 22, if you want to learn more.
However I'd work like this:
Code: |
//Understand that all of these can be 'elsewhere'.
//Don't have to be in the routine, but as a configuration
//block at the start of the code.
#word DMA0CNT = getenv( "SFR:DMA0CNT" );
#word DMA0PAD = getenv( "SFR:DMA0PAD" );
#word DMA0REQ = getenv( "SFR:DMA0REQ" );
#word DMA0STA = getenv( "SFR:DMA0STA" );
#word DMA0STB = getenv( "SFR:DMA0STB" );
#word DMA0CON = getenv( "SFR:DMA0CON" );
#bit FORCE = DMA0REQ.15
//Note this addition. This is the 'force' bit to force a transfer to start
dma_start( 0, DMA_ONE_SHOT, &dmaPacket[0] );
//setup the transmission but do not trigger the start
DMA0CNT= packetLength-1;
//Now you have setup the transfer length _after_ CCS has done it's work
FORCE=TRUE;
//This should force the transfer to start.
|
So I let CCS do all it's setup, except for actually triggering the transfer, then set the new reduced count, and then force the transfer.
Unless they are doing something else behind the scenes, this should transfer packetLength bytes.
On the 2048 byte default buffer. Seems a little 'OTT'. This is the largest size a DMA transfer can move 1024*words.
Best Wishes |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
|
Posted: Mon Feb 17, 2014 8:30 am |
|
|
Seems to work!
So, the changes are as follows:
In my main():
Code:
void main( void )
{
// User code
setup_dma( 0, DMA_OUT_UART1, DMA_BYTE );
g_bCanDMA = true;
enable_interrupts( INT_DMA0 );
enable_interrupts( INT_TBE ); // Microchip states that the TX interrupt
// has to be active, haven't tested
// without it.
// More user code
while( 1 )
{
// ... And more user code
}
}
These three lines are moved to my .h file:
Code:
#word DMA0CNT = getenv( "SFR:DMA0CNT" )
#word DMA0REQ = getenv( "SFR:DMA0REQ" )
#bit FORCE = DMA0REQ.15
#BANK_DMA unsigned char dmaPacket[133];
Then, in my function where I need to transmit data using the DMA channel, this is what I do:
Code:
void MyFunction( unsigned char* sData, unsigned int packetLength )
{
FORCE = false;
while(g_bCanDma == false);
g_bCanDma = false;
// Create my packet out of the *Data string
// In the case of my specific application, I need to add a header
// and a footer to my data so...
dmaPacket_AddHeader();
dmaPacket_AddBody( sData );
dmaPacket_AddFooter();
dma_start( 0, DMA_ONE_SHOT, &dmaPacket[0] );
DMA0CNT = packetLength - 1;
FORCE = true;
}
And lastly, the DMA interrupt:
Code:
#INT_DMA0
void INT_DMA_CH0( void )
{
FORCE = false;
g_bCanDMA = true;
}
That's it!
Ttelmah, thank you!
I won't change the title to SOLVED until I am fully satisified.
Benoit |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19515
|
|
Posted: Mon Feb 17, 2014 9:40 am |
|
|
Worth just saying there is no point in setting 'FORCE' false.
You cannot clear it.
It is cleared by the hardware, when the transaction is completed.
It is a 'one shot' bit. The write to it forces the transaction to start.
Anyway, very good news that it works. |
|
|
benoitstjean
Joined: 30 Oct 2007 Posts: 566 Location: Ottawa, Ontario, Canada
|
PIC24 DMA help needed please [SOLVED] |
Posted: Mon Feb 17, 2014 1:55 pm |
|
|
So yes, it is now working. I am using CCS 4.141 and their documentation is wrong. Here's a reply from CCS:
-----------------------
You need to make the array as big as at least the largest number of bytes you need to transfer. Then when you call the dma_start() function you need to specify the number of DMA transfers to do with the optional fourth parameter. The value you actual pass for the forth parameter is one less then the total number of transfers, for example to transfer 8 bytes you would do it as follows:
dma_start(0, DMA_ONE_SHOT | DMA_FORCE_NOW, &array[0], 7);
You're using a older version of the compiler, I have since updated the documentation to be clearer. The way I told you to do it is correct. When not using ping-pong mode the fourth parameter is the number of DMA transfers to do minus one. If you don't pass that parameter then the compiler will set it based on what is passed for the third parameter.
When using ping-pong mode then the fourth parameter is the address of the second buffer and the fifth parameter is the number of DMA transfer to do minus one.
-----------------------
So my comment to them was that the way the docs explain how to use the function is different and inconsistent with the actual function itself.
So basically, setting-up DMA is easy once you know how and the code is all here in this thread. The only hickup is with the dma_start funciton which in the documentation, it indicates that parameter 4 is the addressB if using ping-pong mode and parameter 5 is the length of data to send... otherwise use parameter 4 as the length of data to send... but that's not explained.
So basically:
1) SETUP DMA:
main file:
setup_dma( 0, DMA_OUT_UART1, DMA_BYTE );
g_bCanDMA = true; // Global variable
enable_interrupts( INT_DMA0 );
header file:
#BANK_DMA unsigned char DMAPacket[133];
DMA 0 interrupt:
#INT_DMA0
void INT_DMA_CH0( void )
{
g_bCanDMA = true;
}
DMA data transfer:
while( g_bCanDMA == false );
g_bCanDMA = false
dma_start( 0, DMA_ONE_SHOT | DMA_FORCE_NOW, &DMAPacket[0], packetLength - 1 );
What will happen here is when you are ready to send data to DMA memory, the g_bCanDMA flag needs to be set to false. Then, the dma_start function requires the first address of the DMA block (&DMAPacket[0]) and will transfer <packetlength-1> bytes. The '0' in the function is the DMA channel and the <DMA_ONE_SHOT | DMA_FORCE_NOW> are flags to tell the DMA controller to send data only once AND will send it "now".
Once the full block of data has been sent, the #INT_DMA will fire and the g_bCanDMA flag will be set to true, meaning that a new DMA transfer can occur.
That's it, it works.
Ttelmah, thank you for all your help.
Benoit |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|