Trouble simulating high-frequency sine-wave using PWM

Hello there!

My goal is to simulate a sine wave of variable frequency buy utilising PWM on the ATmega2560 microcontroller. To do so, I chose a fixed number of evenly spaced samples of the sine wave period, in my case 6, and calculated their values using a for loop, eventually storing them in a array called sine.

This array actually repesents duty cycles of the PWM, so a value of 1 means 100% duty cycle and so on. Next, I created another array for storing the desired frequencies of the sine wave. That would be 16 different frequencies, ranging from 1000Hz to 15000Hz with a step of 1000Hz. For testing purposes (because I don't have an oscilloscope at home), I set the first frequency in the array to be 10Hz.

The frequencies are selected by using a potentiometer connected to the analog pin A0. To reduce the influence of noise, I shifted the digital result of the analogRead function by 6 bits to the right in order to get 16 possible digital values, which are then linked to the number of a element in the frequency array.

Finally, I used Timer1 in fast-PWM non-inverted mode, with the ICR1 register representing the TOP value and the OCR1B register representing the compare value, to generate a PWM signal of certain frequency. Note that in order to get the sine wave of the desired frequency, the frequency of the PWM must be higher by the number of samples my sine wave is using, so in my case 6 times higher. That means a sine wave of 1000Hz would be "carried" on a 6000Hz PWM wave.

So, each time the timer counter reaches the ICR1 value, the ISR changes the duty cycle by reading the value of the next element in the sine array and thus setting the OCR1B register value. If the frequency has changed, it instead changes the ICR1 register value.

Unfortunately one error came up, which I am not able to understand. When testing at test frequency of 10Hz, which equals a PWM frequency of 60Hz, because of the 6 samples per period sine wave, everything seemed to work fine, but when I turned the potentiometer to increase frequency above 1000Hz, I could still see my LED blinking. I went on to inspect the ICR1 register values using the serial monitor and found out that it's value increases and eventually overflows with increasing frequency, although it should be decreasing. This is contrary to the formula I found and used in the datasheet for calculating the ICR1 TOP value for fast-PWM mode:

Fwanted = Fsystem/(2N(1+ICR1)), where N is the prescaler.

The ICR1 register is a 16-bit register, and the TOP value at 10Hz sine wave, i.e. 60Hz PWM and a TCCR1B prescaler of N=8, is 33332, which is also confirmed by the serial monitor.

Any suggestions what could be wrong with my code are welcomed!

Thank you!

//number of samples per period of sine wave
#define NS 6        
#define pi 3.141592

float sine[NS];
int freq[16];

int k = 0;

int potentiometer;
int frequency_new;
//a value, which is not in array, in order to trigger frequency change at start of program
int frequency_old = 500;

long int SystemCoreClock = 16000000;

void setup(){

  Serial.begin(9600);
  
  pinMode(12, OUTPUT);
  pinMode(A0, INPUT);

  //array of duty cycle values (1, 0.75, 0.25, 0, 0.25, 0.75)
  for(int i=0; i<NS; i++){ 
    sine[i] = 1*0.5*(sin((2*pi/NS)*i + pi/2)+1);
  }

  //array of sine frequency values (8, 1000, 2000, ..., 15000)
  freq[0] = 8;             
  for(int j=1; j<16; j++){
    freq[j] = j*1000;    
  }
  
  TCCR1A = 0;
  TCCR1B = 0;

  //enable non-inverting mode, enable fast-PWM mode, set prescaler to 1
  TCCR1A |= (1 << COM1B1) | (1 << WGM11);             
  TCCR1B |= (1 << WGM13) | (1 << WGM12) | (1 << CS11);  

  TCNT1 = 0;                

  ICR1 = 0;

  OCR1B = 0;

  TIMSK1 = 0; 
  TIMSK1 |= (1 << TOIE1);  //enable timer1 overflow interrupt

  sei(); //enable global interrupts
}

ISR(TIMER1_OVF_vect){
  //when changing PWM frequency, set new TOP value (ICR1)    
  if(frequency_new != frequency_old){
    ICR1 = SystemCoreClock/((frequency_new*NS)*8) - 1;
    OCR1B = sine[0]*ICR1;
    frequency_old = frequency_new;
  }
  //else, set new duty cycle for next pwm period
  else{
    if(k < NS-1){
      k++;
    }
    else{
      k=0;
    }
    //compare value = duty cycle*TOP value
    OCR1B = sine[k]*ICR1;
  }
}

void loop() {
  //get 16 possible digital values from potentiometer
  potentiometer = analogRead(A0) >> 6;

  //read appropriate frequency value from frequency array
  frequency_new = freq[potentiometer];

  Serial.println(ICR1);
}  

There is a very different well-known way to synthesize waveforms using computers.

So maybe if no interest, if you've seen it and rejected it, take a look at this thread

on your coffee break if you haven't.

The code is all available, so you could repurpose it if it goes about doing too much more than you want, or you want to make it your own.

a7

Note: CS11 sets prescale to 8. You want CS10 to set prescale to 1

Note: Once you set the CS bits to a non-zero value the timer will run and start generating interrupts. Best to save that for last.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.