1

I am trying to make a delay generator. I already found an excellent example in Generating a short pulse after a delay that works in the range from ~5-32767 microseconds (it is limited by a 16 bit microsecond counter divided by 2).

How would I go about extending that range into the millisecond range (say up to 1s or so)? Since this example is interrupt-based, adding delay() statements do not really seem to work.

I have also tried changing the timer prescaler, but then the jitter is also scaled up in an unacceptable way.

This is the code I have now. Send commands via the arduino serial console, D500 for 500µs delay, L100 for a 100µs long pulse. Letters M and N change the prescaler.

uint16_t pulse_delay = 10; //half-microseconds
uint16_t pulse_length = 10;
String inputString = "";         // a string to hold incoming data
boolean stringComplete = false;  // whether the string is complete
long read_value = 0;
long ms_delay = 0;

void printvals() {
  Serial.print(F("Delay: "));
  Serial.print(pulse_delay / 2);
  Serial.println(F(" µs"));
  Serial.print(F("Length: "));
  Serial.print(pulse_length / 2);
  Serial.println(F(" µs"));
}
long getcommandvalue(String inputstring) {
  inputString.remove(0,1);
  return inputString.toInt();
}
void setup(){
  pinMode(8, INPUT);
  pinMode(9, OUTPUT);
  TCCR1A = 0;
  TCCR1B = _BV(ICNC1)  //input capture noise cancel
      | _BV(ICES1) //positive edge
      | _BV(CS11); // /8 prescaler
  TIMSK1 = _BV(ICIE1); //enable input capture interrupt
  Serial.begin(9600);
  inputString.reserve(200);
  Serial.println(F("RDY"));
  printvals();
}
void loop(){
  if (stringComplete) {
      switch(inputString.charAt(0)) {
        case 10: //\n (Newline; Line Feed) 
          // previous line ending was CRLF, so now LF was carried to the next string
          break;
        case 63: //?
          //print current vales
          printvals();
          break;
        case 68: //D (Delay)
          read_value = getcommandvalue(inputString);
          if (read_value >= 1) {
            pulse_delay = 2 * read_value;
            printvals();
          }
          break;
        case 76: //L (Length)
          read_value = getcommandvalue(inputString);
          if (read_value >= 1) {
            pulse_length = 2 * read_value;
            printvals();
          }
          break;
        case 77: //M (additional prescaling)
          TCCR1B = _BV(ICNC1)  //input capture noise cancel
            | _BV(ICES1) //positive edge
            | _BV(CS10) // 64 prescaler
            | _BV(CS11); // /8 prescaler
          break;
        case 78: //N (8x prescaling)
          TCCR1B = _BV(ICNC1)  //input capture noise cancel
            | _BV(ICES1) //positive edge
              | _BV(CS11); // /8 prescaler
          break;
        case 79: //O
          ms_delay = getcommandvalue(inputString);
          // I don't know where to execute this ms_delay
          break;
      }
      inputString = "";
      stringComplete = false;
  }
}



void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:Z
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if ((inChar == '\n') or (inChar == '\r')) {
      stringComplete = true;
    }
  }
}

ISR(TIMER1_CAPT_vect){
  TCCR1A = _BV(COM1A0) | _BV(COM1A1); //set OC1A on match
  TIFR1 = _BV(OCF1A);  // clear interrutp flag
  TIMSK1 |= _BV(OCIE1A); //enable match interrupt
  OCR1A = pulse_delay; //pulse begin time

  TCNT1 = TCNT1 - ICR1; //TCNT1 now contains time since input pulse, even if 
                        //the interrupt isn't run immediately
}
ISR(TIMER1_COMPA_vect){
  TIMSK1 &=~ _BV(OCIE1A); //disable match interrupt
  TCCR1A = _BV(COM1A1); //clear OC1A on match
  OCR1A = pulse_delay + pulse_length;
}
  • Put it in a loop and run it multiple times. But also consider the loop overhead. – Chris Stratton Jun 5 '18 at 18:15
1

You could count timer compare matches. The following illustrates the principle, maybe not accurate as written but I am sure the counter loop will be precise enough for your needs, considering you want to use an Arduino. Depending on the time resolution you want, you may be able to run other code while the software counter is active.

The following can count to several seconds, depending on values of OCR1A and counter_target.

unsigned int state, pulse_delay;
byte counter[3]={0,0,0}; // 24 bit counter, you can make it longer in principle

byte counter_target[3]={0,0,0}; // defines length of delay you want

void setup(){
state=0;
TCCR1A=0;
TCCR1B=0;
}

void loop(){
switch (state){
case 0: // idle state machine // you other code here
counter_target[0]=0; // For example
counter_target[1]=0;
counter_target[2]=1; // corresponds to (1+65536)*pulse_delay/clock_scaler
// when you are ready to start counting
state=1;
break;

case 1: // setup timer
counter[0]=0; // Zero the 24-bit counter
counter[1]=0;
counter[2]=0;
OCR1A = pulse_delay; // number of pulses to count = 1+OCR1A
OCR1B = OCR1A+1;
ICR1  = OCR1A+1;    // Just to make sure these don't cause compare match 
TIFR1 |= (_BV(ICF1) | _BV(OCF1A) | _BV(OCF1B)); // Clear match flags
state=2;
TCCR1A = B00000011; // Fast PWM mode 15 on UNO, OCR1A=TOP
TCCR1B = B00011001; // Clock prescaler 1
break;

case 2: // Counting up
loop_until_bit_is_set(TIFR1,OCF1A); 
TIFR1 |= (_BV(ICF1) | _BV(OCF1A) | _BV(OCF1B)); // Clear match flags
counter[0]+=1; // Increment counter by 1
if(counter[0]==0x00){counter[1]+=1;
if(counter[1]==0x00){counter[2]+=1;}} // etc

if(counter[0]==counter_target[0] && counter[1]==counter_target[1] && counter[2]==counter_target[2]){state=0;}  // Check if we have reached the target delay
break;

}
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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