2

Requirement:

  • I have input 4KHz pulse input
  • Divide it's frequency in half
  • Arbitrarily adjust phase
  • Arbitrarily adjust duty
  • Out put the modified pulse

My solution (CPU):

uint8_t cnt = 0;
uint16_t t1 = 235;
uint16_t t2 = 5;
String cmd;

void setup() {
  pinMode(3, OUTPUT);
  pinMode(2, INPUT);
     
  Serial.begin(9600);
  delay(500);
  attachInterrupt(digitalPinToInterrupt(2), rising_edge, RISING);
  Serial.println("PWM init");
}

void loop() {
  if (Serial.available() > 0) {
    cmd = Serial.readString();

    if (cmd.substring(0,8) == "setdelay"){
      t1 = cmd.substring(9).toInt();
      Serial.print("setting delay to ");
      Serial.print(t1);
      Serial.println(" us");
    }
    else if (cmd.substring(0,7) == "sethigh"){
      t2 = cmd.substring(8).toInt();
      Serial.print("setting high time to ");
      Serial.print(t2);
      Serial.println(" us");
    }
  Serial.flush();
  }
}
void rising_edge() {
  cnt = cnt + 1;
  if (cnt >= 2) {                           //divide by 2
    delayMicroseconds(t1);
    digitalWrite(3,HIGH);
    delayMicroseconds(t2);
    digitalWrite(3,LOW);
    cnt = 0;
  }
}

However this makes heavy use of CPU, and I need to use CPU for something else.Is it possible to implement all this with TC ?

Thanks in Advance !

1
  • You could use the incoming clock pulses as an external (asynchronous) clock source for timer2. See the ASSR register in the datasheet, for starters. You can't change the duty cycle with only this method. Though you use use an additional timer in "one-shot mode", but that would still require interrupts.
    – Gerben
    Commented Jan 29, 2021 at 14:09

1 Answer 1

1

this makes heavy use of CPU, and I need to use CPU for something else

The issue with this code is that is heavily uses the CPU for busy waiting rather than for doing useful work. You should try to get rid of those waiting loops:

For the pulse generation, you can keep roughly the same logic as your existing code: output a single pulse every other time a rising edge is sensed on the input. This pulse can be conveniently generated by Timer 1 on pin 9 by using a PWM mode and stopping the timer after a single cycle. I would use inverting PWM mode, with the timer starting at zero on the input pulse:

  • at t1 microseconds, a compare match sets the output HIGH

  • at t1+t2 microseconds, the timer overflow sets the output LOW, and the overflow interrupt stops and resets the timer

Here is a tentative, untested implementation:

// Set the pulse timings. To be called from loop() on user's request.
void set_timings(uint16_t t1, uint16_t t2)
{
    uint16_t ocr1a = t1 * 16 - 1;        // initial time in LOW state
    uint16_t icr1 = (t1 + t2) * 16 - 1;  // timer period
    noInterrupts();  // protect critical section
    OCR1A = ocr1a;
    ICR1 = icr1;
    interrupts();
}

// Configure Timer 1. To be called from setup().
void setup_timer() {
    DDRB  |= _BV(PB1);    // set pin digital 9 = PB1 = OC1A as output
    TCCR1A = _BV(COM1A0)  // PWM on OC1A, inverting mode
           | _BV(COM1A1)  // ditto
           | _BV(WGM11);  // mode 14: fast PWM, top = ICR1
    TCCR1B = _BV(WGM12)   // ditto
           | _BV(WGM13)   // ditto
           | 0;           // stopped
    TCNT1  = 0;           // reset
    TIFR1  = _BV(TOV1);   // clear overflow flag
    TIMSK1 = _BV(TOIE1);  // enable overflow interrupt
    set_timings(235, 5);  // initial timings
}

// Interrupt triggered by a rising edge of the input.
void on_rising_edge() {
    static uint8_t count;
    if (++count % 2) return;   // divide by 2
    TCCR1B |= _BV(CS10);       // start timer, clock @ F_CPU
}

// Stop and reset the timer when it overflows.
ISR(TIMER1_OVF_vect) {
    TCCR1B = 0;  // stop
    TCNT1  = 0;  // reset
}

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.