CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

[Off Topic] back to back DMA transfers

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
jeremiah



Joined: 20 Jul 2010
Posts: 1349

View user's profile Send private message

[Off Topic] back to back DMA transfers
PostPosted: Thu Feb 02, 2023 12:43 pm     Reply with quote

I'm semi new to using DMA. The problem conditions:
I have a buffer of data that I on the fly convert to another format and send via DMA. I have to do the conversion on the fly as the fully formatted data would be larger than the amount of RAM available. So I have chosen to break it up in chunks and send each chunk over DMA individually, BUT I want to make sure to send the data "back to back" as the final data is considered "asynchronous" by the receiver, so I cannot have pauses between DMA chunks.

I'm sending the data out via SPI for convenience (again, not actually sending the clock, just MOSI).

Is there a good method to ensure each chunk is "back to back" with no pauses in between.

My first attempt was something like this:

2 buffers, dma1 and dma2

fill dma1
initiate dma transfer (one shot) on dma1
fill dma2
wait on dma1 transfer to complete AND spi buffer to not be full
initiate dma transfer (one shot) on dma2
repeat above until all data is sent


Basically, I am filling one buffer while the other is sending and flipping them each time I send new data.

For the bench top settings I have, this appears to send the data without pause, but I am worried if I bump up the bit rate on the SPI if the part where I "initiate dma transfer (one shot) on dma2" would take too long to keep the data back to back (there's a lot of instructions there)

So I was wondering:
Is there a better way to fill/send/fill/send without having to call dma_start() for each send? I thought about using one large buffer and using the half full flag, but I can't always guarantee that the data will be a multiple of 2 buffers.

I tried using continuous mode, but that didn't work unless I set the size param of dma_start() to 1 and also negated my check on if the previous transfer was done.
Ttelmah



Joined: 11 Mar 2010
Posts: 19520

View user's profile Send private message

PostPosted: Fri Feb 03, 2023 2:38 am     Reply with quote

That sounds to be a perfect candidate for using the DMA in ping-pong
mode rather than one shot.
Have a single DMA buffer twice the size of your data block. Setup ping-
pong, with the half empty/half full interrupt, and the auto enable set
so the transfer will automatically wrap back to the start of the buffer.
You can then fill the bottom half of the buffer, start the transfer, and
start filling the second half. When the interrupt comes, you then fill the
low half. The transfer will be continuous without any pause.
It'll need some careful setup, but there are two examples using ping-pong
with the compiler.
That is very much what the half way interrupt is 'for'.
You can also do this with two DMA buffers and two DMA channels. You
specify the second channel to be triggered by the finish on the first and
vice versa. Advantage the two buffers are separate, and you get a
different interrupt for each finishing (rather than having to keep track
of which half you are using yourself). Downside, uses two DMA channels,
and not automatically continuous. This depends on whether your particular
chip supports the DMA transfer end as a trigger event (varies). You
don't tell us what chip your are using.

As a comment, remember you have a whole byte time after a byte is
loaded, before the next byte can be sent (two byte times in 16bit mode).
Again depending on what chip this is, it may support enhanced buffer
mode. If so, you have several bytes of buffer between the actual DMA,
and the SPI output, so a lot of time to prepare the next transmission.
jeremiah



Joined: 20 Jul 2010
Posts: 1349

View user's profile Send private message

PostPosted: Fri Feb 03, 2023 9:33 am     Reply with quote

Sorry, I didn't mention the chip since was an off topic / general query. I'm reusing an existing board (so we don't have to make a board for this) with a PIC24FJ1024GB610.

So I did some digging, my chip doesn't seem to have a ping pong mode (and the CCS header doesn't have a #define for it). I think I could set it up as a "one shot repeated" mode, which sounds like ping pong and use the half and done interrupts for notifying for each half.

However, a followup question, and this applies to my situation (and maybe also chips that support ping pong mode), how does this method handle buffers that are not sized multiples of two? If I setup two buffers and rotate through them, the DMA is gonna auto hop to the next one. If I only have say 3 buffers worth of data sometimes, then how do I handle not letting it hit the 4th buffer with random or old data in it.
Ttelmah



Joined: 11 Mar 2010
Posts: 19520

View user's profile Send private message

PostPosted: Fri Feb 03, 2023 11:06 am     Reply with quote

I don't think you can use ping-pong with an odd number of bytes, The
transfer size value stays the same, so the total would have to be a
multiple of 2.
Your chip does support triggering off a DMA channel interrupt, so you
can load the buffer for DMA1, and set DMA2 to trigger when this completes.
Have DMA1 set to trigger when DMA2 completes, and it'll alternate between
the channels, with no delay. Have your own handler for the same interrupts,
and you have the entire time takes to send the DMA1 buffer to load the
DMA2 buffer (and vice versa). On the DMA1 interrupt you have to fill the
DMA2 buffer (again and vice versa). Using two channels like this, you
ould have different sizes for the two channels, so could handle an odd
number.

So, what you currently have, except you setup the secnd channel to
trigger on DMA1 complete.
manually trigger DMA one (using one shot), and then when you get to
the DMA1 interrupt, reprogram it to trigger on DMA2 completing.
The two channels will then trigger sequentially, with no delays.
Obviously you'll have to stop the DMA when the transfer is complete.
jeremiah



Joined: 20 Jul 2010
Posts: 1349

View user's profile Send private message

PostPosted: Fri Feb 03, 2023 11:25 am     Reply with quote

Ok, thanks. I'll play around with that.
Ttelmah



Joined: 11 Mar 2010
Posts: 19520

View user's profile Send private message

PostPosted: Sat Feb 04, 2023 5:56 am     Reply with quote

Your question, made me go and study a few data sheets.
It appears that the chips that don't support ping-pong, are the ones that
allow a DMA channel to be triggered by the completion of another DMA
transfer, while the ones that don't allow this, are the ones that support
ping-pong. So on the chips without ping-pong, you can get the effect of
ping-pong, by using two channels and triggering each off the other,
while the ones supporting ping-pong allow this to be done with one
channel. Each approach has advantages and disadvantages. Obviously
the two channel approach loses a channel, but ping-pong is slightly more
limited. It looks as if Microchip give these abilities as either/or, rather
than offering both.
Hadn't 'twigged' this before.
jeremiah



Joined: 20 Jul 2010
Posts: 1349

View user's profile Send private message

PostPosted: Thu Feb 09, 2023 9:44 am     Reply with quote

Followup question: if I am transferring out of SPI, but reprogram DMA1 to trigger off of DMA2 and DMA2 to trigger off of DMA1, how does the PIC know to only transfer each bite to the SPI buffer only when it is available?

When I started messing around, I had to tell it to trigger off of SPI to even get it to start (even with the FORCE_NOW option).
Ttelmah



Joined: 11 Mar 2010
Posts: 19520

View user's profile Send private message

PostPosted: Thu Feb 09, 2023 12:07 pm     Reply with quote

Don't confuse the transfer trigger, with the restart trigger. The restart
trigger is where the 'force now' is in the setup. You will need to 'force now'
on the firts start, and then change this to trigger off the other dMA
channel in it's termination interrrupt. The transfer trigger has to stay set
to SPI, so it loads a new byte for each byte request.
jeremiah



Joined: 20 Jul 2010
Posts: 1349

View user's profile Send private message

PostPosted: Fri Feb 10, 2023 8:54 am     Reply with quote

So these are my only available options for that parameter:

Code:

// Constants used in dma_start() second param are:
#define DMA_SOURCE_PIA_MODE        0x00C0
#define DMA_DEC_SOURCE_ADDR        0x0080
#define DMA_INC_SOURCE_ADDR        0x0040
#define DMA_SOURCE_ADDR_UNCHANGED  0x0000
// or (via |) one of the following with the above
#define DMA_DEST_PIA_MODE          0x0030
#define DMA_DEC_DEST_ADDR          0x0020
#define DMA_INC_DEST_ADDR          0x0010
#define DMA_DEST_ADDR_UNCHANGED    0x0000
// or (via |) one of the following with the above
#define DMA_CONTINUOUS             0x0008
#define DMA_ONE_SHOT               0x0000
// or (via |) the following with the above
#define DMA_REPEATED               0x0004
#define DMA_FORCE_NOW              0x0100


My first call to dma_start() is:
Code:

   dma_start
      (/* channel => */ 0,
       /* mode    => */ DMA_INC_SOURCE_ADDR | DMA_DEST_ADDR_UNCHANGED | DMA_REPEATED | DMA_FORCE_NOW,
       /* target  => */ getenv("SFR:SPI3BUFL"),
       /* source  => */ dma,
       /* count   => */ DMA_BUFFER_SIZE);


My later call to update it
Code:

      dma_start
         (/* channel => */ 0,
          /* mode    => */ DMA_INC_SOURCE_ADDR | DMA_DEST_ADDR_UNCHANGED | DMA_REPEATED,
          /* target  => */ getenv("SFR:SPI3BUFL"),
          /* source  => */ dma,
          /* count   => */ DMA_BUFFER_SIZE);


The only way I see to set a DMA based trigger is in setup_dma(), but that's the transfer trigger and where I tell it to use SPI, so if I set that to DMA it will stop using SPI? I don't see a place to set a dma restart trigger
temtronic



Joined: 01 Jul 2010
Posts: 9229
Location: Greensville,Ontario

View user's profile Send private message

PostPosted: Sat Feb 11, 2023 6:25 am     Reply with quote

have a question.....
re:
Quote:
I want to make sure to send the data "back to back" as the final data is considered "asynchronous" by the receiver, so I cannot have pauses between DMA chunks.

Maybe 'silly' of me but HOW do you signal 'end of data transfer' ? Usually ( always ?) there's some kind of unique 'end of text' marker ANDed with a 'timeout' condition to ensure the serial data gets from 'A' to 'B' properly.

Just trying to understand why you can't have 'pauses between DMA chunks'.

I've done active code translations in the past ( first 18F46K22 project ) where incoming RS232 data on COM1 was modified and sent out COM2,so I'm curious.
Must get that from my 2 cats......
Ttelmah



Joined: 11 Mar 2010
Posts: 19520

View user's profile Send private message

PostPosted: Sat Feb 11, 2023 9:05 am     Reply with quote

If you look, he is saying that though the interface is synchronous, the
receiving device expects it at a constant rate (effectively asynchronous).
Must admit on most devices there is some latitude. Usually having some
degree of buffering so there can be short pauses. Would be interesting to
know 'what' the device is so this can be studied.
Ttelmah



Joined: 11 Mar 2010
Posts: 19520

View user's profile Send private message

PostPosted: Sun Feb 12, 2023 1:55 am     Reply with quote

I have to say, with your chip, simplify.

Just setup your DMA buffer to be twice the size of the ones you currently use
for the two channels.
Setup one DMA channel, just as you currently are, but set it up for
continuous. Add 'DMA_HALF_INT' to the setup.

Then start by filling the low half of the buffer.
Trigger the DMA.
Fill the second half of the buffer.

You will get a DONEIF at the end of the buffer, and a HALFIF at the midpoint.
According to which flag is set, fill the other half of the buffer.
(Remember you have to work half the buffer 'ahead' of where it is
reading, so at HALFIF fill the bottom of the buffer, and at DONEIF
fill the top).

You will need to clear the HALFIF flag yourself. It is a 'non persistent'
interrupt, being set when the transfer gets to halfway.

Simple, and does what you want.
jeremiah



Joined: 20 Jul 2010
Posts: 1349

View user's profile Send private message

PostPosted: Mon Feb 13, 2023 11:20 am     Reply with quote

Yep yep, that's the method I am going for currently (twice the buffer).
jeremiah



Joined: 20 Jul 2010
Posts: 1349

View user's profile Send private message

PostPosted: Mon Feb 13, 2023 11:22 am     Reply with quote

temtronic wrote:
have a question.....
re:
Quote:
I want to make sure to send the data "back to back" as the final data is considered "asynchronous" by the receiver, so I cannot have pauses between DMA chunks.

Maybe 'silly' of me but HOW do you signal 'end of data transfer' ? Usually ( always ?) there's some kind of unique 'end of text' marker ANDed with a 'timeout' condition to ensure the serial data gets from 'A' to 'B' properly.

Just trying to understand why you can't have 'pauses between DMA chunks'.

I've done active code translations in the past ( first 18F46K22 project ) where incoming RS232 data on COM1 was modified and sent out COM2,so I'm curious.
Must get that from my 2 cats......


Beginning and end are encoded into the actual data. If they are missed, it is just ignored until the next message. The lack of pause is a hardware limitation. I can't go into detail on that unfortunately. Custom hardware belonging to a customer.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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