Audio Oscillator in AVR

Preamble

Having tackled the AVR Timers and Interrupts to make a Noise Chip for the BOSS DR-110 in the article Noisey chips, I now feel confident to take on and answer this question on Stack Exchange, Program an ATtiny13 as an audio oscillator with variable frequency and pulse-width. The added bonus is that it can be used to create an Atari punk Console (APC)-esque device.

Initially I will use an ATmega328…

Useful links

Process

Initial fixes

So, starting off with the base code of vicatcu’s answer:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>

// global variables defining number of ticks on and off
uint8_t on_time_ticks, off_time_ticks, csxx_bits=0; 

void setup_timer(double p_ms, double duty){
  TCCR0A = _BV(COM0A0) // toggle OC0A on Compare Match
  TCCR0B = _BV(WGM02); // set CTC mode WGM0[2,1,0] = 0b100

  // ... do some stuff based on your CPU frequency
  // to define the csxx_bits of TCCR0B when the timer is running
  // and consequently, to set on_time_ticks and off_time_ticks
  OCR0A = on_time_ticks;
  TCCR0B |= your_settings_here;
}

void start_timer(){
  //start the timer running at the desired rate
  TCCR0B |= csxx_bits; 
}

int main(int argc, char **argv){
  double period_ms, duty_cycle;
  setup_timer(period_ms, duty cycle);
  start_timer();  
  for(;;){
    //spin or sleep or whatever
  }
}

ISR(TIM0_COMPA_vect){
  if(OCR0A == on_time_ticks){
    OCR0A = off_time_ticks;
  }
  else{
    OCR0A = on_time_ticks;
  }
}

This method is using the CTC method (however, as CTC  can not do variable duty cycle automatically, then, in addition, interrupts are used and the OCR0A value is altered to affect the duty cycle, else the duty cycle would be 50%). See  Newbie’s Guide to AVR Timers – Part Five – CTC Mode using Interrupts, for the correct way to do it.

However, there are number of issues with this code.

As noted by this comment:

Your choice of 0b100 for WGM0[2,1,0] won’t set CTC mode. (In fact it’ll set a mode that’s reserved by Atmel.) The ATtiny13 datasheet says CTC mode needs value 2; you’ve accidentally given it bit_number 2 instead (i.e. value 4). Because of that it’s also necessary not only to change (i.e. clear) WGM02 in TCCR0B but also to set bits WGM01 and WGM00 to 1 and 0 respectively. Those bits are in TCCR0A, so it’s not sufficient to set TCCR

This confusion with WGM02 may have arisen from Timer1 using WGM12 to set CTC mode (Table 13-4 of the datasheet).

In addition, csxx_bits is set to zero, and then applied to TCCR0B in start_timer(). However, this will actually stop the timer, as 000 means “No clock source”. So, either the line  TCCR0B | = your settings_here;can be discarded (along with csxxx_bits), or placed in a new function, stop_timer(), which isn’t called in this code (and it isn’t clear why it ever would be).

Likewise, the line TCCR0B | = your settings_here; is redundant in setup_timer(), and those settings (CS02:0) should be set in start_timer().

Also, on_time_ticks and off_time_ticks are never actually set anywhere.

The global interrupts are not enabled (sei()), see Newbie’s Guide to AVR Timers – Part Five – CTC Mode using Interrupts. The OCF0A  flag, bit 1 in the TIFR0 register, also needs to be set (page 100 of the datasheet) is set by the Timer0 itself internally, to indicate that an interrupt can occur. However, the OCIE0A flag, bit 1 in the TIMSK0 register, also needs to be set (page 111 of the datasheet).

The output pin (PB3 = OC0 for the ATmega16, the Atmega48/88/168/328 uses PD6= OC0A) is not set, see Newbie’s Guide to AVR Timers – Part Six – Pure Hardware CTC:

DDRB |= (1 << 3);  // ATmega16
DDRD |= (1 << 6);  // ATmega328

Finally, two typos: there is a missing ; in the TCCR0A line, and; a missing _ in duty_cycle.

So, let’s address these issues first:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>

// global variables defining number of ticks on and off (global for ISR)
uint8_t on_time_ticks = 124;
uint8_t off_time_ticks = 160;
uint8_t csxx_bits = 0; 

void setup_timer(double p_ms, double duty){
//  DDRB |= (1 << 3);  // ATmega16
  DDRD |= (1 << 6);  // ATmega328

  TCCR0A = _BV(COM0A0); // toggle OC0A on Compare Match
  TCCR0B = _BV(WGM01); // set CTC mode WGM0[2,1,0] = 0b010

  TIMSK0 |= _BV(OCIE0A); // Enable CTC interrupt

  // ... do some stuff based on your CPU frequency
  // to define the csxx_bits of TCCR0B when the timer is running
  // and consequently, to set on_time_ticks and off_time_ticks
  OCR0A = on_time_ticks;
}

void start_timer(){
  //start the timer running at the desired rate
  TCCR0B |= ( (0 << CS02) | (0 << CS01) | ( 1 << CS00)); // CS02:0 - No prescaling - Set as required
  sei();
}

void stop_timer(){
  TCCR0B = csxx_bits;
}

int main(int argc, char **argv){
  double period_ms, duty_cycle;

  setup_timer(period_ms, duty_cycle);
  start_timer();  
  for(;;){
    //spin or sleep or whatever
  }
}

ISR(TIM0_COMPA_vect){
  if(OCR0A == on_time_ticks){
    OCR0A = off_time_ticks;
  }
  else{
    OCR0A = on_time_ticks;
  }
}

This code now works, for a fixed frequency, as determined by on_time_ticks and off_time_ticks.

Changing the frequency

However, period_ms and duty_cycle are never set or used. We need a function which calculates on and off time from both period_ms and duty_cycle.

First, lets’ assume that the ADC input from the potentiometer, that controls the frequency (rather than its inverse, the period), is given by period_ADC, and the period of the square wave, period, for any given frequency is therefore  period = 1024 - period_ADC . Note that we are not using period in milliseconds, p_ms, yet, but a digital representation of the period (from 0 to 1024).

Likewise, let’s assume that the ADC input from the potentiometer, that controls the duty cycle, is given by duty_ADC, and the duty_cycle is duty_cycle = duty_ADC (no adjustment is required). Note that we are not using the duty cycle as a percentage, duty_cycle_pcnt, yet, but a digital representation of the period (from 0 to 1024).

Now, period_ms will depend upon the maximum number of ticks in a cycle of the square wave (both on and off phases), for a given output frequency.

The output frequency is given by:

Frequency of CTC output
Frequency of CTC output

The maximum frequency is when OCRnx is set to zero. The minimum frequency, Fmin, and maximum period, Tmax, for any given prescaler value of N, is when OCRnx is set to 255. Therefore, this maximum period is given by the reciprocal of the maximum frequency, thus:

Tmax = (2*N*256)/Fclock

Tmax (attained when period == 1024) is attained when the potentiometer (or whatever control) is turned right down, i.e. Vmin  (i.e. zero) input on the ADC pin.

So, defining these globals (the non-const variables could be made local):

const int clock_frequency = 16000000; // 16 MHz
int prescaler = 1; // change this is to the correct value (not a const, as we may make this variable later on)
int ticks_per_second = clock_frequency/prescaler; // 16M? or 16/N? (not a const as we may change prescaler in the future)

// Maximum values - not used
float p_max = (2*prescaler*256)/clock_frequency; // maximum period, for a given prescaler (not a const as we may change prescaler in the future)
int max_ticks = ticks_per_second * p_max; // maximum ticks for a given prescaler

However, this doesn’t give the total ticks in a cycle when the frequency is changed from the minimum, Fmin.

Given that OCRAmax = 255 is when period == 1024 then

Tmax = (2*N*(1+OCRAmax)/Fclock

and, in general,

T = (2*N*(1+OCRA)/Fclock

So, OCRA is equivalent to period/4 and the code becomes:

float p_s = (2*prescaler*(1 + period/4))/clock_frequency; //period for given frequency
int total_ticks = ticks_per_second * p_s; // ticks in that period
// or
//int ticks_per_millisecond = ticks_per_second / 1000;
//int p_ms = p_s * 1000;
//int total_ticks = ticks_per_millisecond * p_ms; // ticks in that period

This code is assuming p_ms is in milliseconds and duty is a percentage:

void calc_on_off_mspcnt(double p_ms, double duty){
  double total_ticks = ticks_per_millisecond * p_ms;
  on_time_ticks = total_ticks * (duty/100);
  off_time_ticks = total_ticks - on_time_ticks;
}

This code is assuming period_ADC and duty_ADC are absolute readings from the ADC pins (ignore for the moment):

void calc_on_off_abs(int period_ADC, int duty_ADC){
//  ticks_per_millisecond = 1/clock_frequency * k_ticks * prescaler; // sort of nonesense
  ticks_per_millisecond = clock_frequency / prescaler / 1000; 

//  double max_ticks = p_ms * ticks_per_millisecond; // sort of correct (see below)
//  double total_ticks = max_ticks * (p_ms/1024); // sort of nonsense

//  p_ms = /* conversion equation */ * (p_ms/1024) // interesting method using fraction - examine further
  double p_ms = 2*prescaler*(1 + (1024-period_ADC)/4))/clock_frequency/1000;

  int total_ticks = p_ms * ticks_per_millisecond;
  on_time_ticks = total_ticks * (duty/1024);
  off_time_ticks = total_ticks - on_time_ticks;
}

This above code is sort of WRONG! The following is better –

This code is assuming period and duty_cycle are absolute readings, derived from the ADC pins (i.e. period = 1024 - period_ADC and duty_cycle = duty_ADC, not the ADC pin’s readings themselves (even though duty_cycle actually is)):

void calc_on_off_abs_derived(int period, int duty_cycle){
  float p_s = 2*prescaler*(1 + period/4)/clock_frequency; //period for given frequency
  int total_ticks = ticks_per_second * p_s;
  on_time_ticks = total_ticks * (duty_cycle/1024);
  off_time_ticks = total_ticks - on_time_ticks;
}

This code is assuming p_msis in milliseconds and duty_cycle is an absolute reading from the duty cycle ADC pin:

void calc_on_off_msabs(double &p_ms, int &duty_cycle){
  int total_ticks = ticks_per_millisecond * p_ms;
  on_time_ticks = total_ticks * (duty_cycle/1024);
  off_time_ticks = total_ticks - on_time_ticks;
}

Converting from absolute values (adjusted period) to milliseconds and percent

void convert_abs_adj (double &p_ms, double &duty_pcnt){
  p_ms = 2*prescaler*(1 + p_ms/4)/clock_frequency/1000; //period for given frequency
  duty_pcnt = 100 * duty_pcnt/1024;
}

Converting from absolute DAC values to milliseconds and percent

void convert_abs (double &p_ms, double &duty_pcnt){
  p_ms = 2*prescaler*(1 + (1024-p_ms)/4))/clock_frequency/1000; //period for given frequency
  duty_pcnt = 100 * duty_pcnt/1024;
}

Converting from milliseconds and percent to absolute DAC values

void convert_mspcnt (double &p_ms, double &duty_pcnt){
  p_ms = 1024 - (1000*clock_frequency*p_ms/2/prescaler-1)*4; //period for given frequency
  duty_pcnt = 1024 * duty_pcnt/100;
}

Adjusting and converting from absolute ADC values to milliseconds and percent:

void adjust_inputs (period_ADC, period_ms, duty_ADC, duty) {
  int period = 1024 - period_ADC;
  int duty_cycle = duty_ADC;
  convert_abs_adj (period, duty_cycle);
  period_ms = period;
  duty = duty_cycle;

// or
//  convert_abs(period_ADC,duty_ADC)
//  period_ms = period_ADC;
//  duty = duty_ADC;   
}

Lastly, we need to assign and read the pins to get the period (frequency) and duty cycle:

// change as appropriate
const int pinPeriod_ADC = A0;
const int pinDuty_ADC = A1;

void get_inputs(int &period_ADC, int &duty_ADC){
// your pin reads here...
  period_ADC = analogueRead(pinPeriod_ADC);
  duty_ADC = analogueRead(pinDuty_ADC);
}

Putting that little lot together, and shortening a few variable names, gives:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>

// global variables defining number of ticks on and off (global for ISR)
uint8_t on_time_ticks = 124;
uint8_t off_time_ticks = 160;
uint8_t csxx_bits = 0; 

const int clock_frequency = 16000000; // 16 MHz
int prescaler = 1; // change this is to the correct value (not a const, as we may make this variable later on) 
int ticks_per_second = clock_frequency / prescaler; // 16M? or 16/N? (not a const as we may change prescaler in the future)
int ticks_per_millisecond = ticks_per_second / 1000;

// change as appropriate
const int pinPeriod_ADC = A0;
const int pinDuty_ADC = A1;

void convert_abs (double &p_ms, double &duty_pcnt){
  p_ms = (2*prescaler*(1 + (1024-p_ms)/4))/clock_frequency/1000; //period for given frequency
  duty_pcnt = 100 * duty_pcnt/1024;
}

void adjust_inputs (double p_ADC, double &period_ms, double dc_ADC, double &duty) {
  convert_abs(p_ADC,dc_ADC);
  period_ms = p_ADC;
  duty = dc_ADC; 
}

void get_inputs(int &period_ADC, int &duty_ADC){
// your pin reads here...
  period_ADC = analogRead(pinPeriod_ADC);
  duty_ADC = analogRead(pinDuty_ADC);
}

void calc_on_off_abs_derived(int period, int duty_cycle){ 
  float p_s = 2*prescaler*(1 + period/4)/clock_frequency; //period for given frequency
  int total_ticks = ticks_per_second * p_s;
  on_time_ticks = total_ticks * (duty_cycle/1024);
  off_time_ticks = total_ticks - on_time_ticks; 
}

void calc_on_off_mspcnt(double p_ms, double duty){
  double total_ticks = ticks_per_millisecond * p_ms;
  on_time_ticks = total_ticks * (duty/100);
  off_time_ticks = total_ticks - on_time_ticks;
}

void setup_timer(double p_ms, double duty){

//  DDRB |= (1 << 3); // ATmega16
  DDRD |= (1 << 6); // ATmega328 

  TCCR0A = _BV(COM0A0); // toggle OC0A on Compare Match
  TCCR0B = _BV(WGM01); // set CTC mode WGM0[2,1,0] = 0b010

  // ... do some stuff based on your CPU frequency
  // to define the csxx_bits of TCCR0B when the timer is running
  // and consequently, to set on_time_ticks and off_time_ticks

  calc_on_off_mspcnt(p_ms, duty);
  OCR0A = on_time_ticks;
}

void start_timer(){
  //start the timer running at the desired rate
  TCCR0B |= ( (0 << CS02) | (0 << CS01) | ( 1 << CS00)); // CS02:0 - No prescaling - Set as required
  sei();
}

void stop_timer(){
  TCCR0B = csxx_bits;
}

int main(int argc, char **argv){
  double period_ms = 50, duty_cycle = 50;

  setup_timer(period_ms, duty_cycle);
  start_timer();  
  for(;;){
    //spin or sleep or whatever
    int period_ADC = 0, duty_ADC = 0;
    get_inputs(period_ADC, duty_ADC);
    adjust_inputs(period_ADC, period_ms, duty_ADC, duty_cycle);
    calc_on_off_mspcnt(period_ms, duty_cycle);
  }
}

ISR(TIM0_COMPA_vect){
  if(OCR0A == on_time_ticks){
    OCR0A = off_time_ticks;
  }
  else{
    OCR0A = on_time_ticks;
  }
}

Getter simplification

Simplifying the getters, we need getters for the period and the duty:

int get_period(){
// your pin reads here...

  int p_ADC = random(1024);  // test code
 
  Serial.print ("\nperiod_ADC = ");
  Serial.println (p_ADC);

  return (p_ADC);

// or just
//  return (analogueRead(pinPeriod_ADC));
}

int get_duty(){
// your pin reads here...

  int dc_ADC = random(1024);  // test code
  Serial.print ("\nduty_ADC = ");
  Serial.println (dc_ADC);
  return (dc_ADC);

// or just
// return (analogueRead(pinDuty_ADC));
}

This simplifies main() and gives us:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>

// global variables defining number of ticks on and off (global for ISR)
uint8_t on_time_ticks = 124;
uint8_t off_time_ticks = 160;
uint8_t csxx_bits = 0; 

const int clock_frequency = 16000000; // 16 MHz
int prescaler = 1; // change this is to the correct value (not a const, as we may make this variable later on) 
int ticks_per_second = clock_frequency / prescaler; // 16M? or 16/N? (not a const as we may change prescaler in the future)
int ticks_per_millisecond = ticks_per_second / 1000;

// change as appropriate
const int pinPeriod_ADC = A0;
const int pinDuty_ADC = A1;

void convert_abs (double &p_ms, double &duty_pcnt){
  p_ms = (2*prescaler*(1 + (1024-p_ms)/4))/clock_frequency/1000; //period for given frequency
  duty_pcnt = 100 * duty_pcnt/1024;
}

void adjust_inputs (double p_ADC, double &period_ms, double dc_ADC, double &duty) {
  convert_abs(p_ADC,dc_ADC);
  period_ms = p_ADC;
  duty = dc_ADC; 
}

int get_period(){
// your pin reads here...
  return (analogRead(pinPeriod_ADC));
}

int get_duty(){
// your pin reads here...
  return (analogRead(pinDuty_ADC));  
}

void calc_on_off_abs_derived(int period, int duty_cycle){ 
  float p_s = 2*prescaler*(1 + period/4)/clock_frequency; //period for given frequency
  int total_ticks = ticks_per_second * p_s;
  on_time_ticks = total_ticks * (duty_cycle/1024);
  off_time_ticks = total_ticks - on_time_ticks; 
}

void calc_on_off_mspcnt(double p_ms, double duty){
  double total_ticks = ticks_per_millisecond * p_ms;
  on_time_ticks = total_ticks * (duty/100);
  off_time_ticks = total_ticks - on_time_ticks; 
}

void setup_timer(double p_ms, double duty){
// DDRB |= (1 << 3); // ATmega16
 DDRD |= (1 << 6); // ATmega328 

  TCCR0A = _BV(COM0A0); // toggle OC0A on Compare Match
  TCCR0B = _BV(WGM01); // set CTC mode WGM0[2,1,0] = 0b010

  // ... do some stuff based on your CPU frequency
  // to define the csxx_bits of TCCR0B when the timer is running
  // and consequently, to set on_time_ticks and off_time_ticks

  calc_on_off_mspcnt(p_ms, duty);
  OCR0A = on_time_ticks;
}

void start_timer(){
  //start the timer running at the desired rate
  TCCR0B |= ( (0 << CS02) | (0 << CS01) | ( 1 << CS00)); // CS02:0 - No prescaling - Set as required
  sei();
}

void stop_timer(){
  TCCR0B = csxx_bits;
}

int main(int argc, char **argv){
  double period_ms = 50, duty_cycle = 50;

  setup_timer(period_ms, duty_cycle);
  start_timer();  
  for(;;){
    //spin or sleep or whatever
    adjust_inputs(get_period(), period_ms, get_duty(), duty_cycle);
    calc_on_off_mspcnt(period_ms, duty_cycle);
  }
}

ISR(TIM0_COMPA_vect){
  if(OCR0A == on_time_ticks){
    OCR0A = off_time_ticks;
  }
  else{
    OCR0A = on_time_ticks;
  }
}

Now, that works as it should! The period and duty cycle can be varied.

After that, it is all, pretty much, plain sailing. You poll your inputs (on the ADC input pins), from potentiometers used to set the period and the duty cycle, in the same way as an APC operates. From those input readings you adjust your period_ms and duty_cycle  (either ranging both variables from 0-1024, or as milliseconds and a percentage respectively), and then call calc_on_off() (there is no need to call setup_timer() again). And so on, in the for loop.

However, we could still improve on it further…

Changing the prescaler

The frequencies will be fixed within a range as determined by the prescaler, N, which is set by the CS02:0 bits. It is possible (and might be necessary) to change these bits, and hence the prescaler, in order to obtain a sufficiently wide frequency range.

First we need a multidimensional array to bind the bits to the prescaler values, from Table 12-9 of the datasheet, where the array members are of the form ​[index, value, CS02, CS01, CS00]:

const int prescale_values_bits[][4] = { {    0, 0, 0, 0 },
                                        {    1, 0, 0, 1 },
                                        {    8, 0, 1, 0 },
                                        {   64, 0, 1, 1 },
                                        {  256, 1, 0, 0 },
                                        { 1024, 1, 0, 1 },
                                        {  666, 1, 1, 0 },
                                        {  777, 1, 1, 0 }};

Here I am using 666 and 777 as markers for the external clock settings. These lines could be omitted.

Now, start_timer() becomes

void start_timer(int index){ //start the timer running at the desired rate
  TCCR0B |= ( (prescale_values_bits[index][1] << CS02) | (prescale_values_bits[index][2] << CS01) | (prescale_values_bits[index][3] << CS00)); // CS02:0 - No prescaling - Set as required 
  sei();
}

Looking at that it is a bit unintuitive, and it could be better to reverse the elements of the array to be  ​[index, CS00, CS01, CS02, value]. But then the array definition would look weird.

To change the frequency you could use:

  • A resistive ladder and one ADC input
  • Three data input pins and three switches, to emulate the CS02:0 bits (only five states needed)
  • Two data input pins and two switches, thus discarding one of the prescaler values (presumably 001 as it will not be audible anyway. See Which frequencies for which prescale.
  • Just one data input pin and one switch and just settle on the two, most appropriate, frequency ranges
  • Most ambitiously, you could use the signal frequency potentiometer, and divide up the input range by four (or five) parts, so that the scalar is set using those input ranges, and the actually frequency adjust is now done within a narrower range of input (0-255, 256-511, 512-767, 768-1024). The advantage is that the frequency ranges would then by incrementally contiguous (without drastic frequency changes when the scaler is changed using switches), but you would lose resolution of the frequencies within those ranges.

Then you would just need to read the switch, or what have you, to detect a change, and upon a change, you set the index appropriately and call start_timer(int index) with the new index.

Assuming we are using three pins to emulate the CS02:0 bits

const int pinCS00 = 1;
const int pinCS01 = 2;
const int pinCS02 = 3;

Reading the scalar input on those pins

int read_scalar_input(){
  int index=0;

  index+=digitalRead(pinCS00);
  index+=digitalRead(pinCS01)*2;
  index+=digitalRead(pinCS02)*4;
  return index;
}

Checking if the inputs have changed:

boolean index_changed(int &index){
  boolean changed = false;

  int new_index = read_scalar_input(); 
  if (new_index != index) {
    changed = true;
    index = new_index;
  }
  return (changed);
}

Calling that, in main() with the following code, which resets the timer and updates the new prescaler value:

  if (index_changed(index)) {
    start_timer(index);
    prescaler = prescale_values_bits[index][0];
    ticks_per_second = clock_frequency / prescaler;
    ticks_per_millisecond = ticks_per_second / 1000;
  }

So, adding the scaler checking code gives:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>

// global variables defining number of ticks on and off (global for ISR)
uint8_t on_time_ticks = 124;
uint8_t off_time_ticks = 160;
uint8_t csxx_bits = 0; 

const int clock_frequency = 16000000; // 16 MHz
int prescaler = 1; // change this is to the correct value (not a const, as we may make this variable later on) 
int ticks_per_second = clock_frequency / prescaler; // 16M? or 16/N? (not a const as we may change prescaler in the future)
int ticks_per_millisecond = ticks_per_second / 1000;

// change as appropriate
const int pinPeriod_ADC = A0;
const int pinDuty_ADC = A1;

const int prescale_values_bits[][4] = { { 0, 0, 0, 0 },
                                        { 1, 0, 0, 1 },
                                        { 8, 0, 1, 0 },
                                       { 64, 0, 1, 1 },
                                      { 256, 1, 0, 0 },
                                     { 1024, 1, 0, 1 },
                                      { 666, 1, 1, 0 },
                                      { 777, 1, 1, 0 }}; 

// change as appropriate
const int pinCS00 = 1;
const int pinCS01 = 2;
const int pinCS02 = 3;

int read_scalar_input(){
  int index=0;

  index+=digitalRead(pinCS00);
  index+=digitalRead(pinCS01)*2;
  index+=digitalRead(pinCS02)*4;
  return index;
}

boolean index_changed(int &index){
  boolean changed = false;

  int new_index = read_scalar_input(); 
  if (new_index != index) {
    changed = true;
    index = new_index;
  }
  return (changed);
}

void convert_abs (double &p_ms, double &duty_pcnt){
  p_ms = (2*prescaler*(1 + (1024-p_ms)/4))/clock_frequency/1000; //period for given frequency
  duty_pcnt = 100 * duty_pcnt/1024;
}

void adjust_inputs (double p_ADC, double &period_ms, double dc_ADC, double &duty) {
  convert_abs(p_ADC,dc_ADC);
  period_ms = p_ADC;
  duty = dc_ADC; 
}

int get_period(){
// your pin reads here...
  return (analogRead(pinPeriod_ADC));
}

int get_duty(){
// your pin reads here...
  return (analogRead(pinDuty_ADC));
}

void calc_on_off_abs_derived(int period, int duty_cycle){ 
  float p_s = 2*prescaler*(1 + period/4)/clock_frequency; //period for given frequency
  int total_ticks = ticks_per_second * p_s;
  on_time_ticks = total_ticks * (duty_cycle/1024);
  off_time_ticks = total_ticks - on_time_ticks; 
}

void calc_on_off_mspcnt(double p_ms, double duty){
  double total_ticks = ticks_per_millisecond * p_ms;
  on_time_ticks = total_ticks * (duty/100);
  off_time_ticks = total_ticks - on_time_ticks; 
}

void setup_timer(double p_ms, double duty){
// DDRB |= (1 << 3); // ATmega16
 DDRD |= (1 << 6); // ATmega328 

  TCCR0A = _BV(COM0A0); // toggle OC0A on Compare Match
  TCCR0B = _BV(WGM01); // set CTC mode WGM0[2,1,0] = 0b010

  // ... do some stuff based on your CPU frequency
  // to define the csxx_bits of TCCR0B when the timer is running
  // and consequently, to set on_time_ticks and off_time_ticks

  calc_on_off_mspcnt(p_ms, duty);
  OCR0A = on_time_ticks;
}

void start_timer(int index){ //start the timer running at the desired rate
 TCCR0B |= ( (prescale_values_bits[index][1] << CS02) | (prescale_values_bits[index][2] << CS01) | (prescale_values_bits[index][3] << CS00)); // CS02:0 - No prescaling - Set as required 
  sei();
} 

void stop_timer(){
  TCCR0B = csxx_bits;
}

int main(int argc, char **argv){
  double period_ms = 50, duty_cycle = 50;
  int index = 0;  // start with clock off

  index_changed(index);  // set index according to inputs
  setup_timer(period_ms, duty_cycle);
  start_timer(index);  
  for(;;){
    //spin or sleep or whatever

    if (index_changed(index)) {
      start_timer(index);
      prescaler = prescale_values_bits[index][0];
      ticks_per_second = clock_frequency / prescaler;
      ticks_per_millisecond = ticks_per_second / 1000;
    }

    adjust_inputs(get_period(), period_ms, get_duty(), duty_cycle);
    calc_on_off_mspcnt(period_ms, duty_cycle);
  }
}

ISR(TIM0_COMPA_vect){
  if(OCR0A == on_time_ticks){
    OCR0A = off_time_ticks;
  }
  else{
    OCR0A = on_time_ticks;
  }
}

Which frequencies for which prescale?

Given the maximum and minimum frequencies are:

Fmin = Fclock/(2*N*256)

and

Fmax = Fclock/(2*N)

These are the frequencies available for the different scaler values, for a 16 MHz clock

  • N=1: 4 MHz – 31.25 kHz
  • N=8: 1 MHz – 3.9 kHz
  • N=64: 125 kHz – 488 Hz
  • N=256: 31.25 kHz – 122 Hz
  • N=1024:  7.812 kHz – 30 Hz

Further improvements

  • No real need for double in some cases, int would be fine, although there may be some loss in accuracy, using int.
  • adjust_inputs() and convert_abs() could be combined
  • Having both ticks_per_second and ticks_per_millisecond seems a bit of a waste
  • There is a very slight delay in the changing of the period in the timer, from when it is detected at the ADC.  The setting of the change in period will only occur when the wave changes state, so there is a maximum delay of half of the current period (or whatever duty cycle is set to, and whether it is on the HIGH or the LOW phase of the square wave.
  • There may be some loss in accuracy in the duty cycle by converting to percentages. Maybe just use the absolute ADC values.
  • There may be some loss in accuracy in the period by converting to milliseconds/seconds. Maybe just use the absolute ADC values.
  • Using Timer0 is dangerous in the Arduino context (although fine in a independent AVR µController), in as much that the millis() function uses it. Or rather, in this case it can’t, as we have disabled it (is this entirely true? See Adjust time calculation after Timer0 frequency change). See also:

Musings and Doubts

From page 100 of datasheet:

However, changing TOP to a value close to BOTTOM when the counter is run- ning with none or a low prescaler value must be done with care since the CTC mode does not have the double buffering feature. If the new value written to OCR0A is lower than the current value of TCNT0, the counter will miss the compare match. The counter will then have to count to its maximum value (0xFF) and wrap around starting at 0x00 before the compare match can occur.

  • If, by some miracle the change in the ADC period reading is so drastic that the new value of either on_time_ticks or off_time_ticks is lower than where the OCR0A counter has counted to, then the counter will continue to count to 255 (or clock around?? Check datasheet?), until it changes the state of the wave then the output will toggle and the OCR0A will be set to off_time_ticks, even if the output was off and is now on.
    • This could be avoided by having an old_ticks_time set that only changes upon interrupt, after having been checked against OCR0A – or;
    • simply changing the == to >= in the ISR. This will make the change more immediate. However…
      • If the wave is HIGH and Time0 has surpassed the new value of on_time_ticks then it will stay high for the next period, instead of toggling? No, the edge case is as follows…
      • If the wave is LOW, and has a low duty cycle, then the Timer0’s count will probably be greater than the on_ticks_time and then OCR0A will be set to the off_ticks_time again! In effect, whilst the wave will toggle, it will end up using the off_ticks_time as a half period permanently, until the duty cycle is increased sufficiently so that the Timer finally exceeds the on_ticks_time and gets back into sync.  Therefore using >= is not such a good idea. Thus…
  • It is a good idea to add a boolean variable that sanity checks the state of the wave and/or read the OC0 register/pin, to ensure that everything is in sync, else the Timer0 may be HIGH for the off_ticks_time, or LOW for the on_ticks_time.
  • Likewise, never checking OCR0A against the low_ticks_time seems like a bad idea, for sync reasons. This could be avoided by having another Boolean variable that monitors whether the current wave state is high or low and toggles the use of on_ticks_time and off_ticks_time accordingly… maybe used in conjunction with the old_ticks_timer suggestion above.

Rethinking some of the above

  • If, by some miracle the change in the ADC period or duty cycle reading is so drastic that the new value of either on_time_ticks or off_time_ticks is lower (or just not equal to) than where the OCR0A counter has counted to, then the OCR0A will be set to the off_ticks_time regardless of whether the wave has been toggled to LOW or HIGH. Therefore it will be out of sync. This could be avoided by having an old_ticks_time set that only changes upon interrupt, after having been checked against OCR0A – or simply changing the == to >= in the ISR. This will make the change more immediate. However…
  • By comparing the OCR0A only against the on_ticks_time, it will, if the ​​off ticks_time is higher than the ​on_ticks_time, i.e duty cycle < 50%, only use the off_ticks_time as half period time.

Further thinking:

  • The changes don’t have to be drastic, to cause an issue. If either the period or duty cycle are changed, even slightly, then at the time of the interrupt, the on_time_ticks will no longer equal the OCR0A count, and the OCR0A register will be set to off_time_ticks, regardless of whether the square wave was previously HIGH or LOW. Why does this matter? Because the ON and OFF times will get out of sync with the use of on_time_ticks and off_time_ticks, possibly permanently, until some fortuitous change, puts them back into sync.

All of these issues make me think that the original premise of using CTC and interrupts is suspect, simply because CTC is designed to only give 50% duty cycle. Messing about with the OCR0Amid-count, or mid-period, or end of period mid count is bound to have some issues. However, page 100 of the datasheet states (emphasis is mine):

An interrupt can be generated each time the counter value reaches the TOP value by using the OCF0A Flag. If the interrupt is enabled, the interrupt handler routine can be used for updating the TOP value.

So, it is officially sanctioned. However, it seems much more sensible to use… phase-correct PWM with OCRnA top, as this method provides for a stated duty cycle in OCR0B. See Secrets of Arduino PWM – Ken Shirriff’s blog

Using old_on_time_ticks and old_off_time_ticks

Using old_on_time_ticks and old_off_time_ticks, which are defined as additional global variables, the ISR becomes:

ISR(TIM0_COMPA_vect) {
  if(OCR0A == old_on_time_ticks) {
    OCR0A = off_time_ticks;
    old_off_time_ticks = off_time_ticks;
  } else if(OCR0A == old_off_time_ticks) {
    OCR0A = on_time_ticks;
    old_on_time_ticks = on_time_ticks;
  } else {
    // we have a problem
  }
}

This setup works better, although if on_time_ticks == off_time_ticks i.e. a duty cycle of 50% then confusion can still occur, as the wave may have been in the LOW phase and will encounter the ON time conditional first, thus resulting in off_time_ticks being assigned to OCR0A when it should be on_time_ticks. Also, it is not clear what would happen in the case of an error.

An additional reading of the previous/current state of the output (OC0A), or a software Boolean variable, is required to keep track. Reading the pin is the best idea, see How to read OC0A when in CTC (toggle) mode?, using bitRead(PINB,3);  (for the Atmega16), bitRead(PIND,6);  (for the Atmega328) or preferably PIND & _BV(PD6) – remembering that by the time the ISR is called the output will have already toggled:

ISR(TIM0_COMPA_vect) {
//  if(bitRead(PINB,3)) { // ATmega16
  if(PIND & _BV(PD6)) { // ATmega328
    OCR0A = off_time_ticks;
  } else {
    OCR0A = on_time_ticks;
  }
}

This is much more reliable.

Phase-correct PWM with OCRnA top

Now, as noted previously, in Attempt #2 in Noisey chips, using phase-correct PWM with OCRA top, the duty cycle can be varied.

Note the ATmega16 only has Timer1 that supports phase-correct PWM with OCRnA top, and the specified duty cycle, page 101 of the datasheet.

According to page 151 of the ATmega328 datasheet Timer2 only supports 50% duty cycle in Fast PWM mode – it is not mentioned in the Phase-correct PWM section. This leads one to question the accuracy of Ken Kirriff’s blog (unless it really is a secret!)

Using Timer2:

void setup() {
  pinMode(3, OUTPUT); // Set as output - Timer2
  pinMode(11, OUTPUT); // Set as output - Timer2
  TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(WGM22) | _BV(CS22); // prescale 64
  OCR2A = 53; // top limit for 8 kHz
  OCR2B = 27; // 50% duty cycle for output B (output A is fixed at 50%)
}

Now we take the code from the previous example (minus the ISR as this is not needed) and mold it around this setup(). Note that we set the value of the OCR2A and OCR2B registers, in set_period_duty(), upon every iteration of the for loop in main(). We will leave on_ticks_time and off_ticks_time as globals for the moment:

void set_period_duty() {
  OCR2A = on_ticks_time + off_ticks_time; // period (in ticks)
  OCR2B = on_ticks_time; // on time (in ticks) 
}

void setup_timer(double p_ms, double duty) {
  pinMode(3, OUTPUT); // Set as output - Timer2
  pinMode(11, OUTPUT); // Set as output - Timer2
  TCCR2A = _BV(COM2A0) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(WGM22) // not setting prescale here | _BV(CS22); // prescale 64
  calc_on_off_mspcnt(p_ms, duty);
  set_period_duty();
}

void start_timer(int index) { //start the timer running at the desired rate
  TCCR2B |= ( (prescale_values_bits[index][1] << CS22) | (prescale_values_bits[index][2] << CS21) | (prescale_values_bits[index][3] << CS20)); // CS02:0 - No prescaling - Set as required
  sei();
} 

void stop_timer() {
  TCCR0B = csxx_bits;
}

main() {

  double period_ms = 50, duty_cycle = 50;
  int index = 0; // start with clock off
  index_changed(index); // set index according to inputs
  setup_timer(period_ms, duty_cycle);
  start_timer(index); 

  for (;;){
    //spin or sleep or whatever

    if (index_changed(index)) {
      start_timer(index); 
      prescaler = prescale_values_bits[index][0];
      ticks_per_second = clock_frequency / prescaler;
      ticks_per_millisecond = ticks_per_second / 1000;
    } 
    adjust_inputs(get_period(), period_ms, get_duty(), duty_cycle);
    calc_on_off_mspcnt(period_ms, duty_cycle); 
    set_period_duty();
  }
}

Note that the frequencies are divided by two, when using phase correct PWM (as compared to CTC (is that right? Check equations in datasheet), so adjustment is required in set_period_duty(), and the period should be divided by 2, to counter the reduction in frequency:

void xxxxxx() {
  on_time_ticks /=2;
  off_time_ticks /=2;
}

However, as we aren’t creating a frequency sensitive device, but rather a random tune, then it doesn’t really matter – but it is worth bearing in mind, that the frequency ranges as specified in Which frequencies for which prescale? (above) for the various values of the scaler will be different (by a factor of two, i.e. half of those stated), unless the above adjusted is performed.

blah

Musings and Doubts

There are also issues with this approach. As pointed out in section 15.7.4 Phase Correct PWM Mode, of the ATmega328 datasheet, if the change in OCR2A occurs when the counter is beyond that value then the match will be missed for that cycle:

  • The timer starts counting from a value higher than the one in OCR2A, and for that reason misses the Compare Match and hence the OCn change that would have happened on the way up.

The change really needs to be made at the change in phase (i.e. the toggling of the output). It is possible to generate an interrupt after one complete cycle, when the counter reaches BOTTOM:

The Timer/Counter Overflow Flag (TOV2) is set each time the counter reaches BOTTOM. The Interrupt Flag can be used to generate an interrupt each time the counter reaches the BOTTOM value.

Such an interrupt would provide an ideal time to set the new values of OCR2A and OCR2B. Enabling the interrupt (page 163):

TIMSK2 |= (1 << TOIE2);

and the ISR is as follows (note that there is no conditional this time, compared to the CTC with interrupts version):

ISR(TIM2_OVF_vect){
  OCR2A = on_time_ticks + off_time_ticks;
  OCR2B = on_time_ticks;
}

This will result in needless reassignments of the OCR values, admittedly.

 

blah

Hardware mode

Using a fully hardware mode as described in Newbie’s Guide to AVR Timers – Part Six – Pure Hardware CTC will not work, as this can only give a 50% duty cycle, i.e. a square wave, which is no good for an APC:

#include 
#include 

int main (void)
{
   DDRD |= (1 << 5); // Set LED as output

   TCCR1B |= (1 << WGM12); // Configure timer 1 for CTC mode

   TCCR1A |= (1 << COM1A0); // Enable timer 1 Compare Output channel A in toggle mode

   OCR1A   = 15624; // Set CTC compare value to 1Hz at 1MHz AVR clock, with a prescaler of 64

   TCCR1B |= ((1 << CS10) | (1 << CS11)); // Start timer at Fcpu/64

   for (;;)
   {

   }
}

 

blah

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s