7

I want to generate a 15 kHz pulse with an Arduino using Timer1, but the problem is that if we want a 15000 Hz clock we need to initialize the timer with 1/15000 seconds or 66.66 microseconds, but we can only pass integers without any decimal precision in the Timer1.initialize(66); function which generates a frequency of 15155 Hz.

So is it possible by any other means to generate an exact 15000 Hz or frequencies between 15000 Hz - 15155 Hz?

#include <TimerOne.h>

void setup() {
  pinMode(9,OUTPUT);
  Timer1.initialize(66);  // Frequency, 100 µs = 10 kHz
  Timer1.pwm(9,255);
}
 
void loop() {}
11
  • 4
    No you can't, at least not with a standard Arduino Uno and no other components. Trouble is, an Arduino Uno runs at 16MHz, which is not evenly divisible by 15,000Hz. You can only ever get an approximation using internal timers (which are all based on the main clock or some fraction of it).
    – StarCat
    Jan 19, 2021 at 7:49
  • 4
    Using a PIC32MX1xx or 2xx series running at 40MHz it's possible to generate 14999.992675785Hz using the reference clock output - that is, the system clock divided by (2*(1333+(171/512))).
    – Majenko
    Jan 19, 2021 at 11:58
  • 12
    @StarCat while technically correct, it's kind of misleading, as I double OP will care if it's only a very tiny bit of. Dividing 16Mhz by 1067 would give you 14995Hz. Which is only 0.03% off. The 16Mhz crystal on the UNO probably has a higher error than that.
    – Gerben
    Jan 19, 2021 at 14:19
  • 2
    @Gerben, I realized that the OP was interested in a close(r) approximation after I'd already typed my comment. It was not meant to be misleading.
    – StarCat
    Jan 19, 2021 at 19:15
  • 7
    There is no such thing as "exact", only "to within tolerance", which you haven't really specified (unless you mean 15077.5±77.5). Jan 19, 2021 at 21:14

6 Answers 6

20

You can get pretty close if you program Timer 1 directly (not through the library), and have it run with the prescaler set to 1. Ideally, you want the period of the timer in clock cycles to be:

F_CPU / 15 kHz = 16,000 kHz / 15 kHz ≈ 1066.67 CPU cycles

If you round this to the nearest integer, you get

F_CPU / 1,067 = 16,000 kHz / 1,067 ≈ 14.9953 kHz

This is about 0.03% too slow, well within the tolerance of the ceramic resonator clocking the Uno.

Here is my attempt at this. I have tested it.

constexpr float PWM_FREQUENCY = 15e3;  // 15 kHz
constexpr uint16_t PERIOD = round(F_CPU / PWM_FREQUENCY);

void setup() {
    // Configure Timer 1 for 15 kHz PWM on pin 9 = PB1 = OC1A.
    DDRB  |= _BV(PB1);    // set pin as output
    TCCR1B = 0;           // stop timer
    TCCR1A = _BV(COM1A1)  // non-inverting PWN on OC1A
           | _BV(WGM11);  // mode 14: fast PWM, TOP = ICR1
    TCCR1B = _BV(WGM12)   // ditto
           | _BV(WGM13)   // ditto
           | _BV(CS10);   // clock @ F_CPU
    ICR1   = PERIOD - 1;  // period
    OCR1A  = PERIOD / 4;  // duty cycle
}

void loop(){}
3
  • What is the accuracy of the ceramic resonator? 0.5%? Jan 21, 2021 at 18:57
  • @PeterMortensen: Typical tolerance is 0.5%. The tolerance is the worst-case frequency error. Typical error is more like 0.1%. Jan 21, 2021 at 20:37
  • @PeterMortensen: If you calibrate the frequency error, you can adjust PERIOD to compensate for it. You then have about 0.1% resolution in the calibrated frequency. Jan 21, 2021 at 20:41
10

Since the timer1 library only accepts whole numbers for the µs parameter you get a error. You could skip using the library and configure the timer directly. Or you could have a look at the source code of the library, and see that you can kind of bypass the limitation it has by only calculating a more accurate value for the ICR1 register.

Look at cycles = ((F_CPU/100000 * microseconds) / 20);. If you were to insert your 66.66666µs into this, instead of 66µs, you'd get 533 instead of 528.

This value is then used to set the PWM frequency in line 213. So you'd need to overwrite the ICR1 register with the more accurate value.

Your final code should look like:

pinMode(9,OUTPUT);
Timer1.initialize(66);
ICR1 = 533;
Timer1.pwm(9,255);

PS you might need to tweak this 533 value to get an even more accurate resulting frequency.

2
  • Can you please describe the role of ICR1 register here? I need to know how is it working with timer1.
    – astrick
    Jan 22, 2021 at 5:31
  • 1
    Timer one keeps incrementing it counter (TCNT1) till it reaches ICR1. By increasing ICR1 by 5 is will take a bit longer to count to that, and thus give you a slightly lower frequency, than the 15155Hz you were getting. Hope that explains it a bit better. If not, feel free to let me know.
    – Gerben
    Jan 22, 2021 at 14:12
6

A better way to do the above average 15 kHz (or any other frequency) is with a phase accumulator scheme. There are no IF tests; on each tick of an interrupt, you add a step to an accumulator and output the state of its MSB. This can give incredible resolution, and is probably the best you can do. But though the average frequency will be dead-on, the jitter can be terrible. I use this method in my open-source DaqPort sketch https://www.daqarta.com/dw_rraa.htm#00ff, which is used by DaquinOscope https://www.daqarta.com/dw_rroo.htm and Arduino_Oscillators https://www.daqarta.com/dw_rrss.htm mini-apps running in Daqarta. There's also a Jitter_Tbl macro to compute the jitter for arbitrary sample rates and output frequencies. Although not relevant here, this phase accumulator method can also go to exceptionally low frequencies, into the micro-hertz range, and the jitter is extremely small there.

3

I can get within 5 Hz of 15,000 using a 16 MHz clock on a Nano, Uno or 2560. Here is the code...

// RTM_TimerCalc 1.20
// Timer-1 Mode_14_16Bit_Fast_TOP_is_ICR

TCCR1B = 0x18; // 0001 1000, Disable Timer Clock 
TCCR1A = 0xA2; // 1010 0010

ICR1 = 1067-1;
OCR1A = (int) (ICR1 * 0.25);
OCR1B = (int) (ICR1 * 0.50);
TCNT1=0x0;

// UnComment following lines for UNO-NANO Timer-1 Pins 
// pinMode(9, OUTPUT);  // OC1a
// pinMode(10, OUTPUT); // OC1b

// UnComment following lines for 2560 Timer-1 Pins 
// pinMode(11, OUTPUT);  // OC1a
// pinMode(12, OUTPUT);  // OC1b

TCCR1B |= 1; // Prescale=1, Enable Timer Clock

Here is the Error Results... ASK: 15,000 Hz CALC: 14,995.3139643861 Hz OFFSET: -4.6860356139 Hz

hth

2

In answer to a comment from the OP asking for another microcontroller that can do this, and in addition to Majenko's reply comment:

A SAMD21G running at 48 MHz (as seen on some Arduinos) can make 15 kHz, in several ways.

Note that this assumes that the clock is exactly 48 MHz, and stable, and both assumptions could well be wrong, unfortunately. It helps if the clock is derived from a crystal; not all SAMD21G-based Arduinos do that, if any. This possible lack of clock precision and stability applies to all MCUs.

The SAMD21G does have a fractional PLL, and you can of course adjust the PER register, so some fine-tuning is possible if you have the equipment to measure the actual output frequency.

If you decide to go with one of the SAMD21G-based Arduinos: there is a PWM library for those (I know it well, as I wrote it) that will let you set a clock divider, a prescaler, and a resolution, which should make it easy enough to arrive at a (nominal) 15 kHz. There's a frequency table under "extras".

4
  • "It helps if the clock is derived from a crystal" are you saying that not all Arduino have crystal closk?
    – user0193
    Jan 19, 2021 at 21:16
  • 4
    Yes, I am; there are also some that use a ceramic resonator, and some use the MCU's internal RC oscillator.
    – ocrdu
    Jan 19, 2021 at 22:14
  • 2
    @JhonnyS: The Arduino Uno (and its many clones) uses a 0.5% ceramic resonator (there is a crystal on the PCB, but it is only used for the USB part). The official pages about Arduino Uno are quite misleading (by omission, wilful or not). They don't technically lie, but it is close. See Does the Arduino Uno have two crystals?. From the question: " the "big silvery" thing on the board (red) is the famous 16 MHz crystal of the Arduino. That's what I've believed until very recently." Jan 21, 2021 at 19:19
  • @PeterMortensen thanks for the reference! It makes so much sense now! So three are two clock sources in Arduino! Was just wondering if these are synchronous in nature.
    – user0193
    Jan 21, 2021 at 20:26
-1

Yes, you can. Sort of...

A 66.6 µs delay means one delay of 66 µs and then two delays of 67 µs. You could keep a close loop that calculates the micros() divided by 66 and modulo by 2 for every iteration. This will give you an alternating 1 and 0. Save the previous value to check this value for a change and digitalWrite HIGH or LOW.

I have no means at the moment to test the code, but in (semi-pseudo) code this would look something like:

void loop()
{
  int freq=15000;
  float interval = ((float)1000000/(float)freq)/2;//33.333333
  byte state=0;
  byte previousState=0;
  while (1)
  {
    unsigned long currentMicros=micros();
    previousState=state;
    state = (unsigned long)((float)currentMicros / interval) % 2;
    if (state==1 && previousState==0) digitalWrite(9,HIGH);
    if (state==0 && previousState==1) digitalWrite(9,LOW);
  }
}

This method will guarantee that after one second exactly 15000 periods have passed. The timing of the individual periods, however, might vary a little.

9
  • 2
    Note that this will provide a output that AVERAGES very close to 15KHz but will alternate between 1 cycle of 15.15 kHz and 2 cycles of 14.92 kHz. That may or may not be acceptable depending on the use-case.
    – jwh20
    Jan 19, 2021 at 14:03
  • I guess that listening to the tone produced by this method will sound horrible.
    – Hacky
    Jan 19, 2021 at 14:05
  • 2
    I fixed the obvious errors (missing parentheses after micros, missing factor 2 in interval, state variable shadowing a previous declaration) and tested it on an Uno. Not only is the jitter huge, but the average frequency is wrong: about 8.5 kHz. The floating point calculations make the code so slow that it misses pulses. Jan 19, 2021 at 15:35
  • I'd guess the frequency would be 7.5 kHz, but your factor 2 should have fixed that. I guess that the digitalWrite() take several clock cycles to execute. Too bad, because in theory this could have worked.
    – Hacky
    Jan 20, 2021 at 7:30
  • 1
    @JhonnyS: Re “avoid floating point”: if you need to be fast, yes. If you have lots of time, or a microcontroller with an FPU, then floating point is fine. Re “what causes jitter in your case”: I guess it is mostly the resolution of micros(), which is 4 µs. Jan 20, 2021 at 12:29

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.