Skip to main content
Arduino

Return to Question

Tweeted twitter.com/StackArduino/status/1164598117641138178
Updated with full solution
Source Link
Ramrod
  • 187
  • 1
  • 1
  • 5

EDIT: @RussellMcMahon pointed me in the right direction for this, so I accepted his answer. However, I wanted to share my final code and my misconceptions I encountered. Hopefully this will assist someone else in the future. I'm obviously by no means an expert, and I will try to not give any misleading information here.

My original goal was, although poorly stated, to generate as clean a 40KHz PWM with 50% duty cycle as possible.

Problems with my original code above were:

  • Using an 8-bit pre-scaler
  • Using an interrupt
  • Using digitalWrite()

8-Bit Pre-Scaler
The problem here was that the counter only ticked over once every 8 clock cycles. 16MHz clock / 8 (pre-scaler) = 2MHz clock When I asked one of my professors about this he stated something along the lines of "a cycle could be missed due to an interrupt, and would cause it to wait 7 more cycles to 'tick'". Indeed I was originally using interrupts, and when we did the calculations it seemed like it could plausibly cause up to a a 1KHz swing at 40KHz.
To rectify this I took out the pre-scaler, and ran straight at the base clock speed to increase resolution.

Using an interrupt
Essentially the interrupt could cause clock cycles to not be counted, further complicated by using an 8-bit pre-scaler. If one clock cycle was missed, with a 2MHz clock (after pre-scale), you add 5E-7 seconds to the counter, changing the output frequency.

Using digitalWrite()
The digitalWrite function is pretty slow. I'm not a guru, but I read this in multiple places and there seems to be a good comparison of methods here: Digital Pin Oscillation To get around this problem it had been suggested I toggle a single bit, which is what I set out to do in my final code. (It is worth noting, now that I've completed this project, that the previous link also makes reference to a library addition for faster digitalWrite/Read/etc....)

I went through a few variations and scrounged code from all over, and made a couple working examples but they didn't do it the way I wished. I believe part of the project I based this version off of was a melding between a previous version I had working and a class example I found online.

Without further ado, I present the code that took me an embarrassing amount of time to write from numerous sources... I really should tinker with this stuff more often:

#include <avr/io.h>
#include <avr/interrupt.h>
void setup()
{
//Disable interrupts
cli();
// Clear Timer1 registers
TCCR1A = 0;
TCCR1B = 0;
// Set OCR1A (TOP): 16MHz/40KHz /2 = 200 steps. 
// Divide 200 by 2 = 100 because waveforms are centered around OCR1A (because toggle)
OCR1A = 100;
// Configure Timer/Counter 1
// Select P & F Correct PWM Mode, and OCR1A as TOP. Use WGM Bits 10 and 13.
// WGM Bits are in TCCR1A and TCCR1B. Any previous settings are cleared.
// Enable COM1A0 to toggle OC1A on compare match
TCCR1A = _BV(WGM10) | _BV(COM1A0);
// CS10 to enable base clock without pre-scale 
TCCR1B = _BV(WGM13) | _BV(CS10);
//Set OC1A (Digital Pin 9 / Port B Pin 1) to output
DDRB |= _BV(1);
}
void loop()
{
}


For reference, here is the clock frequency formula. In this instance, I had to divide the result by 2 again since I am toggling.
Clock frequency formula


Resultant output verified by PicoScope: Picoscope Oscilloscope Trace


In closing, you will notice that it isn't exactly 40KHz. Research turned up that the Arduino Uno makes use of a less accurate ceramic resonator to clock the ATMEGA328P(although a 16MHz crystal oscillator is onboard to clock the USB). To further increase accuracy in a generated signal will require the Arduino to be driven with an external clock source.

EDIT: @RussellMcMahon pointed me in the right direction for this, so I accepted his answer. However, I wanted to share my final code and my misconceptions I encountered. Hopefully this will assist someone else in the future. I'm obviously by no means an expert, and I will try to not give any misleading information here.

My original goal was, although poorly stated, to generate as clean a 40KHz PWM with 50% duty cycle as possible.

Problems with my original code above were:

  • Using an 8-bit pre-scaler
  • Using an interrupt
  • Using digitalWrite()

8-Bit Pre-Scaler
The problem here was that the counter only ticked over once every 8 clock cycles. 16MHz clock / 8 (pre-scaler) = 2MHz clock When I asked one of my professors about this he stated something along the lines of "a cycle could be missed due to an interrupt, and would cause it to wait 7 more cycles to 'tick'". Indeed I was originally using interrupts, and when we did the calculations it seemed like it could plausibly cause up to a a 1KHz swing at 40KHz.
To rectify this I took out the pre-scaler, and ran straight at the base clock speed to increase resolution.

Using an interrupt
Essentially the interrupt could cause clock cycles to not be counted, further complicated by using an 8-bit pre-scaler. If one clock cycle was missed, with a 2MHz clock (after pre-scale), you add 5E-7 seconds to the counter, changing the output frequency.

Using digitalWrite()
The digitalWrite function is pretty slow. I'm not a guru, but I read this in multiple places and there seems to be a good comparison of methods here: Digital Pin Oscillation To get around this problem it had been suggested I toggle a single bit, which is what I set out to do in my final code. (It is worth noting, now that I've completed this project, that the previous link also makes reference to a library addition for faster digitalWrite/Read/etc....)

I went through a few variations and scrounged code from all over, and made a couple working examples but they didn't do it the way I wished. I believe part of the project I based this version off of was a melding between a previous version I had working and a class example I found online.

Without further ado, I present the code that took me an embarrassing amount of time to write from numerous sources... I really should tinker with this stuff more often:

#include <avr/io.h>
#include <avr/interrupt.h>
void setup()
{
//Disable interrupts
cli();
// Clear Timer1 registers
TCCR1A = 0;
TCCR1B = 0;
// Set OCR1A (TOP): 16MHz/40KHz /2 = 200 steps. 
// Divide 200 by 2 = 100 because waveforms are centered around OCR1A (because toggle)
OCR1A = 100;
// Configure Timer/Counter 1
// Select P & F Correct PWM Mode, and OCR1A as TOP. Use WGM Bits 10 and 13.
// WGM Bits are in TCCR1A and TCCR1B. Any previous settings are cleared.
// Enable COM1A0 to toggle OC1A on compare match
TCCR1A = _BV(WGM10) | _BV(COM1A0);
// CS10 to enable base clock without pre-scale 
TCCR1B = _BV(WGM13) | _BV(CS10);
//Set OC1A (Digital Pin 9 / Port B Pin 1) to output
DDRB |= _BV(1);
}
void loop()
{
}


For reference, here is the clock frequency formula. In this instance, I had to divide the result by 2 again since I am toggling.
Clock frequency formula


Resultant output verified by PicoScope: Picoscope Oscilloscope Trace


In closing, you will notice that it isn't exactly 40KHz. Research turned up that the Arduino Uno makes use of a less accurate ceramic resonator to clock the ATMEGA328P(although a 16MHz crystal oscillator is onboard to clock the USB). To further increase accuracy in a generated signal will require the Arduino to be driven with an external clock source.
Added missing code
Source Link
Ramrod
  • 187
  • 1
  • 1
  • 5

I'm trying to work out the best way to generate a stable frequency with my Uno R3

I'm using interrupts to generate an approximately 40KHz frequency which drives some ICs/MOSFETs to effectively generate 40KHz AC which powers a transducer.

I need to receive the frequency at a second transducer and calculate any frequency shifts.

  • The problem is that the generated frequency drifts a little bit off of 40KHz. Is there a way to lock it in at 40KHz via software? I'm receiving approximately 40KHz output (as measured using PicoScope software) with this code:

     #define LEDPIN 13
     void setup()
     {
     pinMode(LEDPIN, OUTPUT);
     // initialize Timer1
     cli(); // disable global interrupts
     TCCR1A = 0; // set entire TCCR1A register to 0
     TCCR1B = 0; // same for TCCR1B
     // set compare match register to desired timer count:
     OCR1A = 24;
     // turn on CTC mode:
     TCCR1B |= (1 << WGM12);
     // Set CS11 for 8-bit prescaler:
     TCCR1B |= (1 << CS11);
     // enable timer compare interrupt:
     TIMSK1 |= (1 << OCIE1A);
     // enable global interrupts:
     sei();
     }
     void loop()
     {
     // main program
     }
     ISR(TIMER1_COMPA_vect)
     {
     digitalWrite(LEDPIN, !digitalRead(LEDPIN));
     }
    
  • If there isn't a way to lock it in, is there atleast a way for the Uno to accurately display/store the frequency of the signal it generated at a given time?

    Or is this not plausible because the Uno would "think" it is generating 40KHz?

Thanks for any hints or suggestions.

I'm trying to work out the best way to generate a stable frequency with my Uno R3

I'm using interrupts to generate an approximately 40KHz frequency which drives some ICs/MOSFETs to effectively generate 40KHz AC which powers a transducer.

I need to receive the frequency at a second transducer and calculate any frequency shifts.

  • The problem is that the generated frequency drifts a little bit off of 40KHz. Is there a way to lock it in at 40KHz via software? I'm receiving approximately 40KHz output (as measured using PicoScope software) with this code:

     #define LEDPIN 13
     void setup()
     {
     pinMode(LEDPIN, OUTPUT);
     // initialize Timer1
     cli(); // disable global interrupts
     TCCR1A = 0; // set entire TCCR1A register to 0
     TCCR1B = 0; // same for TCCR1B
     // set compare match register to desired timer count:
     OCR1A = 24;
     // turn on CTC mode:
     TCCR1B |= (1 << WGM12);
     // Set CS11 for 8-bit prescaler:
     TCCR1B |= (1 << CS11);
     // enable timer compare interrupt:
     TIMSK1 |= (1 << OCIE1A);
     // enable global interrupts:
     sei();
     }
    
  • If there isn't a way to lock it in, is there atleast a way for the Uno to accurately display/store the frequency of the signal it generated at a given time?

    Or is this not plausible because the Uno would "think" it is generating 40KHz?

Thanks for any hints or suggestions.

I'm trying to work out the best way to generate a stable frequency with my Uno R3

I'm using interrupts to generate an approximately 40KHz frequency which drives some ICs/MOSFETs to effectively generate 40KHz AC which powers a transducer.

I need to receive the frequency at a second transducer and calculate any frequency shifts.

  • The problem is that the generated frequency drifts a little bit off of 40KHz. Is there a way to lock it in at 40KHz via software? I'm receiving approximately 40KHz output (as measured using PicoScope software) with this code:

     #define LEDPIN 13
     void setup()
     {
     pinMode(LEDPIN, OUTPUT);
     // initialize Timer1
     cli(); // disable global interrupts
     TCCR1A = 0; // set entire TCCR1A register to 0
     TCCR1B = 0; // same for TCCR1B
     // set compare match register to desired timer count:
     OCR1A = 24;
     // turn on CTC mode:
     TCCR1B |= (1 << WGM12);
     // Set CS11 for 8-bit prescaler:
     TCCR1B |= (1 << CS11);
     // enable timer compare interrupt:
     TIMSK1 |= (1 << OCIE1A);
     // enable global interrupts:
     sei();
     }
     void loop()
     {
     // main program
     }
     ISR(TIMER1_COMPA_vect)
     {
     digitalWrite(LEDPIN, !digitalRead(LEDPIN));
     }
    
  • If there isn't a way to lock it in, is there atleast a way for the Uno to accurately display/store the frequency of the signal it generated at a given time?

    Or is this not plausible because the Uno would "think" it is generating 40KHz?

Thanks for any hints or suggestions.

Source Link
Ramrod
  • 187
  • 1
  • 1
  • 5

Stable frequency generation

I'm trying to work out the best way to generate a stable frequency with my Uno R3

I'm using interrupts to generate an approximately 40KHz frequency which drives some ICs/MOSFETs to effectively generate 40KHz AC which powers a transducer.

I need to receive the frequency at a second transducer and calculate any frequency shifts.

  • The problem is that the generated frequency drifts a little bit off of 40KHz. Is there a way to lock it in at 40KHz via software? I'm receiving approximately 40KHz output (as measured using PicoScope software) with this code:

     #define LEDPIN 13
     void setup()
     {
     pinMode(LEDPIN, OUTPUT);
     // initialize Timer1
     cli(); // disable global interrupts
     TCCR1A = 0; // set entire TCCR1A register to 0
     TCCR1B = 0; // same for TCCR1B
     // set compare match register to desired timer count:
     OCR1A = 24;
     // turn on CTC mode:
     TCCR1B |= (1 << WGM12);
     // Set CS11 for 8-bit prescaler:
     TCCR1B |= (1 << CS11);
     // enable timer compare interrupt:
     TIMSK1 |= (1 << OCIE1A);
     // enable global interrupts:
     sei();
     }
    
  • If there isn't a way to lock it in, is there atleast a way for the Uno to accurately display/store the frequency of the signal it generated at a given time?

    Or is this not plausible because the Uno would "think" it is generating 40KHz?

Thanks for any hints or suggestions.

lang-cpp

AltStyle によって変換されたページ (->オリジナル) /