Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 15 of 15

Thread: Serial.write delay on Teensy 3.6

  1. #1
    Junior Member
    Join Date
    Dec 2022
    Posts
    7

    Serial.write delay on Teensy 3.6

    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.

    Click image for larger version. 

Name:	teensy_rec_8byte.jpg 
Views:	19 
Size:	47.4 KB 
ID:	29888
    picture 1, teensy receives 8 bytes


    Click image for larger version. 

Name:	teensy_rec_6byte.png 
Views:	17 
Size:	18.3 KB 
ID:	29889
    picture 2, teensy receives 6 bytes

    Click image for larger version. 

Name:	teensy_rec_7byte.png 
Views:	16 
Size:	18.5 KB 
ID:	29890
    teensy receives 7 bytes

    Click image for larger version. 

Name:	teensy_rec_9byte.png 
Views:	19 
Size:	19.5 KB 
ID:	29891
    teensy receives 9 bytes

    I would appriciate any suggestions.
    Thank you!

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    17,140
    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)
    {
      while (Serial1.available())
    Not sure that explains the time jump - but it will eliminate repeat calls to empty any buffered data.

  3. #3
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Location
    Cheltenham, UK
    Posts
    1,243
    Your code does NOT COMPILE.
    Full of Errors!

  4. #4
    Junior Member
    Join Date
    Dec 2022
    Posts
    7
    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;
      }
    }
    Click image for larger version. 

Name:	short_t36_6bytes.png 
Views:	14 
Size:	10.6 KB 
ID:	29892
    6 Bytes sent, timing issue

    Click image for larger version. 

Name:	short_t36_8bytes.png 
Views:	16 
Size:	11.3 KB 
ID:	29893
    8 Bytes sent, no issue

  5. #5
    Junior Member
    Join Date
    Dec 2022
    Posts
    7
    Quote Originally Posted by defragster View Post
    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);
    }
    Quote Originally Posted by defragster View Post
    <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.


    Quote Originally Posted by defragster View Post
    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.

  6. #6
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Location
    Cheltenham, UK
    Posts
    1,243
    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
        }
    }

  7. #7
    Junior Member
    Join Date
    Dec 2022
    Posts
    7
    Thank you for you effort @BriComp !

    Unfortunately there seems to be no difference but I appriciate you help.

  8. #8
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Location
    Cheltenham, UK
    Posts
    1,243
    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
        }
    }

  9. #9
    Junior Member
    Join Date
    Dec 2022
    Posts
    7
    @BriComp I really appreciate you taking the time to help me

    Quote Originally Posted by BriComp View Post
    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.

    Quote Originally Posted by BriComp View Post
    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.

  10. #10
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    11,658
    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())
      {
        digitalToggleFast(2);
        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.

  11. #11
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Location
    Cheltenham, UK
    Posts
    1,243
    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]);
      }

  12. #12
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    11,658
    Quote Originally Posted by BriComp View Post
    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
    ...

  13. #13
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Location
    Cheltenham, UK
    Posts
    1,243
    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!!

  14. #14
    Junior Member
    Join Date
    Dec 2022
    Posts
    7
    Quote Originally Posted by KurtE View Post
    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".

    Click image for larger version. 

Name:	teensy_toggle_noissue.png 
Views:	14 
Size:	18.6 KB 
ID:	29894

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

    Click image for larger version. 

Name:	teensy_toggle_issue.png 
Views:	13 
Size:	15.8 KB 
ID:	29895

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

    Click image for larger version. 

Name:	mult_of_4.png 
Views:	13 
Size:	16.0 KB 
ID:	29896

    Thank you for helping me identify the issue.
    And also thanks to everyone else that took time out of their day to help me.

  15. #15
    Junior Member
    Join Date
    Dec 2022
    Posts
    7
    Quote Originally Posted by BriComp View Post
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •