Help with UART DataReceived

Im trying to send some text data over UART, the write part is working ok, i need help with the read part

as per docs and the most worthless example one could write, i have a few questions?

  1. how do i know how big should the rxbuffer be?
  2. how many times does the data receive event fire?
  3. how do i consistently concatenate the data?
  4. how do i know when all data was received?
  5. should i ClearReadBuffer() when? where?

this is what i got so far:

WRITE:

wireless.Rf.Transmit(@"Welcome to Wikipedia,
the free encyclopedia that anyone can edit.
6,295,170 articles in English
The arts
Biography
Geography
History
Mathematics
Science
Society
Technology
All portals");

READ:

private static void MyUart_DataReceived(UartController sender, DataReceivedEventArgs e)
        {
            if (myUart.BytesToRead > 0)
            {
                rxBuffer = new byte[myUart.BytesToRead];
                var bytesReceived = myUart.Read(rxBuffer, 0, myUart.BytesToRead);
                Debug.WriteLine(Encoding.UTF8.GetString(rxBuffer, 0, bytesReceived));
            }
        }

First receive:

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Welcome to Wikipedia,
the free encyclopedia that anyone can edit.
6,295,170 articles in English
The arts
Biography
Geography
History
Mathematics
Science
Society
Technology
All portals

Second receive:

W
elcome 
to Wikiped
ia,
the 
fr
ee encyclo
pedia that
 anyone c
an edit
.
6,295,1
70 article
s in En
glish
Th
e arts
Bi
ography
G
eograp
hy
Histor
y
Mathema
tics
Sci
ence
S
ociety
Te
chnology

All porta
ls

can you check did it finish like ‘\n’ or like ‘\n\r’

Not relate to your question, just a note, those code looks OK but actually it has a potential bug.

if (myUart.BytesToRead > 0) => myUart.BytesToRead could be 1
rxBuffer = new byte[myUart.BytesToRead]; => now it could be 2
myUart.Read(rxBuffer, 0, myUart.BytesToRead) => now it could be 3 and out of range…

Most of the time it will work but high baudrate then may failed.

Max is 50 times / second. Every 20ms the event is checked and raised if any data in buffer.

Got any example code that works?

I was looking at how this is implemented, but few things missing from TinyCLR
SerialPort.cs ReadExisiting()

This is a problem you face in any serial driver where you want to break out strings or packets. There’s no guarantee that you will receive chunks in the same size that they were sent.

It has nothing to do with TinyCLR. You don’t get whole strings - you just get chunks based on a lot of internal driver, buffer, and protocol factors. You need the same approach for Netmf, TinyCLR, FreeRTOS, whatever…

  1. Within DataReceived, you need to collect whatever is available via a Read and then append that data into a buffer large enough for the longest string you expect (or do a direct read into the large buffer using a starting offset). I tend to use my own implementation of a ring buffer.

  2. Accumulate in that buffer until the first \n.

  3. When you detect a \n, fire your own ‘string received’ event (or make a call with the assembled string). Note that it is also possible to receive two short string in one packet, so you loop on this until no \n bytes remain

  4. Remove the processed string from your large buffer and shift any remaining data left (which is why a ring buffer is attractive - no shifting required)

Sample ring buffer : https://github.com/PervasiveDigital/serialwifi/blob/c8519b24285573d97c4019c3850455f5106c6fef/src/Common/PervasiveDigital.Utility.Shared/CircularBuffer.cs
Sample of serial to string/packet parsing : https://github.com/PervasiveDigital/serialwifi/blob/master/src/Common/PervasiveDigital.Hardware.ESP8266.Shared/Esp8266Serial.cs

In the Esp8266Serial, see PortOnDataReceived at line 320
ProcessBufferedInput at line 366 breaks the received data into string, but is a bit complex because it also handles streaming data. The ESP8266 can send back multiple streams of network data, but if you strip that away, you will see that ProcessBufferedInput just reassembles things back into \n delimited strings.

This is a complex example, but it’s the pattern necessary for a reliable reassembly of randomly packetized strings from Bluetooth, network adapters, RF pipes, etc.

The code also contains examples of SendAndExpect or SendAndReadUntil with timeouts plus related functions useful for building serial protocols. For example, sending some sort of command or request and waiting for a response terminated by something like ‘OK’, but with a timeout.

4 Likes

I feel better with these:

var byte2read = myUart.BytesToRead;

if (byte2read > 0) {
                rxBuffer = new byte[byte2read ];
                var bytesReceived = myUart.Read(rxBuffer, 0, byte2read );
                Debug.WriteLine(Encoding.UTF8.GetString(rxBuffer, 0, byte2read ));
}
1 Like

Dat is right - never call BytesToRead more than once in a callback handler. It can change on every call which in your code would result in an array overflow.

That said, you also still need the packet-reassembly logic too.

Another hint - never use Encoding.UTF8.GetString with bytes you get from a streaming read. It’s possible that you got a stream of bytes that ends with an invalid UTF8 sequence, and GetString will throw. Use this instead:

    public static String ConvertToString(Byte[] byteArray)
    {
        var _chars = new char[byteArray.Length];
        bool _completed;
        int _bytesUsed, _charsUsed;
        Encoding.UTF8.GetDecoder().Convert(byteArray, 0, byteArray.Length, _chars, 0, byteArray.Length, false, out _bytesUsed, out _charsUsed, out _completed);
        return new string(_chars, 0, _charsUsed);
    }

This will convert to a string, but will stop at the first invalid UTF8 sequence instead of throwing and leaving you with no string at all.

3 Likes

I prefer to use a thread to handle the RX from the serial port and set flags etc. This has worked reliably from old .NETMF to SITcore projects.

Yeah, we could go way deep down that hole. I use a thread pool of not more than three threads and then QueueUserWorkItem(). The trouble is that the MCUs under the CLR are single core so you are guaranteed to have context switches, and context switching in netmf/tinyCLR is expensive, and CLR threads are heap-expensive, and I’m a bit obsessive about performance, so I optimize for low thread count and low ctx switch rate.

Thanks @mcalsyn for the extended explanation.

I managed to get things to work by including an EOT character in the Transmit, and then looking out for that character in the Receive event.
Its working consistently.
This or something like this should be wrapped in TinyCLR to make it FEZ

   public void TransmitWithEOT(string command)
        {
            byte[] data = Encoding.UTF8.GetBytes(command);

            byte[] dataWithEOT = new byte[data.Length + 1];
            data.CopyTo(dataWithEOT, 0);
            dataWithEOT[dataWithEOT.Length - 1] = 0b_0000_0100; // EOT

            uart.Write(dataWithEOT, 0, dataWithEOT.Length);
            
            Debug.WriteLine($"Serial Transmitted: {command}");
        } 

class Program
    {
        static Wireless wireless;
        static string inputString;
        static bool stringComplete;

        static void Main()
        {
            wireless = new Wireless();
            wireless.Rf.DataReceived += Rf_DataReceived;

            Thread.Sleep(Timeout.Infinite);
        }

        private static void Rf_DataReceived(UartController sender, DataReceivedEventArgs e)
        {
            int bytesToRead = wireless.Rf.uart.BytesToRead;
            byte[] receiveBuffer = new byte[bytesToRead];
            if (bytesToRead > 0)
            {
                int bytesReceived = wireless.Rf.uart.Read(receiveBuffer, 0, bytesToRead);
                int hasSpecialChar = Array.IndexOf(receiveBuffer, 0b_0000_0100); // EOT

                string data = Encoding.UTF8.GetString(receiveBuffer, 0, bytesReceived);

                if (hasSpecialChar != -1)
                {
                    inputString += data.Substring(0, data.Length - 1);
                    stringComplete = true;
                }
                else
                {
                    inputString += data;
                }

                if (stringComplete)
                {
                    // process input string here
                    Debug.WriteLine("................Process input string.......................");
                    Debug.WriteLine(inputString);
                    stringComplete = false;
                    inputString = string.Empty;
                }
            }
        }
    }
1 Like

Yes sir. Events are good for causal incoming data but a thread is the way to go.

Is this skipping one cycle?? can someone help me with properly receiving the data?

So i have a device (radar) that spits out exactly 10 bytes of data few times a second, when its “tracking” it does so even faster. 1st byte is always 0xAA so this is what im looking out for.

Here is my code:

private void Doppler_DataReceived(UartController sender, DataReceivedEventArgs e)
        {
            int bytesToRead = Doppler.uart.BytesToRead;
            byte[] receiveBuffer = new byte[bytesToRead];
            if (bytesToRead > 0)
            {
                int bytesReceived = Doppler.uart.Read(receiveBuffer, 0, bytesToRead);
                int hasSpecialChar = Array.IndexOf(receiveBuffer, 0xAA);

                if (hasSpecialChar != -1)
                {
                    if (!receiveBegin)
                    {
                        receiveBegin = true;
                    }
                    else 
                    {
                        receiveBegin = false;
                        receiveComplete = true;
                    }

                    if (receiveBegin) {
                        //Array.Copy(receiveBuffer, receivedData, bytesReceived);
                        Debug.WriteLine(BitConverter.ToString(receiveBuffer));
                    }
                }
                else
                {
                    if (receiveBegin) {
                        //Array.Copy(receiveBuffer, receivedData, bytesReceived);
                        Debug.WriteLine(BitConverter.ToString(receiveBuffer));
                    }
                }

                if (receiveComplete)
                {
                    // get data and set properties here, then clear array
                    Debug.WriteLine("----------------- Complete ---------------------");
                    //Array.Clear(receivedData, 0, receivedData.Length);
                    receiveComplete = false;
                }
            }

And this is the output:
Notice that ONE time somewhere in the middle of the output it failed, but i think when i get to the specialChar 0xAA the second time i miss one cycle

Is there a better way of doing this?

AA
55-5D-01
00-00-00-00-00-32
----------------- Complete ---------------------
AA
55-5F-01
00-00-00-00-00-32
----------------- Complete ---------------------
AA
55-61-01-00-00-33-00-00-32
----------------- Complete ---------------------
AA
55-63-01-4A-3F
33-85-00-32
----------------- Complete ---------------------
AA
55-65-01-4A
5A-00-85-00-32
----------------- Complete ---------------------
AA
55-67-01-4A-51-31-87-00
32
----------------- Complete ---------------------
AA
55-69-01-4A-51-31-85-00-32
----------------- Complete ---------------------
AA
55-6B-01-4A-3F-31-86-00
32
----------------- Complete ---------------------
AA
55-6D-01-4A-3F-31-85
00-32
----------------- Complete ---------------------
AA
55-6F-01-4A-3F-31-85-00
32
----------------- Complete ---------------------
AA
55-71-01-4A-3F-31-86-00
32
----------------- Complete ---------------------
AA
55-73-01-4A-3F-31-87-00-32
----------------- Complete ---------------------
AA
55-75-01-4A-3F-31-85-00-32
----------------- Complete ---------------------
AA
55-77-01-4A-3F-32
86-00-32
----------------- Complete ---------------------
AA
55-79-01-4A-3F
31-85-00-32
----------------- Complete ---------------------
AA
55-7B-01-4A-3F-31
84-00-32
----------------- Complete ---------------------
AA
55-7D-01-4A-3F-31-86
00-32
----------------- Complete ---------------------
AA
55-7F-01-4A-3F
31-84-00-32
----------------- Complete ---------------------
AA
55-81-01
4A-3F-31-81-00-32
----------------- Complete ---------------------
AA
55-83-01-4A-3F-31-83-00
32
----------------- Complete ---------------------
AA
55-85-01-4A-3F-30-87-00
32
----------------- Complete ---------------------
AA
55-87-01-4A-3F-30-86
00-32
----------------- Complete ---------------------
AA
55-89-01-4A-3F-31-87
00-32
----------------- Complete ---------------------
AA
55-8B-01-4A-3F-30-89-00-32
----------------- Complete ---------------------
AA
55-8D-01-4A-3F
2F-87-00-32
----------------- Complete ---------------------
AA
55-8F-01-4A-3F
32-87-00-32
----------------- Complete ---------------------
AA
55-91-01
4A-3F-31-85-00-32
----------------- Complete ---------------------
AA
55-93-01-4A-3F
2E-85-00-32
----------------- Complete ---------------------
AA
55-95-01-4A-3F
30-82-00-32
----------------- Complete ---------------------
AA
55-97-01-4A-3F
30-81-00-32
----------------- Complete ---------------------
AA
55-99-01-4A-3F-2E-81-00
32
----------------- Complete ---------------------
AA
55-9B-01
4A-3F-2E-81-00-32
----------------- Complete ---------------------
AA
55-9D-01-4A-3F
2F-80-00-32
----------------- Complete ---------------------
AA
55-9F-01-4A
3F-30-81-00-32
----------------- Complete ---------------------
AA
55-A1-01
4A-3F-2F-80-00-32
----------------- Complete ---------------------
AA
55-A3-01
4A-3F-2D-80-00-32
----------------- Complete ---------------------
AA
55-A5-01-4A-3F
2F-83-00-32
----------------- Complete ---------------------
AA
55-A7-01-4A
3F-30-82-00-32
----------------- Complete ---------------------
AA
55-A9-01-4A
3F-2E-81-00-32
----------------- Complete ---------------------
55-AA-01-4A-3F-2E-81-00-32
----------------- Complete ---------------------
AA
55-AC-01-4A
3F-2E-82-00-32
----------------- Complete ---------------------
AA
55-AE-01-4A-3F-2D-83
00-32
----------------- Complete ---------------------
AA
55-B0-01
4A-3F-2E-83-00-32
----------------- Complete ---------------------
AA
55-B2-01-4A-3F-2F-84-00-32
----------------- Complete ---------------------
AA
55-B4-01-4A-3F-2E-85-00-32
----------------- Complete ---------------------
AA
55-B6-01
4A-3F-2F-85-00-32
----------------- Complete ---------------------
AA
55-B8-01-4A-3F-2E
84-00-32
----------------- Complete ---------------------
AA
55-BA-01-4A-3F-30-86-00-32
----------------- Complete ---------------------
AA
55-BC-01-4A-3F-2F-85-00-32
----------------- Complete ---------------------
AA
55-BE-01-4A-3F-2F-84-00-32
----------------- Complete ---------------------
AA
55-C0-01
4A-3F-2F-83-00-32
----------------- Complete ---------------------
AA
55-C2-01-4A-3F-2F-85
00-32
----------------- Complete ---------------------
AA
55-C4-01-4A-3F-31
87-00-32
----------------- Complete ---------------------
AA
55-C6-01-4A
3F-30-85-00-32
----------------- Complete ---------------------
AA
55-C8-01
4A-3F-2D-84-00-32
----------------- Complete ---------------------
AA
55-CA-01
4A-3F-2F-83-00-32
----------------- Complete ---------------------
AA
55-CC-01
4A-3F-2F-84-00-32
----------------- Complete ---------------------
AA
55-CE-01
4A-3F-30-82-00-32
----------------- Complete ---------------------
AA
55-D0-01-4A-3F-2E-7F-00
32
----------------- Complete ---------------------
AA
55-D2-01
4A-3F-2C-80-00-32
----------------- Complete ---------------------
AA
55-D4-01-4A-3F-31-83
00-32
----------------- Complete ---------------------
AA
55-D6-01-4A-3F-30-83-00-32
----------------- Complete ---------------------
AA
55-D8-01
4A-3F-2E-81-00-32
----------------- Complete ---------------------
AA
55-DA-01-4A
3F-2F-85-00-32
----------------- Complete ---------------------
AA
55-DC-01
4A-3F-2D-87-00-32
----------------- Complete ---------------------
AA
55-DE-01-4A-3F
2F-87-00-32
----------------- Complete ---------------------
AA
55-E0-01-4A-3F-2E
85-00-32
----------------- Complete ---------------------
AA
55-E2-01-4A-3F-2F-85-00
32
----------------- Complete ---------------------
AA
55-E4-01
4A-3F-2F-87-00-32
----------------- Complete ---------------------
AA
55-E6-01-4A-2E-00-87-00-32
----------------- Complete ---------------------
AA
55-E8-01-4A-2E-00-83-00-32
----------------- Complete ---------------------
AA
55-EA-01-4A-2E-00-00-00-32
----------------- Complete ---------------------
AA
55-EC-01-4A-2E-00-00-00
32
----------------- Complete ---------------------
AA
55-EE-01-4A-2E-00-00
00-32
----------------- Complete ---------------------
AA
55-F0-01
00-00-00-00-00-32
----------------- Complete ---------------------

Yes, use a thread and do not allocate a buffer every time you receive data.

Why do you recommend thread over event? I am seeing some performance data which says the reverse?

With a stream of incoming data, you can have a flood of events queuing up if the system get blocks, like you hit a breakpoint. A thread can be delayed and the data gets buffered internally without any additional system requirements.

But it all depends on how you handle the thread and the event, and also depends on what kinds of data throughput is flowing.

I think the sleep time of the thread really matters here, doesnt it? Since the uart receive buffer must be finite.
Do you know the size of this buffer?

Mike, what are you talking about? Using threads versus events I see a performance improvement of almost 10X!

Oh… Maybe I will learn something new today