.. _avr-timer-intro: Programming the AVR Timers ########################## .. include:: /header.inc .. vim:filetype=rst spell: Let's explore some "C" code and see if we can create a simple signal that can drive a Piezo buzzer. The Piezo Buzzer **************** * Here is the buzzer we will use .. image:: images/PiezoBuzzer.jpg :align: center * a crystal inside of this flexes when a voltage is applied across the leads * when it flexes, it pushes air out of the way. * Flex it fast enough and those air pulses hit your ear making noise * You control the tone by the pulse rate Buzzer Code *********** * Here is the start of our code .. literalinclude:: code/cBuzzer/src/cBuzzer.c :lines: 1-9 This code sets up the chip clock so it runs at a slower than full speed. There are defines that set up the output pin and toggle that pin on and off. We will use these macros in the main code. Chip setup ********** Next we configure the chip .. literalinclude:: code/cBuzzer/src/cBuzzer.c :lines: 11-13 We are going to connect the buzzer between Arduino pin 12 (PORTB bit 4) and the one of the ground lines on the opposite side of the board. The buzzer loop *************** Finally, we enter the main loop .. literalinclude:: code/cBuzzer/src/cBuzzer.c :lines: 14-19 Connecting the buzzer ********************* Here is how to hook up a simple Piezo buzzer to output pin 12 on the Arduino_ board (``PB4`` on the chip itself). .. image:: images/PiezoCircuit.png :align: center .. note:: This image shows the buzzer connected to another pin on the Arduino. Any output pin will do, you just need to modify the configuration definitions to make it work. Here is a picture of the chip, with all of the pins identified. This will help in hooking up other stuff to the board: .. image:: images/Atmega328p-PinMap.png :align: center What is wrong with this? ************************ Running this code makes noise (it did on my system, but not very loud!) The program works, but it consumes all the processor's attention That wastes power we could use for other purposes. One way to solve this is to us an internal ``Timer``. .. warning:: There is one more problem with this code: IT WILL NOT SHUT UP! The code runs forever, generating an annoying tone. At least we know it works! A Timer is just a simple counter that cab be set up and let run. The timer counts using the system clock. Once the timer reaches a max value, it rolls over and we can detect that! AVR Timers ********** The chip on the Arduino has four internal timers that go by these names: * ``Timer0`` - 8 bit counter * ``Timer1`` - 16 bit counter * ``Timer3`` - 16 bit counter * ``Timer4`` - 10 bit counter Hmmm, wonder what happened to ``Timer2``? Clock source ************ We mentioned that the timer counts using the system clock. To give more control of the timing we can divide that system clock using something called a ``prescaler`` to extend the time it takes reach the max count value. The possible prescaler settings available on these timers are: * Clock / 8 * Clock / 64 * Clock / 256 * Clock / 1024 Timer counting rates ******************** On the Arduino_, we can configure timers to increment the counter every: * 8/16000000 = 0.5us * 64/16000000 = 4us * 256/16000000 = 16us * 1024/16000000 = 64us Setting the timer count *********************** Each timer has a fixed number of bits available for its counting register. That means they can count up to: * 255 for an 8-bit timer * 1024 for a 10-bit timer * 65536 for a 16-bit timer We can detect when the timer "rolls over" from the max value it can hold to zero. That event can signal an interrupt, or simply set a flag we can check in our code. That is called "polling". The timers begin operation when we load a count into their counting register. If we start the timer with zero, it will rake time to reach the maximum count. If we choose to load some value other than zero in the counter, the time it takes to "roll over" will be shorter. Maximum timer times ******************* We will be using the 8-bit Timer0 timer for this experiment. With the available prescallar values, we cna get these times out of the timer: * 256 * 0.5us = 128us = 7812Hz * 256 * 4us = 1.024ms = 976Hz * 256 * 16us = 4.096ms = 244Hz * 256 * 64 = 16.384ms = 61Hz Of course, we can adjust these numbers. We also have control over how fast the main clock ticks, usina another scaling register that divides down the main system oscillator. Detecting the roll over *********************** We can deal with the signal one of two ways: * write code that checks for roll over every so often (polling) * configure the chip to generate an ``interrupt`` when it happens For this experiment, we will use "polling". Configuring the Timer ********************* In order to use a timer, we must set it up and start it running. There are two tasks we need to do: * set the prescaler value * preload the counter For ``Timer0``, the control registers we use to control the count rate is: * Timer/Counter Control Register B - ``TCCR0B`` * Bit 0 = ``CS0`` * Bit 1 = ``CS1`` * BIT 2 = ``CS2`` Setting the prescaler ===================== +-----+-----+-----+---------------+ | CS2 | CS1 | CS0 | Function | +-----+-----+-----+---------------+ | 0 | 0 | 0 | Timer stopped | +-----+-----+-----+---------------+ | 0 | 0 | 1 | clock/1 | +-----+-----+-----+---------------+ | 0 | 1 | 0 | clock/8 | +-----+-----+-----+---------------+ | 0 | 1 | 1 | clock/64 | +-----+-----+-----+---------------+ | 1 | 0 | 0 | clock/256 | +-----+-----+-----+---------------+ | 1 | 0 | 1 | clock/1024 | +-----+-----+-----+---------------+ Setting the preload count ************************* THe register ``Timer0`` uses for counting is: * Timer/Counter0 count register - ``TCNT0`` * This register is set with an initial counter value. * Setting the count starts the timer (if enabled) Detecting the roll over *********************** Each timer has a bit that is set whenever the timer overflows. For ``Timer0`` thise bit we will use is this: * Timer Interrupt Flag Register - ``TIFR0`` * The bit we will examine is ``TOV0`` (overflow). Polling code ************ We will write the polling code in assembly language. THis file looks like others we have set up, except that we need to make the single function in this file `global``. THe "C" code will reference this function to do the delay. In this example, we are not passing any data between "C" and assembly language code. Doing that is a topic for later. Here is the timer code. This function configures the timer, starts it up for the delay time, then shuts it down when done. .. literalinclude:: code/pollBuzz/timer.S :linenos: :caption: timer.S Skipping instructions ===================== Some AVR instructions are a bit funny. In this code you will see the ``sbis`` instruction: * Test some condition (bit in register is set) * If the condition is met, skip the next instruction * Otherwise, they execute the next instruction * Usually this is a jump of some kind The result is like our conditional branch, but it takes two instructions Is this better? *************** With this code, the processor is not really stuck in a loop until the overflow bit gets set, even though that is how we set this delay function to work. We could be off doing other things until that flag bit gets set. This is sure easier to control that that ugly nested counting loop we used earlier! Rewriting the Code ******************* We will set up this project in a new folder with a single ``Makefile``. The ``Makefile`` shown here can build any project involving ``C`` or ``AVR Assembly`` code: .. literalinclude:: code/pollBuzz/Makefile :linenos: :caption: Makefile main.c: Setting up ================== We start off by writing the configuration file we will use for this project: .. literalinclude:: code/PollBuzz/config.h :caption: config.h :linenos: We will set this project up a bit differently. This one will use a standard "C" main function that handles the basic buzz logic. We will use Timer0 in compare match mode to do the delay logic. That routine will be written in assembly language. .. note For this setup, we need to let the compiler set up the interrupt table, even though we will not be using it. .. literalinclude:: code/pollBuzz/main.c :caption: main.c :linenos: How it works ************ * Main sets up the chip and timer system * It then loops toggling buzzer pins and calling the timer delay routine * ``timer.S`` configures the timer timer * It uses polling in the delay loop to wait for the timer overflow * Overall logic is identical to the "C" example The output signal ================= .. image:: images/BasicTimer.png :align: center :height: 450 Examing the Code **************** The ``Makefile`` used in this project creates a listing file (with en extension of ``.lst``). This file is a "disassembly" of the final binary code sent to the processor. Eamining this file is how I discovered the problems with the current linker. I am still working on figuring out how to control the link step so I can generate pure assembly language files properly!