This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC2640R2F: UART flow control and loss of data

Part Number: CC2640R2F

I am having problems with the uart flow control (using RTS/CTS) in my application that is based on the Ti ble5_simple_serial_socket_server_cc2640r2lp example.

I have a separate processor that sends data through the BLE to an Android device, but my application is not transmitting all of the data - particularly the last few bytes of the message gets missed. I can see the data being send on a scope and can see the RTS pin being raised periodically and the processor responding to this and stops sending data until RTS is lowered again.

The only changes I have made to the example are - extra pin definitions, UUIDs, advertData and scanRspData to match my Android app. and obviously assigning the .ctsPin and .rtsPin in UARTCC26XX_HWAttrsV2 uartCC26XXHWAttrs[]. I am also controlling two other IO pins to signal that a connection has been made and the stream is open (in the GAP_LINK_ESTABLISHED_EVENT and GAP_LINK_TERMINATED_EVENT and also in SimpleStreamServer_cccUpdateCB() )

To help debugging, I set a hardware pin in SimpleSerialSocketServer_processAppMsg for the OUTGOING_DATA event and cleared it again after the uart data has been passed to SimpleStreamServer_sendData(). This signals that the uart data has been read correctly. Code snippet follows for this part (basically the same as the example with just the test pin added)

static void SimpleSerialSocketServer_processAppMsg(ssssEvt_t *pMsg)
{
  switch (pMsg->hdr.event)
  {
   // Outgoing data event
   case SSSS_OUTGOING_DATA:
     {
       // You could do processing of data here ...
PIN_setOutputValue(ledPinHandle, Board_TEST_IO_11, 1);      // This is the UART_RD test pin
       // For now, just Send the data read from UART over the air
       bStatus_t status = SimpleStreamServer_sendData(connHandle, &uartReadBuffer, pMsg->arg0);

       // If status is not successful, register for connection events for further processing
       if (status != SUCCESS) {
           SimpleSerialSocketServer_RegisterToAllConnectionEvent(FOR_STREAM);
       }
PIN_setOutputValue(ledPinHandle, Board_TEST_IO_11, 0);
       // Start another read
       UART_read(uartHandle, uartReadBuffer, UART_MAX_READ_SIZE);

       break;
     }

..

..

..

This all works as expected but although the data is being read by the uart, it seems that the very last few bytes are not passed into the output queue.

I am thinking that SimpleStreamServer_sendData() fails on SimpleStreamServer_allocateWithHeadroom(), and as there are no further uart messages, the uart read is never retried so so the data is not passed to  SimpleStreamServer_sendData() again. Is this likely?

I am also curious how the uart hardware flow control works in these circumstances. I would have thought that if there is no space in the queue, the uart flow control would prevent the external processor sending more data.

I have attached a couple of scope traces. The first trace is the complete message (requested by the Android) and the second is a zoom of just the last part. You can see the RTS line does it job and also the uart is being read after the last few bytes. (TX1, RTS, CONN_UP, STRM and UART_RD are outputs from the cc2640r2.  RX1 and CTS which are outputs from the external processor)

 Any help would be appreciated.

  • I should have also said that I increased the size of UART_MAX_READ_SIZE to 2000. I didn't see any RAM issues with this. 

  • Hello,

    It could be interesting to confirm if some data is dropped instrumenting the function SimpleStreamServer_sendData() (I think toggle a GPIO when some data is dropped can help).

    For the rest, and as you mentioned, the example drops without warning all the data it cannot handle. Using the SimpleStreamServer_sendData(), you can identify when some data is dropped and take some actions.

    I hope this will help,

  • Hi Phil,

    Starting with the application in question, it will always restart the read, no matter if the sendData call fail (it will simply drop this data). You increasing the UART_MAX_READ_SIZE would not give you any reliability increase, it would just increase the possible maximum chunks you get (which is not a good thing).

    As you need to allocate space for this data PLUS space for the BLE packet that is going to carry it, you would not really benefit from getting a 2 kB packet (i doubt you did get such a packet but just putting it out there).

    This would also strip roughly 1.8 kB of the available heap which means the application should have around 7 KB when you have compiled the application (you could check the .map file for this). Take away the 4 KB headroom and you have theoretically 3 KB of heap left for your application before the SSS profile would start to reject you. Keep in mind that it is not only the UART and SSS profile here that uses the heap, so this first 3 KB could disappear rather quickly as you connect to devices etc. Once the 4 KB limit is reached, the SSS profile would reject any data sent into it and the application would then drop it. 

    In a scenario where you need to be able to buffer more data "in the back" in between the read calls, you need to increase the internal ring buffer. How to do this is described in the "server" readme file.

  • Thanks for the responses so far.

    Also thanks for clarifying the different buffers. I understand about the buffer sizes and obviously, there is always going to be a limit on how much data that can be held in these buffers but I thought the uart flow control would deal with this..

    Over the last few days, I have tried changing the ringbuffer size (to 1500) which is about 50 bytes larger than my 1450 byte message. This still did not fix the problem - the Android app. still never gets the last few bytes of the message.

    After the last message has been sent from the external processor, I always get a final call with SSSS_OUTGOING_DATA that then calls the function SimpleStreamServer_sendData  to move the uart buffer into the output queue. From what I can see, it is ONLY SimpleStreamServer_sendData that transfers the buffer (I have proved this by adding another test pin in sendData). Therefore, if no more data is sent from the external processor there will not be another request to move the pending data into the output queue. In my case, it appears that the previous attempt to send data failed due to the buffer size

    i.e. (SimpleStreamNode_t*) SimpleStreamServer_allocateWithHeadroom(sizeof(SimpleStreamNode_t) + len) returns NULL

    When this fails, I only get the continuous calls to transmit whatever is in the output queue, but the data still in the uart read buffer never gets to the queue so never gets sent. The only way I can get it to send is if I add a 50ms delay on the last few bytes (I presume the buffer is being empty to allow space for the extra few bytes). Another question would be why is the data taking so long to be transmitted in the first place?

    I was expecting to see the UART flow control pins try to hold up the external processor from sending more data if the buffer is full. If these pins only work at the uart buffer level (not on the output queue), then they are of no real use.

    I am thinking of driving the RTS/CTS pin directly from the application and disable them in the uart settings.

    If I were to set the pin for RTS if the output queue is full (detected in SimpleStreamServer_sendData) and then clear it in SimpleSerialSocketServer_processConnEvt in the case of CONNECTION_EVENT_REGISTRATION_CAUSE(FOR_STREAM), would this be a better approach? Is there a danger of holding up the external processor indefinitely?

    Thanks again for your help..

  • I just tried this again and can confirm it still fails with a UART_MAX_READ_SIZE of 64, uartCC26XXRingBuffer size of 1500. MIN_HEAP_HEADROOM is set to 4000.

    I put my test pin in SimpleStreamServer_sendData and set it if the buffer size test returned NULL

        newNode = (EDTStreamNode_t*) EDTStreamServer_allocateWithHeadroom(sizeof(EDTStreamNode_t) + len);
        if (newNode == NULL) TestPin(true);

    It seems this is set after only about 230 bytes have been sent to the uart (not 1500) as I was expecting with the above settings. Has this example been tested with RingBuffer sizes of > 256 bytes?

    Here is the scope trace with this test pin.

    Instrument screen

    Also, is there a quick and easy way to read the size of the uart read buffer in the _processConnEvt event? I tried to use uartCurrentMsg->len but it didn't seem to work.

    Thanks again.

  • Hi Phil,

    Starting with the HW flow control that you get when assigning CTS/RTS pins to the driver, this only works on a HW level. This means it will only affect the internal UART HW FIFO, not the higher level software where the ring buffer lives. This means that even in a case where the flow control does its job, the ring buffer could for example wrap around.

    Note that increasing the ring buffer will only allow you to buffer more data in between calls. Basically, it is the immediate buffer that is used while you move from the UART read callback to when you call the UART_read() next. It really only needs to be the size of the maximum amount of data that is expected to receive during this period (which should not be the entire message unless the baud rate is very high or the application flow is super slow).

    It could be an idea to drive the flow control from SW in your case, it gives you more control on the conditions. If it is safe for your host controller or not depends on how the flow control is handled on that side. 

    Looking back at the example above where I assume that you have roughly 7 KB of heap from start (based on my assumptions further up, you could verify your own numbers by looking at the .map file). If we take away the 4 KB headroom here to get the amount of memory available until the SSS profile will deny you, we got 3 KB to play with.

    Now factor in the cost of connecting a device, this is ~1KB (plus potential overhead depending on BLE setup). We now have (maybe) around 2KB free. Now assume that you have received around 250 bytes and now request to send these out. If there is room, the SSS profile would start of by adding them to the internal queue, that is 250B + some overhead, we are now down to potentially 1.75 KB free heap. For each notification packet the profile then queues up, you will have duplication of the data. For example, say we queue up 5 * 20 bytes worth of notifications. This means we need to allocate 5 notification packets with room for 20 bytes. This is likely around +200B if we include all overhead. This means we are down to maybe having 1.5KB of heap left until we reach the magic limit. 

    Now we need to also consider the stack also using the same heap. This means that the actual available heap will fluctuate as the stacks allocate and de-allocate messages used to send out advertisements / notification / connection event as well as communicating internally. This could easily bring you below the 4 KB level. In fact, this is why I sat the 4 KB level in the first place, this is to assure that there is enough heap for the stack to properly function so that you avoid crashing the stack due to over allocating the SSS data buffers with data you want to send out. 

    I guess the take away from all of this is that you need to know that the stacks will use a significant amount of heap to operate which means you can't throw to much memory at the problem as you are quite low RAM as it is on the CC2640R2F. This means that you in the application needs to handle this according to your own needs. I made the SSS example to be dead simple. It provides a single sendData() function and drops any data that it can't fit. This to avoid making the IO input part to complex as this could shadow the actual take away from the example which is the SSS profile.

    It seems to me that it in your case is not a matter of the data being "stuck" in the ring buffer and that the event never comes. The problem seems to be that the event for theses bytes is actually just dropped as there is no heap to store the data in. I would suggest that you might consider doing something along these lines:

    When the sendData return a fail status, do NOT perform an additional UART_read, instead make this conditional to the if check just before it so that you either register for events or perform a UART_read(). In the case the return is a failure, set the RTS/CTS pins accordingly in SW before registering for the connection event. 

    Inside SimpleSerialSocketServer_processConnEvt() where the connection events is processed, if SimpleStreamServer_processStream() is successful you will enter into the if statement to unregistered again. At this point, you again try to send the data that was in the UART RX buffer. If it is successful, you perform a new UART_read() call and release the UART CTS/RTS constraints. If it fails, you don't unregistered to the event and instead wait for the next connection event and try again. 

    This should assure that you do not drop data. It could however mean you throttle the UART quite a bit depending on the overall memory usage of the application.

    As for using bigger ring buffers, yes this have been tested, maybe not 1500 bytes as there a diminishing return to having to big buffers as you can see from all the examples above. Basically bigger buffers == smaller heap == less room available to buffer the data before send out. I would think that you should be fine with maybe 256 bytes worth of ring buffer space. 

    A final question, which stack du you actually use, the BLE5 or BLE one?

  • Thank you for your detailed reply.

    I think I have been expecting too much from the hardware flow control pins.

    I now have UART_MAX_READ_SIZE=32, MIN_HEAP_HEADROOM=4000, uartCC26XXRingBuffer size = 256.

    I made this change in SimpleSerialSocketServer_processAppMsg to set the RTS pin as suggested

    	case SSSS_OUTGOING_DATA:
    	{
    		// You could do processing of data here ...
    
    		// For now, just Send the data read from UART over the air
    		bStatus_t status = SimpleStreamServer_sendData(connHandle, &uartReadBuffer, pMsg->arg0);
    
    		// If status is not successful, register for connection events for further processing
    		if (status != SUCCESS) {
    		        RtsPin(true);           // Hold off sending more data to the BLE
    			SimpleSerialSocketServer_RegisterToAllConnectionEvent(FOR_STREAM);
    		}
    		else
    		{
    		    // Start another read but only if there is still space in the ring buffer
    		    UART_read(uartHandle, uartReadBuffer, UART_MAX_READ_SIZE);
    		}
    		break;
    	}

    But I could not quite work how to call _sendData with the correct parameters. Here is what I tried (is this the correct interpretation of your suggestion?) I am not sure if the last parameter (uartCurrentMsg->len) is correct.

    if (CONNECTION_EVENT_REGISTRATION_CAUSE(FOR_STREAM))
    {
        // retry sendData in case there is data remaining in the UARTread buffer
    TestPin(true);
    
    	// The stream is active, process the stream at every connection event.
    	bStatus_t status = SimpleStreamServer_processStream();
    
    	// If status is successful there is no data pending for processing
    	if (status == SUCCESS)
    	{
    
    	    bStatus_t sendStatus = SimpleStreamServer_sendData(connHandle, &uartReadBuffer, uartCurrentMsg->len);
    	    if (sendStatus == SUCCESS)
    	    {
    	        RtsPin(false);                                                      // Release RTS to allow BLE to receive more uart data
    	        UART_read(uartHandle, uartReadBuffer, UART_MAX_READ_SIZE);          // restart another uart read  here
    	        SimpleSerialSocketServer_UnRegisterToAllConnectionEvent(FOR_STREAM);
    	    }
    	}
    TestPin(false);
    }


    Unfortunately, it gave me other problems as the stream gets dropped half way through a message.
    This scope trace shows the full picture (stream gets dropped too soon). Test pin on connection event is around 50ms



    This zoom of the first part shows the RTS is also doing something a bit strange. It goes high (and low) every 8 bytes or so. The only other place in the code that clears RTS is when a link is made and closed.
    It almost looks as though RTS is still being driven by the uart at this point (occurs at same time as a UART_read. My .ctsPin and .rtsPin have PIN_UNASSIGNED in the code.



    What is the best way to get the space remaining in the buffers?

    I am using BLE5 (with SDK 4_10_0_10) .

    Thanks
    Phil

  • Last bit should have read -
    Unfortunately, it gave me other problems as the stream gets dropped half way through a message. This scope trace shows the full picture (stream gets dropped too soon). Test pin on connection event is around 50ms
     
    This zoom of the first part shows the RTS is also doing something a bit strange. It goes high (and low) every 8 bytes or so. The only other place in the code that clears RTS is when a link is made and closed. It almost looks as though RTS is still being driven by the uart at this point (occurs at same time as a UART_read. My .ctsPin and .rtsPin have PIN_UNASSIGNED in the code.
    What is the best way to get the space remaining in the buffers?
     
    I am using BLE5 (with SDK 4_10_0_10) .
    Thanks
    Phil
  • It seems we might need to be a bit more picky on the if statement here (actually, it made sense from the beginning, my suggestion was simply to simple). 

    Consider the "sendData" function. It can return you a bunch of errors, it is really only one return value that should prompt us to stall the UART and that is memory errors (we do not care if the error says that we are out of notification slots for this). That means you likely want to do something like this:

    // If status is not successful, register for connection events for further processing
    if (status != SUCCESS) {
        SimpleSerialSocketServer_RegisterToAllConnectionEvent(FOR_STREAM);
    }
        
    if (status == bleMemAllocError)
    {
        RtsPin(true);           // Hold off sending more data to the BLE
    }
    else
    {
        // Start another read but only if there is still space in the ring buffer
        UART_read(uartHandle, uartReadBuffer, UART_MAX_READ_SIZE);
    }

    The thing with this is that "sendData" can return this error due to two reasons:

    1) It failed to store the UART data internally. THIS is what we want to catch.
    2) It failed to allocate the notification message struct.

    While we are really only interested in 1), there should be no harm (that I see now) with reacting to the second in the same way. They both signal that there is a lack of heap. In fact, 2) does not take headroom into consideration so it should always require 1) to happen first anyway.

    This should give you a bit more stable RTS pin. Now, in the connection event handler we also need to take the state of the RTS pin into consideration. We only want/need to do anything special IF the RTS is set, right?

    if (CONNECTION_EVENT_REGISTRATION_CAUSE(FOR_STREAM))
    {
        // retry sendData in case there is data remaining in the UARTread buffer
    TestPin(true);
    
        // The stream is active, process the stream at every connection event.
        bStatus_t status = SimpleStreamServer_processStream();
    
        // If status is successful there is no data pending for processing
        if ((status == SUCCESS) && rtsPinSet())
        {
            bStatus_t sendStatus = SimpleStreamServer_sendData(connHandle, &uartReadBuffer, **LAST_UART_READ_LEN**);
            if (sendStatus == SUCCESS)
            {
                RtsPin(false);                                                      // Release RTS to allow BLE to receive more uart data
                UART_read(uartHandle, uartReadBuffer, UART_MAX_READ_SIZE);          // restart another uart read  here
                SimpleSerialSocketServer_UnRegisterToAllConnectionEvent(FOR_STREAM);
            }
        }
       else if (status == SUCCESS) 
    {
    SimpleSerialSocketServer_UnRegisterToAllConnectionEvent(FOR_STREAM);
    }

    TestPin(false);
    }

    Note the variable **LAST_UART_READ_LEN**, this is only there to highlight that you need to store away the read length globaly when setting the RTS pin so that you can later re-use it. Once you leave the "SSSS_OUTGOING_DATA", this information will be lost unless you do. 

  • Thanks M-W, I think it is working now.

    I did have to make another change to the connection event as the very last uart read did not get sent. The _UnRegisterToAllConnectionEvent(FOR_STREAM), immediately after the UART_read meant that the event was not called again so the last UART read was not sent. I added a "pendingData" flag that unregistered the event the next time around.

    My code is as follows (there is probably a better way to do it!). The RtsPinState gets updated in the RtsPin() function.

    	if (CONNECTION_EVENT_REGISTRATION_CAUSE(FOR_STREAM))
    	{
    	    // retry sendData in case there is data remaining in the UARTread buffer
    
    	    // The stream is active, process the stream at every connection event.
    	    bStatus_t status = SimpleStreamServer_processStream();
    
    	    // If status is successful there is no data pending for processing
    	    if ((status == SUCCESS) && RtsPinState)                         // RtsPinState is set/cleared in RtsPin() function and reflects current state of RTS pin
    	    {
    	        // Try to send data again. Use length of last uart read (stored when RTS set in SimpleSerialSocketServer_processAppMsg
    	        bStatus_t sendStatus = SimpleStreamServer_sendData(connHandle, &uartReadBuffer, LastUartReadLen);
    	        if (sendStatus == SUCCESS)
    	        {
    	            RtsPin(false);                                                      // Release RTS to allow BLE to receive more uart data
    	            UART_read(uartHandle, uartReadBuffer, UART_MAX_READ_SIZE);          // restart another uart read  here
    	            pendingData = true;
    	        }
    	    }
    	   else
    	   {
    	       if (status == SUCCESS)
    	       {
    	          SimpleSerialSocketServer_UnRegisterToAllConnectionEvent(FOR_STREAM);
    	       }
    	       if (!pendingData) {
    	           pendingData = false;
                       SimpleSerialSocketServer_UnRegisterToAllConnectionEvent(FOR_STREAM);
    	       }
    	   }
    	}

    Thanks again for your assistance.

    Phil