Project using multiple UARTS

Status
Not open for further replies.

MolsonB

Active member
I'm starting a project that has 2 serial devices connected to Teensy 3.2. I'm looking to see if I'm on the right track on setting everything up. This will expand to another serial uart and a CAN device, along with SPI port.

Serial 1 - Host
Format - HEX
RX - Gets load of information in hex with frameflags, CRC, etc
TX - Read data from RX, subtract the TTL (Time to Live) field and send back out
I had to create my own buffer (head & tail) to handle the flow, even though the serial has it's own buffer in the 'print' libraries. Not sure if I had to do that, or if there is a better way?


Serial 2 - Wifi
Format - Human readable
TX - Takes certain data fields from Serial 1, and sends out human readable to ESPwifi to Android tablets.
RX - Gets the odd user command back from tablet
As my project got more complicated, these simple "print" commands started to block S1 from receiving complete transmissions and started to get more CRC errors.

I attached the small zip file of the project. Also here is the code.

Serial_V1
Code:
#include <Metro.h>      //Timer 
#include <TimeLib.h>

#define S1 Serial1
#define S3 Serial3


boolean S1Stat, S3Stat;     //Connected?
uint16_t S1Cnt, S3Cnt;      //Transmission counts

uint16_t Rx1_CrcError, Rx1_CrcValid;   //Track valid & invalid frames

/* S1 */
void S1_Setup();
void S1_Check();

/* S3 */
void S3_Setup();
void S3_Check();



void setup() {
	Serial.begin(57600);
  S1_Setup();
  S3_Setup();
}


void loop() {
  //Check GRT from interlink 
  S1_Check();

  //Check WIFI from android
  S3_Check();

}


S1
Code:
#include <FastCRC.h>
#include <FastCRC_cpu.h>
#include <FastCRC_tables.h>


Metro S1Chk_TMR = Metro(10000);     // Check connections every 10 second
Metro Hello_TMR = Metro(10000);   // Send Hello packet to GRT every (10 sec)


// set this to the hardware serial port you wish to use
FastCRC16 CRC16;      //CRC16_X25    http://www.sunshine2k.de/coding/javascript/crc/crc_js.html

const uint16_t SERIALBUFF = 512;
const uint8_t FRAMEFLAG = 0x7E, //Frame Flag
  VENDORCODE = 0x5B,            //Vendor protocol code  
  SOURCE = 0x05,                //SourceID is 5
  DEST = 0xFF,                  //Broadcast to all devices (255)
  TTL = 0x0A;                   //Time to Live (number of hops between devices)



void Tx1_Prep(uint8_t msg[], uint16_t len);
void Tx1_Store(uint8_t val, uint8_t chrstuff);
void Tx1_Send();
void Rx1_Process(uint8_t data[], uint16_t len);


uint16_t Rx1Len, Tx1Head, Tx1Tail, Tx1Cap=SERIALBUFF, Tx1Len, Tx1Time, Rx1Time;    
uint8_t Rx1Msg[SERIALBUFF], Rx1PrevByte, Tx1Msg[SERIALBUFF], Tx1Packet, Tx1Payload[100];
boolean Rx1Header=false, Tx1Sending=false;
boolean newData = false;



void S1_Setup() {
  S1.begin(19200); 
}


void S1_Check() {

  // If the timer is reached, S1 is not connected
  if (S1Chk_TMR.check()) {
    S1Stat = false;
  }

  //Send Hello packet to S1
  if (Hello_TMR.check()) { 
    S1_Hello();
  }

  
  /*  Check if there is anything to TX in array */
  if (Tx1Head != Tx1Tail) {
    if (Tx1Sending == false) {
      Tx1Sending = true;
    }
    Tx1_Send();
  }
  else {
    if (Tx1Sending == true) {
      Tx1Sending = false;
    } 
  }  


  /* Check incoming serial and store to array one byte at a time */
  static boolean recvInProgress = false;
  static boolean stuffedByte = false;
  static uint16_t ndx = 0;
  uint8_t rb;
   
  while (S1.available() > 0 && newData == false) {
    rb = S1.read();

    if (recvInProgress == true) {
      if (rb != FRAMEFLAG) {   //Start and End frame flag
        
        //Character stuffing
        if (stuffedByte == true) {   //previous byte was stuffed
          stuffedByte = false; 
          if (rb == 0x5E) {       //7D5E = 7E
            Rx1Msg[ndx-1] = 0x7E; 
          }    
          else if (rb == 0x5D) {  //7D5D = 7D 
           Rx1Msg[ndx-1] = 0x7D; 
          }    
          //stop here and don't do anything else now. go to next char
        }
        else {
          if (rb == 0x7D) {stuffedByte = true;}   //Store stuffed byte for next byte compare
    
          Rx1Msg[ndx] = rb;
          ndx++;
          if (ndx >= SERIALBUFF) {
            ndx = SERIALBUFF - 1;
          }
        }
      }
      else {
        if (ndx == 0) {  //grabbed end frame first, now we are at the the start flag
          recvInProgress = true;
        }
        else {
//        Rx1Msg[ndx] = '\0';  //terminte the string
          recvInProgress = false;
          newData = true;
          Rx1Len = ndx;
          ndx = 0;
        }
      }
    }
    else if (rb == FRAMEFLAG) {
      recvInProgress = true;
    }
  }


  //There is new data, test the CRC now
  if (newData == true) {
    newData = false;
    if (CRC16.x25(Rx1Msg, Rx1Len-2) == (uint16_t)((Rx1Msg[Rx1Len-1] << 8) | Rx1Msg[Rx1Len-2])) {
      Rx1_CrcValid++;
      Rx1_Process(Rx1Msg, Rx1Len-2); //Subtract the checksum bytes since we don't need to calculate it anymore
    }
    else {        
      Rx1_CrcError++;               //Crc do not match
    }
  }

}





void S1_Hello() {  
  Tx1Packet = 0x00;        //Hello
  Tx1Len = 3;
  memset(Tx1Payload,0,Tx1Len);
  Tx1Payload[0] = 0x01;    //Interlink version
  Tx1Payload[1] = 0x00;    //MSB of serial number
  Tx1Payload[2] = 0x01;    //LSB of serial number
  
  Tx1_Payload(Tx1Packet, Tx1Payload, Tx1Len);
}


void Tx1_Payload(uint8_t packet, uint8_t payload[], uint16_t len) {
  uint8_t msg[500];
  msg[0] = VENDORCODE;
  msg[1] = SOURCE;    
  msg[2] = DEST;    
  msg[3] = TTL;
  msg[4] = packet;
  
  for (uint16_t i = 0; i < len; i++) {
    msg[i+5] = payload[i];
  }

  Tx1_Prep(msg, len+5);
}


/* Add the Frameflags and checksum to store in Tx1Buffer */
void Tx1_Prep(uint8_t msg[], uint16_t len) { 
  uint16_t crc = CRC16.x25(msg, len);

  Tx1_Store(FRAMEFLAG, 0);        //Start frame
  for (uint16_t i = 0; i < len; i++) {Tx1_Store(msg[i], 1);}       //Store msg
  Tx1_Store(lowByte(crc), 1);
  Tx1_Store(highByte(crc), 1);
  Tx1_Store(FRAMEFLAG, 0);        //End frame
}


/* Store into Tx1Buffer array and increase Tx1Tail */
void Tx1_Store(uint8_t val, uint8_t chrstuff) {
  if (Tx1Tail == (Tx1Head-1 + Tx1Cap) % Tx1Cap) {Serial.println("** Tx1 Buffer full **");}

  // character stuffing
  if (chrstuff == 1 && val == 0x7E) //7E = 7D5E
  { Tx1Msg[Tx1Tail] = 0x7D; 
    Tx1Tail = (Tx1Tail + 1) % Tx1Cap;  
    val = 0x5E;     
  }  
  if (chrstuff == 1 && val == 0x7D) //7D = 7D5D
  { Tx1Msg[Tx1Tail] = 0x7D; 
    Tx1Tail = (Tx1Tail + 1) % Tx1Cap;  
    val = 0x5D;     
  }  
  
  Tx1Msg[Tx1Tail] = val; 
  Tx1Tail = (Tx1Tail + 1) % Tx1Cap;  
}


/* Send what is in TX array to SerialPort */
void Tx1_Send() {

  uint16_t avl = S1.availableForWrite();
  uint16_t remain;
  if (Tx1Head > Tx1Tail)  //looped over back to start of buffer
  {  remain = (Tx1Tail + Tx1Cap) - Tx1Head; } 
  else
  {  remain = Tx1Tail - Tx1Head; }

  if (avl > 0) 
  {
    if (avl > remain) {avl = remain;} //only process the lower of 1)what's remaining, 2)available for write

    for (uint16_t i=0; i < avl; i++) 
    {
      S1.write(Tx1Msg[Tx1Head]); 
      Tx1Head = (Tx1Head + 1) % Tx1Cap;  //Roll over when past the buffer capacity
    }
  }  
}










/* Process data packets */
void Rx1_Process(uint8_t data[], uint16_t len){

  uint8_t msg[SERIALBUFF];
  memcpy(msg, data, len+2);

  uint8_t scr, ttl, packet;

//  vendorcode = msg[0];
  scr = msg[1];
//  dest = msg[2];
  ttl = msg[3];
  packet = msg[4];

 
  if (scr == SOURCE) {return;} //Packet has bounced back to you, ignore
  
  if (packet == 0x00) {    //Hello packet
    S1Chk_TMR.reset();
    S1Stat = true;
  }

  //Pass along message, -1 from Time to Live
  ttl--;    
  msg[3] = ttl;
  if (ttl == 0x00)
  { Serial.print("Packet TTL is dead");
    return;
  }
  Tx1_Prep(msg, len);  //repeat serial packet for next device in loop
}


S3
Code:
Metro S3Chk_TMR = Metro(10000);     // Check connections every 10 second
Metro S3Data_TMR = Metro(500);     // Send Data every 0.5 second


int readline(int readch, char *buffer, int len);

const uint16_t SERIAL3BUFF = 512;

uint16_t Rx3Len, Tx3Head, Tx3Tail, Tx3Cap=SERIAL3BUFF, Tx3Len, Tx3Time, Rx3Time;    
uint8_t Rx3Msg[SERIAL3BUFF], Rx3PrevByte, Tx3Msg[SERIAL3BUFF], Tx3Packet, Tx3Payload[100];
boolean Rx3Header=false, Tx3Sending=false;
boolean newData3 = false;



void S3_Setup() {
  S3.begin(57600);
  S3Cnt = 0;
  S1Cnt = 0;
}





void S3_Check() {

  //If timer hits here, then the BCM is not connected
  if (S3Chk_TMR.check()) {
    S3Stat = false;
  }

  if (S3Data_TMR.check() ) {  
    S3Cnt = S3Cnt + 1;  //65536
  
    String data = "*S3"
      +String(",")+ S3Cnt
      +String(",")+ S1Stat                       //Is S1 Connected?  0 - No, 1-Yes
      +String(",")+ S3Stat;                      //Is S3 Connected?  0 - No, 1-Yes
    S3.println(data);
  
  
    if (S1Stat) {
      S1Cnt = S1Cnt + 1;  //65536
      S3.println("*S1"
        +String(",")+ S1Cnt
        +String(",")+ Rx1_CrcValid
        +String(",")+ Rx1_CrcError
      );
    }
  }

 
  if (S3.available() > 0) {
    static char buffer[255];
    if (readline(S3.read(), buffer, 255) > 0) {
      String msg = String(buffer);
      
      if (msg.substring(0,4) == "BCM:") {
        if (msg.substring(4) == "HELLO") {    //Receives 'Hello' to see if tablet is connected
          S3Chk_TMR.reset(); 
          S3Stat = true;
        }
      }
    }
  }

}




//ASCII sent over serial, look for newline (CR) for end of tranmission
int readline(int readch, char *buffer, int len)
{
  static int pos = 0;
  int rpos;

  if (readch > 0) {
    switch (readch) {
      case '\n': // Ignore new-lines
        break;
      case '\r': // Return on CR
        rpos = pos;
        pos = 0;  // Reset position index ready for next time
        return rpos;
      default:
        if (pos < len-1) {
          buffer[pos++] = readch;
          buffer[pos] = 0;
        }
    }
  }
  // No end of line has been found, so return -1.
  return -1;
}
 

Attachments

  • Serial_v1.zip
    3.8 KB · Views: 123
The Teensy Serial buffers to need to be kept from filling to preserve data, so I'd say proper sized local buffers is a good choice.

In my various sampling efforts I've come to rely on the built in functions called after every loop() call, or yield() or delay().

Demonstrated here for USB/Serial:: https://www.arduino.cc/en/Tutorial/SerialEvent. This is not an interrupt based mechanism so anything goes there, but other than interrupts - nothing else happens while in this code.

In your case an instance of serialEvent1() and serialEvent2() would be used to transfer to the local buffer like Rx1Msg[SERIALBUFF].

If you do nothing but 'necessary' preprocessing in the serialEvent#() functions you can call yield() at any time to keep the Teensy Native buffers safely empty without spending a great deal of time there or cluttering your main code - while doing the processing from local buffers as the data appears.

Note: Hardware FIFO support makes the Serial1 and Serial2 the best for high data throughput channels with less processor overhead than Serial3. Also note the pre-allocated Rx and Tx queue size is different for the 3 ports so you may find the need to adjust those. They are allocated if used or not - so the non-primary ones were diminished in size to reduce resources.
 
Status
Not open for further replies.
Back
Top