Skip to content

Instantly share code, notes, and snippets.

@obskyr
Last active February 25, 2019 03:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save obskyr/a6ef6b9e9851be69b2faef0c2792e8ac to your computer and use it in GitHub Desktop.
Save obskyr/a6ef6b9e9851be69b2faef0c2792e8ac to your computer and use it in GitHub Desktop.
Test baud rates on Arduino compatibles – does your desired combination of clock frequency and baud rate work?

Oh My Baud

This is a tool for testing which baud rates your particular combination of microcontroller, crystal, and serial port adapter work with. Depending on clock frequency and baud rate, there may be timing errors, and whether these pose a problem for your particular setup can only really be determined by testing. So do so using this script, and go forth without pause! Follow this simple three-step guide and you'll never have problems in your life again:

  1. Download and extract this script and rename the folder to ohmybaud.

  2. Open ohmybaud.ino using the Arduino IDE and upload it to your device.

  3. From the command line, positing that your Arduino is located on serial port COM5, run the test script as:

    ./ohmybaud.py COM5

And presto! You'll get a list of which common baud rates your setup supports and which it doesn't support.

This assumes you have Python installed – if not, install it first. To find out which serial port your Arduino is connected to, you can run ohmybaud.py without any arguments.

Does your Arduino reset when a serial connection is opened?

At the time of writing, on some platforms, pySerial's DTR control doesn't work. DTR is what triggers Arduinos to automatically reset when uploading code, etc., so… that's a bit of a problem. If you're on one of these platforms, you can place a 10 µf capacitor between RST and GND, or a 120 Ω resistor between RST and 5 V (resistor values for compatibles with other voltages may be different). This'll require you to press the reset button when uploading code, so either do that or upload the code before connecting the workaround component.

#include <math.h>
// Implements the "client" side of the testing protocol of ohmybaud.py.
#define SAFE_BAUD_RATE 9600
#define TEST_DURATION_TARGET 2.5
const uint32_t BAUD_RATES[] = {
2400,
4800,
7200,
9600,
14400,
19200,
38400,
57600,
115200,
128000
};
inline int readByteWithTimeout(uint8_t &b)
{
if (!Serial.available()) {
unsigned long int startedWaitingForByte = millis();
while (!Serial.available()) {
if (millis() - startedWaitingForByte >= 1000) {
return 1;
}
}
}
b = Serial.read();
return 0;
}
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
Serial.begin(SAFE_BAUD_RATE);
}
void loop()
{
if (Serial.available()) {
uint8_t received = Serial.read();
Serial.write(received);
if (received == 0x01) {
digitalWrite(LED_BUILTIN, HIGH);
testBaudRates();
digitalWrite(LED_BUILTIN, LOW);
}
}
}
void testBaudRates()
{
for (size_t i = 0; i < sizeof(BAUD_RATES) / sizeof(*BAUD_RATES); i++) {
if (testBaudRate(BAUD_RATES[i]) != 0) {
return;
}
}
}
int testBaudRate(uint32_t baud_rate)
{
Serial.end();
Serial.begin(baud_rate);
// Divided by two since the test is done in both directions.
size_t testLength = static_cast<size_t>(ceil(
(TEST_DURATION_TARGET / 2.0) / (1.0 / (baud_rate / 8.0))
));
bool receivedAlright = receiveAndValidateTest(testLength);
// Just in case some junk was left due to baud rate error tomfoolery.
while (Serial.available()) {
Serial.read();
}
sendTest(testLength);
Serial.end();
Serial.begin(SAFE_BAUD_RATE);
uint8_t endCommand;
if (readByteWithTimeout(endCommand) != 0) {return 1;}
if (receivedAlright) {
Serial.write(0);
} else {
Serial.write(1);
}
return 0;
}
bool receiveAndValidateTest(size_t testLength)
{
bool valid = true;
uint8_t n = 0;
for (size_t i = 0; i < testLength; i++) {
uint8_t b;
if (readByteWithTimeout(b) != 0) {return false;}
if (b != n) {valid = false;}
n++; // Relies on the byte overflowing!
}
return valid;
}
void sendTest(size_t testLength)
{
uint8_t n = 0;
for (size_t i = 0; i < testLength; i++) {
Serial.write(n);
n++; // As in the receive test, this relies on the byte overflowing.
}
}
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
"""Test which baud rates you can successfully communicate at using a given
serial port (or USB-to-serial adapter) and serial device.
To test an Arduino compatible, use in conjunction with ohmybaud.ino.
"""
import os
import sys
import serial
from collections import namedtuple
from math import ceil
from serial.tools.list_ports import comports
# How many seconds to test for per baud rate.
TEST_DURATION_TARGET = 2.5
SAFE_BAUD_RATE = 9600
BAUD_RATES = (
2400,
4800,
7200,
9600,
14400,
19200,
38400,
57600,
115200,
128000
)
Capability = namedtuple('Capability', ('pc_to_device', 'device_to_pc'))
def serial_without_dtr(port, *args, **kwargs):
"""Construct a Serial object with DTR immediately disabled.
On systems where pySerial supports this, this will prevent an Arduino from
resetting when a serial connection is opened. On other systems, a hardware
workaround (e.g. a 10 µF capacitor between RST and ground) is needed.
"""
ser = serial.Serial(None, *args, **kwargs)
ser.port = port
ser.dtr = 0
if port is not None: # To match what pySerial does.
ser.open()
return ser
def single_exchange_works(port, baud_rate):
with serial_without_dtr(port, baud_rate, timeout=1) as ser:
try:
ser.write(b'\xA5')
return ser.read(1) == b'\xA5'
except serial.SerialTimeoutException:
return False
def send_start_command(port, baud_rate):
with serial_without_dtr(port, baud_rate, timeout=1) as ser:
ser.write(b'\x01')
assert ser.read(1) == b'\x01'
def test_baud_rate(port, baud_rate, test_length):
timeout = ((1 / (baud_rate / 8)) * test_length) * 1.5
bytes_to_write = bytearray(range(0x100))
bytes_to_write = bytes_to_write * int(ceil((test_length / 0x100)))
bytes_to_write = bytes_to_write[:test_length]
pc_to_device = True
device_to_pc = True
with serial_without_dtr(port, baud_rate, timeout=timeout) as ser:
try:
ser.write(bytes_to_write)
except serial.SerialTimeoutException:
pc_to_device = False
try:
received_bytes = ser.read(test_length)
except serial.SerialTimeoutException:
device_to_pc = False
else:
i = 0
for n in received_bytes:
if n != i:
device_to_pc = False
break
i += 1
i %= 0x100
with serial_without_dtr(port, SAFE_BAUD_RATE, timeout=1) as ser:
ser.write(b'\x00')
error_code = ser.read(1)[0]
pc_to_device = error_code == 0
return Capability(pc_to_device, device_to_pc)
def main(*args):
SCRIPT_NAME = os.path.split(__file__)[-1]
try:
port = args[0]
except IndexError:
print("Usage: {} <serial port>".format(SCRIPT_NAME))
print("Examples:")
print(" {} /dev/ttyUSB0".format(SCRIPT_NAME))
print(" {} COM5".format(SCRIPT_NAME))
print()
print("Currently available serial ports:")
for port in sorted(comports()):
description = port.description if port.description \
and port.description != 'n/a' else None
if description is not None:
parenthetical_index = description.find(' ({})'.format(port.device))
if parenthetical_index != -1:
description = description[:parenthetical_index]
print(" {}{}".format(
port.device,
" ({})".format(description) if description else ''
))
return 1
if not single_exchange_works(port, SAFE_BAUD_RATE):
print("The \"known working\" baud rate of {} does not work, so tests cannot be performed.".format(SAFE_BAUD_RATE), file=sys.stderr)
return 1
send_start_command(port, SAFE_BAUD_RATE)
for rate in BAUD_RATES:
# Divided by two since the test is done in both directions.
test_length = (TEST_DURATION_TARGET / 2) / (1 / (rate / 8))
test_length = int(ceil(test_length))
try:
print("{} baud: ".format(rate), end='')
sys.stdout.flush()
pc_to_device, device_to_pc = test_baud_rate(port, rate, test_length)
if pc_to_device and device_to_pc:
print("OK!")
elif pc_to_device and not device_to_pc:
print("Error receiving from device.")
elif not pc_to_device and device_to_pc:
print("Error transmitting to device.")
else:
print("Couldn't transmit either way.")
except OSError:
print("Could not open port {}. It may be in use... or just non-existent.".format(port), file=sys.stderr)
return 0
if __name__ == '__main__':
sys.exit(main(*sys.argv[1:]))
pyserial >= 3.4, < 4.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment