Introduction: AVR Assembler Tutorial 1
I have decided to write a series of tutorials on how to write assembly language programs for the Atmega328p which is the microcontroller used in the Arduino. If people remain interested I will continue to put out one a week or so until I run out of free time or else people stop reading them.
I am running Arch linux and I am working on an atmega328p-pu set up on a breadboard. You can do it the same way as me or you can simply plug an arduino into your computer and work on the microcontroller that way.
We will be writing programs for the 328p like the one that is in most arduino's but you should note that these same programs and techniques will also work for any of the Atmel microcontrollers and later on (if there is interest) we will work with some of the other ones as well. The details of the microcontroller can be found in the Atmel data sheets and the Instruction Set Manual. I am attaching them to this instructable.
Here is what you will need:
1. A breadboard
2. An Arduino, or just the microcontroller
3. A computer running Linux
4. The avra assembler using git: git clone https://github.com/Ro5bert/avra.git or if you are using ubuntu or a debian based system just type "sudo apt install avra" and you will get both the avr assembler and avrdude. HOWEVER, if you get the latest version using github then you will also get all of the necessary include files, in other words it already has the m328Pdef.inc and tn85def.inc files.
5. avrdude http://www.nongnu.org/avrdude/
The complete set of my AVR assembler tutorials can be found here: https://www.instructables.com/id/Command-Line-AVR-T...
Step 1: Construct a Testing Board
You can simply use your arduino and do everything in these tutorials on that if you like. However, since we are talking about coding in assembly language our philosophy is inherently to strip away all the periferals and interact directly with the microcontroller itself. So don't you think it would be more fun to do it that way?
For those of you who agree, you can pull the microcontroller out of your arduino and then start by constructing a "Breadboard Arduino" by following the instructions here: http://arduino.cc/en/Main/Standalone
In the picture I show my set up which consists of two standalone Atmega328p's on a large breadboard (I want to be able to keep the previous tutorial wired and loaded on one microcontroller while working on the next one). I have the power supply set up so that the very top rail is 9V and all of the others are 5V from the voltage regulator. I also use an FT232R breakout board to program the chips. I bought them and put bootloaders on them myself, but if you just pulled one out of an Arduino then it is fine already.
Note that if you are trying this with an ATtiny85 then you can just get the Sparkfun Tiny Programmer here: https://www.sparkfun.com/products/11801# and then simply plug it into the USB port on your computer. You will need to install a bootloader on the Attiny85 first and the easiest way is just to use the Arduino IDE. However, you will need to click on file, and preferences, and then add this New Boards URL: https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json which will enable you to install the bootloader (if your ATtiny85 didn't already come with one.)
Step 2: Install the Assembler and Avrdude
You can now download and install the assembler and avrdude from the links given on the first step of this tutorial. It is likely that if you have already been working with Arduino's then you already have avrdude installed.
After you have avra installed you will notice that there is a subdirectory that comes with it called "sources" and inside that directory are a bunch of include files. These are all of the microcontrollers that you can program with avra. You will notice right away that there is no file for the 328p that we are using here. I have attached one. The file should be called m328Pdef.inc and you should put inside the includes directory or anywhere else you like. We will be including it in our assembly language programs. All this does is give each of the registers in the microcontroller names from the data sheet so that we don't have to use their hexidecimal names. The above include file contains "pragma directives" since it was designed for C and C++ programming. If you get tired of seeing the assembler spit out "ignoring pragma directive" complaints just go into the file and delete or comment out all the lines beginning with #pragma
Okay, now that you have your microcontroller ready, your assembler ready, and your programmer ready, we can write our first program.
Note: If you are using the ATtiny85 instead of the ATmega328P then you need a different include file called tn85def.inc. I will attach it also (note I had to call it tn85def.inc.txt so that Instructables would allow me to upload it.) HOWEVER, if you got the avra assembler from github then you already have both of these files with it. So I recommend getting it and compiling it yourself:
git clone https://github.com/Ro5bert/avra.git
Step 3: Hello World
The goal of this first tutorial is to build the standard first program one writes when learning any new language or exploring any new electronics platform. "Hello World!." In our case we simply want to write an assembly language program, assemble it, and upload it to our microcontroller. The program will cause an LED to turn on. Causing an LED to "blink" like they do for the normal Arduino hello world program is actually a much more complicated program in assembly language and so we won't do that just yet. We are going to write the simplest "bare bones" code with minimal unnecessary fluff.
First connect an LED from PB5 (see the pinout diagram) which is also called Digital Out 13 on an arduino, to a 220 ohm resistor, then to GND. I.e.
PB5 ----> LED ----> R(220 ohm) ----> GND
Now to write the program. Open up your favorite text editor and create a file called "hello.asm"
;hello.asm ; turns on an LED which is connected to PB5 (digital out 13) .include "./m328Pdef.inc" ldi r16,0b00100000 out DDRB,r16 out PortB,r16 Start: rjmp Start
The above is the code. We will go through it line-by-line in a minute, but first lets make sure we can get it working on your device.
After you have created the file, then in a terminal you assemble it as follows:
this will assemble your code and create a file called hello.hex which we can upload it as follows:
avrdude -p m328p -c stk500v1 -b 57600 -P /dev/ttyUSB0 -U flash:w:hello.hex
if you are using a breadboard arduino you will have to push the reset button on the breadboard arduino just before you execute the above command. Note that you may also have to add a sudo in front or execute it as root. Also note that on some arduino's (like the Arduino UNO) you will probably have to change the bitrate to -b 115200 and the port -P /dev/ttyACM0 (if you get an error from avrdude about an invalid device signature just add a -F to the command)
If everything has worked as it should you will now have an LED lit up..... "Hello World!"
If you are using the ATtiny85 then the avrdude command will be:
avrdude -p attiny85 -c usbtiny -U flash:w:hello.hex
Step 4: Hello.asm Line-by-line
To finish this introductory tutorial we will go through the hello.asm program line-by-line to see how it works.
;hello.asm ; turns on an LED which is connected to PB5 (digital out 13)
Everything after a semicolon is ignored by the assembler and hence these first two lines are simply "comments" explaining what the program does.
This line tells the assembler to include the m328Pdef.inc file which you downloaded. You may want to put this in a directory of similar include files and then change the above line to point to it there.
ldi r16, 0b00100000
ldi stands for "load immediate" and tells the assembler to take a working register, r16 in this case, and load a binary number into it, 0b00100000 in this case. The 0b in front says that our number is in binary. If we wanted we could have chosen another base, such as hexidecimal. In that case our number would have been 0x20 which is hexidecimal for 0b00100000. Or we could have used 32 which is base 10 decimal for the same number.
Exercise 1: Try changing the number in the line above to hexidecimal and then to decimal in your code and verify that it still works in each case.
Using binary is simplest though because of the way Ports and Registers work. We will discuss the ports and registers of the atmega328p in more detail in future tutorials but for now I'll just state that we are using r16 as our "working register" meaning that we are just going to use it as a variable that we store numbers in. A "register" is a set of 8 bits. Meaning 8 spots that can either be 0 or 1 (`off' or `on'). When we load the binary number 0b00100000 into the register using the above line we have simply stored that number in the register r16.
This line tells the compiler to copy the contents of the register r16 into the DDRB register. DDRB stands for "Data Direction Register B" and it sets up the "pins" on PortB. On the pinout map for the 328p you can see that there are 8 pins labeled PB0, PB1, ... , PB7. These pins represent the "bits" of "PortB" and when we load the binary number 00100000 into the DDRB register we are saying that we want PB0, PB1, PB2, PB3, PB4, PB6, and PB7 set as INPUT pins since they have 0's in them, and PB5 is set as an OUTPUT pin since we put a 1 in that spot.
Now that we have fixed the directions of the pins we can now set the voltages on them. The above line copies the same binary number from our storage register r16 to PortB. This sets all of the pins to 0 volts except pin PB5 to HIGH which is 5 volts.
Exercise 2: Take a digital multimeter, plug the black lead into ground (GND) and then test each of the pins PB0 through PB7 with the red lead. Are the voltages on each of the pins exactly those corresponding to putting 0b00100000 in PortB? If there are any that aren't, why do you think that is? (see the pin map)
Start: rjmp Start
Finally, the first line above is a "label" which labels a spot in the code. In this case labelling that spot as "Start". The second line says "relative jump to the label Start." The net result is that the computer is placed into an infinite loop that just keeps cycling back to Start. We need this because we cannot have the program just end, or fall off a cliff, the program has to just keep running in order for the light to stay lit.
Exercise 3: Remove the above two lines from your code so that the program falls off a cliff. What happens? You should see something that looks like the traditional "blink" program used by Arduino as their "hello world!". Why do you think it acts this way? (Think about what must happen when the program falls off a cliff...)
Step 5: Conclusion
If you have gotten this far then congratulations! You are now able write assembly code, assemble it, and load it onto your microcontroller.
In this tutorial you have learned how to use the following commands:
ldi hregister, numberloads a number (0-255) into a upper half register (16-31)
out ioregister, registercopies a number from a working register to an I/O register
rjmp labeljumps to the line of the program labeled by "label" (which cannot be further than 204 instructions away -- i.e. relative jump)
Now that these basics are out of the way, we can continue to write more interesting code and more interesting circuits and devices without having to discuss the mechanics of compiling and uploading.
I hope you have enjoyed this introductory tutorial. In the next tutorial we will add another circuit component (a button) and expand our code to include input ports and decisions.