Tuesday, February 5, 2013

An Intro to Hardware Hacking for CS folk: Part 3, Digital I/O

The previous post discussed how the process works to get some code running on a microcontroller. This post ought to be a bit more productive, as it will discuss how to actually do some stuff with that code. All microcontroller software has something to do with input and output (I/O), so let's first discuss what digital I/O is.

What constitutes I/O?

Since we are working with electronic devices, I/O is all about the flow of electrons. When we discuss that in the context of circuits, there are two quantities at play: voltage and current. Physics class tells us that voltage describes the potential energy of the moving electrons, and current describes their quantity. In hardware hacking, voltage is how we measure a signal, and current is what lets us know when things may or may not blow up :) The reason for this distinction is that the digital devices we work with, like microcontrollers, are built out of transistors. Transistors on the most basic terms are voltage-controlled switches (some gifs to get the point across),  so it is much easier for such devices to make decisions based off the voltage of an incoming signal. Since a switch is either closed or open, on or off, we can refer to binary or digital I/O as signals that have only two voltage states, each either leading to a switch closing or opening. Since voltage, like all potential energy, is measured as a difference between a high value and a low value, we should make things easy and force all of our signals to be at the same high and low values. The low value is called ground, and the high value is called Vcc.

The microcontroller itself requires a supply of current and a voltage drop across it to function. For the Arduino, this is supplied to us over USB. USB has a 5V drop (Vcc = 5V), and the Arduino exposes this current source through some pins. Here they are:

Arduino Pinout image, source http://arduino.cc/en/Reference/Board
Arduino Pinout, source
The 5V Vcc and ground reference pins are at the bottom of the pinout diagram. At the top in green are the digital I/O pins. These pins have two modes (have a guess, one starts with I and the other with O). In the input mode a transistor switches on or off to indicate the voltage drop across the pin, and that state is exposed to software. In output mode, we can use transistors to make the pin either a current source (high voltage = Vcc) or a current sink (pin connected to ground). Let's see how we can manipulate these pins with software.

Memory-Mapped I/O

Microcontrollers strive to be uncomplicated devices. I/O presents an interesting challenge in how to easily present it to the C programmer. A convenient way is to use existing paradigms. Since all programs read and write memory, why don't we extend that concept to the I/O pins? A portion of the address space is mapped to the control of the I/O pins. We can write to these bytes (called registers) as if they were memory, but behind the scenes the address and data lines are routed straight to circuits controlling the pins. That's the beauty of memory-mapped I/O.

Example Time


Alright already! Let's finally see this memory-mapped I/O work in a program to make an LED (light-emitting diode) blink. First, we need a circuit. Let's hook up an LED to pin 13 on the Arduino. Here's an image showing this:

Arduino Blink circuit, image from http://arduino.cc/en/Tutorial/blink
Arduino Blink Circuit image, source
More professionally, we draw circuits as a schematic, like below:

Arduino Blink Circuit schematic, image from http://arduino.cc/en/Tutorial/blink
Arduino Blink Circuit schematic, source
If we trace the circuit, we see that there is a direct connection to the Arduino ground pin, indicating that it must be the current sink. Therefore, we must be using digital pin 13 (D13) as the current source. Another point that enforces this is that LEDs only work in one direction, i.e. one particular leg (the longer one) must have a higher potential than the other. The schematic shows this with the LED symbol, which kinda looks like an arrow. The second important piece to notice is the resistor. Without it, the current would be too high, and as was stated before, stuff would blow up (although in this case, there sadly won't be explosions; the LED will just get really hot and then fizzle out). How do we know what resistor value to use? The datasheet. If you bought LEDs from Sparkfun, their product page you would have gone to links to an all-knowing document called the datasheet that gives (ideally, sadly not always reality) all public knowledge about the product. Think of it as the API for the hardware at hand. This document tells us that the LED requires a forward voltage of 1.8-2.2V (it's a diode, so it needs that minimum voltage for the electrons to move across the junction) and a maximum sustainable forward current of 20mA. We need some resistor to make sure that current limit is not broken. Since there are no branches, the current through the resistor is the same as through the LED, so we can use Ohm's Law 
(V = IR). The voltage drop across the resistor is 5-1.8V at maximum = 3.2V, the current max is 20mA, leaving Rmin = 160Ω. The 220Ω resistor will do just nicely then.

Now that the circuit is ready, we can run the code. Without further ado, here it is:


/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.
 
  This example code is in the public domain.
 */
 
// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);     
}

// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);               // wait for a second
}

We've discussed almost all of the concepts in this program. The setup() routine initializes our digital I/O pin as an output pin, so we can make it a voltage source. The loop() routine continuously oscillates the voltage between Vcc and gnd to turn the LED on and off. The only part we've missed is the delay(), but that will be saved for another post.

For better or worse though, the Arduino libraries abstract away much of the I/O concepts under the hood. To see the memory-mapped I/O in action, we need to see the source of the Arduino libraries. Thankfully, they're open. We can see the guts of pinMode() and digitalWrite() in wiring_digital.c, and like most professional C program guts, it isn't the prettiest. Arduino uses a few abstraction layers to hide the actual memory address of the registers. One is the concept of the "pin #." The Arduino software uses tables stored in code memory to map the pin # (in our case, 13) to a port (an I/O pin group) and a pin on the ATmega microcontroller. We can see these actual ports and pins in the microcontroller datasheet, or in Arduino's handy mapping image. Pin #13 is actually pin PB5, a general-purpose I/O pin (commonly referred to as GPIO) resident on Port B. What pinMode() really does then is write a 1 to the bit 6 in the data direction register DDRB through the address 0x24 (see page 426) to make PB5 an output pin. The function digitalWrite() then writes either a 1 or 0 accordingly to bit 6 of the data register PORTB through the address 0x25 to set the voltage on the pin. The Arduino abstraction layers are wonderfully nice at hiding all of this. If you ever use another microcontroller platform though, or run into some performance troubles, remember that the I/O registers are always there for you.

Wrap-up

"Blink" is only the beginnings of working with digital I/O, but it is one of the best foundations. From here, can you write a program that only blinks when a switch or button across another I/O pin is hit? Or hook up a buzzer to an output pin and toggle fast enough to output a song? I'll admit I've done a horrific rendition of "I Believe I Can Fly" this way. There are many other facets of I/O to cover, such as communications protocols, analog signals, clocked signals, and interrupt techniques, and I hope this has been a good start so far.

Monday, January 28, 2013

An Intro to Hardware Hacking for CS folk: Part 2, How the code works

(N.B. This post tends to be a bit more about how things work instead of how to use them. For Arduino/microcontroller quick starts, see Instructables or Adafruit)

The last post (a really long time ago) talked about what a microcontroller was and its purpose. This post aims to take the next step of introducing how software development works for these devices. I'll begin with what any proper CS blog post should, a language discussion.

What language do I use?


Microcontroller programming is typically done in C or C++, and assembly is not uncommon. There are a few reasons why these lower level languages are de rigeur:
  • Resource consumption - Going back to the previous post on microcontroller specs, a few KB of program memory does not allow for managed runtimes. Usually the only code on the chip are the routines that the programmer developed or explicitly linked against (I say usually because operating systems and bootloaders for microcontrollers do exist, and perhaps I'll discuss them later). Even if the program memory did exist, microcontrollers typically do not have the cycles to spare for garbage collection or handling unchecked runtime exceptions. The goal is to have just enough code to do the job. As microcontrollers gain more hardware resources, the use of languages like C++ grows as the overhead of object-oriented programming can be mitigated by increased developer productivity.
  • Determinism - Microcontrollers are often used in mission critical situations. Couple that with the hardware resource constraints mentioned above, and embedded systems developers become wary of every cycle spent in a routine. Many often used functions are written in assembly to ensure any time or space constraints are met, and that they are met consistently.
  • Portability - While in the PC world software is almost exclusively written for x86, and in the mobile world software is almost exclusively written for ARM, in the embedded devices landscape there are numerous ISAs (AVR, ARM Thumb, m68k, Renesas RX, PIC, TI MSP430, and many many more). Chances are there is a C/C++ compiler available to help keep your software ISA-agnostic.
  • Toolchain - C and C++ undoubtedly have some of the most mature software toolchains. This is especially important for embedded software, where compiler optimizations are a necessary tool for reducing code size but cannot endanger software correctness. On the latter note, there are also many lint tools available for C.
Now that we have some basis for why the code is in asm/C/C++, let's start looking into the actual code itself.

What does the code look like?


At the minimum, it is something like this:

void main()
{
    while( 1 );
}

To readers that are used to writing scripts or non-REPL shell applications, the mandatory infinite loop seems a bit weird. The reason for its existence is simple: there's no other code to execute other than your own. If there wasn't an infinite loop, the program counter would just keep stepping through some undefined or zeroed-out code memory until it hits something ugly (like interrupt vectors, but that's another post down the road). Regardless, more than likely the job of your microcontroller program will be to constantly respond to input. Typically then we can segment the code into two parts: one for setting up the hardware for receiving that sensory input, and the other the loop to continuously process and act on it. Conveniently, the Arduino framework separates this out for us. Here's what a blank Arduino sketch (fancy word for program) would look like:

//// Copied from the Arduino BareMinimum Example

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly: 
  
}

As a teaser, here's an example program showing how those functions could be filled out:

//// Copied from the Arduino DigitalReadSerial Example

/*
  DigitalReadSerial
 Reads a digital input on pin 2, prints the result to the serial monitor 
 
 This example code is in the public domain.
 */

// digital pin 2 has a pushbutton attached to it. Give it a name:
int pushButton = 2;

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  // make the pushbutton's pin an input:
  pinMode(pushButton, INPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  // read the input pin:
  int buttonState = digitalRead(pushButton);
  // print out the state of the button:
  Serial.println(buttonState);
  delay(1);        // delay in between reads for stability
}

There is a lot left to cover though to explain how that program works. One last piece I want to explain in this post is how does the microcontroller load the program in the first place.

Where does the code go?


On a general purpose computer, executing a program is rather complicated. The program has to be loaded from disk, its address space allocated, and its code and globals copied into memory. Since the microcontroller is generally a single purpose device, we don't have to worry about resource allocation. Our principal concern is how to get a binary we compiled on a PC to executing on a microcontroller. Our analogue to a disk is the EEPROM, and to get the code there we need a programmer.

The EEPROM, also called Flash memory since it uses the same floating-gate transistor technology as your SSD or your smartphone storage, is programmable read-only memory that can be erased electrically (ta-da!). In the case of the Arduino Uno, the Atmel AVR ATmega328P microcontroller onboard has 32KB of Flash memory (Atmel distinguishes Flash as the program storage area, and EEPROM as a smaller buffer for the programmer to store non-volatile variables, but the underlying technology should be the same and I'll use the terms interchangeably in this post). 32KB is a surprisingly large amount of space when you have the only program; enough for even autopilot software with GPS support. Since it is non-volatile, cutting the power to the microcontroller will not erase the program. The Flash memory on the ATmega328P is good for 10,000 writes, so feel free to keep "flashing."

To actually write to the EEPROM, a programmer is typically required. This procedure can get complicated, since we have to transfer a binary file from a PC with typically only USB as I/O to an EEPROM using its write commands. If we just had an AVR microcontroller we would have to buy a programmer (an AVR-ISP in this case, since it would use AVR's in-system programming protocol). Luckily, the combination of the Arduino board and software simplifies this process through their bootloader. The Arduino bootloader is a small program permanently resident in EEPROM that runs on microcontroller reset. It receives data from the PC through the Arduino IDE (and through a USB to serial chip, will discuss later), writes it to the EEPROM, and then starts the written program. The mbed microcontroller also has a similar mechanism, and cleverly exposes itself as a USB Mass Storage Device so that you can drag-n-drop the program binary onto the device. Once the EEPROM is loaded, execution starts, and continues ad infinitum (unless you forgot that infinite loop!)

Where do we go from here?


So far this post has been about code, but not what to code. The next post will be about I/O, which will be the basis for all interactions with the microcontroller. Any feedback in the meanwhile is definitely appreciated!