I try to get data from a UART via DMA. The problem is that the size of the frame is not constant, so I cannot build a TD with a given size. So my idea is to create a big (more than the maximum size) TD and loop on it. Then to know if data has arrived, I'd like to check at which address the next byte will be written and check with the address I got the last time I checked.
So my DMA configuration is like that:
DMA_XBee_RX_channel = DMA_XBee_RX_DmaInitialize(1, 1, HI16(CYDEV_PERIPH_BASE), HI16(CYDEV_SRAM_BASE));
DMA_XBee_RX_TD = CyDmaTdAllocate();
CyDmaTdSetConfiguration(DMA_XBee_RX_TD, 256, DMA_XBee_RX_TD, TD_INC_DST_ADR);
CyDmaTdSetAddress(DMA_XBee_RX_TD, LO16((uint32)UART_XBee_RXDATA_PTR), LO16((uint32)XBee_RX_buffer));
then to know if data has arrived, I call:
CyDmaTdGetAddress(DMA_XBee_RX_TD, NULL, &dest_addr);
and I compare dest_addr with the one of the last time I checked.
BUT, that doesn't work. dest_addr is never increased. I managed to make it work by changing the last line of the DMA initialization with :
This way dest_addr is increased as soon as I receive data, I get the data and treat them. But the DMA loop fails of course because TD is modified. So as soon as the 256 buffer is full, DMA continue to write data in the SRAM, but not at the begining of the buffer, it writes after the end of the buffer!!
So my question is : is there a way to make a DMA loop run indefinitely, and know from time to time how many data has been transfered to RAM? Maybe there is a register to know where the TD points instead of using CyDmaTdGetAddress?
Solved! Go to Solution.
is there a way to make a DMA loop run indefinitely? Yes. You appear to have it set correctly to infinitely loop. The third argument to CyDmaTdSetConfiguration() should be set to the starting TD which is what you did. The reason you get a buffer overrun is that you changed to: CyDmaChEnable(DMA_XBee_RX_channel, 0); The last argument should be 1 to preserve the original TD configuration.
and know from time to time how many data has been transfered to RAM? Maybe there is a register to know where the TD points instead of using CyDmaTdGetAddress? I don't believe there is a API call to determine where in the DMA buffer transfer the TD is current at.
When you specified CyDmaChEnable(DMA_XBee_RX_channel, 1) it preserved the TD parameters and CyDmaTdGetAddress(DMA_XBee_RX_TD, NULL, &dest_addr) returned a non-changing value in dest_addr. When you changed it to CyDmaChEnable(DMA_XBee_RX_channel, 0), the TD address was allowed to change because TD parameters were not preserved. However, you received a buffer overrun.
Suggestion: place an 8-bit counter (max count 256 = transfer count) that increments every drq of your DMA. You can do your transfer count math on the x_GetCounter() of the counter.
Thanks for your suggestion. I didn't thought about this counter which could indeed do exactly what I need. But:
- if I use a BasicCounter, I can't get the counter value in my C code.
- If I use a counter, I can't set the rx_interrupt as clock. It must be a digital clock or bus clock.
I'm not sure using the bus clock and put the rx_interrupt on the enable pin could work.
Apart from some specific cases, simple UART_Rx-DMA-RAM transfer has no benefits. If you need intermediate buffer to store incoming data, UART offers this option. Or you can make own circular buffer, see demo here
Using DMA prevents interruptions, and interruptions priorities problems. I don't need to know exactly when a byte arrive, but I need to check from time to time if some data has arrived
If some byte arrived, the ISR will fire, indicating a need to process input buffer (eventually). UART speeds are quite slow and asynchronous, so it is better done by CPU+ISR. Serving DMA buffer will require CPU intervention anyway, and will take more clocks.
DMA is intended for high-load deterministic streams, and CPU is better for slow asynchronous tasks (like UART).
Thanks for your hints. But we had trouble with a SPI master read running in a timer interrupt while a UART interrupt arrived. We missed some bits for the SPI and the while (!SPI_Mast_GetTxBufferSize()); never ends.
If you want to use the BasicCounter, you need to connect a StatusRegister to the outputs.
I don't see why you can't use the rx_interrupt as a clock. It's not only possible but sometimes a good idea.
Ok I did it with BasicCounter + StatusRegister. It compiles. I'll test in my system tomorrow.
For the Counter solution, here is the error message:
I finally try with basicCounter with this schema:
But that doesn't work either
when I call XBee_RX_cpt_Status_Read(), it is always 0, even when I'm sure data is received.
Thanks for the solution. I am now able to make it work as expected. The only problem I have is some setup time violation. And they are critical. If I keep my PsOC at 80MHz, I have setup time violation, and the counter doesn't work correctly.
If I decrease the frequency of the PLL, it works.
So in your example solution, if I change the CPU to Cy8C5888LTQ, I add a 24MHz quartz, set the PLL output to 80MHz (or even 40MHz), bus clock at 40MHz, you'll see the setup time violation.
I guess there is no way to keep the CPU clock at 80MHz and make it work?
Using a UDB counter (Counter_1) limits the input clock (fclock) to 39MHz. This would cause a a setup time violation at 80MHz or 40MHz input clock. However using a Fixed Function Counter the input clock can go up to 67.01 MHz.
Based on the schematics presented with the input counting being the data rate of the UART_1 (@ 115.2Kbps assuming 8N1) you are expecting to count the number of BYTEs received. Therefore at 115.2Kbps you would be receiving 11520 Bps. In the circuit proposed by AH, you only need to be slightly faster than two times 11520 Hz to capture the rx_interrupt and count them. 12MHz is OK but a bit of overkill.
Try this. Setup the following:
When I configure the above, I get no setup violations.
May I ask, what part of the circuit needs to be clocked at 80MHz? With the above, I can achieve 71MHz CPU clocking.
You can use the Sync block to re-synchronize the signals.
Please try this out and let us know if it works according to the requirement.
Thanks and regards
That indeed helps in the example project, but not in my full project. I don't understand why re-syncing helps as the count input don't need to be in sync with the clock.
That may also be because I use more than 70% of UDB, so the chip planner maybe has too much constraints?
Anyway thank you all for the hints. Ill continue playing with CPU speed / sync component to see if I can come to something.