UARTE internals (bare metal programming): Continuous reception, BREAK condition detection and how to use UARTE like a FIFO

Hi,

I'm making progress with using the UARTE in bare metal programming. My goal is to have maximum control over my system and provide a reliably and robust realtime implementation. So this time, I have some more sophisticated questions that are not documented in the Product Specification.

Reliable handling of UART data flow

It seems like the UARTE modules have an internal FIFO in addition to the EasyDMA feature. The documentation in the Product Specification suggests that there might be multiple bytes in waiting in the internal FIFO when the last byte of the current RX buffer is written and the ENDRX event is generated. It is especially mentioned in reference to the STPORX task, which might trigger an ENDRX event with data remaining in the internal FIFO (which needs a FLUSHRX task to write the remaining bytes via EasyDMA).

Question 0: How big is the internal FIFO of the UARTE modules?

Question 1: What happens to the remaining bytes in the internal FIFO when a new STARTRX task is triggered? Will the FIFO content be written to the EasyDMA buffer without date being lost? Or will the bytes in the internal FIFO be discarded when a STARTRX task is triggered? For reliable data reception (that does not miss any bytes on the UART while the receive buffer is switched), it would only make sense to continue writing the internal FIFO content when STARTRX is triggered, especially in combination with the ENDRX-STARTRX short which immediately restarts reception if the buffer is full.

Question 1a: When using the ENDRX-STARTRX short, will there no data be lost if the old EasyDMA buffer is full, and the STARTRX task continues reception into the next buffer?

Question 1b: When using the ENDRX-STARTRX short, if STOPRX is triggered before the current buffer is full, because of the short, reception should automatically be continued into the next buffer, just like question 1a mentioned. Will there also be no data loss in this case? Is this the way to manually trigger a safe/robust buffer switch and handle available data?

Question 2: I documented my approach in the following flow chart. Will this work without data loss? My goal is to take advantage of the UARTE capabilities and use large buffers that allow for using low priority UARTE interrupts that do not interfere with other time critical code in the system.

How to detect a BREAK condition

I consider using a BREAK condition for synchronizing communication shortly before the beginning of a data packet. Unfortinately, there is very little information available about the BREAK bit in the ERRORSRC status word.

Question 3: Is the BREAK bit just set by the SoC (when RxD stays low for too long) and must be reset by software? Or is it automatically reset of a rising edge is detected on the RxD line, so that it does automatically reflect the current RxD state?

Question 4: Is the BREAK bit only set while reception is active (STARTRX triggered, ENDRX not triggered yet)? Or can I completely stop reception, wait for the BREAK bit to become active (e.g. by polling the bit), and then manually start reception?

Question 5: If I detect a BREAK condition and trigger STARTRX while RxD is still low. Will I get an error event (interrupt) again? Or will the UARTE module just wait for the next falling edge on RxD (which is the trigger for the Start bit of a transmission) and start receiving?

Quetion 6: There is a ERROR-STOPRX short. If this short is combined with the ENDRX-STARTRX short, can this combination be used to trigger a receive buffer switch as described in my flowchart n question 2? Or will there be two ERROR events (one for frame error and one for break condition) in short sequence that will trigger a double buffer switch within very short time and mess up my approach from above? (I guess it would be nice to have an additional BREAK event for this kind of configuration...)

Update: Oh my... I must have confused something with the shorts definitions... it is actually ENDRX-STOPRX, not ERROR-STOPRX. Just assume that I use the ERROR event in combination with the PPI to trigger the STOPRX task. (Or maybe the STARTRX task, if there is no need to manually stop a previous reception - see question 7.)

By the way, it would be nice to have a BREAK event in a future version of the UARTE device for such purposes (and to prevent double events because of the frame error). Just a little suggestion for the engineers ;)

Question 7: What happens if the STARTRX task is triggered while reception is already active? (Will reception be restarted at the next RXD.BUFFER? Will ENDRX be triggered? Or will nothing happen at all?)

Using UARTE as FIFO

It would be very nice to use the UARTE as a simple FIFO. Just configure a large enough circular buffer and use the current EasyDMA reception state as the head, and an additional softwware variable which is advanced by polling as the tail. Reception is automaticaly continued at 0 by using the ENDRX-STARTRX short. The problem is that the RXD.AMOUNT value, which is considered the head of the fifo, is only updated once reception has stopped.

Question 8: Is there any way to access the current state of the RXD EasyDMA unit which reflects the current buffer state in RAM? (If not, can you consider making it available in the next generation of devices? Naming it something like RXD.CURRENTAMOUNT or RXD.PROGRESSED or so? The information is already available in the system anyway...)

Question 9: Considering what I have read in the documentation, it is not reliable to use the RXDRDY event in combination with a counter to manually count the received bytes and use it as the FIFO head, as the reflected data might not have been written to the EasyDMA buffer yet. (Also, I would be a bit worried about glitches when the counter module and the EasyDMA module go out of sync.) Do you agreee that this is not a reliable way to implement a simple and efficient FIFO with UARTE? Do you have any other suggestions?

Best regards,

Michael

  • Hi Michael

    In general I would not recommend implementing low level peripheral drivers from scratch, unless you have very specific and challenging requirements. Our low level team have intimate knowledge of the Nordic hardware, and have developed the nrfx drivers over several years based on internal and external input. Even more importantly, these drivers are implicitly tested by thousands of customers (and our own internal tests), meaning any issues in the driver will be reported and fixed much quicker. Even if you are a better developer the most likely outcome is that you will spend a lot of time developing a similar driver that might be better designed but that will have gone through significantly less testing. 

    With that out of the way, I will try to respond to your specific questions below:

    Question 0: How big is the internal FIFO of the UARTE modules?

    The RX FIFO is 4 bytes in size. 

    For TX there is no FIFO. 

    Question 1: What happens to the remaining bytes in the internal FIFO when a new STARTRX task is triggered? Will the FIFO content be written to the EasyDMA buffer without date being lost? Or will the bytes in the internal FIFO be discarded when a STARTRX task is triggered? For reliable data reception (that does not miss any bytes on the UART while the receive buffer is switched), it would only make sense to continue writing the internal FIFO content when STARTRX is triggered, especially in combination with the ENDRX-STARTRX short which immediately restarts reception if the buffer is full.

    Yes, if there are any remaining bytes in the FIFO when you issue STARTRX these will be written first, before any new incoming data. 

    Question 1a: When using the ENDRX-STARTRX short, will there no data be lost if the old EasyDMA buffer is full, and the STARTRX task continues reception into the next buffer?

    That is correct. The double buffer mechanism of the EasyDMA PTR register ensures that handover between the buffers happens immediately, and as long as you ensure to always provide the next buffer before the current buffer is filled up there is no risk of data loss. 

    Question 1b: When using the ENDRX-STARTRX short, if STOPRX is triggered before the current buffer is full, because of the short, reception should automatically be continued into the next buffer, just like question 1a mentioned. Will there also be no data loss in this case? Is this the way to manually trigger a safe/robust buffer switch and handle available data?

    Yes. This is the safe way to be able to read out RX data before the buffers fill up, in case you want a timeout type behavior on the RX side where you can process data after a period of inactivity (our higher level drivers in the SDK provides a similar functionality). 

    Question 2: I documented my approach in the following flow chart. Will this work without data loss? My goal is to take advantage of the UARTE capabilities and use large buffers that allow for using low priority UARTE interrupts that do not interfere with other time critical code in the system.

    Yes, this should work fine. The way we handle timeout in our drivers is that the timeout gets reset every time a new byte is received over the UART, meaning you will get an interrupt if there is no activity on RX for a predetermined amount of time, set by the timeout value. 

    This means that if you are receiving asynchronous messages of unknown length any new message will be processed after a delay set by the timeout period. 

    I consider using a BREAK condition for synchronizing communication shortly before the beginning of a data packet. Unfortinately, there is very little information available about the BREAK bit in the ERRORSRC status word.

    My recommendation would be to add some packetization protocol to the data instead, rather than use breaks intentionally. I haven't heard about anyone doing that with our UARTE peripheral, and I am not sure how well it would work in practice. 

    Question 3: Is the BREAK bit just set by the SoC (when RxD stays low for too long) and must be reset by software? Or is it automatically reset of a rising edge is detected on the RxD line, so that it does automatically reflect the current RxD state?

    The ERRORSRC register is set by HW and must be cleared by SW, more info here.

    estion 4: Is the BREAK bit only set while reception is active (STARTRX triggered, ENDRX not triggered yet)? Or can I completely stop reception, wait for the BREAK bit to become active (e.g. by polling the bit), and then manually start reception?

    You will not get framing or break errors if you haven't started RX, no. RX has to be running for these errors to occur. 

    Question 5: If I detect a BREAK condition and trigger STARTRX while RxD is still low. Will I get an error event (interrupt) again? Or will the UARTE module just wait for the next falling edge on RxD (which is the trigger for the Start bit of a transmission) and start receiving?

    You won't get a BREAK condition in this case as explained in 4, but if you start RX while RXD is low you will get a framing and break error shortly after RX is started. 

    By the way, it would be nice to have a BREAK event in a future version of the UARTE device for such purposes (and to prevent double events because of the frame error). Just a little suggestion for the engineers ;)

    This you would have to discuss with one of our sales directors, they are the channels for suggesting new software or hardware features. I can tell you that the hardware guys, like many engineers, are risk averse and won't make changes lightly ;)

    Question 7: What happens if the STARTRX task is triggered while reception is already active? (Will reception be restarted at the next RXD.BUFFER? Will ENDRX be triggered? Or will nothing happen at all?)

    I am 90% sure it will just be ignored if you trigger STARTRX while you are already in RX, but I will have to get this confirmed with the designers. 

    Question 8: Is there any way to access the current state of the RXD EasyDMA unit which reflects the current buffer state in RAM? (If not, can you consider making it available in the next generation of devices? Naming it something like RXD.CURRENTAMOUNT or RXD.PROGRESSED or so? The information is already available in the system anyway...)

    There is not unfortunately. The easiest way we have found to do this is to count the number of RXDRDY events (either in hardware using a TIMER and PPI channel, or through an interrupt), as mentioned earlier, but this event could happen before the data is actually available in RAM. In order to be 100% sure how many bytes you have received you need to stop RX and read the AMOUNT register. 

    Question 9: Considering what I have read in the documentation, it is not reliable to use the RXDRDY event in combination with a counter to manually count the received bytes and use it as the FIFO head, as the reflected data might not have been written to the EasyDMA buffer yet. (Also, I would be a bit worried about glitches when the counter module and the EasyDMA module go out of sync.) Do you agreee that this is not a reliable way to implement a simple and efficient FIFO with UARTE? Do you have any other suggestions?

    Like indicated in Q8 you can use RXDRDY as an indication that data has been received, but there still might be some delay between this event occurring and the data being available. The safe way is to stop RX and read the AMOUNT register, and use this as a final count that you can then use to update the FIFO. 

    I guess the problem of having a larger application FIFO that you fill with UART data directly is what to do when you stop the RX in the middle of a transaction. At this point the next buffer will already be configured, and you will get a gap in the FIFO depending on how many bytes were remaining in the buffer when the UART was stopped. 

    Either you need to keep the FIFO separate from the EasyDMA buffer, which will introduce another memcpy, or you need to accept some gaps in your FIFO, and include a length parameter for each block where you store the exact number of bytes in that block. 

    Best regards
    Torbjørn

Related