============== AVR Training ============== :Author: djerem .. contents:: Table of Contents Introduction ============ AVR chips are quite like PC but smaller. They have many advantages: simple you just need to wire them and they work out of the box; no need to add more components. You just need to program them to control their input-output; open they are compatible with open-source softwares; interface we can made them work with tons of components. .. figure:: avr_architecture.png :align: center :alt: AVR architecture diagram Structural AVR diagram. AVR are powered with 5V. Two input pins of the AVR can be used to make it work with an external clock, generally generated by a quartz. This way, you can increase both its speed and accuracy. The communication between the AVR and the PC can be done with the serial port. **Be careful**, you need to convert the -12/+12V of the PC to the 0/5V of the AVR (you can use a MAX232 component to achieve this). Input and output ================ Input and output are regrouped by port, each one composed of 8 pins. Ports management are thus done by 8 bits words. Each port disposes of 3 registers: each register have 8 bits and each bits are mapped to a pin of the port. Here is a description of the 3 registers of each port: ``PIN`` it is a *read* register, reflecting the current state of the port. It is thus used to read the input and output state of the port. Of course, you can only read it and never write to it; ``DDR`` it used to to select the port *direction*: * input mode: it is set to ``0``. This is the default case; * output mode: it is set to ``1``. ``PORT`` it has got two functionalities: * if we are in input mode, it can be used to enable a *pull-up* mechanism; * if we are in output mode, it can be used to define the *state* of the pin (higher or lower state). Here is a summary of the use of the ``PORT`` register: +---------+----------+------------------+ | ``DDR`` | ``PORT`` | Result | +=========+==========+==================+ | 0 | 0 | No pull-up, | | | | High impedance | +---------+----------+------------------+ | 0 | 1 | pull-up in use | +---------+----------+------------------+ | 1 | 0 | output state = 0 | +---------+----------+------------------+ | 1 | 1 | output state = 1 | +---------+----------+------------------+ Example of port A use ********************* With one led ~~~~~~~~~~~~ A led is connected to the first pin of the A register (we called this pin *A0*). We would like to make the led blinked. Here is the algorithm you should used:: // enable output mode for the pin A0 DDRA = 1 // infinite loop while (1) { // Switch on the led by putting the A0 pin in higher state PORTA = 1 // Wait a few moment (to be sure we have seen the led on) Delay (tps) // The delay function already exist on the APBteam // repository // Swith off the led by puttin the A0 pin in lower state PORTA = 0 // Wait again Delay (tps) } Remember to protect your led with a resistor. With two led ~~~~~~~~~~~~ Now, we would like to add a second led, connected to the pin *A3* and that will light on when the first one is switched off and vice-versa. By using our previous algorithm, we just need to change the following code:: // We would like to put a 1 (high state) on the third bit // So 2³ = 8 DDRA = 1 -> DDRA = 8 PORTA = 1 -> PORTA = 0 PORTA = 0 -> PORTA = 8 The problem with this algorithm is that we will also change the other bits of the ``PORTA``. For example, setting ``PORTA`` to 8 (00001000 in binary) will make us loose the first bit we have previously set to 1. Consequence, we will never switch on the first led (connected to the *A0* pin). Not really useful... To prevent the change of the bits of the other pins of the port, we need to use some logical function: to put a bit to 1 you need to use a logical *or*. Here is a sample algorithm to change the change from 0 to 1 the third bit of the ``PORTA`` register:: // 8 correspond, in binary, to 00001000. PORTA |= 8 to put a bit to 0 you need to use a logical *and* between the ``PIN`` register and an 8 bits word with all bits set to 1 expect the bit you want to change. Here is a sample algorithm to change from 1 to 0 the third bit of the ``PORTA`` register:: // ~8 correspond to the opposite of 8. In binary, it means the opposite // of 00001000, that is to say 11110111. Only the third bit is set to 0 // because it is the one we would like to change. PORTA &= ~8 In fact, there is a macro ``BV_`` (defined in ``common/io.h``) which lets you directly work with the desired bit of a byte. If we use this macro, the previous code will become:: // 1 << 3 = 8 PORTA &= ~_BV(3) PORTA |= _BV(3) You can also work with multiple bits at the same time. For example, if you want to change both second and third bit of the ``PORTA`` register to 1, you can do the following:: // 2³ + 2² = 12 PORTA |= 12 // You can also use the BV_ macro: PORTA |= _BV(2) | _BV(3) In fact, you should try to use the ``BV_`` macro as much as possible: it usually make the code easier to read and understand. Many devices ready to use in the AVR ==================================== AVR chip contains some ready to use devices. For example: * an analog/digital converter: it uses a specific register; * a serial port: it uses configuration, reception and emission registers; * an I2C/TWI port; * a watchdog timer to make the AVR restart when it crashes; * PWM; * ... Polling mode versus interrupts ================================= Polling mode ************ The polling mode corresponds to the action of regularly check something to see if there is change and take actions according to. For example, with the serial port, you need to do something like:: If there is a byte arrived on the serial port If no: Go on with the main program; If yes: I manage it now; We need to do this algorithm between each program instruction to be able to manage data as they arrive. Expect the complexity of doing this every time, it decrease the performance by losing time for the verification of data on the serial port. To solve this issue, we can use interrupts. Interruptions ************* Some pins of the AVR on specific ports can trigger some interrupts. For example, when there is a logical state change on a pin, an interrupt is triggered. It can be handle or not, depending on its level. Let's take our previous example with the serial port in polling mode and adapt it for interrupts:: I am currently executing the instruction of my program, normally; Coming from nowhere without a warning, an interrupt is triggered because a has arrived on the serial port; My program is stopped and sent to the handler of this interrupt: I get the data received on the serial port and manage it; When my interrupt handler is finished, I am going back to the place where I have been stopped in my program; Interrupts are managed in a linear way: when two interrupts are triggered at the same time or within a little time, there is no way to predict which one has come first. Things to keep in mind for interrupts: volatile variables they force the compiler to never optimize their access. This way, the AVR need to get the value where it is stored and never caches it. You need to use them every time you have a variable that it is used in the interrupt handlers and in other functions of the program. It ensures you the value you are working is the actual one, anywhere in your program; enable interrupts in order to enable interrupts, you must use the function ``sei ()`` in your code; disable interrupts in order to disable interrupts, you must use the function ``cli ()`` in your code; .. I do not understand this part: * Interrupteur en entrée Il est possible de mettre un interrupteur en entrée sur les AVR. Mais lorsque l'interrupteur est ouvert, il faut que la patte soit branchée. Elle ne peut rester dans le vide. Pour cela, il suffit d'activer le mode ''pull-up'' de la patte correspondante. Attention au rebond ! AVR at APBTeam ============== AVR used ******** We use two kind of AVR, from Atmel: ATmega64 and ATmega128. The difference between the tow is, expect the price, the size of the memory flash to store the program (64k and 128k). AVR code ******** I can only advise you to have a look at the code in the SVN repository, in the directory `trunk/digital/avr `_. You can find here some documentations (this one for example), the code of the program of the different card (IO, Asserv), a simulator to make the code work on PC and even some examples. Repository organisation ~~~~~~~~~~~~~~~~~~~~~~~ The directory `trunk/digital/avr `_ of the repository is organised in the following way:: |-- common | >>> Some general defines to include in programs |-- doc | >>> Some documentations |-- make | >>> Makefile used for the build `-- modules | >>> Many modules ready to use |-- adc | >>> Analogic to digital conversion |-- host | >>> Build AVR program for PC architecture |-- math | >>> floating point computation, random number generation |-- proto | >>> Serial protocol management |-- twi | >>> I2C bus management |-- uart | >>> Serial bus management `-- utils >>> Some useful functions, like reset, delay, ... To create a project, have a look at the `build.txt `_ document. Resources ========= * Datasheets of the AVR component (available on the Atmel website); * Documentation on the AVR libc: http://www.nongnu.org/avr-libc/user-manual/index.html; * `AVRFreaks `_ with many things on everything; Acknowledgment ============== Thanks to Ni for taking the time to give us this training while he was ill!