As long as blinking a led is a fairly simple task. Arduino Library is very handy and can really improve the speed of development of a new projects, making your idea real & tangible. Something similar in C would be useful. At this moment one may ask - why to create a new library, when there are already plenty of those available and ready to use ? Well, thinking like that, we may ask ourselves straight away why to even bother and play with an 8-bit machine when there is so powerful and cheap stuff available out there at the moment everywhere nearly ? The answers for both are short and straight forward:
- Because we can and it's fun
- Because we want to learn something
- Because we want to create some real, useful stuff
Another reason to create a library is to have some basic tool set which will be absolutely essential in most of any further developments. First thing which will need in this tool set is a proper (by proper I mean - timer/interrupt based) delay function. I have nothing against a "busy" delay from AVR libc but there is a small problem with that - the delay time cannot be passed as a parameter in the runtime since the delay is calculated during compilation, based upon the defined clock value (F_CPU). Now that's a real problem. We could write a similar function as well, which would spin in a loop for a defined number of time but this is more tricky than it seems - probably the optimizer would simply remove our code since it would detect that it has no functional meaning, the second thing is that it would be really hard to maintain any kind of usable accuracy. Since we have a proper hardware to do that (timers) we will use it.
At the time being I already created a simple library implementing most of the essential functionality, so this post will continue more or less as a supplement to the documentation on how to really use it. The library is hosted on GitHub:
I suggest cloning it to pca directory, since current repository name is my GitHub remote convention naming scheme, which isn't really convenient for every day use:
git clone email@example.com:dagon666/avr_Libpca pca
The documentation for the library, is a doxygen generated html page, available here:
But going back to the topic of timers. Let's discuss how the delay could be implemented. If we program the timer to overflow every 1 ms then automatically we have a 1 ms delay accuracy functionality implemented. It's really simple, here are the steps needed in a little bit more details:
- Program a timer in CTC mode, to overflow every 1 ms.
- Enable timer COMPA/COMPB interrupt
- Set global variable, let's call it "duration" to the number of milliseconds required
- With every timer overflow, decrement the "duration" variable in the interrupt handler
- Block until "duration" > 0
- Disable timer interrupt, since the required amount of time has passed by
First of all we need to program a timer in CTC mode, to be able to specify the exact time needed for it to overflow. Please have a look at the datasheet for more details about how timer behaves in that mode. Let's walk through the code bellow:
Starting from the main function. I setup PORTB as output and initiate it's value to 0x00; Further on I toggle all lines of PORTB with a 0.5 Hz frequency. Let's focus on the delay implementation itself.
I strongly suggest to go through this code with datasheet opened - it will help to understand the setting and the register values a lot. First thing is to enable global interrupts and power to Timer0 - I'll use this timer for delay purposes. I use a macro from AVR libc to power on the timer - all it really does is writes zero to bit PRTIM0 in PRR register which controls the power reduction settings of the MCU, so we can easilly replace that with an explicit bit operation if we want:
PRR &= ~_BV(PRTIM0);
_BV is another nifty macro from avr-libc. It performs a bit shift, so in that example it's equivalent to:
PRR &= ~(0x01 << PRTIM0);
Next I setup the Timer0 registers. First in TCR0A I configure Timer0 in CTC mode (WGM01 bit set). I do not care about COM0A1/COM0A0 and COM0B1/COM0B0 settings since I do not plan to output anything on the MCUs OC0A/OC0B pin. Next thing is to configure the prescaler value in TCR0B to 64 (CS01 and CS00 bits are set). At that stage the timer is running. I then configure the value of OC0A to 0x7c (124), accordingly to the formula in the datasheet, the frequency in CTC mode for Timer0 with these settings will be:
f = 16 Mhz / [ 2 * 64 * (1 + 124)] = 1 kHz
and the period:
T = 1/f = 1 ms
Exactly what we need in order to have a 1 ms accurate delay. The timer now is running and overflows with a required frequency. In the next line I configure the duration variable to the requested amount of milliseconds. All that has to be done at that point is to simply enable the OCIE0A (output compare interrupt which happends always when the counter reaches value configured in OC0A) interrupt and block in a while loop until the "duration" variable becomes 0.
In the interrupt handler, the duration variable is decremented (if it has a positive value) each time the interrupt occurs. Once "duration" is equal to zero, I simply mask the OCIE0A interrupt and disable to clock to Timer0 by configuring prescaler to 0. The timer now has been stopped and no further interrupts will be generated.
Because interrupt decrements the "duration" variable every 1 ms. After 1000 ms it will become zero, and the tdelay_ms function will end it's execution. Simple as that :).
The implementation in libpca is exactly the same as the example above, it's a little bit more complex only to provide more flexibility and not to duplicate the code inside of the library, but in general the principle stays the same. How to use timer delays with libpca then ? It's even simpler than our reference code above. Let's start by cloning the library and creating directory for our "test project":
git clone firstname.lastname@example.org:dagon666/avr_Libpca pca
Once inside of our project directory. Let's create two files main.c for our code and Makefile for compilation:
The code is really simple. The first difference is that there are two functions tdelay_init() and the tdelay_ms() function itself. The reason is that we need to configure the timer only once, after that all that needs to be done is to configure the "interrupt variable" and re-enable interrupts. This save some time when tdelay_ms() is called in a very busy loop. The second difference is that you must specify the timer explicitly for every function by using the timer enumeration (E_TIMER0 - E_TIMER2) The library tries to be as much flexible as it is possible. We can use any Timer we want, not only Timer0. One thing needs to be mentioned. In order for the timer delay implementation to work a timer interrupt implementation must be enabled for adequate timer in the library configuration (include/config.h), in case of the example above this should be:
#define TDELAY_IMPLEMENT_T0_INT 1
A complete example can be downloaded here.
I hope that this short introduction has been helpful. Going further there is not much that has to be changed to our existing reference code in order to implement the Arduino Library tone() function - I'll try to cover that next time.