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 !
-
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– Gerben01/29/2021 14:09:00Commented Jan 29, 2021 at 14:09
1 Answer 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:
Serial.readString()
: see Majenko's blog post Reading Serial on the ArduinodelayMicroseconds()
: for this, you can indeed use a timer.
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 HIGHat
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
}