Thursday

Arduino AVR Timers and Waveform Generation

I've had a number of questions regarding Arduino timers and the generation of signals and waveforms, so here is (hopefully) a simple tutorial on using Arduino timers.

This tutorial requires a basic understanding of the difference between bits and bytes, along what it means to read or write bits and bytes to AVR registers.

Overview:
Timers can be seen as additional components used to offload work from the processor. A timer running on its own can create a signal or waveform without any interaction from the CPU, allowing the CPU to do things like interact with the timer to modify the waveform or handle other tasks.

The most basic function of a timer is to count from 0 to a specified number, lets say 255, and then start all over again and count from 0 to 255 again. Using a standard Arduino board running at 16 million cycles/second, the timer will count from 0 to 255 at a set frequency, which can be determined using a simple calculation:

CPU Freq / TIMER TOP  =   Timer Frequency (cycles/second)
16mHz     /         255          =   62,745 cycles/second  ( 62.75kHz )

The UNO has both 8-bit timers (TIMER0, TIMER2) and a 16-bit timer (TIMER1) which can all be used to perform various functions. This means that timer0 and timer2 can handle or count in increments ranging from 0 to 255, and TIMER1 will handle values from 0 to 65,535.


Timer Parameters & How to generate a signal:

Timer TOP:
Basic timer functionality is simple, we set a TOP value, and the timer will count from 0 to TOP, then start counting at 0 again.

Timer COMPARE:
Timers can also be set to toggle an output or trigger an interrupt when the timer value reaches a set value. Lets say the TOP value has been set to 255, the compare value is 127, and an LED is connected to pin 9 on an Arduino UNO. The following will take place:

a: The timer will increment its value by 1 every 16-millionth of a second, starting at 0
b: The LED stays lit until the counter value reaches 127, then is turned off.
c: The LED turns back on once the counter value is reset to 0 (after reaching its defined TOP value)
d: GoTo b:

Result: This produces an approximately 50% duty cycle PWM signal at 62.75kHz

Example Code:

void setup(){

    pinMode(9,OUTPUT);
 
   //This sets the timer1 mode, prescaler and enables timer PWM output on Uno pin 9 (not covered in this tutorial)
    TCCR1A = _BV(WGM11) | _BV(COM1A1);
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);

    //This sets the TOP value, or the value that the timer will count up to before resetting to 0
    ICR1 = 255;
 
   // This sets the OUTPUT COMPARE value A ( Compare A for pin 9, Compare B is linked to pin 10)
   OCR1A = 127;

}

Changing the Duty Cycle (how long the pin output is HIGH vs LOW):

Changing the duty cycle is as simple as changing the value of the OCR1A register. Since these examples use timer1, 16-bit (0-65535) values could be used, but since the TOP value is set at 255, only those values can be used for this demonstration.

Example Code (Uses the above setup() code) :

void loop(){
    OCR1A = OCR1A + 1;
    delay(15);
    if(OCR1A == 255){
         OCR1A = 0;
    }
}

Result: The example above will increment the duty cycle by 1/255 (0.4%) every 15 milliseconds until it has reached 100%, then it will reset to 0 and continue to increment. A connected LED will be seen to get brighter and brighter until the timer compare register reaches the TOP value, and the duty cycle (OCR1A) will be reset to 0.

Changing the Frequency:

To change the frequency, simply adjust the timer TOP value in the above example code:
ICR1 = 30000;

Calculation:  16,000,000 / 30000 = 533.33... HZ (cycles/second)

Note: The compare register (OCR1A) can be set to any value between 0 and 30,000 in this example.

Prescaling:

The frequency can be made lower by adjusting the prescaler. The system clock signal will be divided by the prescaler before being made available to the timer. This is not required to be understood for this tutorial.

Calculation: CPU Freq. / Pre-Scaler / TIMER TOP = frequency


Basic Theory of Creating Audio:

One of the simplest and most common forms of uncompressed audio is the WAV or PCM format. PCM stands for Pulse-Code-Modulation, which can be replicated using the Arduino timers, and the information provided above.

In the case of recording 8-bit audio, the voltage of an audio signal is converted to a number ranging from 0 to 255. The higher the voltage, the higher the number. This is done at a set frequency, say 16kHz or 16,000 times a second.

Recreating an audio signal can get fairly complicated and in-depth, but basically just needs to meet the following requirements:

1. Frequency of the timer generally should equal the frequency that the audio was sampled at (sample rate)
2. Sample values need to be fed into the timer every cycle

To create audio of 16khz sample rate, the timer needs to run at 16,000 cycles/second, and a new sample value needs to be fed into the timer every time it reaches its TOP value (every 62.5 microseconds) This generally requires the use of interrupts, which is out of the scope of this tutorial.

The above example code can be used to create simple audio tones by varying the frequency from 0 to about 10,000 hz max, and volume controlled by adjusting the duty cycle.

Summary:

This overview is intended to cover very basic timer usage in Fast PWM mode via TIMER1. Additional timers, modes, details, usage, etc can be determined by referencing the datasheet, which is really the best reference for any kind of advanced timer usage. All of the timers are accessed in very similar ways, but some settings like prescaling are different between timers.








No comments:

Auto Analog Audio & NRF52840: Now working on non-Mbed cores

 Auto Analog Audio & NRF52840: Now working on non-Mbed cores Playing around with the AAAudio library So I finally decided to make the ne...