2

My system requires that I read two independent PWM signals (60hz) and output two corresponding square waves. These output square waves vary in frequency according to their corresponding input duty cycle. The output frequencies will vary between 1-60Hz. (outside of tone() range)

I'm planning out the best way to approach this project. My initial thought is to use the two available external interrupts set to CHANGE to measure the inputs. I believe I'll also need to reference a timer in this routine, to determine the time since the last change.

Unless I'm mistaken, it looks like I will also need a timer interrupt for each output, to control the frequency properly.

The Arduino UNO has 3 timers. timer0: 8bit (used internally), timer1: 16bit, and timer2: 8bit. In short, can I run all of these interrupts in the same code? Will I run out of timers, or can I multitask the timers between my requirements?

Thanks!

  • I never used timers/interrupts directly on an Arduino, and it is possible the best way. However, for so slow rates (upto 60 Hz), polling the 2 sensors and even use a delay would still be valid (1000 ms / 60 hz) = 16 ms. That is a lot of time , even for an Arduino. – Michel Keijzers Aug 22 '17 at 15:59
  • If I approach it that way, I suppose I could probably read/write all the information with a timer interrupt every 100us or so, as long as I keep the code brief. Resolution isn't a large problem for me here, so that could work - thanks. – mrmojo6 Aug 22 '17 at 20:19
  • You can even not using a timer at all ... if resolution is not a problem, even polling (maybe with one ms delay) could work. But if you want to learn more about a better way, use timers/interrupts. – Michel Keijzers Aug 22 '17 at 20:21
1

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.

0

Your approach is generally sound.

Depending on the square waves you are generating, one timer can generate two pulse train with independent duty cycles but dependent phases.

That leaves two times for measuring the duty cycles of the input pulse train.

But replicating the input pulse trains makes zero practical sense.

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.