
Series: Exploring MCUs
UART (Universal Asynchronous Receiver/Transmitter) is one of the simplest and most widely used serial communication protocols in embedded systems. In this project, I connected two Arduino Nano boards to exchange sensor data reliably.
Hardware Setup
Arduino A (TX) → Arduino B (RX)Arduino A (RX) ← Arduino B (TX)GND → GNDNo level shifting needed — both boards run at 5V logic.
Protocol Design
Raw UART has no notion of message boundaries. I used a simple frame format:
[0xAA] [LEN] [DATA...] [CRC]0xAA— start byteLEN— payload lengthDATA— the actual payloadCRC— simple XOR checksum
Implementation
#define START_BYTE 0xAA
uint8_t send_frame(uint8_t *data, uint8_t len) { uint8_t crc = 0; Serial.write(START_BYTE); Serial.write(len); for (uint8_t i = 0; i < len; i++) { Serial.write(data[i]); crc ^= data[i]; } Serial.write(crc);}On the receiving end, I implemented a simple state machine:
enum { WAIT_START, WAIT_LEN, WAIT_DATA, WAIT_CRC } state = WAIT_START;
void process_byte(uint8_t b) { switch (state) { case WAIT_START: if (b == START_BYTE) state = WAIT_LEN; break; case WAIT_LEN: expected = b; idx = 0; state = WAIT_DATA; break; case WAIT_DATA: buf[idx++] = b; if (idx == expected) state = WAIT_CRC; break; case WAIT_CRC: // verify checksum and reset state = WAIT_START; break; }}Key Takeaways
- Always use a frame protocol — raw byte streams are error-prone
- A CRC or checksum catches corrupted data
- State machine parsing is robust and simple
- At 115200 baud, a Nano can handle several thousand frames per second without missing data