Your signals are slow enough that you may not need any timer. You can
just use the Arduino micros()
function to handle all your timings. Of
course, micros()
itself relies on Timer 0, but you don't need to
access any timer directly.
I would use the external interrupts in CHANGE
mode to measure the duty
cycle. For example:
const uint8_t INPUT_0_PIN = 2; // digital 2 is also INT0
const uint8_t OUTPUT_0_PIN = 4;
// Measured duration of the HIGH level of input 0.
static volatile uint32_t input_0_high_time;
// Interrupt handler invoked when input 0 toggles.
static void on_input_0_change()
{
static uint32_t time_rise; // last time the signal rose
uint32_t now = micros();
if (digitalRead(INPUT_0_PIN) == HIGH) // detected rising edge
time_rise = now;
else // detected falling edge
input_0_high_time = now - time_rise;
}
void setup()
{
pinMode(INPUT_0_PIN, INPUT);
pinMode(OUTPUT_0_PIN, OUTPUT);
attachInterrupt(0, on_input_0_change, CHANGE);
}
For handling two inputs, you can duplicate everything that has a 0
in
its name and name it with a 1
. If your signal is no slower than
60 Hz, you can also replace all uint32_t
by uint16_t
to make
the code slightly faster.
For generating the outputs, do something similar to the Blink Without
Delay example:
void loop()
{
uint32_t now = micros();
// Determine suitable period for output 0.
uint32_t input_0_high_time_copy;
noInterrupts(); // avoid race condition while reading
input_0_high_time_copy = input_0_high_time;
interrupts();
uint32_t output_0_half_period =
map(input_0_high_time_copy, 0, 16666, 8333, 500e3);
// Toggle output 0 when needed.
static uint32_t time_output_0_toggle;
static uint8_t output_0_state;
if (now - time_output_0_toggle >= output_0_half_period) {
time_output_0_toggle += output_0_half_period;
output_0_state = !output_0_state; // toggle state
digitalWrite(OUTPUT_0_PIN, output_0_state);
}
}
Again, you can handle two outputs by duplicating the code and replacing
0
with 1
. Note that you cannot replace uint32_t
by uint16_t
,
as it would not work for the slowest frequencies.
Edit: Now we may ask the question whether it is possible to use some
timers to make all this more accurate. The answer is yes, you could
achieve maximum accuracy by using four 16-bit timers:
- You could use the input capture function of a 16-bit timer, with the
prescaler set to 8, to measure the transitions of one of your
inputs with a resolution of 0.5 µs.
- You could use a 16-bit timer in CTC mode, with the prescaler set
to 256, to toggle one of your outputs at the desired frequency, with a
16 µs resolution.
For reference, micros()
has a resolution of 4 µs. The 16 µs
resolution of the timer-generated output seems poor in comparison, but
the timer has the advantage of having zero jitter.
Now, for doing that that, you would need an Arduino Mega 2560. The Uno
has only one 16-bit timer, so it could help with only one of your two
channels.
There is still something you can do with a single 16-bit timer. Set it
to normal counting mode, with the prescaler at 8, and use it
instead of micros()
for timing your inputs: in setup()
// Configure Timer 1.
TCCR1A = 0; // normal counting mode
TCCR1B = _BV(CS11); // clock at F_CPU/8
and in the interrupt handler, you replace uint32_t now = micros();
by
uint16_t now = TCNT1;
And now you have input_0_high_time
(which should also be uint16_t
)
in units of 0.5 µs.