High Speed Serial comms between 2 Teensy 3.1

Donziboy2

Well-known member
I have to say it sounds easy enough unless your like me and just play with C on the weekend :eek:

Needless to say after pulling my hair this afternoon I have managed to get 2 Teensy 3.1's talking to each other at 4.8Mhz.
My large scale plan is to send data from a motor controller on my EGoCart to a touch screen mounted on the steering wheel and I needed a little more then the standard Serial Baud rates could do, I will be using RS485 to make up for the cable lengths. Adding RS485 to the testing will come soon.

Below is the Transmit(motor controller) portion stripped down and the Receiving side(ILI9341 LCD w/touch) in its current WIP glory. Right now I am only transmitting 2 bytes, which will be 1 of several analog values im reading and sending in the final build along with a dozen or so other pieces of data.

I think the Serial can go faster but I will leave it for another day.... Like tomorrow.


Questions, comments, "hey this is lame do this instead". all welcome.


Transmit
Code:
/////////Donziboy2/////////

#define SERIAL_BAUD_RATE 4800000


void setup() {


  Serial.begin(9600);
  Serial1.begin( SERIAL_BAUD_RATE, SERIAL_8N1 );

  delay(200);
}

elapsedMillis serialtesting; //
int batterySOC = 1234;     //serialtestvalue
byte buffer[1] = {0};

void loop() {
  int i;

 if(serialtesting > 200) {    //10s
buffer[1] = (byte) (batterySOC & 0xFF);
buffer[0] = (byte) ((batterySOC >> 8) & 0xFF);

 Serial1.write(buffer[0]);
 Serial1.write(buffer[1]);
   serialtesting = 0;
   batterySOC = batterySOC + 1;
   if(batterySOC > 9999){
    batterySOC = 0; 
   }
 }
}

Receive

Code:
/***************************************************
Adafruit graphicstest used as a base.
 ****************************************************/
//////////////////////////////////////////////////////
//Modified by Donziboy2
//////////////////////////////////////////////////////


#include "SPI.h"
#include "ILI9341_t3.h"

// For the Adafruit shield, these are the default.
#define TFT_DC  9
#define TFT_CS 10
#define TFT_RST 8
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST);


#define SERIAL_BAUD_RATE 4800000

//Test perams
int frequency2 = 259;
float BatterySOC = 0;     // percentage of charge (assumed)
float BatterySOCold = 0;
volatile int32_t executiontime = 0;
byte boxset = 0;
byte drivestate = 1;
byte drivestateold = 0;
byte  Redfull = 0;
byte  Yellowfull = 0;
byte  Greenfull = 0;
  int a = 0;
  int b = 0;
float xold = 0;
float yold = 0;
float zold = 0;
float aold = 10;
float bold = 10;
float cold = 10;
float dold = 9;
int h = 0;
int i = 0;
byte buffer[1] = {0};


void setup() {

  tft.begin();
  tft.fillScreen(ILI9341_BLACK);
  tft.setRotation(3);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(3);
  tft.println("Initializing.....");
  delay(1000);
  tft.fillScreen(ILI9341_BLACK);
  tft.fillRect(5, 5, 20, 230, ILI9341_WHITE);     //blank battery bar once
  mainScreen();
 
  Serial.begin( 9600 );
  Serial1.begin( SERIAL_BAUD_RATE );
 
}


void loop() {

    
  
  if (Serial1.available() > 1){
 
  buffer[0] = Serial1.read(); 
  buffer[1] = Serial1.read();
    
    int a = buffer[0] << 8;
    a = a + buffer[1];
    BatterySOC = a;
  }

  
    refreshtime();
    
    mainScreen();
    

 //   delay(50);
 // frequency2++;
    h++;
 //   i++;
    if(h == 30){
    if(frequency2 >= 999){
      a = 1;
    }
    if(frequency2 <= 0){
     a = 0; 
    }
    if(a == 0){
      frequency2 = frequency2 + 1;   
    }
    else
        {
             frequency2 = frequency2 - 1; 
        }
    h = 0;
    }
  /*  
    if(i == 2){
    if(BatterySOC >= 9999){
      b = 1;
    }
    if(BatterySOC <= 0){
     b = 0; 
    }
    if(b == 0){
      BatterySOC = BatterySOC + 1;   
    }
    else
        {
             BatterySOC = BatterySOC - 1; 
        }  
  i = 0;
    }*/
}

unsigned long refreshtime(){

  
  tft.fillRect(35, 110, 75, 15, ILI9341_BLACK);
  tft.setCursor(35, 110);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
  tft.print(executiontime);
  tft.setCursor(35, 130);
  tft.print("Microseconds");

}


unsigned long mainScreen() {
  elapsedMicros elapsedtesttime;
  
  if(boxset < 1){                                 //removes redraw for no reason
  tft.drawRect(145, 5, 170, 30, ILI9341_GREEN);   //drive status box 
  tft.drawRect(35, 185, 65, 50, ILI9341_WHITE);
  tft.drawRect(105, 185, 65, 50, ILI9341_WHITE);
  tft.drawRect(175, 185, 65, 50, ILI9341_WHITE);
  tft.drawRect(245, 185, 65, 50, ILI9341_WHITE);
  tft.drawRect(5, 5, 20, 230, ILI9341_WHITE);     //battery bar outline
 
  tft.setTextColor(ILI9341_WHITE);   tft.setTextSize(2);
  tft.setCursor(74, 5);   
  tft.print(".");
  tft.setCursor(110, 5);
  tft.print("%");
  
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
  tft.setCursor(50, 22);
  tft.print("Charge");
  tft.setTextColor(ILI9341_BLUE);    tft.setTextSize(4);  
  tft.setCursor(240, 150);  
  tft.println("MPH"); 
  tft.setTextColor(ILI9341_YELLOW);   tft.setTextSize(5);
  tft.setCursor(255, 110);   
  tft.print("."); 
 
  boxset = 1;
}  
  
  
  
  
  if(BatterySOCold > BatterySOC){
    float w = (1 - (BatterySOC / 10000)) * 228; 
  tft.fillRect(6, 6, 18, w, ILI9341_WHITE);     //blank battery bar
  Redfull = 0;
  Yellowfull = 0;
  }
 
 
 
 
 
  if(BatterySOC <= 3333 && BatterySOC != 0){      //red battery bar only
    float x;
    float y;
    x = BatterySOC / 3333;
    y = x * 76;                  //rectangle size
    x = 235 - y;                 //rectangle starting position
  tft.fillRect(6, x, 18, y, ILI9341_RED);
  Redfull = 0;
  Yellowfull = 0;
  }
  
  if(BatterySOC <= 6666 && BatterySOC > 3333){      //red and yellow battery bars only
    float x;
    float y;
    x = BatterySOC - 3333;    //remove red region
    x = x / 3333;
    y = x * 76;                 //rectangle size
    x = 159 - y;                //rectangle starting postion
    if(Redfull == 0){
  tft.fillRect(6, 158, 18, 76, ILI9341_RED);
  Redfull = 1;
    }
  tft.fillRect(6, x, 18, y, ILI9341_YELLOW);
  Yellowfull = 0;
  }
  
  if(BatterySOC <= 10000 && BatterySOC > 6666){      //red, yellow and green battery bars only
    float x;
    float y;
    x = BatterySOC - 6666;    //remove red and yellow region
    x = x / 3333;
    y = x * 76;                 //rectangle size
    x = 83 - y;                //rectangle starting postion
  if(Redfull == 0){
  tft.fillRect(6, 158, 18, 76, ILI9341_RED);
  Redfull = 1;
  }
  if(Yellowfull == 0){
  tft.fillRect(6, 82, 18, 76, ILI9341_YELLOW);
  Yellowfull = 1;
  }
  tft.fillRect(6, x, 18, y, ILI9341_GREEN);
  }
  
  
//  tft.fillRect(50, 5, 59, 14, ILI9341_BLACK);     //clears % value
  
 // tft.setTextColor(ILI9341_BLACK); tft.setTextSize(2);
 // tft.setCursor(50, 5);
 // tft.print((float)BatterySOCold * 100,2);     tft.print("%");
  
                                                                //1234 as example
  float a = BatterySOC / 1000;                                  //1.234
  float b = BatterySOC / 100;                                   //12.34  
  float c = BatterySOC / 10;                                    //123.4
  int aa = a;                                                   //1
  int bb = b;                                                   //12
  int cc = c;                                                   //123
  float aaa = aa;                                                //1
  float bbb = bb;                                                //12
  float ccc = cc;                                                //123
  a = aaa;                                                       //1
  b = bbb - (aaa * 10);                                          //2
  c = ccc - (bbb * 10);                                          //3
  float d = BatterySOC - (ccc * 10);                             //4
  
  

  tft.setTextSize(2);
  if(a != aold){
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(50, 5);
  tft.print(aold, 0);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(50, 5);  
  tft.print(a, 0);
  aold = a; 
  }

  if(b != bold){
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(62, 5);
  tft.print(bold, 0);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(62, 5);  
  tft.print(b, 0);
  bold = b;
  }

////decimal is printed once with rest of boxes
  if(c != cold){ 
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(86, 5);
  tft.print(cold, 0);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(86, 5);  
  tft.print(c, 0);
  cold = c;
  }

  if(d != dold){
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(98, 5);
  tft.print(dold, 0);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(98, 5);  
  tft.print(d, 0);
  dold = d; 
  }

  BatterySOCold = BatterySOC;
  
  
  
 // tft.setCursor(50, 5);
 // tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(2);
 // tft.print((float)BatterySOC * 100,2);    tft.print("%");
 // tft.fillRect(195, 110, 115, 35, ILI9341_BLACK);   //clears MPH Value
  
  
 
  
                                                 //239 as example
float x = frequency2 / 100;                      //2.39
float y = (frequency2 / 10);                     //23.9
int xx = x;                                      //2
int yy = y;                                      //23
float xxx = xx;                                  //2
float yyy = yy;                                  //23
x = xxx;                                         //2
y = yyy - (x * 10);                              //3
float z = frequency2 - (yyy * 10);               //9

  
  
  tft.setTextSize(5);
  if(x != xold){
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(195, 110);
  tft.print(xold, 0);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(195, 110);  
  tft.print(x, 0);
  xold = x; 
  }

  if(y != yold){
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(225, 110);
  tft.print(yold, 0);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(225, 110);  
  tft.print(y, 0);
  yold = y;
  }

////decimal is printed once with rest of boxes
  if(z != zold){ 
  tft.setTextColor(ILI9341_BLACK);    
  tft.setCursor(285, 110);
  tft.print(zold, 0);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(285, 110);  
  tft.print(z, 0);
  zold = z;
  }


  
  
  //tft.print((float)frequency2, 1);  // this rounds 25.98765 to 26.0  :(

  
  
  if(drivestateold != drivestate){   //only change is drivestate changes
  tft.setTextColor(ILI9341_RED);    tft.setTextSize(2);
  tft.setCursor(170, 12);
  tft.println("DriveStatus");
  drivestateold = 1;
  }
  
  executiontime = elapsedtesttime;

}

P.S. not sure if this would be better in Tech Support :confused:
 
I'm logging data on the receiving micro that will tie up SPI, I'm also refreshing the screen that will tie the SPI up some. I also have to deal with a 12'+ cable from the motor micro to the display micro. Add to that I would need to limit the SPI clock over that distance, which would mean slower screen and NVflash writes. Serial transfers between micros is hardly documented as it is and SPI is practically an urban legend compared to Serial.

Also i'm not even sure if i'm using Serial correctly atm....
 
Do you ever increase 'serialtesting' in the transitter loop ?

Sending handful of values ever second or ten seconds, do you really need to push the serial speed between your twop boards?

Top serial speeds is needed for fast command/response exchanges, with not trivial data lengths, and for big data volumes, neither seems to be these case here.
 
Do you ever increase 'serialtesting' in the transitter loop ?

Sending handful of values ever second or ten seconds, do you really need to push the serial speed between your twop boards?

Top serial speeds is needed for fast command/response exchanges, with not trivial data lengths, and for big data volumes, neither seems to be these case here.

My plan is to send up to 64 bytes between ADC reads of my motor controller, I plan to read the ADC's at 2KHz. I plan to send periodically to the screen micro after completing an ADC reads and doing any processing that is needed before sending all the ADC averages and any drive state info I think is needed.
 
Yeah, for control loops 2kHz can be important, for display anything above 10Hz total overkill. The user cannot process information faster than this.
 
Got Serial1 going at 6Mhz @96Mhz. Seems to be sending the 16 byte data pretty well (6x 0-4095adc values and 1x 32bit timing value). Looking at Frank B's CRC library to determine if im seeing errors, I just have to figure out how to use the library......

Another thing worth mentioning...... Checking Serial1.available() > 1 and sending lots of bytes is a bad idea, at 4.8Mhz I was only getting the first 2 bytes before the data got processed and the others got read as a new transmission. It took me hours to find this simple mistake along with several others I made.

Code:
if (Serial1.available() > 1)

This works much better.
Code:
if (Serial1.available() >= (n of bytes - 1))  //if using 16 bytes set this to 15

edit 2....
Just found another interesting thing.

I have the serial byte buffer set to 1, on both my transmit test and the current sketch im running 16 bytes on and somehow its working.....

Pieces from my 16byte sketch..

Declared before setup in my 16 byte transmit and after setup in my receive sketch.
Code:
byte buffer[1] = {0};

receiving chunk
Code:
  if (Serial1.available() >= 15){
 
    
    for(i = 0; i < 16; i++){
  buffer[i] = Serial1.read(); 
//  buffer[1] = Serial1.read();
    }
    
int a = 0;
uint32_t b = 0;

    a = buffer[0] << 8;
    a = a + buffer[1];
    newaverages[0] = a;    //throttleaverage
    a = 0;
    a = buffer[2] << 8;
    a = a + buffer[3];
    newaverages[1] = a;    //currentaverage
    a = 0;
    a = buffer[4] << 8;
    a = a + buffer[5];
    newaverages[2] = a;    //busvaverage
    a = 0;
    a = buffer[6] << 8;
    a = a + buffer[7];
    newaverages[3] = a;    //battvaverage   
    a = 0;
    a = buffer[8] << 8;
    a = a + buffer[9];
    newaverages[4] = a;    //motortaverage 
    a = 0;
    a = buffer[10] << 8;
    a = a + buffer[11];
    newaverages[5] = a;    //hstaverage

    b = buffer[12] << 8 + b;
    b = buffer[13] << 8 + b;
    b = buffer[14] << 8 + b;
    b = buffer[15] + b;
    frequency1 = b;    //frequency1    
 
      newdata = 1;
  }

transmit chunk

Code:
 if(serialcounter > 2000) {   
  buffer[0] = (byte) ((throttleaverage >> 8) & 0xFF); 
  buffer[1] = (byte) (throttleaverage & 0xFF);
  buffer[2] = (byte) ((currentaverage >> 8) & 0xFF); 
  buffer[3] = (byte) (currentaverage & 0xFF);
  buffer[4] = (byte) ((busvaverage >> 8) & 0xFF); 
  buffer[5] = (byte) (busvaverage & 0xFF);
  buffer[6] = (byte) ((battvaverage >> 8) & 0xFF); 
  buffer[7] = (byte) (battvaverage & 0xFF);
  buffer[8] = (byte) ((motortaverage >> 8) & 0xFF); 
  buffer[9] = (byte) (motortaverage & 0xFF);
  buffer[10] = (byte) ((hstaverage >> 8) & 0xFF); 
  buffer[11] = (byte) (hstaverage & 0xFF);
  buffer[12] = (byte) ((frequency1 >> 24) & 0xFF); 
  buffer[13] = (byte) ((frequency1 >> 16) & 0xFF);
  buffer[14] = (byte) ((frequency1 >> 8) & 0xFF); 
  buffer[15] = (byte) (frequency1 & 0xFF);

  
 Serial1.write(buffer[0]);
 Serial1.write(buffer[1]);
 Serial1.write(buffer[2]);
 Serial1.write(buffer[3]);
 Serial1.write(buffer[4]);
 Serial1.write(buffer[5]);
 Serial1.write(buffer[6]);
 Serial1.write(buffer[7]);
 Serial1.write(buffer[8]);
 Serial1.write(buffer[9]);
 Serial1.write(buffer[10]);
 Serial1.write(buffer[11]);
 Serial1.write(buffer[12]);
 Serial1.write(buffer[13]);
 Serial1.write(buffer[14]);
 Serial1.write(buffer[15]);
 
 
 serialcounter = 0;
 }

Not sure if its idiot proof or a bug lol.

edit 3...
and i found more mistakes, really missing those debug tools now :/
 
Last edited:
Just an update im running Serial @ 6Mhz over RS485 and I have implemented CRC thanks to Frank B and his Fast CRC library.

I will leave my project running overnight to see how many CRC fails I can get.
 
Wow, I think 6 Mbits/sec is probably the fastest anyone's ever reported running hardware serial.

I know I've only tested up to 1 Mbit/sec.
 
I am using 16' of Cat5 and MAX3087 transceivers.

I had not expected to be able to send data this fast, the biggest hurdle for me is timing. The higher speed lets me do more other things like adding extra ADC inputs, even increasing the frequency at which I read my A/D inputs.

I did run the test setup all night.
In order.
6x 12bit analog values, 1 motor speed value in microseconds/pulse(external interrupt), a crc fail count, the current crc value and lastly the running time in seconds.
As you can see it ran 10hours overnight with no crc fails, 10 transmits per second.

As you can tell from the ghosting on the MPH value, it is updating pretty fast, the microseconds counter says 12. That is the amount of time the last screen update took.
KPrYDlj.jpg



So far i'm pleased with the results, i'm doing everything on solder-less breadboards and have had very little in the way of noise issues on both the ADC channels and the serial. I am tearing the current setup apart and have started building everything up the way I have in my motor controller schematics. With any luck I will be running a small test motor (200W) by the end of the weekend.
 
I am using 16' of Cat5 and MAX3087 transceivers.
As you can tell from the ghosting on the MPH value, it is updating pretty fast, the microseconds counter says 12. That is the amount of time the last screen update took.

Are you saying you are able to drive that screen at 83FPS (1000/12)?? Wow, that is pretty damn fast. I bet you will get only get like 7FPS by using the Ada-fruit library.
 
Are you saying you are able to drive that screen at 83FPS (1000/12)?? Wow, that is pretty damn fast. I bet you will get only get like 7FPS by using the Ada-fruit library.
Its in microseconds not milliseconds. It ranges from around 10uS to about 15-20mS depending on what is getting updated. Had a few discussions on the Highly-optimized-ILI9341 thread about optimizing redraws on the screen.

It boils down to only updating what has to be updated. Blacking just the text is far faster then making a large black rectangle then redrawing the text.
 
Wow, I think 6 Mbits/sec is probably the fastest anyone's ever reported running hardware serial.

I know I've only tested up to 1 Mbit/sec.

I have found one issue so far and that is a pause between transmitting bytes that is roughly 2.4uS(longer then it takes to actually transmit the byte). Is there a way to reduce this dead time?

edit....
Yay it gets stranger, I went ahead and took it down to 3Mhz and now its transmitting without pauses and its taking less time then 6Mhz... :(
Went from 63uS for 17 Bytes @ 6Mhz to 49uS to transmit the same 17 Bytes at 3Mhz.
 
Last edited:
Post below that may relate to post #15 dead time - where KurtE observed and got a fix in 1.29 for T_2 - the T_3 code seems to do the same::

Teensy 2 - Hardware Serial: Serial1.write() not as fast as Arduino code base...

...
So I took a look at: size_t HardwareSerial::write(uint8_t c)
in HardwareSerial.cpp and noticed the speed hack added to arduino in about 1.5.3 was not added here. The hack was when you call this function, if the queue is empty and the data register is empty, simply stuff it into the data register without adding it to queue and relying on interrupt handler. I found that this helped a lot at fast baud rates.
 
Just an update to anyone who comes across this thread. To fix the Slower then expected 6Mhz transmits is to replace the Serial1.write(buffer[0]); with Serial1.write(buffer, n);, where n is the number of bytes in the buffer.
 
FYI - as this was mentioned in other thread: https://forum.pjrc.com/threads/46696-Serial-communication-at-high-baudrates?p=154895#post154895

Thought I would mention, that I verified your speedup (on T3.2) using same program (slightly modified for 3rd case of output one byte at a time using Serial.write)...

Code:
void setup() {
  while (!Serial && (millis() < 2000)) ;
  Serial.println("Test Start");
  Serial1.begin(6000000);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
}

void loop() {
  uint32_t start_time = millis();
  uint8_t data[32];

  for (uint8_t i = 0; i < 32; i++) {
    data[i] = i;
  }
  // We wish to output 1600 characters. 
  digitalWriteFast(2, HIGH);
  for (int i = 0; i < 50; i++) {
    digitalWriteFast(3, HIGH);
#if 1
    // This code assumes that the serial registers are setup and TX and RX are enabled but the interrupt is not... 
    for (int j = 0; j < 32; j++) {
#if 1
        Serial1.write(data[j]);
#else        
        while  (!(UART0_S1 & UART_S1_TDRE)) ;   // Wait for uart1 to say it has room to put
        UART0_D = data[j];
#endif
    }
 
#else    
    Serial1.write(data, 32);
#endif
    digitalWriteFast(3, LOW);
  }
  digitalWriteFast(2, LOW);
  // quick and dirty..
  delay(500-(millis()-start_time));
}

The time to output the 1600 characters was the same using Serial1.write(buf, cnt) and using hardware registers: 2.667ms
The time doing one write at a time was: 6.8ms... At that speed you can not queue up enough characters to be able to fill the queue...
 
Back
Top