Blinky on the ATtiny861
Blinky is the most basic program in the embedded world: blink an LED. This article will focus on using either Eclipse IDE or Atmel Studio to program the chip, as well as a few programming concepts specific to microcontrollers (or at least uncommon elsewhere that may be unfamiliar even to programming veterans).
Eclipse IDE vs. Atmel Studio
As with most <this vs. <that>, the answer is "it depends" and it comes down to personal preference. Try them both and decide for yourself. I prefer Eclipse, in general, but it's difficult to setup for this and strictly for programming chips while Atmel Studio contains simulators, debuggers, and more. Depending on your programmer, it may not be possible to use both interchangably, or even at all. Atmel Studio does not support USBtiny programmers and AVR ISP mkII programmers will only work with one IDE at a time due to driver issues (you can use Zadig to switch, libusb for Eclipse and WinUSB for Atmel Studio). Before continuing, get one or both IDEs up and running using the following articles.
C, C++, or Assembly
This, actually, has a definitive answer, at least for now. Assembly is just impractical (though, breaking into small blocks of ASM inside of the other languages may be necessary). C++ can be used but is often scoffed at and is a religious war as heated as they come in this industry. At the moment, it seems that Eclipse's AVR plugin doesn't support C++ out of the box anyway. C is our champion by default due to acceptability, more available examples, community support, and ease of development.
Everything from here on out assumes you have a working knowledge of C, including pointers, macros, structs, and enums. Bitwise math is in abundance.
Here are a few aspects of the code specific to embedded programming. They'll make more sense after reading the code.
- The main function receives no arguments.
- The main function should never reach the end.
- Registers are used to control the hardware's pins and peripherals or to interact with them. These look like, and are treated as, variables but are macros mapped to a special memory location. Many use different bits or bit ranges to configure one or more parts. Bit math is used frequently to read or manipulate them. Below are three registers used for general I/O pins. Each is suffixed with a letter, declaring the port (or pin group), with each bit modifying/reading its respective pin.
- DDR registers, or data direction registers, control whether pins are inputs or outputs: ones for outputs and zeroes for inputs.
- PORT registers are used to modify pin states.
- PIN registers are used to read pin states.
Connect port A, pin 7, or PA7 on the pinout, to ground using an LED and resistor, in series. The resistor value depends on the LED, but a 1kΩ resistor is probably a reasonable guess and there's a very wide margin for error (a resistor isn't even required, but is highly advised). A resistor too high will decrease the LED brigthness considerably but it should still be visible; a resistor too small (or no resistor) may cause the LED to shine too brightly, heating up and burning out eventually.
- The longer terminal coming from the LED usually goes towards the chip. To determine which way it goes, briefly connect it between power and ground and see if it lights up.
- Which side of the LED the resistor is on doesn't matter, choose whichever is easier to wire up.
If your chosen pin is different, change the registers accordingly.
- "avr/io.h" contains the port registers
- volatile is far more common in the embedded world than it is in "normal" software development. It tells the compiler that a variable's value can be modified outside of its current code. Without it, the compiler may optimize away (i.e.: remove) the entire loop used for sleeping, understanding that it's not accomplishing anything.
- "avr/io.h" also contains an alias, _BV (for Bit Value), to bit shift a one to correspond to pin numbers. It's done at compile-time, so there's no run-time overhead while improving readability.
- "util/delay.h" contains _delay_us(double), and _delay_ms(double) for sleeping a predictable amount of time, regardless of chip speed. These functions take microseconds (u often stands for the Greek mu, which stands for micro) and milliseconds, respectively.
- If you run this code, it may not actually sleep for 125 milliseconds. On many (most, or all?), new, fresh-from-the-factory chips, it'll actually sleep for a full second. This will be tackled in the next article of this series.
By now, we should have our first AVR chip all wired up and a very basic program written to it, even if pointless or flawed. Next, we'll learn how to configure the chip itself at write-time, modifying how the chip works instead of just pins and peripherals.