Skip to main content

Generating Tones with Timers

Previously I presented a basic way of using timers in order to generate delays. At this time, we'll learn how to use timers and piezo buzzer to generate tones (beeps). The code for that won't be very different from what we were doing previously. Let's start with defining the requirements of our implementation. Our "beeper" function should allow us to generate a beep of given frequency and duration. Simple as that. Just to quickly summarize, when using timer for delay purposes we configured it in CTC mode and configured the prescaler and OCRXA register value so the timer "overflowed" with a 1 kHz frequency (1 ms period), the delay duration was specified by the number of "overflows". Previously, as long as counting the 1 ms cycles was good enough to determine the duration, now the duration depends on both parameters, the actual duration specified as an input parameter as well as the frequency itself. 

Let's list steps that our "beep" generation algorithm will have to take:
  1. Program the timer in CTC mode.
  2. Program the OCXA pin as output.
  3. Program the timer's compare output mode to toggle the OCXA on each compare match.
  4. Calculate the prescaler and the OCRXA values to generate the frequency of 2*f.
  5. Calculate the amount of cycles needed to "realize" the requested duration.
  6. Enable interrupts.
  7. Decrement the cycles counter (calculated in step 5) inside of the ISR.
  8. Stop generation and disable interrupts once cycles counter reaches zero.


That looks a lot more complicated than previously. Don't worry it only appears that way. The trickery is hidden in steps 4 and 5, the rest is more or less the same. In point 4, let's think a for moment; why does the frequency should be twice as high as requested ? The reason behind it is really trivial. The output will be generated on pin OCXA by the hardware, which in CTC mode and COM0A1/COM0A0 configured to 0x01 - will be toggled each time a timers TCNTX register value will be equal to the one programmed in OCRXA, after that the TCNTX register will be reloaded with zero. What that means is a single timer cycle generates only half of the square wave output. So, we need the frequency twice as high for the pin to be toggled with the one requested. 

First thing that we will have to think about is a universal function which will calculate the values of both the prescaler and the OCRXA register to program the timer to the requested frequency. The formula for the frequency for the Timer in CTC mode (as stated in the datasheet) is:

f = fclk / [ 2 * N * (1 + OCRXY) ]

where:

fclk - system clock (16 MHz for most of the Arduinos)
N - the prescaler value (1,8,64,256,1024 - for timers 0 and 1)
OCRXY - the value in the register OCR0A/OCR0B (register for timer 0 used as an example, by the way, through out this post and in most of my code I focus only on Output compare register and output compare A, but You can use register/output B good as well - it's a matter of preferences).

Using the formula above we will have to calculate the values of N and OCRXA in runtime. 

Let's have a look bellow on the code which does that for Timer0: 



Let's analyze this code. There are some improvements introduced inside to make it a little bit faster/smaller. In fact it is completely decoupled from hardware, since it's a routine performing calculations only, so we can successfully compile it on an x86 machine and do some debugging. First of all a table of "prescalers" is declared. At first glance it contains some strange values. It stores the number by which a 0x01 should be bitshifted in order to get a valid prescaler value. It has been designed this way to save memory. The prescaler values for timer0 are 1,8,64,256,1024. To store those values we would need an uint16_t data type, consuming 5 bytes more than now. Since the prescalers are a powers of two we can only store the number of bitshifts required to get the needed value. The Atmega is a very fast core, we have a lot more computing power than memory available, that's why in most of the applications we should try to minimize the memory usage over computing resources. The calculation of prescaler and the OCR value is done in the while loop, the formula has been transformed to the following one:

OCR = [ fclk / (2 * N * f)] - 1

In every loop roll we compute the OCR value using another available prescaler. The loop brakes as soon as the OCR value is bellow 256 - which means that we have found a usable set of prescaler and OCR values in order to program the timer to generate the requested frequency. The values are returned by reference. Let's test this function offline. Consider & compile the followig code:

gcc -o pocr_test pocr_test.c



Let's do some tests:

$ ./pocr_test 1000
Frequency: [1000 Hz], Prescaler: [  64], OCR: [124]

$ ./pocr_test 13000
Frequency: [13000 Hz], Prescaler: [  8], OCR: [ 75]

$ ./pocr_test 8000
Frequency: [8000 Hz], Prescaler: [   8], OCR: [124]

$ ./pocr_test 30
Frequency: [30 Hz], Prescaler: [1024], OCR: [255]

./pocr_test 31
Frequency: [31 Hz], Prescaler: [1024], OCR: [251]

$ ./pocr_test 20
Frequency: [20 Hz], Prescaler: [1024], OCR: [255]

Let's verify one of those manually:

- f = 16MHz / [ 2 * 64 * (1 + 124) ] = 1000
- f = 16MHz / [ 2 * 8 * (1 + 75) ] = 13157
- f = 16MHz / [ 2 * 8 * (1 + 124) ] = 8000
- f = 16MHz / [ 2 * 1024 * (1 + 255) ] = 30

Looks quite promising. Of course the function does not minimize the frequency error, it only tries to find first matching pair of values in order to satisfy to request. In the second example a values of (prescaler: 8, ocr: 76) would provide a better fit (12987 Hz, 13 Hz difference instead of 157), but for most of the applications (especially tone generation using buzzer) such error is really acceptable. For more precision we would have to gather the OCR results for all prescaler values and return the one which is less different from the requested frequency. To have a real control we would probably define those values ourselves and use some sort of lookup table. At the moment we do not have to worry about that.

Second thing that we need to do is to determine how many timer overflows we need to count in order to determine the duration. Let's think about it. For a given frequency 1 cycle lasts for :

T = 1/(2*f)

The frequency is multiplied by two since, the timer is running with frequency twice as high as needed. The duration will be specified in millisecods, so we need to determine how many cycles (x) do we need do realize it. This can be calculated using proportions:

duration * (1/1000) = x * (1/2*f)

so, x:

x = (2 * f * duration) / 1000

Simplifying:

x = (f * duration) / 500


and that's the answer. Now we have all the necessary tools to finish our implementation. Let's start:


Let's connect the buzzer to the Arduino board digital pin 7 (PORTD6) (no resistor is needed), and test our code.

Connecting piezo buzzer to Timer0 OC0A output.

This functionality is covered by the libpca as well. As previously it's very easy to use. The timer_freq_prescale() function equivalent is implemented there as well in a very similar fashion (it's called _timer_freq_prescale()). Again, it's done in way to provide flexibility and be as much universal as it's possible. Let's cover the implementation in the beeper library module. Most important functions are:

- void beeper_init(e_timer a_timer);
- void beeper_beep(e_timer a_timer, uint32_t freq, uint32_t duration);

The interface is self explanatory. Let's write a simple program which will do more or less the same as our previous example:

$ git clone git@github.com:dagon666/avr_Libpca pca
$ mkdir beep_libpca
$ cd beep_libpca
$ touch beep_libpca.c




We cannot forget to enable the Timer interrupt implementation in the library configuration file in order for the code to work (this will be TDELAY_IMPLEMENT_T0_INT variable which must be defined as 1). The implementation presented as well as the one in the library can be extended further on. We could use the PWM capability to provide the volume control to the beep generator. For the moment though I do not plan to cover this topic. I hope that this post has been useful. In my next posts I'm going to cover the basic usage of the UART port. After that I'll proceed to some more interesting stuff (I hope :)).


Comments

Popular posts from this blog

RTC without RTC ? How good is my crystal ?

Clock - I think that it's the most popular idea for a project with the MCU . It's not that hard from both software and hardware side to realize but not that trivial as well, not mentioning the fact that once realized it sits somewhere in the house, constantly reminding about itself and brings a kind of satisfaction :). There are a lot of options really to implement, a buzzer alarm, perhaps a thermometer, display and buttons servicing, perhaps maybe even serial port usage for NTP synchronization. But ... The most challenging thing - like in every clock is the time reference source. There are a couple of options: well calibrated frequency source acting as an interrupt source Real Time Clock module with external crystal ( < 20 ppm ) internal clock These days RTC chips are so cheap and widely available that they are really the only reasonable choice, but if we're only planning to play around a bit with the software without paying much attention to accura...

Arduino R-2R ladder Audio DAC

There is a lot of projects out there which use  R-2R ladder and an Arduino to recreate sounds from either SD card or short audio clips programmed directly to MCU's flash memory. Although using SD card is fairly reasonable and provides a lot of flexibility it's not very challenging (from the software point of view) and it requires some additional hardware. Believe it or not we already have all the needed hardware in the Arduino itself. Assumptions In this project I'll play mp3 or any other multimedia files from the PC using Arduino. Bellow are the details: Play PCM 8kHz 8bit Audio with Arduino Audio samples will be transfered via USART from the PC in binary format using SLIP Audio files will be decoded on the PC side and only the RAW data will be send to Arduino Timing Arduino is a powerful machine, powerful enough that it's possible to play audio with even higher sampling rates and bit-resolutions than assumed above, the bottleneck in this...

Simple Serial Port Command Line Interface (CLI)

It's often very useful to have some sort of interface through which a basic management can be done of our application. Whether you're designing a giant Marquee with a LED display or a plotter or you simply need to get some diagnostics from your device occasionally, a simple CLI system can come very handy.  Of course, designing something similar to "bash" or any other unix shell is completely out of scope since those applications are huge and are simply an overkill for our needs. It's pretty simple though to create some basic, yet flexible & easilly extensible CLI system. First thing needed is a command type definition. This will bind a keyword with an actual underlying routine executed for that keyword typed in the CLI . typedef struct _t_cmd { const char *cmd; void (*fh)(void*); } t_cmd; The command type is pretty simple. There's the CLI command/keyword pointer, it holds a pointer to the execution function and that's it. OK, so...