This article first appeared in Make: Volume 41.

This article first appeared in Make: Volume 41.

A microcontroller is a self-contained, but very limited computer — halfway between a computer and a component.

The top reasons to integrate a microcontroller into your projects are connectivity and interactivity, and one easy way to get your microcontroller talking with the outside world is standard asynchronous serial I/O. Many devices can communicate this way, from wi-fi routers to GPS units to your desktop or laptop computer. Getting comfortable with serial I/O makes debugging your AVR programs much easier because the AVR can finally talk to you, opening up a huge opportunity for awesome.

In this Skill Builder, we’ll set up two-way communication between an AVR microcontroller and your computer. Your computer will command the AVR to blink an LED, then the AVR will open a web page of your choice in a browser at the push of a breadboarded button using serial I/O.

You’ll need the following software:

Asynchronous Serial Communications — A Quick Technical Overview

Computers like to talk to each other in binary: ones and zeros. A simple way to send these binary bits between devices is to connect them together with a wire, and let a high or low voltage on the wire denote a one or zero. Sending bits one at a time like this is serial communication because the bits are sent in series, one after the other.


For asynchronous serial communication (above), there is no common clock signal between the two devices, so to interpret a stream of voltages, each device needs to know how fast the bits are being sent: the baud rate.

Atmel’s megaAVR family of AVRs (the ATmega series) have a built-in universal synchronous and asynchronous serial receiver/transmitter (USART for short) hardware peripheral that takes care of all of the hard bits — setting and reading voltages on the serial communication lines at just the right times. Using the USART is not hard: We configure the baud rate, turn the transmitter and receiver sections on, and then feed it data. The USART hardware takes care of the rest.


The AVR chips set the baud rate by taking the CPU clock and dividing it down to the right speed. Your main job, in configuring the chip, is to figure out the correct division factor.

The AVR USART hardware samples each bit multiple times to make sure that it’s reading a consistent voltage level. In particular, it has two modes: one where it samples 16 times per bit (“normal” mode) and another where it samples 8 times per bit (“double speed” mode). So you’re going to divide the CPU speed by 16 and 8 times the baud rate respectively, and then use that result to set the USART timing clock divider.

There are two more catches, though. First, you need to subtract 1 from the result. This is because the AVR’s counter starts counting with 0 rather than 1. So if you want the AVR to divide by 4, select 3 for the counter value: 0, 1, 2, 3.

The second catch is tricky, and this is why I think it’s worth doing the math by hand as follows at least once. (The AVR standard library includes utilities that help you set up the baud rate automatically.) The AVR only deals in whole numbers, but unless you’ve chosen your CPU speed to be a multiple of the baud rate, the result of the division above is unlikely to be a round number. So when you round this result off to fit in the AVR, you’re introducing some error in the baud rate. A little error is fine, but too much makes the serial communications unreliable.

Baud Rate Example

Say I’m running the CPU at full speed off the internal CPU clock — at 8MHz — and sampling the default 16 times per bit. Let’s see which of the standard baud rates work well:

8,000,000 / 16 / 9,600 = 52.08   = 0.2%

8,000,000 / 16 / 14,400 = 34.72   = 0.8%

8,000,000 / 16 / 19,200 = 26.04   = 0.2%

8,000,000 / 16 / 28,800 = 17.36   = 2.1%

8,000,000 / 16 / 38,400 = 13.02   = 0.2%

8,000,000 / 16 / 57,600 = 8.68   = 3.7%

There are a few things to notice here. First, 9,600 baud, 19,200 baud, and 38,400 baud all look pretty good — with the same 0.2% error. The 14,400 baud rate isn’t horrible, but around 1% error is pushing it. Rates of 28,800 and 57,600 baud may not work at all with this CPU speed and multiplier combo.

The FTDI Friend, a USB/serial converter. Get it: makershed.com/products/ftdi-friend-v1-0

The FTDI Friend, a USB/serial converter. Get it: makershed.com/products/ftdi-friend-v1-0

Mini Project — Blinking Web-Page-Loader Button

Now you’ll breadboard an AVR, program it with an ISP, and connect it via USB-serial so it can communicate with your computer and launch a web page.

1. Breadboard the ISP and Test Power

TIP: The USBtinyISP must have the front jumper closed to provide power.

TIP: The USBtinyISP must have the front jumper closed to provide power.

This example uses the USBtinyISP to provide 5V to the breadboard. If your ISP doesn’t provide power, you’ll have to add it.

2. Complete the Programming Circuit and Test Communications

Note: If you use a different ISP or AVR chip, you’ll need to adjust the Makefile.

Note: If you use a different ISP or AVR chip, you’ll need to adjust the Makefile.

Add the ATmega and the capacitor, resistor, and LEDs. Attempt to talk to the AVR via your terminal by typing avrdude -c usbtiny -p m168. If you get an error, your wiring is incorrect or your chip is bad. Double-check the connections shown below.

NOTE: The USBtinyISP has two different connectors: a 6-pin and a 10-pin.

NOTE: The USBtinyISP has two different connectors: a 6-pin and a 10-pin.

3. Flash the C Code to the AVR

In terminal, go to serialMiniproj, then compile and load the C program onto the AVR by typing make. See hardware setup Step 1 and below.


The C Code Explained

At the top, we include some standard AVR libraries: io.h defines the pins and port mnemonics, power.h provides the clock_prescale_set() command that we use to instruct the processor to run at full speed, and delay.h provides _delay_ms() that we use to stall the AVR for a second.

Then we define some convenience macros and the pins that we’ve hooked up the LED and button to.

Skip down to the main() routine and look at the “Initialize serial” section. Calculate the baud rate clock divider as shown in the comments: using the AVR’s built-in 8MHz CPU clock at full speed, “normal” mode serial sampling 16 times per bit, and a target rate of 38,400 baud. Doing the division and rounding we get 12; set the USART baud-rate register (UBRR0) accordingly. Then enable both the transmit and receive sides of the USART hardware.

The while(1) loop is the routine’s event loop, which runs forever. Inside it, we do two things: Check for data on the serial line, and check for a button press. Within the serial receive code, we directly compare the received byte in UDR0 with the letter “L”. As soon as the USART data register is read, the AVR resets the receive-complete flag, RXC0, so that we’ll only execute this section of code once per incoming letter. Finally, if the AVR sees the button pressed, it waits until the USART is ready to send and then loads the data register UDR0, with the “X” we wish to transmit. It waits for a second before checking the button again and sending another command. If you leave this bit out, you end up opening multiple web pages before you can pull your finger off the button.

Serial with Python

Now that we’ve got the AVR speaking serial, it’s time to connect it to your desktop, and then on to the world. For the computer-side scripting, I’ll be using the Python language because it’s simple to learn and lets you do a tremendous amount with very little code.

4. Identify the USB-Serial Port

When using a USB-serial converter, you’ll need to figure out how your computer addresses it, and then replace the address /dev/ttyUSB0 in the Python code below. On Windows, the USB device will register as a COM port. You can find out which one by looking in Device Manager.

On Linux, the USB-serial device will show up as /dev/ttyUSB0 or /dev/ttyACM0. On Mac, open terminal and type ls /dev/tty.usb* to see the path. It will show up as /dev/cu.<something> or /dev/tty.usbserial-<something>.

We import the serial and the built-in webbrowser libraries, and then open up a serial connection object. We specify the serial port (again, yours may be different) and the baud rate. I’ve also included the optional timeout argument. This is important because otherwise the Python routine will just sit there waiting for data to come in over the serial line.

The other trick here is the sp.flush() line — depending on your operating system and what you’ve connected to the serial port in the past, there may already be data sitting around in your computer’s serial buffer. We want to clean that out first, so sp.flush() assures that we start with a clean slate.

The Python code is also structured as an infinitely repeating while loop. Inside, we read a byte. If there is a byte available, the Python code immediately tests it for “X” and then opens a web page. If there is no byte available within the 2-second timeout, the blink-LED command (“L”) is sent.

5. Run the Python Code on Your Computer

Note: To finish up the project, connect the LED (through a 220-ohm current-limiting resistor) to the AVR’s PB0, and connect the pushbutton to the AVR’s PB1 and then to ground.

Note: To finish up the project, connect the LED (through a 220-ohm current-limiting resistor) to the AVR’s PB0, and connect the pushbutton to the AVR’s PB1 and then to ground.

You’ll need to connect a USB-serial converter to your AVR circuit and add the LED and pushbutton, as shown in the diagram above. The FTDI cable plugs into your computer’s USB port. For two-way (full-duplex) communication, all that’s required are two signal wires and a common ground between the two devices to reference the communication-line voltages.

To run the Python program, open terminal, navigate to the serialMiniproj directory, and then type python webLauncher.py to run the program.

Then with the circuit powered and the USB-to-serial attached to your computer, push the breadboarded button and launch a web page.

That’s all there is to it! Two-way communication and control of your AVR and hardware through serial, from your desktop or laptop computer.

Expanding on this simple example is where it gets fun. What can you come up with?