Serial Comm Ports for ever and ever… (Part 1)

Part 12345678

Serial Comm Ports have been a standard for years and years, before USB arrived from the Apple world and made the 9 pins subD connectors disappear from the back of our legacy computers.
I wonder what could have been the future of the RS232 serial communication protocol if this subD connector had received a +5 Volts pin!
Thanks to FTDI and some other constructors, we can still operate the RS232 protocol thanks to fancy compact converters. “SubD are dead, hurray for RS232”! (Raw interpretation of “Le Roi est mort, vive le Roi”). It is even written in the FTDI name « Future Technology Devices International » !!!

Let’s talk about serial comm. on Arduino. We are all aware about the PIND0:1 which are reserved for serial communication with USB through a FTDI converting chip. We will leave these pins in peace for the moment. Now what about adding an other serial communication port, or more, to our Arduino platform. I found a couple of suggestions, but they most often limited by fixed baudrates. For the purpose of designing an interface to the ECU of my car, I need a serial interface which is able to run at 5 (yes 5!) baud and 10400 baud.

This post contains a description of the read and write functions that I wrote and tested. Requirements were: As simple code as possible (no interrupts, because I do not need polling, just reading a feed back), pluggable on any pin, 5 to 2 times 10400 baud (For the sake of safety). 1 start bit (Is there an alternative?), 1 stop bit, no parity.

This is the write function. I got rid of the delay functions which could have added cumulative errors and preferred looping while waiting for an expected time (in micro seconds). The status led stays one while writing the byte.

/* 
Write byte
baudRate: any integer
nbrBits: 1 to 8
timeOut: in ms 
*/
void writeByte(byte byteValue, long baudRate, int nbrBits){
  PORTB |=  (1 << ledPin); // turn status led on
  //
  long interval = long(1000000 / baudRate); // in µs
  long startTime = micros(); // Set reference time to half first bit interval
  long stopTime = startTime + interval;
  PORTB &= ~(1 << TxPin); // Set start bit (low level) on TxPin
  while (micros() < stopTime);  // Wait until stop pulse is reached
  // data bits: LSB first, MSB last
  for (int i = 0; i < nbrBits; i++) {
    byte bitValue = ((byteValue >> i) & 0x01);
    if (bitValue) {
      PORTB |=  (1 << TxPin); 
    }
    else {
      PORTB &= ~(1 << TxPin); 
    }
    stopTime = startTime + (interval * (i+2));
    while (micros() < stopTime); // // Pulse width
  }
  // send stop bit and stays in idle state
  PORTB |=  (1 << TxPin); // Set stop bit (high level) on TxPin
  stopTime = startTime + (interval * (nbrBits+2));
  while (micros() < stopTime); // // Pulse width
  //
  PORTB &= ~(1 << ledPin); // turn status led off
}

This is the read function. Once again, I got rid of the delay functions which could have added cumulative errors and preferred looping while waiting for an expected time (in micro seconds). The status led is turned on when the start bit has been detected and stays one while reading the byte. The bits level is measured at half their expected duration, based on the detection of the falling edge of the start bit. This is a logical diagram for asynchronous serial comm signal

idle
-----------     ----------------------------------------mark
           |Sta| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |Sto|
           ----------------------------------------      space/break
           | *   |   |   |   |   |   |   |   |   |  

The vertical tick mark represent the critical sensing times, the star shows the reference time. 
A falling edge indicates a start bit. 
The fisrt bit level is read at start + interval and a half. 
And so on for the following bits until the stop bit

And here is the code

/* 
Read incoming byte
baudRate: any integer
nbrBits: 1 to 8
timeOut: in ms 
*/
byte readByte(long baudRate, int nbrBits, int timeOut){
  long interval = long(1000000 / baudRate); // in µs
  byte byteValue = 0; 
  long abortTime = (millis() + timeOut);
  errorCode = ERR_UNKNOWN;
  // Wait for the start bit (low level)
  while (PINB & (1 << RxPin)) { // Loop until level is high
    if (millis() > abortTime){ // Monitor timeout
      errorCode = ERR_TIMEOUT;
      return (0);
    } 
  }
  // 
  PORTB |=  (1 << ledPin); // turn status led on
  //
  long referenceTime = micros() + long(500000 / baudRate); // now + half interval
  long nextSensingTime = 0;
  // Read bits
  for (int i=0; i < nbrBits; i++) {  
    // Set next sensing time
    nextSensingTime = (referenceTime + (interval * (i + 1)));
    // Wait for data bits
    while (micros() < nextSensingTime);
    byte bitValue = ((PINB >> RxPin) & 0x01);
    byteValue |= (bitValue << i);
   }
  // Wait for the stop bit
  nextSensingTime = referenceTime + interval * (nbrBits + 1);
  while (micros() < nextSensingTime);
  byte bitValue = ((PINB >> RxPin) & 0x01);
  PORTB &= ~(1 << ledPin); // turn status led off
  if (bitValue){ // (High level)
    errorCode = ERR_NONE;
    return (byteValue);
  } 
  else {// (Low level)
     errorCode = ERR_FRAME;
    return (0);
  }
}

Next post on same subject

Leave a Reply

You must be logged in to post a comment.