Teensy as Benito, at 57600 baud

Submitted by paul on Sun, 2010-05-30 12:10

At the last Monday meetup, Scott mentioned my Teensy-as-Benito code wasn't working with the '328-based Arduino and Dorkboards, which now run at 57600 baud. So this weekend I dusted off my Dorkboard to investigate.

Read on for details, and updated code with a workaround....

The UART (serial port) in these AVR chips generates its baud rate from the main clock, which is usually a very accurate crystal or pretty accurate ceramic resonator. However, it can only divide that clock by integer multiples of 8 or 16. With a 16 MHz clock, you can get close, but not exactly the correct baud rates. Some small error is ok. Usually about 2% to 2.5% is the maximum error before reliability problems occur.

The Arduino bootloader is actually using 58824 baud, which is 16e6 / (17 * 16). That's +2.124% error.

The UART code in Teensyduino, when 57600 baud is requested, uses 57143, which is 16e6 / (35 * 8). That's -0.794% error.

Either can communicate with a FTDI chip or PC, which generates a very accurate 57600 baud rate, but when used together, the total error is too much because Arduino's is 2.1% too fast and Teensy's is 0.8% too slow.

As a workaround, I just added a check for when the PC requests 57600 baud, and instead the baud rate is set to 58824, to exactly match the Arduino bootloader's actual baud rate.

You can download the updated code. I also added code to blink the LED, and I included pre-compiled copies for Teensy 2.0, Teensy 1.0, and Don's Benito, so you can just load the appropriate code onto your board using the Teensy Loader or DFU programmer. Of course, you can customize it as easily as tweaking any Arduino-based project, because it's built with Teensyduino!

Here is the source code:

unsigned long baud = 19200;
HardwareSerial Uart = HardwareSerial();
const int reset_pin = 4;
const int led_pin = 11;  // 11=Teensy 2.0, 6=Teensy 1.0, 16=Benito
const int led_on = HIGH;
const int led_off = LOW;

void setup()
	pinMode(led_pin, OUTPUT);
	digitalWrite(led_pin, led_off);
	digitalWrite(reset_pin, HIGH);
	pinMode(reset_pin, OUTPUT);
	Serial.begin(baud);	// USB, communication to PC or Mac
        Uart.begin(baud);	// UART, communication to Dorkboard

long led_on_time=0;

void loop()
	unsigned char c, dtr;
	static unsigned char prev_dtr = 0;

	if (Serial.available()) {
		c = Serial.read();
		digitalWrite(led_pin, led_on);
		led_on_time = millis();
	if (Uart.available()) {
		c = Uart.read();
		digitalWrite(led_pin, led_on);
		led_on_time = millis();
	dtr = Serial.dtr();
	if (dtr && !prev_dtr) {
		digitalWrite(reset_pin, LOW);
		digitalWrite(reset_pin, HIGH);
	prev_dtr = dtr;
	if (millis() - led_on_time > 3) {
		digitalWrite(led_pin, led_off);
	if (Serial.baud() != baud) {
		baud = Serial.baud();
		if (baud == 57600) {
			// This ugly hack is necessary for talking
			// to the arduino bootloader, which actually
			// communicates at 58824 baud (+2.1% error).
			// Teensyduino will configure the UART for
			// the closest baud rate, which is 57143
			// baud (-0.8% error).  Serial communication
			// can tolerate about 2.5% error, so the
			// combined error is too large.  Simply
			// setting the baud rate to the same as
			// arduino's actual baud rate works.
		} else {

Mon, 2010-05-31 05:01    [Permalink]


If you're using Windows, you'll also need this Serial Installer, which just installs an INF file. Mac OS-X and Linux recognize the serial device without loading anything special. On linux, you'll need udev rules for non-root permissions and to make a name the Arduino IDE can find.

Thu, 2010-06-03 12:04    [Permalink]


I hate baud rate problems, so I wrote an autobauder for my bootloader. (I forget which boot protocol I copied, but it's probably not the Arduino one, because that would be too easy.) It's pretty good at guessing the right baud rate and doesn't take up much code space. Let me know if you'd like to add it to your codebase. Or, if you're a do-it-yourselfer, my bootloader method is this: set WDT to max timeout (around 2.2s), set up a timer to count as fast as possible, and hang out waiting for Rx to go low. If it doesn't, then the WDT will fire and I use that reset to jump to the beginning of the Flash and run whatever is there. If Rx goes low, start the timer. Every time Rx toggles, note the timer value. When the timer rolls over, or after a few Rx transitions, analyze the recorded values by finding the greatest common divisor. That's the value you plug into the UART.

Yup, this problem probably never would have happened if the Dorkboards and Arduinos used a more sophisticated bootloader like yours (assuming you use the UART's 2X mode so you can get more accurate baud rates). But bootloader development is tricky, and once a certain non-upgradable bootloader gets widely deployed, there's a tremendous resistance to making any changes, no matter how beneficial. The Arduino increase to 57600 was motivated by the slow load times for larger Arduino sketches, especially with newer boards offering more memory. Unfortunately, the protocol itself is very synchronous, so it suffers from software and USB latency, which is particularly bad on Windows. It'd be awesome if someone were to create a bootloader with a fully streaming protocol, and of course code in the bootloader to fully utilize the RWW feature to be receiving the next block into RAM while the previous is writing to flash memory. But unless you're making your manufacturing and selling boards and providing your own software, it's a long and slow process to get others to accept a new bootloader design. Then again, the Arduino developers did switch baud rates when they moved to the larger chip, so maybe there's opportunity for widespread usage of a dramatically faster bootloader on future boards with different chips? Anyway, this code is just for a USB to Serial converter, not the bootloader itself. If you're interested in sharing your bootloader for others to use, you might talk with Don about his Dorkboards, or maybe post to the Arduino developer mail list about their boards. All the boards I make use native USB, so baud rate mismatches aren't an issue.