.. _avr-assembly: Basic AVR assembly language ########################### .. include:: /header.inc .. vim:filetype=rst spell: To get started writing AVR assembly language programs, you need a reference that details the instructions you can use. The chip we will be using is an Atmel ATmega328P, found on the Arduino_ Uno boards. Here is the "datasheet" for this chip: * :download:`/files/ATmega328P_Datasheet.pdf` And an instruction set summary: * :download:`/files/AVRinstructions.pdf` Now, we can lay out a code file. Basic File Layout ***************** The basic layout of a program file looks like this : * Comments begin with a semicolon * We mark a line of code with a label. This is just a name beginning on column 1 * Directives tell the assembler what to do (usually start with a dot) * Assembly files are named ``something.S`` (note the capital S) Headers ======= Use a comment block at the top of the file to identify the project, and who created it. .. literalinclude:: code/avr-blink/src/blink.S :lines: 1-3 :caption: blink.S Put any additional documentation you need here as well. For simple code files, letting the user know how to build the project is a good idea! Chip Definitions ================ We need to define a few things for most programs we write for this chip. We will go over all of that later. For now, we will place all of this definition logic in a separate include file, to make it easy to change as needed. .. literalinclude:: code/avr-blink/src/blink.S :lines: 5 :caption: blink.S Here is the configuration file we will use: .. litera;include:: code/avr-blink/include/config.h :linenos: :caption: config.h .. note:: There is code at the end of this file that makes writing real assembly language for the AVR using ``avr-gcc`` a bit easier. Some instructions only had enough room for a limited number of bits to specify operands. The number you enter in the instruction is actually off by 0x20 from the real number used in the instruction. The ``avr-gcc`` folks solved this problem by making you specify the register you want (usually an internal register with a name, not one of those ``r0-r31`` registers), and use an awful line for specifying that register. I figured out an easier way, they looks nicer in your code. You just wrap up the offending name in parentheses, and stick an underscore in front of that. This preprocessor ``macro`` solves the problem. Data layout =========== .. literalinclude:: code/avr-blink/src/blink.S :lines: 7-8 :caption: blink.S This is a dummy declaration, we will not need this variable in the code. It is here just to show you how to set up a variable. .. note:: There are no other data types, only bytes! In general, we do not do number crunching in these machines! Code layout =========== .. literalinclude:: code/avr-blink/src/blink.S :caption: blink.S :lines: 10-15 The line that defines the "org" (origin) of this program code, which is the address where code will be placed in the chip. Since there will be no other code in the chip when we run our program, we start this code at address zero in the program code area. Before we can actually run any real processing code, we have some chip initialization to do! Initialize the chip =================== The first chunk of code we need sets up the system stack pointer, so we can call procedures. We set up a stack at the top of available memery: .. warning:: Pay attention to those names in all caps surrounded by parentheses, and with a leading underscore in front of them. Clearing the Status register ---------------------------- The ``SREG`` register is used to record various conditions that are generated as we process instructions. We need to clear that flag before we start our real code: .. literalinclude:: code/avr-blink/src/blink.S :caption: src/blink.S :lines: 15-18 That line that uses the ``eor`` instruction is th fastest way to clear all the bits in a register. The ``XOR`` operator is applied bit by bit, and this always results in a zero. It is common in AVR code to leave the zero in ``r1`` for use whenever we need a zero. (We have enough registers to sacrifice one!) Setting Up a Stack ------------------ We need a special area where we will save return addresses as we call procedures, We will look at that in a later AVR lecture. For now, we just initialize a special register to so it is pointing to the top of available memory in this chip: .. literalinclude:: code/avr-blink/src/blink.S :caption: src/blink.S :lines: 21-25 The real stack pointer register is a 16-bit one, so we need to set it in twp steps, one for the low byte, and one for the high byte. ``RAMEND`` is set by ``avr-gcc`` based on the processor you specify when you build your code. The stack will start at the top of available data memory, and grow downward. Now that we have the stack set up, we are free to use procedures in our code. Setting up the clock -------------------- .. literalinclude:: code/avr-blink/src/blink.S :caption: src/blink.S :lines: 27-30 This code is tricky. We can alter the speed of the processor by configuring a special counter used to send "tick" signals every so many physicals oscillations of the hardware clock on the board. We can make the processor go slower when we do not need it to run so fast (odd idea!) There is a special lock on the register that controls the speed of the processor. Writing accidentally into this register could be dangerous. So, the designers made it hard to access. Basically, you write an 0x80 into the register, then immediately write in the value you want on the next instruction. The first write unlocks the register, and the next one puts in a new value. Right after that, the register is re-locked. Initializing the LED pin ************************ There is one more thing we need to do! Before we can make the LED blink, we need to set up the output pin on the chip that drives the LED so it is an output. Here is the code that does this: .. literalinclude:: code/avr-blink/src/blink.S :caption: src/blink.S :lines: 32-35 The names used here are defined in our "config.h" file, so we can change them if needed. Hello, World - Arduino style **************************** Finally, we can write the real code for this project. Everything up to this point is setup, and we need to do almost exactly that same thing for every project we build! Since the Arduino_ has no console to display test on, the equivalent of "Hello, World" is simple" blink a light at some defined rate! .. literalinclude:: code/avr-blink/src/blink.S :caption: src/blink.S :lines: 37-40 See the unusual label here? Labels can be numbers in this assembler. We can refer to a numbered label with an "f" (look forward in the code) or "b" (look backward in the code). The first such numbered label is the one we are referring to. This avoids making up silly labels to mark a line of code for later reference. * No more silly names needed Toggling the LED on/off *********************** Finally, we blink! .. literalinclude:: code/avr-blink/src/blink.S :caption: src/blink.S :lines: 42-47 The ``EOR`` instruction is the same as ``XOR`` in the pentium. We are "toggling" a single bit to make the light blink! This is all the code we need - except for the delay code. You get to tweak that code for a homework project. Here is the complete project code: .. literalinclude:: code/avr-blink/src/blink.S :linenos: :caption: src/blink.S .. literalinclude:: code/avr-blink/src/delay.S :linenos: :caption: src/delay.S .. literalinclude:: code/avr-blink/include/config.h :linenos: :caption: include/config.h