4

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.

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.

7
  • 1
    Have you tried using PWM outputs? Their frequency can be changed, see playground.arduino.cc/Code/PwmFrequency.
    – DaveP
    Feb 16, 2015 at 8:45
  • Presumably processor is crystal controlled? | Cannot the timer control a pin directly without interrupts? - or is that what is happening here ? (glancing through the code it's not obvious (to me) how it completes the function you are implementing.) If this is counter timer mode why are interupts requiired - does not the pin get happily toggled by the timer without invoking an interrupt. If so, an IRQ may add variable latency to the task. Feb 16, 2015 at 11:25
  • 1
    @DaveP, I don't believe I can scale the frequency down to what I need. With the digitalWrite I'm using, basically it creates a PWM from scratch.
    – Ramrod
    Feb 16, 2015 at 16:37
  • @RussellMcMahon I missed a snippet of code when I pasted it originally - please see the updated code. I thought about just running the toggle method through a loop, but wouldn't that potentially tie up the Arduino from doing anything else? Although, if I can rely on it to generate an accurate signal I suppose I wouldn't need the Arduino to do anything else.
    – Ramrod
    Feb 16, 2015 at 16:40
  • You can't have the arduino measure the timer1 generated signal. The clock for timer one is the same as that of the processor, so it will always tell it's right on the money. You'd have to tune it using some external measurement. By the way, I don't get why you aren't using the option of having timer1 toggle the pin, unless there is a need to use pin 13 instead of 9 or 10.
    – Gerben
    Feb 16, 2015 at 19:34

3 Answers 3

3

You can use a timer/counter to output a square wave signal of your choosing without processor intervention or use of interrupts.

In data sheet
"ATmega48A/PA/88A/PA/168A/PA/328/P"Atmel-8271I-AVR- ATmega-Datasheet_10/2014

page 142 it says

  • The double buffered Output Compare Register (OCR2A and OCR2B) are compared with the Timer/Counter value at all times. The result of the compare can be used by the Waveform Generator to generate a PWM or variable frequency output on the Output Compare pins (OC2A and OC2B). See ”Output Compare Unit” on page 143 for details. The compare match event will also set the Compare Flag (OCF2A or OCF2B) which can be used to generate an Output Compare interrupt request.

The interrupt CAN be generated and used but is optional.
It looks like relevant material extends as far as page 159.

1
  • Thanks Russell, I've been struggling with this and that seems to be the right direction to head. I've been reading over the documentation and I think I about have it ironed out.
    – Ramrod
    Feb 21, 2015 at 4:26
1

I know this thread may be dead but if it happens to somebody, the aformentioned method works perfectly fine. You just need to disable timer0 (power_timer0_disable();) and then your 40kHz signal will not fluctuate (in frequency).

In any case, yeah try to avoid digitalWrite if you want speed.

1

PWM is the solution.

Russel's answer is proper. My contribution here is a simplification and to relay my observations. I made a QA about PWM recently and can affirm that "bit banging" and interrupts are not going to be stable. I observed digital writes to be roughly 1 microsecond, so toggling a pin is 2 µsec. This did not affect my project, so no further tests performed about the digitalWrite() function. Writing changes to the port macro as a whole is much faster than using digitalWrite(), however this still will not have the inherent stability setting up the PWM and its output pin will provide. Anecdotally, analogRead() is roughly 100 µsec.

I set up my timer with a prescaler and then applied a count to OCRxA. Via my prescaler and WGM bits, it turns out that the OCR registers were 1:1 for microseconds. OCRxB perfectly set the pulse width I was seeking.

Observing the period on an o'scope, it might have been off from the exact integer in OCRxA but did not drift. The error I observed was in the tens of microseconds and the values in OCRxB were between 6300 and 63000. Code examples are in aforementioned link to my QA. From researching my own solution, I learned this error is likely within tolerance of the crystal providing CPU freq.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.