AVR Programming 04: Writing code, etc.
posted Nov 19th 2010 3:00pm by Mike Szczysfiled under: how-to, Microcontrollers
Welcome back to this fourth and final installment of the series. The first three parts should have been enough to get you off the ground, but a few more learning examples wouldn’t hurt. It’s also a good time to discuss some of the other things these little chips can do. Join me after the break to:
- Expand the sample code, adding features to our simple program while I challenge you to write the code yourself.
- Discuss AVR fuse bits, how to use them, and what to watch out for
- Touch on some of the peripherals you’ll come across in these chips
As a grand flourish to the series, I’ve used the example hardware from this final part to build a bicycle tail light. Hopefully this will inspire you to create something much more clever.
Series roadmap:
- AVR Programming 01: Introduction
- AVR Programming 02: The Hardware
- AVR Programming 03: Reading and compiling code
- AVR Programming 04: Writing code
Adding to the Example Hardware
The example code that I’ve been working with on the last two parts of this tutorial is a bit boring. It makes one LED blink on and off at a rate of about 1 Hz. That LED was connected to the pin for PD0, so let’s start out by adding an LED and resistor to the rest of the PORT D pins for a total of 8 LEDs. We should also talk about inputs, so let’s add a switch on PC0. Here’s a schematic showing our changes:
I moved the original LED over to some open space on the right side of the breadboard. I’m connecting the cathode to the ground rail on the bottom, jumping the trench with a resistor, and connecting a jumper from that resistor to the Port D pins on the microcontroller. I organized the LEDs in ascending order from right to left making it easy to address them when writing code:
If you know your resistor color codes you’ll notice that the Brown-Green-Red resistors I’m using are 1.5 kOhms, strangling the current to a tiny trickle for LEDs. Well, I’m using super bright LEDs, and these resistors were the first that I pulled out. They work just fine for prototyping but should be replaced with a correctly calculated value on a finished product.
Next I hooked up a button. Digital inputs on microcontrollers need to have a value of 0V or VCC (input voltage which is 5V in our case). If they don’t have a clear value they are said to be “floating” which can lead to false button readings and other unhappy occurences. We need to set up hardware that will force a value of 0V or 5V at all times. This turns out to be quite simple. By connect the switch from the pin to ground and a resistor from the pin to VCC (called a pull-up resistor) there will always be a very small 5V current trickling into the pin, except when an unrestricted path to ground is created by pressing the button. We don’t even need our own resistor as there’s one inside the microcontroller that we’ll take advantage of. Here’s a schematic showing what this connection, along with the internal pull-up resistor, looks like:
That description is a mouthful but all we’re really doing is placing a button between PC0 and Ground. Pin 23 is PC0 on the ATmega168 and the pin right next to that (pin 22) is GND. I’ve connected a switch accordingly. In the following image please note that Pin 22 is connected with a jumper wire to the ground rail above it, but is obscured by the black wire from the push button:
And finally, I want to make connections to the chip for In-System Programming. I like to do this using a patch board that I created. This lets me use a 10-pin IDC cable for easy connection to my programmer:
That’s it. I plan to use this hardware with several different firmware examples so double-check your wiring and then start writing code.
Writing Code
Time to practice writing your own code. I have come up with four firmware examples ranging in difficulty from “Hello World” to “Damn That’s Slick”. I’ll discuss each of them briefly but along the way you should try to write your own code, using my examples as… examples. The best way to learn to code is to write a small portion of code, let the compiler yell at you for messing up, and then figure out how to fix it.
Blinking all 8 (8led_1hz)
First thing’s first, can you make the example code from Part 2 blink all 8 LEDs instead of just one?
There’s really only two things that you need to change from the original to make this happen. First, when setting up the input/output, make all of the pins on Port D outputs, then turn them all on. Second, when toggling the bits in the Interrupt Service Routine use a bitmask that affects all eight bits.
The source package for this part of the series includes this alteration. Grab a copy of it and look at the 8led_1hz code. In it you’ll find these changes:
1 | DDRD |= 0xFF; //Set PortD pins as an outputs |
2 | PORTD |= 0xFF; //Set PortD pins high to turn on LEDs |
1 | PORTD ^= 0xFF; //Use xor to toggle the LEDs |
As you can see, both portions of code use 0xFF as a bit mask. This is a byte containing all ones, which will manipulate every pin on the registers to which we apply it. Before I had shifted a bit using this:
'1<<0'
It resulted in a bit mask of 0×01, protected the upper seven bits from being changed during register manipulation.
Make the LEDs do something interesting (m168_led_effects)
Now I’m going to take a big step forward in C code difficulty. But I challenge you to develop three different types of LED effects by yourself:
- A binary counter which counts up at 1 bit per second
- A flasher that alternates lighting every other LED
- Larson scanner (a simple one, doesn’t need to use PWM)
You’ll find my example code in the m168_led_effects directory. Here’s some of the new things I’m using in my code:
Definitions: I’m using definitions for common settings and for I/O pins, ports, and direction registers. These are constants that the compiler will replace with appropriate values but they make your job much easier. If you get most of the way into a project and realize you need to change some of the hardware this will make it simple to do. Need to change from Port D to Port C? No problem, change the #define and the rest of the code will still work
Delay: AVR libc has a nice delay utility called delay.h. You can see that I’ve included it at the top of the source file and also written a function called delay_ms(). This is a moderately accurate way to mark the passage of time. The drawback to using this is that you are literally wasting time when the processor could be doing other things. Still, it’s simple and if you’re new to microcontrollers you’ll probably find yourself using this frequently at first.
Also notable in this version of the code is my use of functions to take the complexity out of MAIN. I like to do this when I can to make program flow more readable. If you use descriptive function names it will be easy for others to see how the firmware works just by looking at main. This is also why I comment my code quite a lot. Not just for others, but so I can read it quickly if I come back to it later and don’t remember what I originally wrote the program to do.
Before we move on here’s a quick synopsis of how I solved the three goals:
- When displaying a binary counter at 1Hz I simply start Timer 2 the same way I did for the blinking LED in Part 2 of the tutorial. Each time it fires I don’t toggle the pins, but set the entire port to an 8-bit variable value while incrementing it at the same time. The ++binary_counter increments that value just before it sets Port D. It is crucial that this value be a global variable using the keyword ‘volatile’ because it is changed by both the ISR and in the main loop. If you don’t make it volatile the compiler might optimize the code in a way that disturbs or disrupts the intended functionality.
- Creating an alternating flasher is much the same as toggling a single LED. I set up for the effect by instantiating a variable with every-other bit as 1. When using an exclusive OR operator (XOR) on this value, all of the bits will flip. I could have set up an interrupt with a shorter delay than the 1 Hz interrupt to take care of this but for learning purposes I used a delay instead.
- The Larson scanner is a classic bit of blinky goodness. The core function is to illuminate one LED and sweep it back and forth. To do this I just created a loop to shift the bits, waiting after each change. Once the LED on the end is lit the program leaves the loop and enters another one to shift bits the other way. The same could have been accomplished with a variable that keeps track of which direction the LED is moving, testing during each iteration.
When you’ve read and understood how this code works it is time to get the button up and running.
Make the button do something (m168_led_button)
We brought a button to the party, let’s alter our LED effects so that the button is used to change between the three possibilities. If you’ve never written code for a button input before there’s little chance you’ll be able to pull this off yourself, so open up the code in the m168_led_button folder and lets walk through it.
Debounce: Buttons often register more than one press if not handled correctly, a process called debouncing. There is a hardware fix for this, but you can learn about that on your own time. Recently, I gathered a post full of different debounce code, but the one I almost always use is based on code by [Peter Dannegger]. It relies on several parts:
- Code to start a timer with an overflow interrupt
- An ISR to service the timer overflow, resetting the timer for 10ms interrupts and polling the button pin.
- A bit mask and pin definitions that identify how the buttons are hooked up
- A function used to check if a button press has been registered
- Code to check that function and act when a button has been pressed.
The magic is in the ISR debounce code. It flips bits in a binary counter to register four successive button press readings totalling 40ms. That signals a legitimate button press and when the get_key_press function is called it will return a populated key mask. To help understand how this debounce code works, I have included a code example called button_debounce. This has been slimmed down to include only the code used to debounce. Pressing the button will toggle the LEDs.
During the hardware setup I talked about using the internal pull-up resistors. I have to remember to set those up at the beginning of the program or the input pin will be floating. The datasheet talks about this on page 71. When a pin is set to input using the Data Direction Register, writing a high value to the Port bit for that pin will enable the pull-up resistor. From there the current status of the pin can be grabbed from the appropriate Pin register. Notice the ISR used for debouncing reads KEY_PIN, which is defined as the PINC register at the top of the source code. You don’t have to read the Pin register because the ISR is doing it for you.
My implementation of button debouncing in the m168_led_button code is just fine, but my use of the button is a hack. I should have used a state machine and gotten rid of the delay functions in the code. For simplicity I just littered calls to get_key_press throughout the code whenever I was trapping the program in a loop. I used the detection of a key press to return to main from the function the program is stuck in.
Pick this apart, writing simple code that you understand and slowly you will build the knowledge base necessary to understand this code as a whole.
Creating something useful (m168_bike_light)
I wanted to finish the code writing section with a useful application for our test hardware. Behold, a bicycle tail light. It has a button to scroll through several different red light patterns, and it uses sleep mode to shut off the LEDs and conserve battery power.
I’ve changed the program flow to use a state machine. This is a bit of a juggling act. I use an interrupt to set a flag called ‘timer’. The main loop constantly polls that flag, as well as the button, and acts accordingly. Whenever that flag is set the next step of the LED effect is performed.
Sleep mode is also used in this example. One thing to note: when in sleep mode the chip uses almost no current, conserving batteries. But the linear power regulator still burns away like crazy. For this to be useful the code should be ported to a chip that operates at low voltages. For instance, you could use a tiny13 and two AA batteries without a regulator. Adjustments would need to be made for less pins and corrected LED resistor values, but these are not difficult changes to make. Have a look at the code in the m168_bike_light folder. The comments and your hard-earned AVR knowledge will help you understand how this works. Good luck!
Now I’ll move on to the discuss one of the most important parts of theses microcontrollers:
AVR Fuse Bits
The fuse bits are a set of registers that control some core features of the AVR line of chips. You can think of them as another type of memory, programmed separately from the code that you want to execute.
Read the datasheet
Fuse bits for the ATmega168 are covered starting on page 285 of the datasheet. You should make yourself thoroughly familiar with this information. Incorrectly programming these registers could render your chip useless unless you have a programmer capable of High Voltage Programming (HVP).
There are three fuse bit registers on our chip, the Extended fuse byte, the High fuse byte and the Low fuse byte. All of them use inverse logic, meaning that a ’1′ means the corresponding feature is NOT selected. I start every project with these registers set to the factory default, information I keep in a text file with the factory fuse defaults for all the chips I work with. At the beginning of every project I try to talk to the chip using the ‘-v’ option of AVRdude to make sure the programmer and chip are both working correctly to save time on later debugging. Here are the ATmega168 defaults:
- efuse: 0b11111111 (0xFF)
- hfuse: 0b11011111 (0xDF)
- lfuse: 0b01100010 (0×62)
I’ll touch on most of these features in the next section. But of particular concern are the bits that select the clock source, and the reset disable bit. If you disable the reset pin, by accident or in order to use it as an I/O pin, you will need to use HVP or debugWire to use ISP programming again. If the clock pins are changed you will need the appropriate external clock signal, or HVP for the same reason.
You can program the fuse bits using AVRdude. In fact, there’s an example in the documentation. This command will reset the fuses to the factory settings:
1 | avrdude -c dragon_isp -P usb -p m168 -U efuse:w:0xff:m -U hfuse:w:0xdf:m -U lfuse:w:0x62:m |
AVR Peripherals (A Whirlwind Tour)
Take a whirlwind tour of the features available to you on this chip. This is gonna be quick, but you already have the core skills you need. Just read the datasheet and using the Internet to connect the rest of the dots.
EEPROM memory
Most (if not all) of the AVR chips come with Electronically Erasable Programmable Read Only Memory. This is persistent memory that stores data between resets and when there is no power to the chip. This is where data loggers store information and often contains things like text strings, font data, etc. AVR-GCC will generate an .EEP file at compile time with any EEPROM data that you use in your programs. This needs to be programmed to the chip separately from flash data.
Timers (Regular and Watchdog)
Timers are where it’s at in terms of functionality. They go far beyond simply measuring time, and can be used to wake the chip up from sleep mode, to generate pulse width modulation frequencies, and much more. Some chips have asyncronous timers, like Timer/Counter 2 on the ATmega168, that can use an external clock signal separate from the other timers.
Also not to be missed is the Watchdog timer. These timers can save money, and even lives. They are a hardware timer enabled through the fuse bits that will reset the microcontroller if not handled in software. Why would you want to do that? Because nobody writes perfect code. When using a Watchdog timer you frequently reset its counter during successful code execution. That way if your code ever hangs or gets caught in a loop the Watchdog timer will automatically reset the device, getting you out of a software-caused bind. See what [Jack Ganssle] has to say about them.
Real time counter
I mentioned above that Timer/Counter 2 can be run asynchronously from the rest of the timer/counters. Why is that valuable? One of the uses is as a Real Time Counter (RTC). This works in conjunction with a clock crystal to keep track of the time and date.
Hardware PWM
Continuing with the theme of timer/counter based featured, these chips have hardware-based pulse width modulation. PWM generates a signal between 0V and VCC by turning a pin output on and off frequently. The frequency used, and the duty cycle (ratio of high versus low over one period) are set in the registers and you don’t have to think about it again until you want to change them. This is useful for a slew of things, like dimming an LED, driving a servo motor, or generating sound on a piezo.
ADC
If you want to measure an analog value you need an Analog-to-Digital Converter. Most AVR chips have several of these with varying degrees of precision. This enables you to do things like measure light levels using a photoresistor and reading the value of a potentiometer (using it like a settings knob).
USART
The ATmega168 has a Universal Synchronous and Asynchronous serial Receiver and Transmitter which allows it to communicate in many different ways. This includes serial communications like USB (by taking advantage of the V-USB stack), as well as chip-to-chip communication standards like SPI, I2C, and TWI.
SPI
The AVR family often incorporates Serial Peripheral Interface bus communications protocols into its hardware. The USART on the ATmega168 offers master SPI functionality, used to control other chips that also use the protocol via three connections; two for data one for clock.
I2C/TWI
The USART also offers hardware I2C and Two Wire Interface features. Like SPI these are common chip-to-chip protocols but they use just two wires; one for data and the other for a clock signal.
Analog comparator
The analog comparator uses two input pins to compare analog signals. Based on their relation, the chip can be set to fire interrupts if one changes value compared to the other. The two inputs can be mapped to any of the ADC pins, but only two values can be compared at one time. I’ve never used this feature and I’m basing this description purely on what I’ve read in the datasheet. Sorry!
Lock bits
Any code you write to these chips can be read back and stored (albeit what comes back out is machine code, the C code we’ve been writing can never be reproduced perfectly from what you get off the chip). That can then be used to program other identical chips. But there is a feature called lock bits that can protect that code. Once set, the chip cannot be read, and depending on which bits are set it may not be able to be reprogrammed. That is, until the chip has been erased, which resets these lock bits.
JTAG, debugWire, and High Voltage Programming
In this tutorial we’ve been using In System Programming, but there are a few other ways to program AVR chips. JTAG is a standard hardware debugging (and programming) interface that some chips have, but the ATmega168 does not. Many of these chips can use the debugWire protocol to program and debug with just one wire communicating on the reset pin. Both JTAG and debugWire protocols are configured using the fuse bits.
High Voltage Programming is used to rescue chips that cannot be reached using other programming methods. There are two kinds, High Voltage Parallel Programming, like the ATmega168 uses, or High Voltage Serial Programming which chips with a low-pin count use. If you disable the Reset pin or enable debugWire, or set the clock source incorrectly in the fuse settings, HVPP or HVSP should be able to reset the fuses and rescue the “bricked” chip.
Power and Sleep
Microcontrollers operate so quickly there is often just wasted time as they scroll the infinite loop waiting for an interrupt to happen. If you are operating under battery power this just wastes juice. By using the power saving and sleep modes batteries can last longer. This is accomplished by turning off power hungry peripherals like the ADC, and shutting down the processor when not needed by putting it to sleep. They’re a bit tricky to understand, but often worth your while
Conclusion
That’s it really. I’ve had a great time writing about this. Fiddling with microcontrollers is my favorite hobby and I hope it has become yours as well. These are really very simple concepts that grow in complexity as you pile them atop each other. Just compare the original Part 2 source code with the bicycle tail light code. But that’s the fun of it. This is the inventor’s equivalent of a choose your own adventure novel. So come up with a challenge and see where it takes you!
Follow Me
Resources
Part 4 Firmware package: Github repository
Atmel AVR ATmega168 Datasheet (PDF)
Recent Posts
- Retro adapter for Canon SLR (11/20/2010)
- Beginner Concepts: Powering your projects (11/20/2010)
- AVR Programming 04: Writing code, etc. (11/19/2010)
- Communicating with an LED matrix (11/19/2010)
- Arduino + Java + Joystick (11/19/2010)
Go there...
http://hackaday.com/2010/11/19/avr-programming-04-writing-code-etc/
Don
No comments:
Post a Comment