Serial Comm Ports for ever and ever… (Part 1)
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); } }