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() {}

Peter Mortensen
  • 435
  • 3
  • 12
astrick
  • 193
  • 1
  • 9

6 Answers6

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(){}

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81
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.

Gerben
  • 11,332
  • 3
  • 22
  • 34
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.

Boggyman
  • 619
  • 1
  • 4
  • 4
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

stockvu
  • 31
  • 1
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".

ocrdu
  • 1,795
  • 3
  • 12
  • 24
-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.

Peter Mortensen
  • 435
  • 3
  • 12
Hacky
  • 154
  • 4