I am not a professional, nor am I particularly well versed in embedded programming or Atmel products, including AVR. These materials started as personal notes that I curbed into instructive materials in order to be shared with others because I found some information hard to collect when it’s spread about online in the form of answers to questions you might not have been asking.
This is a presentation of the information in a way that I felt would be most helpful and efficient in order to learn, remember, and reference. Be warned, there may be misinterpretations, inaccuracies, and flat-out incorrect depictions of some the elements attempting to be conveyed in these materials. If something doesn’t work for you, look elsewhere to confirm whether or not these materials checkout. Having said that, I do believe that everything within is accurate.
The Atmel/Microchip, AVR, and Arduino Relationship
AVR is the microcontroller designed by Atmel, now owned by Microchip. It is very popular with both hobbyists and professionals but has seen a massive uptake in education due to the Arduino products and ecosystem. Popular starter Arduino products include the Uno and the Duemilanove, both of which contain Atmel parts. Then there is an incredibly massive market of Arduino “knockoffs” which are cheaper but still contain the same microcontrollers and are therefore compatible.
To be clear, these materials are not for the Arduino platform/ecosystem. They are for learning the Atmel AVR architecture with C/C++. Additionally, these materials will not cover electrical engineering concepts, such as Ohm’s Law, it is assumed that you already know enough to follow. With that said, you probably won’t need much because most LEDs can be driven from 5v with anything from a 220ohm to 1kohm resistor.
The easiest way to get a functional Atmel AVR microcontroller that is easily programmed is to buy an Arduino Uno or an off-brand equivalent, some of which can be found for about $5, maybe even less. The reason I recommend an Arduino-like product is that they come preloaded with a bootloader that allows for easy flashing of your program. This does make use of the Arduino bootloader, but only for loading the code and does not get in the way of learning bare AVR (until much later). The bootloader allows us to use a USB cable to program the microcontroller instead of wiring up an in-system programmer.
Do note, this series will focus on the ATmega328p which is found in Arduino Uno and its clones. This does, however, include the Nano and clones as well because they are typically just a different form factor (including the actual ATmega328p microcontroller being smaller) but share the same pinout.
One of the nice things about the AVR products is that there is a large open source community around the use of the products and, as such, there exist a wide variety of libraries to help with utilizing different built-in peripherals as well as desktop tools to make for easier development. The Arduino is an example of these libraries, however, the whole point here is to learn rather than to make use of pre-existing code.
Despite our intention to write our own code, we’d be foolish to try it without using an IDE that is set up for AVR programming. There are a few ways to go about this. Firstly, there is Atmel Studio, which is the suite provided by Atmel (again, now owned by Microchip, but still named Atmel Studio) and contains everything you need to develop for the platform. Shipping with all the header files you’ll need for Atmel products as well as the documentation, you’ll have a hard time finding something easier to just get up and going with.
That is, until you run into the fact that you cannot simply push your code to an Arduino(-like) device without installing an extension first. I am no authority on this matter, so you’ll have to figure that one out on your own if you decide to go that route. Additionally, Atmel studio is only available for Windows, which, while most people use Windows in general, that figure skews greatly when only considering the developer community. Atmel studio can be found on the Atmel/Microchip site.
Secondly, there is WinAVR and the AVR-Dude suite, which are essentially the same thing except that WinAVR is for Windows and the AVR-Dude suite is for Unix and Unix-like (Linux) systems. This option also provides all the AVR header files, but allows you to use whichever IDE you want (keeping in mind that they support C and/or C++). The WinAVR/AVR-Dude route is definitely a little more difficult at first glance because of the commands necessary to build and push your project to the hardware, but it will be more beneficial in the long run because your AVR development will be much more flexible with respect to the environment you’re in (Windows vs Mac vs Linux). WinAVR and AVR-Dude installation instructions can be found online (and can very well change every now and then, which is why I’m not including them).
We will be using the WinAVR/AVR-Dude method throughout these materials, however, it won’t be mentioned every time we build a project.
Introduction to Low-level Programming
We will be using C to program our AVR microcontrollers. If you don’t know C, this is not the place to learn it. Come back when you’ve familiarized yourself with the basics as well as have learned binary and bitwise operations.
When developing a program on a computer, you have access only to the memory granted to you by the operating system. In a microcontroller, there often is no operating system and the result is that you have access to all memory. This allows for great flexibility, no segfaults, but also the ability to corrupt memory or alter behavior by writing to the incorrect location in memory. Luckily, this is made easier with the amazingly obvious register names (I’m being sarcastic about register names being obvious, but more about registers in a bit).
On a computing platform with an operating system, that is, any laptop or desktop, if you try to access memory in a location that is not ‘owned’ by your program’s process ID, the program will be denied access and will terminate with a segfault error. This type of error is common when a regular variable is passed as a pointer or if a piece of information is written to a variable that is too small (known as a buffer overflow). When this type of situation occurs on a microcontroller, it just assumes that’s what you meant to do.
Microcontrollers come in all shapes and sizes, but that’s irrelevant to the people programming them because it’s what’s on the inside that counts. Anyone familiar with computers knows the difference between RAM and storage, anyone that knows a little more might know about cache on the CPUs. Microcontrollers, though, are not designed with the same principles in mind as ‘computer’ CPUs.
Unlike ‘computer’ CPUs, a microcontroller (which is/contains a CPU) does not require RAM and program storage (HDD/SSD(/CD-ROM? please don’t)) because it already has it built-in internally, many microcontrollers, including the ATmega328p, have general purpose storage built-in as well. But, while ‘computer’ CPUs also have registers (many, many, more than microcontrollers), we don’t have to concern ourselves with those unless we’re writing an operating system.
When creating a memory map all we have to do is look at the memory-mapped registers (this is the proper term for a CPU register that is accessible by memory addresses), program storage, and RAM, then arrange them according to where they occur in the memory address-space. You can think of this as street addresses. Imagine Main St has an industrial area from address 0 Main St to address 99 Main St, then from 100 Main St to 199 Main St there are office buildings, then from 200 Main St to 299 Main St are houses. The addresses in the AVR architecture (and most computer architectures) are byte-based, meaning that for every byte, there is an address. That doesn’t mean that a variable cannot occupy more than one byte, only that the variable starts at a particular byte address and then occupies all addresses that fall under the variable.
A register is a piece of electronics that can hold data. All computer memory (not to be confused with storage) is a register of some sort. A CPU register is different, though. These terms are used interchangeably in embedded programming, but the distinction can be justified in different contexts, primarily software vs hardware.
You can think of a register as any other variable, except that they are also used by the hardware itself to determine how the hardware will behave. Because memory is addressed as bytes (and not bits, or nibbles), and because it would be wasteful to use a whole byte for a single Boolean value (true or false / 0 or 1), a register will contain multiple relevant values, sometimes a single value will use multiple bits, but not the whole byte.
An example of a register is the SREG register, another is PORTA which corresponds to a group of eight pins on the microcontroller and controls whether or not each pin is a logic high or a logic low (1 or 0). It stands for Status Register. Why Atmel engineers decided to use one letter from ‘status’ and three from ‘register’, despite this asymmetric abbreviation, it remains one of the more obvious and more memorable of the register names. So, if you have difficulty committing things to memory, then you might as well give up. I’m kidding, don’t give up, this is what data sheets and documentation are for. There are many registers and you will most certainly have difficulty remembering them all (and we certainly won’t even cover them all).
The takeaway here is that registers are just semi-special locations in memory that the hardware also uses to know how it should behave and that these locations also have names. We’ll be covering many registers over the course of these materials as they’re needed.
Too much theory and not enough programming makes for a dull learning experience, so stay tuned for the next section. But let’s make a pitstop and grab the manual first, because it will be easier to save now rather than search for it and only find the product summary datasheet and be confused as to why yours only has like 30 pages while everyone else’ has over 600 (by the way, the manual is over 600 pages). I recommend using a PDF reader that can pick up on headings, chapters, links, and other features of the PDF document, it will make navigation much easier. Adobe Reader does a good job in this respect, but it’s unfortunately not available on Linux.