Trying to make accurate 20 KHz clock, not getting there

Here’s my code - I want to send out a burst of 336 pulses at 20KHz rate, with ability to change the duty cycle later to simulate the output of a high speed camera for testing another card.
Am seeing on a scope that the 20KHz is not even - instead I get periods from 44.77uS up to 50.75uS.
How can I improve that?
I need an even 50uS to allow another processer to trigger off the edges and set up 42 bytes into shift registers; the next rising edge of the 20KHz then clocks the data into the output stage of the shift register.

/* sketch to output burst of 336 pulses @ 20KHz rate, 
 simulating high speed camera trigger output
 Need to support variable width as well.
 50/50 to start.
 Run on regular Arduino
 */

byte triggerIn = 2; // button push to start pulse train
byte triggerOut = 3; // waveform out
int pulses = 0; // number of pulses - actually counts # of transitions
byte pinState = 0; // high or low level of waveform
byte pulsing = 0; // pulse train active flag

unsigned long currentMicros;
unsigned long nextMicros;
unsigned long duration = 25UL; // flip every 50uS = 20KHz pulse
unsigned long elapsedMicros;

void setup(){
  pinMode (triggerIn, INPUT);
  digitalWrite (triggerIn, HIGH);
  pinMode (triggerOut, OUTPUT);
  digitalWrite(triggerOut, LOW);
  Serial.begin(115200);
  Serial.println("hello"); // sanity test

}
void loop(){
  //  Serial.println(pulsing);
  if (pulsing == 0){ // not pulsing yet
    if ( digitalRead(triggerIn) == LOW){  // read button to start
      //      Serial.println("starting");
      pulsing = 1;
      currentMicros = micros();  // get times caught up & current
      nextMicros = currentMicros;
    }
  }
  while (pulsing == 1){ // stay in loop until 336 pulses go out
    currentMicros = micros(); // now blink without delay style code
    elapsedMicros = currentMicros - nextMicros;
    if (elapsedMicros >= duration){
      nextMicros = nextMicros + duration;
      pulses = pulses +1; // count pulses

      /*
       pinState = 1 - pinState; // keep track of output state
       if (pinState == 1){
       PORTD = PORTD | 0b00001000; // set the output
       }
       else {
       PORTD = PORTD & 0b00000111; // clear the output, leave Serial & input pin alone
       }
       */
      // alternate method - neither seems to affect period tho
      PIND = 0b00001000;  // toggle IO pin

      if (pulses == 672){ // all created?
        pulses = 0;  // reset count for next time
        pulsing = 0; // turn off active flag
        delay (500); // debounce button push that starts pulse train
      }
    }
  }
}

Got this from the “Arduino Cookbook” by Margolis, seems to be more stable, now to incorporate it to make a burst of pulses.

#include <TimerOne.h>

#include <TimerOne.h>

#define pwmRegister OCR1A    // the logical pin, can be set to OCR1B
const int   outPin =  9;     // the physical pin

long period = 50;      // the period in microseconds - changed for 20KHz
long pulseWidth = 25;   // width of a pulse in microseconds - changed to 50/50 duty cycle at 50 KHz

int prescale[] = {0,1,8,64,256,1024}; // the range of prescale values

void setup()
{
  Serial.begin(9600);
  pinMode(outPin, OUTPUT);
  Timer1.initialize(period);        // initialize timer1, 1000 microseconds
  setPulseWidth(pulseWidth);
}


void loop()
{
}

bool setPulseWidth(long microseconds)
{
  bool ret = false;

  int prescaleValue = prescale[Timer1.clockSelectBits];
  // calculate time per counter tick in nanoseconds
  long  precision = (F_CPU / 128000)  * prescaleValue  ;   
  period = precision * ICR1 / 1000; // period in microseconds
  if( microseconds < period)
  {
    int duty = map(microseconds, 0,period, 0,1024);
    if( duty < 1)
      duty = 1;
    if(microseconds > 0 && duty < RESOLUTION)
    {
       Timer1.pwm(outPin, duty);
       ret = true;
    }
  }
  return ret;
}

Use the compare match ISR to count the number of times the output pin is toggled, and turn off the timer after the desired number with

TCCR1B = 0;

Can you help me work that in? Not quite sure how to count the pulses, and I think I have turning the PWM on/off here, maybe:

/* sketch to output burst of 336 pulses @ 20KHz rate, 
 simulating high speed camera trigger output
 Need to support variable width as well.
 50/50 to start.
 Run on regular Arduino
 
 Updated to use TimerOne library for better period control
 */
#include <TimerOne.h>
#define pwmRegister OCR1A    // the logical pin, can be set to OCR1B

const int   outPin =  9;     // the physical pin

long period = 50;      // the period in microseconds
long pulseWidth = 25;   // width of a pulse in microseconds

int prescale[] = {
  0,1,8,64,256,1024}; // the range of prescale values

byte triggerIn = 2; // button push to start pulse train
//byte triggerOut = 3; // waveform out
int pulses = 0; // number of pulses - actually counts # of transitions
byte pinState = 0; // high or low level of waveform
byte pulsing = 0; // pulse train active flag

int burstCount = 0;
int burstLength = 336;

//unsigned long currentMicros;
//unsigned long nextMicros;
//unsigned long duration = 25UL; // flip every 50uS = 20KHz pulse
//unsigned long elapsedMicros;

void setup(){
  pinMode (triggerIn, INPUT);
  digitalWrite (triggerIn, HIGH);
  //pinMode (triggerOut, OUTPUT);
  //digitalWrite(triggerOut, LOW);

  pinMode(outPin, OUTPUT);
  Timer1.initialize(period);        // initialize timer1, 1000 microseconds

  Serial.begin(115200);
  Serial.println("hello"); // sanity test

}
void loop(){
  if (pulsing == 0){ // not pulsing yet
    if ( digitalRead(triggerIn) == LOW){  // read button to start
      //      Serial.println("starting");
      pulsing = 1;
      //currentMicros = micros();  // get times caught up & current
      //nextMicros = currentMicros;
    }
  }
  while (pulsing == 1){ // turn on PWM for 50uS * 336 = 16,800uS

    setPulseWidth(pulseWidth);

    while (..... >= burstLength){
      setPulseWidth(0); // turn off PWM ??

      pulsing = 0;
    }
  }
}

bool setPulseWidth(long microseconds){
  bool ret = false;

  int prescaleValue = prescale[Timer1.clockSelectBits];
  // calculate time per counter tick in nanoseconds
  long  precision = (F_CPU / 128000)  * prescaleValue  ;   
  period = precision * ICR1 / 1000; // period in microseconds
  if( microseconds < period)
  {
    int duty = map(microseconds, 0,period, 0,1024);
    if( duty < 1)
      duty = 1;
    if(microseconds > 0 && duty < RESOLUTION)
    {
      Timer1.pwm(outPin, duty);
      ret = true;
    }
  }
  return ret;
}
/*
     if (pulses == 672){ // all created?
 pulses = 0;  // reset count for next time
 pulsing = 0; // turn off active flag
 delay (500); // debounce button push that starts pulse train
 }
 */

Sure, give me a bit to look at the TimerOne library, I haven't used it before. Let's see if it's easier to work with that or just program down to the bare metal.

I'm using the code from this Thread to create a 38KHz pulse but it seems to be designed so you can generate other frequencies
http://forum.arduino.cc/index.php/topic,10555.0.html

...R

I've got the 20KHz generated just fine, it's having it only do a burst of 336 pulses that I don't know how to set up.

Give this a try. I didn’t bother wiring up a button, am just generating the burst every 3 seconds.

//Arduino Uno, Arduino 1.0.5
//Output a given number of PWM cycles

#include <TimerOne.h>

const int OUTPUT_PIN = 9;
const unsigned long EVERY = 3000;        //start the burst every this many milliseconds
const int NCYCLE = 336;                  //number of cycles to output
const unsigned long PERIOD = 25;         //half a cycle in microseconds
const int DUTY = 512;                    //duty cycle as a number from 0 to 1023

void setup() 
{
    Timer1.attachInterrupt( timerIsr ); // attach the service routine here
}

void loop()
{
    unsigned long ms = millis();
    static unsigned long lastStart;
    
    if (ms - lastStart >= EVERY) {
        lastStart += EVERY;
        Timer1.pwm(OUTPUT_PIN, DUTY, PERIOD);
    }
}

void timerIsr()
{
    static unsigned int toggleCount;

    if (++toggleCount > NCYCLE * 2) {
        Timer1.disablePwm(OUTPUT_PIN);
        toggleCount = 0;
    }
}

Found a button :smiley:

//Arduino Uno, Arduino 1.0.5
//Output a given number of PWM cycles when button pressed

#include <Button.h>        //https://github.com/JChristensen/Button
#include <TimerOne.h>      //http://playground.arduino.cc/code/timer1

const unsigned int BUTTON_PIN = 8;
const unsigned int OUTPUT_PIN = 9;
const unsigned int NCYCLE = 336;             //number of cycles to output
const unsigned long PERIOD = 25;             //length of half a cycle in microseconds
const unsigned int DUTY = 512;               //duty cycle as a number between 0 and 1023
const boolean BUTTON_PULLUP = true;
const boolean BUTTON_INVERT = true;
const unsigned long BUTTON_DEBOUNCE = 25;    //ms

Button btnStart(BUTTON_PIN, BUTTON_PULLUP, BUTTON_INVERT, BUTTON_DEBOUNCE);    //Declare the button

void setup() 
{
    Timer1.attachInterrupt( timerIsr );
}

void loop()
{
    btnStart.read();        
    if (btnStart.wasPressed()) {
        Timer1.pwm(OUTPUT_PIN, DUTY, PERIOD);
    }
}

void timerIsr()
{
    static unsigned int toggleCount;

    if (++toggleCount > NCYCLE * 2) {
        Timer1.disablePwm(OUTPUT_PIN);
        toggleCount = 0;
    }
}

Thanks Jack, will give it a try soon - dinner about to come out of the oven, home made pizza....

Ok, I simplified the debounce a little vs dealing with github.
This isn’t putting out 20 KHz.

//Arduino Uno, Arduino 1.0.5
//Output a given number of PWM cycles when button pressed

//#include <Button.h>        //https://github.com/JChristensen/Button
#include <TimerOne.h>      //http://playground.arduino.cc/code/timer1

const unsigned int BUTTON_PIN = 2;
const unsigned int OUTPUT_PIN = 9;
const unsigned int NCYCLE = 336;             //number of cycles to output
const unsigned long PERIOD = 25;             //length of half a cycle in microseconds
const unsigned int DUTY = 512;               //duty cycle as a number between 0 and 1023
//const boolean BUTTON_PULLUP = true;
//const boolean BUTTON_INVERT = true;
//const unsigned long BUTTON_DEBOUNCE = 25;    //ms

//Button btnStart(BUTTON_PIN, BUTTON_PULLUP, BUTTON_INVERT, BUTTON_DEBOUNCE);    //Declare the button

void setup() 
{
  pinMode (BUTTON_PIN, INPUT_PULLUP);
  Timer1.attachInterrupt( timerIsr );
}

void loop()
{
//    btnStart.read();        
    if (digitalRead(BUTTON_PIN) == LOW){
      delay(300);
      Timer1.pwm(OUTPUT_PIN, DUTY, PERIOD);
    }
}

void timerIsr()
{
    static unsigned int toggleCount;

    if (++toggleCount > NCYCLE * 2) {
        Timer1.disablePwm(OUTPUT_PIN);
        toggleCount = 0;
    }
}

DS0002.BMP (1.37 MB)

I think I’m getting close - I can get burst at 20 KHz, the burst duration is never the same tho.
336 * .00005 = 16.8mS, I’m getting seemingly random amounts, 1mS up to 16+, but not quite 16.8

/* sketch to output burst of 336 pulses @ 20KHz rate, 
 simulating high speed camera trigger output
 Need to support variable width as well.
 50/50 to start.
 Run on regular Arduino
 
 Updated to use TimerOne library for better period control
 */
#include <TimerOne.h>
#define pwmRegister OCR1A    // the logical pin, can be set to OCR1B

const int outPin =  9;     // the physical pin of the PWM output

long period = 50;      // the period in microseconds
long pulseWidth = 25;   // width of a pulse in microseconds

int prescale[] = {
  0,1,8,64,256,1024}; // the range of prescale values

byte triggerIn = 2; // button push to start pulse train
volatile int toggleCount = 0; // number of pulses - actually counts # of transitions
volatile byte pulsing = 0; // pulse train active flag

void setup(){
  pinMode (triggerIn, INPUT);
  digitalWrite (triggerIn, HIGH);

  pinMode(outPin, OUTPUT);
  Timer1.initialize(period);        // initialize timer1, 1000 microseconds
  Timer1.attachInterrupt (countPulses); // attaches countPulses() as a timer overflow interrupt

  Serial.begin(115200);
  Serial.println("hello"); // sanity test

}

void countPulses(){ // ISR to count pulses
toggleCount = toggleCount +1;
  if (toggleCount == 336) {
    Timer1.disablePwm(outPin);
    toggleCount = 0;
    pulsing = 0;
  }
}


void loop(){
  if (pulsing == 0){ // not pulsing yet
    Timer1.disablePwm(outPin); // just in case
    if ( digitalRead(triggerIn) == LOW){  // read button to start
      Serial.println("starting + debounce delay");
      delay(250);
      pulsing = 1;
      setPulseWidth(pulseWidth);
    }
  }
}


bool setPulseWidth(long microseconds){
  bool ret = false;

  int prescaleValue = prescale[Timer1.clockSelectBits];
  // calculate time per counter tick in nanoseconds
  long  precision = (F_CPU / 128000)  * prescaleValue  ;   
  period = precision * ICR1 / 1000; // period in microseconds
  if( microseconds < period)
  {
    int duty = map(microseconds, 0,period, 0,1024);
    if( duty < 1)
      duty = 1;
    if(microseconds > 0 && duty < RESOLUTION)
    {
      Timer1.pwm(outPin, duty);
      ret = true;
    }
  }
  return ret;
}

Trace 2 is the burst?

No, trace 1, the top yellow.
Here’s a shot showing 50uS.

DS0003.BMP (1.37 MB)

Yeah I think I can see the burst duration varying too. Not sure why yet.

You can look at this book?

Gailun12:
You can look at this book?

??

Here's the direct approach. Seems like we should be able to do it with the library, but not sure what's going on with it.

//Arduino Uno, Arduino 1.0.5
//Output a given number of PWM cycles when button pressed

const unsigned int BUTTON_PIN = 2;
const unsigned int OUTPUT_PIN = 9;
const unsigned int NCYCLE = 336;              //number of cycles to output

void setup() 
{
    pinMode (BUTTON_PIN, INPUT_PULLUP);
    pinMode(OUTPUT_PIN, OUTPUT);
    TCCR1B = _BV(WGM13);        //set mode 8 (phase & freq correct), and stop the timer
    TCCR1A = 0;                 //reset other WGM bits
    ICR1 = 399;                 //counter TOP value
    OCR1A = 199;                //this is the duty cycle, must be between 0 and 399
    TCNT1 = 0;                  //ensure we start at zero
    TIMSK1 = _BV(TOIE1);        //enable overflow interrupt
}

void loop()
{
    if (digitalRead(BUTTON_PIN) == LOW) {
        delay(300);
        TCCR1A = _BV(COM1A1) | _BV(COM1A0);      //inverting mode
        TCCR1B |= _BV(CS10);        //start the timer, prescale 1
    }
}

ISR(TIMER1_OVF_vect)
{
    static unsigned int intCount;

    if (++intCount > NCYCLE) {
        TCCR1B &= ~( _BV(CS12) | _BV(CS11) | _BV(CS10) );     //stop the timer
        TCCR1A &= ~( _BV(COM1A1) | _BV(COM1A0) );    //return the pin to normal mode
        intCount = 0;
        TCNT1 = 0;
    }
}

Going to watch baseball, more testing tomorrow night. Thanks for the help so far.

Haha, saw that was on and thought about you! Good luck, let me know.