Serial.write delay on Teensy 3.6

Timon

Member
Hi,

I am sending serial messages of different sizes from an arduino uno to a teensy 3.6 at 9600 Baud. The Teensy is then reading the first byte of the message, which is representing the data payload, and determins the entire length of the message (6 Bytes default + data payload) aka how many bytes to read from the buffer. After reading the message the teensy is supposed to ackowledge the message. This is where the issue occurred. If I send a message of 8 Bytes in total, the teensy answers in about 170us (picture 1) but if I send more or less data, it takes about 1.2ms to answer(picture 2). This delay is to much for my appication and I cant figure out what the cause coud be. Ive tried different approches of reading the data from the buffer but there has been no change in the timings. Also, both of these messages shown in the picture where send alternating and repeatedly during the same code runtime and the issue is always the same, even after a couple thousand messages. There is no issue with the code other than the timings. The teensy reads the send data correctly every time and anwers accordingly.
I am using the Serial1 channel on the teensy to receive and send the data an I have captured the timings with the az-delivery logic analyzer.

Code:
#define Frame_lenth = 6;
uint16_t bytesRead = 0;
#define BUFFER_SIZE 255
char buffer[BUFFER_SIZE];

void eventListener(void)
{
  if (Serial1.available())
  {
    digitalWriteFast(LED_BUILTIN, HIGH);
    buffer[bytesRead] = Serial1.read();
    bytesRead++;
  }
  if (bytesRead >= (Frame_lenth + buffer[0])) // calc num of bytes to be received
  {
   /* do something with message */
   adress = buffer[1];
   Flags.frmCmp = 1;
   bytesRead = 0;
   digitalWriteFast(LED_BUILTIN, LOW);
  }

char acknowledge;

void sendAckn(void)
{
  if (!Flags.ackmnt) // only acknowledge once
  {
    acknowledge = (sDeviceAdress & 0xF0) + 0x0A; // OK
    Serial1.write(acknowledge);
    Serial1.flush(); // wait for transmission to be complete
    Flags.frmCmp = 0;
    Flags.ackmnt = 1;
    resetTimer();
  }
}

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.begin(BAUD);
  Serial1.setTimeout(0.1);
  M_state = _read;
}

void loop()
{
  switch (M_state)
  {
  case (_read):
    eventListener();
    if (Flags.frmCmp)
    {
      M_state = _acknowledge;
    }
    break;

  case (_acknowledge):
    startTimer();
    if ((adress & 0xF0) == (sDeviceAdress & 0xF0)) // if message was adressed to the teensy
    {
      sendAckn(); // send acknowledgement
    }
    break;
/*rest of the code causes no issue*/
}

I have shortened the code down to the parts that cause the issue. Its written in VS Code with the PlattformIO extension.

First channel is the message beeing send by the arduino
Second channel is the acknowledgement by the teensy (followed by its own message)
Third is the LED_BUILDIN Pin of the teensy, toggled on when it reads the first byte of the message and toggled off after its finished reading.

teensy_rec_8byte.jpg
picture 1, teensy receives 8 bytes


teensy_rec_6byte.png
picture 2, teensy receives 6 bytes

teensy_rec_7byte.png
teensy receives 7 bytes

teensy_rec_9byte.png
teensy receives 9 bytes

I would appriciate any suggestions.
Thank you!
 
Similar usable UNO code not shown - would allow testing.
<EDIT> Also no pic: Are the two unit connected by a common ground.
<EDIT> T_3.6 is not 5V tolerant - is the UNO running at 3.3V or properly voltage controlled?

This
Code:
void eventListener(void)
{
  if (Serial1.available())

Might work better as
Code:
void eventListener(void)
{
  [B][U]while[/U][/B] (Serial1.available())

Not sure that explains the time jump - but it will eliminate repeat calls to empty any buffered data.
 
Sorry, I didnt include all header files. I was hoping someone would spot an issue in my code without having to compile anything.

I rewrote a shorter version of my code for the teensy so it does compile. It also has the same timing issue as before.
Code:
#include <Arduino.h>

// msg.length, adress, msg, checksum
uint32_t received_msg;
char msg_length;
char adress;
uint16_t msg = 0;

char M_state;

#define BAUD 9600
#define sDeviceAdress 0xB0

#define _read 1
#define _acknowledge 2

typedef struct 
{
  bool busIdle:1;  // Idle state of bus
  bool ackmnt:1;   // Acknowledgment
  bool msgRdy:1; // Msg ready to be sent
  bool frmCmp:1;  // Frama complete
} TbusBitFlag;

TbusBitFlag Flags = {.busIdle = 0, .ackmnt = 0, .msgRdy = 0, .frmCmp = 0};

#define Frame_lenth 6
uint16_t bytesRead = 0;
#define BUFFER_SIZE 255
char buffer[BUFFER_SIZE];

void eventListener(void)
{
  while (Serial1.available())
  {
    buffer[bytesRead] = Serial1.read();
    bytesRead++;
  }
  if (bytesRead >= (Frame_lenth + buffer[0])) // calc num of bytes to be received
  {
    adress = buffer[1];                 // adress
    Flags.frmCmp = 1;
    Flags.ackmnt = 0;
    bytesRead = 0;
  }
}

char acknowledge;

void sendAckn(void)
{
  if (!Flags.ackmnt)
  {
    acknowledge = (sDeviceAdress & 0xF0) + 0x0A; // OK
    Serial1.write(acknowledge);
    Serial1.flush(); // wait for transmission to be complete
    Flags.ackmnt = 1;
    Flags.frmCmp = 0;
  }
}

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.begin(BAUD);
  Serial1.setTimeout(0.1);
  Flags.busIdle = 0;
  M_state = _read;
}

void loop()
{
  switch (M_state)
  {
  case (_read):

    eventListener();
    if (Flags.frmCmp)
    {
      M_state = _acknowledge;
    }
    break;

  case (_acknowledge):

    if ((adress & 0xF0) == (sDeviceAdress & 0xF0)) // wenn adresse stimmt
    {
      sendAckn(); // sende acknowledgement
    }

    if (Flags.ackmnt)
    {
      Flags.ackmnt = 0;
      adress = 0;
      M_state = _read;
    }
    break;

  default:
    break;
  }
}

short_t36_6bytes.png
6 Bytes sent, timing issue

short_t36_8bytes.png
8 Bytes sent, no issue
 
Similar usable UNO code not shown - would allow testing.

The arduino code is nothing special but if it helps:
Code:
int msg1[] = {0x00, 0xB3 , 0x11, 0x05, 0xF5, 0x23};
int msg2[] = {0x02, 0xB2 , 0x14, 0x45, 0x45, 0x34, 0x45, 0x34};

void setup() {
  Serial.begin(9600);
  Serial.setTimeout(1);
}

void loop() {
  for(byte i=0; i < (sizeof(msg1)/sizeof(msg1[0])); i++)
  {
    Serial.write(msg1[i]);
  }
  Serial.flush();
  delay(500);
  
  for(byte i=0; i < (sizeof(msg2)/sizeof(msg2[0])); i++)
  {
    Serial.write(msg2[i]);
  }
  Serial.flush();
  delay(500);
}

<EDIT> Also no pic: Are the two unit connected by a common ground.
<EDIT> T_3.6 is not 5V tolerant - is the UNO running at 3.3V or properly voltage controlled?
All devices are connected to my pc via usb, so i assume the share the same GND.
I didnt have a level shifter at hand so the teensy is protected by a voltage devider to lower the HIGH to around 3.3V.


Not sure that explains the time jump - but it will eliminate repeat calls to empty any buffered data.
Thank you for the tip. Unfortunatly it didnt fix the issue.
 
Hi @Timon, the reason for asking for the code is so that it can be run and the problem experienced and hopefully sorted.
I have re-written your send / receive code in a different manner, please give that a try and see if it makes any difference.

Code:
#include <Arduino.h>

// msg.length, adress, msg, checksum
uint32_t received_msg;
char msg_length;
char adress;
uint16_t msg = 0;

char M_state;

#define BAUD 9600
#define sDeviceAdress 0xB0

#define _read 1
#define _acknowledge 2

#define Frame_lenth 6

#define waitingForLength    0
#define waitingForData      1
uint8_t status              = waitingForLength;

typedef struct {
    uint8_t length          = waitingForLength;
    uint8_t buffer[255];
} dataType;

dataType data;

typedef struct
{
    bool busIdle : 1;  // Idle state of bus
    bool ackmnt : 1;   // Acknowledgment
    bool msgRdy : 1; // Msg ready to be sent
    bool frmCmp : 1;  // Frama complete
} TbusBitFlag;

TbusBitFlag Flags = { .busIdle = 0, .ackmnt = 0, .msgRdy = 0, .frmCmp = 0 };

uint16_t bytesRead = 0;
#define BUFFER_SIZE 255
char buffer[BUFFER_SIZE];

bool GetData(void){

    char acknowledge;

    switch (status) {
        case (waitingForLength):
            if (Serial1.available()) {
                data.length = Serial1.read();
                status++;
                return false;
            }
            break;
        case (waitingForData):
            if (Serial1.available() >= (Frame_lenth + data.length)) {
                Serial1.readBytes(data.buffer, Frame_lenth + data.length);
                status = waitingForLength;
                adress = data.buffer[0];

                // Send Ack
                acknowledge = (sDeviceAdress & 0xF0) + 0x0A; // OK
                Serial1.write(acknowledge);
                Serial1.flush(); // wait for transmission to be complete

                return true;
            }
            break;
        default:
            break;
    }
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    Serial1.begin(BAUD);
    Serial1.setTimeout(0.1);
}

void loop()
{
    if (GetData()) {
        // do something
    }
}
 
Thank you for you effort @BriComp !

Unfortunately there seems to be no difference but I appriciate you help.
 
Ok, try deleting the Serial1.setTimeout(0.1); in SetUp.

I don't know why it should make any difference. Equally I don't see why it is required.

I have just had a thought about the serial buffer size. I will get back to you on that one.
EDIT: Code below increases the serial read buffer by 512 bytes and does NOT set a serial timeout.
Please give it a try.

Code:
#include <Arduino.h>

// msg.length, adress, msg, checksum
uint32_t received_msg;
char msg_length;
char adress;
uint16_t msg = 0;

char M_state;

#define BAUD 9600
#define sDeviceAdress 0xB0

#define _read 1
#define _acknowledge 2

#define Frame_lenth 6

#define waitingForLength    0
#define waitingForData      1
uint8_t status              = waitingForLength;

#define sBufSize 512

uint8_t sBuf[sBufSize];

typedef struct {
    uint8_t length          = waitingForLength;
    uint8_t buffer[255];
} dataType;

dataType data;

typedef struct
{
    bool busIdle : 1;  // Idle state of bus
    bool ackmnt : 1;   // Acknowledgment
    bool msgRdy : 1; // Msg ready to be sent
    bool frmCmp : 1;  // Frama complete
} TbusBitFlag;

TbusBitFlag Flags = { .busIdle = 0, .ackmnt = 0, .msgRdy = 0, .frmCmp = 0 };

uint16_t bytesRead = 0;
#define BUFFER_SIZE 255
char buffer[BUFFER_SIZE];

bool GetData(void){

    char acknowledge;

    switch (status) {
        case (waitingForLength):
            if (Serial1.available()) {
                data.length = Serial1.read();
                status++;
                return false;
            }
            break;
        case (waitingForData):
            if (Serial1.available() >= (Frame_lenth + data.length)) {
                Serial1.readBytes(data.buffer, Frame_lenth + data.length);
                status = waitingForLength;
                adress = data.buffer[0];

                // Send Ack
                acknowledge = (sDeviceAdress & 0xF0) + 0x0A; // OK
                Serial1.write(acknowledge);
                Serial1.flush(); // wait for transmission to be complete

                return true;
            }
            break;
        default:
            break;
    }
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    Serial1.begin(BAUD);
    Serial1.addMemoryForRead(sBuf, sBufSize);
//    Serial1.setTimeout(0.1);
}

void loop()
{
    if (GetData()) {
        // do something
    }
}
 
@BriComp I really appreciate you taking the time to help me :)

Ok, try deleting the Serial1.setTimeout(0.1); in SetUp.

I don't know why it should make any difference. Equally I don't see why it is required.

Its a leftover of a previous iteration of the code that i forgot to delete.

I have just had a thought about the serial buffer size. I will get back to you on that one.
EDIT: Code below increases the serial read buffer by 512 bytes and does NOT set a serial timeout.

I probably should have listed all the approaches Ive tried before.
Increasing the serial input/output buffer also doesnt help since it never really fills up to a point where it would cause a delay.

So far I have tried:
- different iterations of Serial.read()/Serial.readBytes() to read the data;
- increasing the serial input/output buffer;
- clearing all buffers after receiving/sending data;
- closing the serial port after receiving data and opening it back up to send data

Again, results are the same as before. Sending 8 bytes causes no issue but sending more or less data results in a delay of about 1.2ms. Maybe I should add that the delay is slightly different every time. Its always between 1240us and 1290us but these differences dont seem sigificant to me.

Thanks for your help.
 
Sorry, it has been a long time since I played with the T3.6 Serial code.

Often times with timing issues like this, where you have a logic analyzer available, I would instrument the code.

Like for example: in the function:
Code:
void eventListener(void)
{
  while (Serial1.available())
  {
[COLOR="#FF0000"]    digitalToggleFast(2);[/COLOR]
    buffer[bytesRead] = Serial1.read();
    bytesRead++;
  }
  if (bytesRead >= (Frame_lenth + buffer[0])) // calc num of bytes to be received
  {
    adress = buffer[1];                 // adress
    Flags.frmCmp = 1;
    Flags.ackmnt = 0;
    bytesRead = 0;
  }
}
You would need some place like setup to do a: pinMode(2, OUTPUT);

This way you can see when you code receives each byte;

What I suspect, is that this difference may be associated with the fact that Serial1 and Serial2 have hardware FIFO queues of 8, where the other Serial ports only have a buffer of 1... That is Serial3 may behave different.

The RX and TX fifos have a concept of watermark. The init code does:
Code:
#ifdef HAS_KINETISK_UART0_FIFO
	UART0_C1 = UART_C1_ILT;
	UART0_TWFIFO = 2; // tx watermark, causes S1_TDRE to set
	UART0_RWFIFO = 4; // rx watermark, causes S1_RDRF to set
	UART0_PFIFO = UART_PFIFO_TXFE | UART_PFIFO_RXFE;
#else
The idea is that the FIFO allows your code to run faster as it tries to minimize how many interrupts that are generated. For example if the TX fifo gets down to only 2 bytes left in it (and it believes there may be more data to output), it will generate an interrupt where the ISR code will take things off the software queue and put as many bytes as it can on the TXFIFO.

Likewise, the rx watermark, is setup to not trigger until there are 4 items within the hardware queue, in which case the interrupt is generated and the ISR will take all of the current stuff off of the hardware FIFO and put it into the Software FIFO queue. Note: the hardware will also then detect if it is not receiving any more RX data and after a certain amount of time will also generate the interrupt...

But again guessing:
But if it were me, another experiment I would try might be something like:

Code:
  Serial1.begin(BAUD);
  uint32_t c2_save = UART0_C2;
  UART0_C2 = 0; // can not modify FIFO if RX is enabled
  UART0_RWFIFO = 1; // rx watermark, causes S1_RDRF to set
  UART0_C2 = c2_save ; //restore the setting

Note with T4.x, the delays were much more pronounced, so I added in code, that calls like:
Serial1.available() and Serial1.read() and peak() would peek into the hardware FIFO, and could retrieve the data from the FIFO.
Note: on T4.x all of the UARTS are handled by one class and all of the UARTS have hardware FIFO queues.
 
Just looking at the arduino code, I am slightly confused by
Code:
for(byte i=0; i < (sizeof(msg2)/sizeof(msg2[0])); i++)
  {
    Serial.write(msg2[i]);
  }
Why did you do that and not
Code:
for(byte i=0; i < sizeof(msg2); i++)
  {
    Serial.write(msg2[i]);
  }
 
Just looking at the arduino code, I am slightly confused by
Code:
for(byte i=0; i < (sizeof(msg2)/sizeof(msg2[0])); i++)
  {
    Serial.write(msg2[i]);
  }
Why did you do that and not
Code:
for(byte i=0; i < sizeof(msg2); i++)
  {
    Serial.write(msg2[i]);
  }

It is because he is storing the data in int:
Code:
int msg2[] = {0x02, 0xB2 , 0x14, 0x45, 0x45, 0x34, 0x45, 0x34};

So sizeof(msg2) would return 16 on AVR? On teensy would return 32

So (sizeof(msg2)/sizeof(msg2[0])
reduces down to 16/2 = 8
...
 
OK I see, so returns the size of the array type not the size of the array.
I like the way C/C++ makes things so uncomplicated!!
 
Likewise, the rx watermark, is setup to not trigger until there are 4 items within the hardware queue, in which case the interrupt is generated and the ISR will take all of the current stuff off of the hardware FIFO and put it into the Software FIFO queue.

This seems to be the issue.

I set up the buildin LED to toggle(third channel) when the teensy is reading data like you suggested and it seems like it only collects the data "every four bytes".

teensy_toggle_noissue.png

When I send less data, it seems to wait until it would have received a fourth byte.

teensy_toggle_issue.png

Sending a different multiple of 4 bytes (in this case 12 bytes) confirms this.

mult_of_4.png

Thank you for helping me identify the issue.
And also thanks to everyone else that took time out of their day to help me. :)
 
Why did you do that and not
Code:
for(byte i=0; i < sizeof(msg2); i++)
  {
    Serial.write(msg2[i]);
  }

The first call of sizeof(msg2) returns the amout of memory the array is using, whereas the second call sizeof(msg2[0]) returns the amout of memory one element is using. Dividing these two returns the amout of elements in the array.
 
Back
Top