How can I reliably make variable sized writes using SPI via DMA?

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
lock attach
Attachments are accessible only for community members.
PaPr_1276616
Level 1
Level 1

Hi,

I'm trying to write data over SPI using DMA.  I have tried to implement methods used in the Cypress examples, but as soon as I add the feature of variable sized payloads using the API function DMA_SetNumDataElements() the resulting data stream is broken.

I have kept this project extremely simple, it is based on the PSoC Creator Example Project "PSoC 4 DMA SPI Example Project 1.0".

I am using the CY8CKIT-043 (4200M).  I removed the slave hardware, to further simplify the project.

I loaded the master Tx buffer as follows:  static const int8 masterTxBuffer[] = {"abcdefghijklmnopqrstuvwxyz1234567890"};

"I have (intended to, at least!) coded the master to alternate writing:

a two byte transfer  [ a, b ]  followed by a 32 byte transfer [ a, b, ... z, 1, 2, ... 6 ], and performing this sequence four times.

I use a Saleae Logic 8 to monitor the SPI master transmit.  See the output file attached.  The first transaction is over 40 bytes, which causes a wrap-around of the masterTxBuffer.   It does not loop properly, either.

I would *greatly* appreciate any help with making this simple DMA scheme run properly.

My project is attached,

Cheers,

Paul

0 Likes
1 Solution
lock attach
Attachments are accessible only for community members.

Hi Hari,

I have found the answer, which I'll share here in full.  There seems to be a subtlety in the use of the DMA component when using the API function DMA_SetNumDataElements(). There is a requirement that no changes to the descriptor be made while it is 'validated'.  There is an API function to validate the descriptor, DMA_ValidateDescriptor(), but not one to invalidate it. The DMA component can be set to automatically invalidate when the transaction completes, using the configurator.

Now, the descriptor is automatically validated when the call to DMA_Start() occurs, which implies the DMA component will always use the user provided value for 'Number of data elements to transfer' from the relevant descriptor tab of the DMA configurator, for the first transaction.  After that transaction is completed, the firmware may call DMA_SetNumDataElements() and then revalidate the descriptor.

I *wrongly* assumed the configurator tabe would be loaded with a 'Number of data elements to transfer' value large enough to take any of the payload I generate.  This caused the first transaction to always be too long.

Other than that, I had a bug where I revalidated the descriptor before attempting to update the number of elements.  This was because I based my code on the Cypress example (PSoC 4 DMA SPI Example Project 1.0) which didn't alter transaction length.

Hari, thank you for all your time and input with this issue, please see my workign solution attached.

Regards,

Paul

View solution in original post

0 Likes
4 Replies
Hari
Moderator
Moderator
Moderator
750 replies posted 500 replies posted 250 solutions authored

Hello PaPr_1276616

From the data shared, the second iteration (the 3rd and 4th Tx) are successful. So, the issue in this case could be the delay not being sufficient to transmit the data. Hence, can you introduce a CyDelay() after TxDmaM_Start()? This would make sure that the SPI block has sufficient time to finish the transfer before the DMA is triggered again. You can even check the return value from CyDmaGetStatus to check if the DMA has finished transferring the elements.

Please let us know if this worked in your case.

Best regards,
Hari

0 Likes
lock attach
Attachments are accessible only for community members.

Hi Hari,

Thank you very much for your response.  I followed your suggestion, first using 10us delay, but the packets were all over length, so I reduced that to 1us.  The addition of a delay seems to allow the DMA to process too many elements.

In the case of the 1us delay, the results (of a single capture) were as follows:

[a, b, c]

15us delay

[a, b, c]  immediately followed by

[e->p, a, b, c]  (15 byte packet with multiple errors)

15us delay

[a, b, c]  immediately followed by

[e->p, a, b]  (14 byte packet with multiple errors)

17us delay

[a, b]               Correct packet

25us delay

[a, b, c]

15us delay

[a->z, 1->6]    Correct 32 byte packet

So there are two correct packets out of 8, and the correct packets are out of sequence! They should alternate, so the whole thing is a mess.

I expect the hardware of the DMA should stop after the specified number of elements are transferred.  Ideally it should not require the user to pad out the operation with blocking waits, since the whole point of DMA is to offload this processing to hardware.

Paul

0 Likes
lock attach
Attachments are accessible only for community members.
Hari
Moderator
Moderator
Moderator
750 replies posted 500 replies posted 250 solutions authored

Hello PaPr_1276616

I tried your requirement with a UART block, since I can see the output directly.

The reason you are observing the issue is due to TxDMAM_Start() being called. This calls the init function which sets the parameter according to the configuration set in the configurator.

I am attaching the project that I used to test it out. you can easily convert this to an SPI as well, since they use the same SCB block internally, it just requires you to change the UART block with SPI in top design and rename the APIs and pointers.

Please try this at your end and let us know if it works.

Thanks,
Hari

Ideally it should not require the user to pad out the operation with blocking waits, since the whole point of DMA is to offload this processing to hardware.

You are right and this is the case. But in the code, you are using the DMA block again and this causes the configurations to be altered. If the DMA is used without CPU intervention in the middle, the operation can complete without any issues.

Thanks,
Hari

0 Likes
lock attach
Attachments are accessible only for community members.

Hi Hari,

I have found the answer, which I'll share here in full.  There seems to be a subtlety in the use of the DMA component when using the API function DMA_SetNumDataElements(). There is a requirement that no changes to the descriptor be made while it is 'validated'.  There is an API function to validate the descriptor, DMA_ValidateDescriptor(), but not one to invalidate it. The DMA component can be set to automatically invalidate when the transaction completes, using the configurator.

Now, the descriptor is automatically validated when the call to DMA_Start() occurs, which implies the DMA component will always use the user provided value for 'Number of data elements to transfer' from the relevant descriptor tab of the DMA configurator, for the first transaction.  After that transaction is completed, the firmware may call DMA_SetNumDataElements() and then revalidate the descriptor.

I *wrongly* assumed the configurator tabe would be loaded with a 'Number of data elements to transfer' value large enough to take any of the payload I generate.  This caused the first transaction to always be too long.

Other than that, I had a bug where I revalidated the descriptor before attempting to update the number of elements.  This was because I based my code on the Cypress example (PSoC 4 DMA SPI Example Project 1.0) which didn't alter transaction length.

Hari, thank you for all your time and input with this issue, please see my workign solution attached.

Regards,

Paul

0 Likes