Nowadays Arduino is the platform of choice for programming AVR
microcontrollers and for good reasons. But there are times when you want to
have full control over what is added to your code.
For example Arduino is enabling by default Timer0 for use in millis function
and other functions and includes some interrupt routines that perhaps your
project is not using and so adding to the code size or perhaps those
interrupts can interfere with your code.Two main alternatives are WinAVR and Atmel Studio. This tutorial covers WinAVR because it's simpler to use for a beginner.
What you will need:
- ATmega328P (used in this tutorial as an example)
- USBTinyISP programmer
- WinAVR software (more on this later)
What you can learn:
-
how to program an AVR microcontroller using an In-System Programmer such as
USBTinyISP and WinAVR
- some bitwise operations for handling the registers
There are two main ways to program a microcontroller:
- ISP (In System Programming) using SPI protocol and a ISP programmer
- With a bootloader using UART protocol and a USB to Serial programmer. Some microcontrollers come with a bootloader already pre-programmed on them.
This tutorial will cover the ISP programming way.
Programming software
There are many tools for programming an AVR microcontroller such as Atmel
Studio, PlatformIO, Eclipse with an AVR plugin, etc but the simplest and
light weight solution that I found is using WinAVR.
Programming hardware
Apart from development software there is also the need of a hardware
programmer that the software uses to communicate with the microcontroller
and upload the code to it. Searching online for 'avr programmer'
reveals lots of options. The most popular I believe is the
USBTinyISP and is very cheap. There is also Atmel-ICE from Atmel. A
bit more expensive but it has the benefit of being able to debug and see in
real time what happens inside the microcontroller.
Getting started with WinAVR
WinAVR is a free suite of executable open source software development tools
for the Atmel AVR series for Windows. WinAVR contains all the tools for
developing on the AVR such as avr-gcc compiler, avrdude for
uploading the code
and avr-gdb for debugging.
To download WinAVR visit the project homepage at
http://winavr.sourceforge.net and in the download section you will find the link
https://sourceforge.net/projects/winavr/files
and on that page click on download the latest version. Some users reported that the installation affected their system PATH. All I can say is that I have installed WinAVR on Windows 10 many times and didn't encounter any problem with it.
Updating WinAVR to the latest AVR-GCC and AVRDude
Scrolling down you should find a link called 'AVR 8-bit Toolchain v3.62 – Windows' which is a zip file. Now in that zip file there should be a folder containing other folders such as 'avr, bin, doc, lib, etc...' Select all these folders and drag them where WinAVR is installed and replace the existing ones. By default WinAVR is installed on C:\WinAVR-20100110.
Now for AVRDude follow this link http://download.savannah.gnu.org/releases/avrdude and download 'avrdude-6.3-mingw32.zip' or newer version then extract the two files from the AVRDude zip file to C:\WinAVR-20100110\bin.
After installation search on windows start for WinAVR and you should see
Programmer's Notepad app.
Programmer's Notepad does everything for you. Is calling the make utility,
which executes your makefile, which in turn calls the compiler, linker, and
other utilities used to build your software. You just need to write the code
and click Make then Program.
Starting first WinAVR project
Inside Programmer's Notepad click File -> New ->
Project. Choose a name for the project then navigate to a folder
where you want to put the project files. I named mine Blink LED.
Now you should see the project in the left side panel. Here you can also
make/rename a project group.
Now to add a file first add some code and save the file as main.c in
the folder where you saved the project file. The name must be
main and the extension can be .c for C or .cpp for C++ languages.
Bellow is a code template that I made for my future projects.
/*************************************************************
INCLUDES
**************************************************************/
#include <avr io.h>
#include <util delay.h>
#include <avr interrupt.h>
/*************************************************************
DEFINES
**************************************************************/
/*************************************************************
GLOBAL VARIABLES
**************************************************************/
/*************************************************************
FUNCTION PROTOTYPES
**************************************************************/
/*************************************************************
MAIN FUNCTION
**************************************************************/
int main(void){
while(1){
}
}
}
/*************************************************************
FUNCTIONS
**************************************************************/
/*************************************************************
ISR Handlers
**************************************************************/
By right clicking on the project name use Add Files to add the main.c file
to the project.
To display the line numbers go to View -> Line Numbers.
Make file
Now it is time to setup the make file. This file tells WinAVR what
microcontroller do we use, what CPU frequency, what programmer, on
what port, etc. For this, search like before for WinAVR but this time open
MFile [WinAVR]. Then select Makefile -> MCU type ->
ATmega -> atmega328p
From Port select usb. Then on the bottom click
Enable Editing of Makefile. Now from Programmer choose your
programmer. If you use USBTinyISP and it doesn't appear in the list simply
pick a random one and then change it with usbtiny.
The last setting is the CPU frequency. Scroll up in the file until you see
F_CPU and put your CPU frequency. For ATmega328 use 8000000 which
means 8MHz - 8 with 6 zeros or 16000000 if you have a 16MHz crystal.
Finally save the file as Makefile (without any extension) inside the
project folder. Later it can be open and edited using Programmers
Notepad.
Learning AVR programming by blinking a LED using ATmega328P
On page 12 you can find the pinout. Say you want to connect the LED to pin
28 of the 28 PDIP package. Near the pin number 28, PC5 indicates that this
pin belongs to port C and has the port pin number 5. By looking at the other
pins we notice that there are 3 ports: B, C and D each one having 8 pins (0
to 7) except port C that has only from 0 to 6. Inside the parenthesis are shown what other functions the pin can have, like ADC for example (Analog to
Digital Converter).
To setup the pins we use the defines
#define LED_DDR DDRC
#define LED_PORT PORTC
#define LED_PIN PC5
After #define any name can be used. Then press TAB a few times to
make the lines look nice and readable and then write the values.
DDRx stands for Data Direction Register and selects the direction of
the pin - input or output. You want it to be an output when driving an LED
or transistor and an input when reading a button press. The x after DDR is
the port and in our case is C.
*A register in an 8 bit microcontroller like this one is just a set of 8
bits that holds a number and the microcontroller uses the bit values to
configures itself.
Configuring ATmega328 port pins - this can replace Arduino digitalWrite to increase the speed
Configuring a pin as a digital output
DDRC |= 1 << PC5
Let's analyze the above line. Somewhere in the avr_io.h included file, PC5
is associated with the value 5 and so PC5 will be replaced with 5. So is the
case with the defines. When we write
LED_DDR |= 1 << LED_PIN
the pre-processor will replace LED_DDR with DDRC and LED_PIN with PC5.
<< is called the left shift and makes part of the bitwise
operations. 1 << 5 is saying move the binary representation of 1, 5
times to the left.
In binary 1 is 0b00000001 and after it has been shifted 5 times to the left
is 0b00100000
We can also write it like this:
The 8 bits represents our DDRC register
and each bit is the pin.
Pin 0 is 0b00000001
Pin 1 is 0b00000010
Pin 2 is 0b00000100
Pin 3 is 0b00001000
Pin 4 is 0b00010000
Pin 5 is 0b00100000
Pin 6 is 0b01000000
Pin 7 is 0b10000000
By putting 1 on that bit position we set the DDR (direction) of the pin to
be an output. Leaving it or setting it to 0 will make it as an input.
So what is with the | symbol? It is called a bitwise or operator.
0b00100000 | 0b00000010 = 0b00100010
because as the name suggests
0 or 1 = 1
1 or 1 = 1
0 or 0 = 0
In a bitwise operation two bytes are compared against each other bit by bit.
Notice the colors: red (bit 0) is compared to the other red, blue (bit 1),
green (bit 2) and so on.
The purpose of the or | operator is to change our desired pin but leave the
others the same as before.
If we have some other pin controlling a motor for example PC1 and we change
pin 5 (PC5)
DDRC before is 0b00000010 - PC1 is 1 (output)
DDRC |= 1 << PC5
DDRC after is 0b00100010
Notice that the use of the or operator, left the PC1 bit unchanged.
The browser's search bar can also be used without pressing enter.
Setting a digital output pin HIGH
After setting the pin as an output, let's make it HIGH to drive the LED.
PORTC |= 1 << PC5
This leaves the other pins unchanged. To make only this pin high and the
rest low (0) we could write:
PORTC = 1 << PC5
Setting a digital output pin LOW
PORTC &= ~(1 << PC5)
Now the pin is connected to ground.
Configuring a pin as a digital input
As before but instead of 1 we write 0 to the specific pin
DDRC &= ~(1 << PC5)
~ is the not operator so 0b00100000 becomes 0b11011111
DDRC before is 0b00100000
DDRC &= 0b11011111 (after not operation)
DDRC after is 0b00000000
because with & bitwise operator both values must be 1
1 and 1 is 1
1 and 0 is 0
0 and 1 is 0
When a pin is set as an input and we write this
PORTC |= 1 << PC5
then the internal pull-up resistor is activated. Now we could add a push
button between the pin and ground and when the button is pressed the pin
will be pulled to ground and when the button is not pressed the pin will be
high due to the pull-up resistor. This is how we can read if a button is
pressed.
if(PINC & (1 << PIN5))
PINC represents all the pins of port C and by some bitwise operations we
check only if pin 5 in 1 or 0.
Toggling a pin
If the pin is low (0) will be made high (1) and vice versa.
In summary to set a bit in a register to 1 use
and to set a bit to 0 use
Another way is to use the _BV() macro function that means bit value.
So the lines above can be replaced by
DDRC |= _BV(PC5)
DDRC &= ~_BV(PC5)
Now finally let's blink that LED with the following code:
int main(void){
// Set the pin as output
LED_DDR |= 1<<LED_PIN;
// Set the pin HIGH
LED_PORT |= 1<<LED_PIN;
while(1){
// Toggle the pin
LED_PORT ^= 1 << LED_PIN;
// Wait 500 milliseconds
_delay_ms(500);
}
}
To compile the code click
Tools ->
Make All.
If the WinAVR wasn't updated using the toolchain from Atmel then the following error message should appear:
'fatal error: opening dependency file .dep/main.o.d: no such file or
directory'
To fix it visit this forum https://www.avrfreaks.net/forum/windows-81-compilation-error?page=all
and you can find a link to a .dll file. Copy this file in the WinAVR
installation folder -> utils -> bin and replace the existing file. Now
it should compile without errors.
To upload the code to the microcontroller click Tools ->
Program. But first lets see how to connect the microcontroller to the
programmer.
Connecting USBTinyISP to ATmega328P AVR
USBTinyISP has 2 headers and each one can be used but I prefer the small one.
In my case the pin headers are mirrored so you should check with a multimeter
first with the power of.
The corresponding pins on the microcontroller can be found in the datasheet.
MOSI - master out, slave in
MISO - master in, slave out
SCK - system clock
RST - reset
This protocol of communication is called SPI (Serial Peripheral Interface)
.
A 10k resistor is needed from VCC to the reset pin of the microcontroller to
keep it high. If the reset pin is pulled to ground then the microcontroller
will be kept in reset mode.
On the USBTinyISP there is a yellow jumper. If removed the programmer will not
provide 5V power to the microcontroller (MCU) and you need to provide power
from other source but be sure to have a common ground. This is useful when you
want to power the micro with 3.3V for example.
Installing drivers for USBTinyISP
The drivers for USBTinyISP can be found on Adafruit website here https://learn.adafruit.com/usbtinyisp/drivers. The install includes drivers for some other programmers. Check the box that has USBtinyISP at the end.
Now if everything is connected properly you should be able to program the
micro using Tools - > Program.
Having a LED blink using the delay function is useful for checking if the
F_CPU is set correctly. If not the LED will blink either to slow or too fast.
ATmega328 comes from the factory with their cpu clock divided by 8. In
the datasheet you will see the term prescaler. The CPU clock can be divided
with a prescaler of 1 (no prescaler), divided by 8, 16... The micro has an
internal RC oscillator at 8MHz and has a fuse set to divide this by 8 so the
actual speed is 1MHz. You could either change the speed in the makefile to
1000000 or change the fuse and set the CPU divider to 1 and keep the 8MHz
specified in the makefile.
There is also the option of connecting a 16MHz crystal oscillator to the
microcontroller and get a more precise clock. For most applications the
internal RC oscillator is just fine but don't build a clock with that.
AVR Fuses
There are 3 bytes stored in a special area of flash memory called fuse bits.
These are Low, High and Extended fuse bytes. Setting some of this bits wrong
can make the micro unusable and will be needed a special high voltage
programmer to make it work again. For this reason there are fuse calculators
and as long as you are careful what options you select it will be fine.
With the first selection menu the source and type for the CPU clock can be
selected. The Clock Startup can be either of the following: 14CK + 0 ms, 14CK
+ 4 ms, 14CK + 65 ms.
The Startup Time is just how long it take for the clock source to stabilize
from when power is first applied. Always go with the longest setting 14CK +
65ms unless you know for a fact your clock source needs less time and 65ms is
too long to wait.
Clock Output
Checking Clock output on PORTB0 will output a square wave with the clock
frequency at pin PB0 and is useful when you want to debug the clock rate or to
drive other chips.
Clock Divide
This divides the clock by 8 and it's the default factory value. You may want
to uncheck this to get the full speed. Although on battery powered devices
the slower the clock the less power is consumed.
Reset Disable
Warning. This fuse turns the reset pin into a normal pin and after doing
this the chip cannot be programmed anymore.
Brown-out Detect (BOD)
When the voltage will drop beneath the specified BOD voltage the micro
will turn off until the voltage returns to normal. This must be tuned on
if the EEPROM memory is used to prevent write errors. The chip has a
lower voltage limit at which it can work reliably and beneath that
voltage the chip can behave erratically, erasing or overwriting the RAM
and EEPROM.
Preserve EEPROM
If this is checked the EEPROM will not be erased during programming.
Here usually settings are kept so is a good idea to check this to
preserve the settings
To calculate the fuses click Apply feature settings. I don't
recommend using the Manual fuse bits configuration unless you
know what you are doing.
Burning the fuses
To program the fuses open a command prompt window and paste
avrdude -c usbtiny -p atmega328p -U lfuse:w:<0xHH>:m
avrdude -c usbtiny -p atmega328p -U hfuse:w:<0xHH>:m
avrdude -c usbtiny -p atmega328p -U efuse:w:<0xHH>:m
Replace 0xHH with the values from the fuse calculator.
Further reading