UART Using Arduino GPIO

This weekend I tried using an Arduino to do UART communication using the digitalWrite() and the delayMicroSeconds() methods provided by the Arduino library. My primary goal was to find out if I can simulate the working of any simple serial communication libraries, simply by using GPIO pins and artificial delays. Before getting into the experiment, here is some basic information about UART. Universal Asynchronous Receive Transmit is basically the simplest form of serial communication in comparison with I2C or other such comparable methods. UART at a minimum needs 2 wires to establish a communication, namely an RX or TX and a Ground (GND). If you send something through the TX line, the receiver should be able to receive the data at their RX pin, and vice versa. Generally the TX line is kept high, and a LOW (digital zero) with reasonable width is sent to denote the start of data transmission. Similarly, you may send one or two HIGH (digital one) to denote the end of transmission as well. Additionally, there is one bit called parity bit, which can be sent right before the stop bits, and this parity bit can be used for a very rudimentary error checking. You may read more about how parity bit works here. For the rest of the experiment, I’m not using parity bit. The following image explains this in detail.

As you have notices, the receiver uses a sampling clock, which is generally running at 10 to 16 times the frequency of incoming data. Which means, the receiver samples at least 10 to 16 times during the period of one bit. Now, to achieve a similar waveform using Arduino, I need to select one output pin, and write digital HIGH and digital LOW with proper delays in between. For simplicity, the data I’m planning to send is nothing but english character A. The capital A is in ASCII terms, represented as integer 65. Step number one is to convert this to hexadecimal, 65 —> 0x41 —> 0100 0001. Now, this data needs to be sent in a particular order. As you may have noticed in the above image, the most significant bit (MSB) is sent at the end of the signal, and the least significant bit (LSB) is sent first. So after re-ordering, the data we need to send looks like 1000 0010. Lets look into the program I have written now.

/**
 * A -> 65 -> 1000001 -> 0100-0001 -> 0x41
 * Data -> 0-0100-0001-1
 * In serial we should send least significant bit first -> so -> 0-1000-0010-11
 */

int output = 13;
int delayd = 3333;

void setup() {
  pinMode(output, OUTPUT);
}

void loop() {

  delay(1000);

  int data[] = { 1, 0, 0, 0, 0, 0, 1, 0 };

  //
  // Start Bit
  //

  digitalWrite(output, LOW); // Start Bit
  delayMicroseconds(delayd);

  //
  // Data
  //
  
  for(int i = 0; i < 8; i++) {
    if(data[i] == 1) {
      digitalWrite(output, HIGH);
    } else {
      digitalWrite(output, LOW);
    }
    delayMicroseconds(delayd);
  }

  //
  // Stop bit
  //

  digitalWrite(output, HIGH); // Stop bit
  delayMicroseconds(delayd);
    
}

/**
 * 1 bit stays for 1/1000 sec -> 1000 bits / sec
 */

This is fairly simple. But, you may be wondering why I have chosen the delays = 3333. This is based on the following table.

Now, this table will come handy down the line. If we reduce the delay between each bits, we can achieve higher data rates. I have compiled this program, and deployed to Arduino. Lets see the output for a second.

This is pretty decent. Now, lets try hooking it back to a serial port device (I run a laptop without an actual serial port, so I would just use one of the UART to USB adapter I have discussed in an earlier article).

As you can see in the above image, my setup is pretty simple. One USB is connected to the Arduino, which sends data, and the data is received at a serial adaptor which is connected to the USB port on the other side. Now, simply look at the data coming from the serial adapter using CoolTerm.

Things are butter smooth so far. Now, lets try achieving higher data rates. I’m going to go step by step by reducing width of each bit. Here is my result as a table.

Data Rate delayd Error Notes
300 b/s 3333 us NO
600 b/s 1667 us NO
1200 b/s 833 us NO
2400 b/s 417 us NO
4800 b/s 208 us NO
9600 b/s 104 us NO
14400 b/s 69 us NO
19200 b/s 52 us YES It starts to send/receive/interpret incorrect data 0x81 in between from this point onward.
38400 b/s 26 us YES Constantly receives 0x81 instead of 0x41.

Looks like there is some tiny timing issues from this point onward. Also the ringing may be causing some issues, I don’t know for sure.

Anyway, it looks like 14400 bits/second is the highest achievable data rate with an Arduino UNO/Duemilanove with simple digitalWrite() and delayMicroSeconds() methods. I will look into this in depth and will update, if I could reach a conclusive finding, and a higher data rate with the same setup.