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.
Resultant output verified by PicoScope:
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.