Arduino multi-tasking

I recently started working on a project commissioned by my son to measure how fast he and his sister can run… “Easy,” I thought. All I would need would be two light barriers and I could calculate speed from the time travelled over a known distance. Turned out that a simpler and cheaper solution might be just to use two ultrasonic distance sensors commonly combined with an Arduino and these would work as my “gates”. I looked up a code snippet and came across this example and many others similar to it…

Here’s what I actually setup…

Setup that I used to characterise the ultrasonic sensor and code.

The basic premise is that you have a transponder that is triggered with a short (10us) pulse, a burst of ultrasound (ping) is emitted from the transmitter and then the receiver produces a high signal whose length is equal to the time of flight (ToF) for the ping. Note that this is the round trip time ie. the time taken for the outward and return journey of the sound. This code worked fine for me however it has a fairly serious limitation in that it blocks the micro’s execution loop while waiting for the received signal here…

duration = pulseIn(echoPin, HIGH);
distance = (duration*.0343)/2;

The cause of the problem is located in pulseIn() which is an Arduino library function that measures the time that an input is high for. This call gives us the ToF however it is blocking and stops the micro from doing anything else. This means that to have two sensors running at the same time would never work. You would have to do a measurement on one then the other continuously – not suitable for a fast-moving target. I should say at this point that the solution provided above is perfectly good for one sensor or if you are very new to microcontrollers and software etc. It does not fully harness the power of the Arduino however. What we need is to do some multitasking and microcontrollers typically use external and internal (timer) interrupts for this purpose. Doing a repeating task at a fixed period is accomplished with timer interrupts and I discovered a nice explanation of how to use the Arduino’s timer interrupts here.

To solve this problem I did the following…

Step 1

I used the example code to set up a 16-bit timer to give me a repeating interrupt at 41.75 Hz – the maximum possible ToF for a target at a maximum measurable range of 4m.

void configureTimer1Interrupt(void)
{
  TCCR1A = 0; // set entire TCCR1A register to 0
  TCCR1B = 0; // same for TCCR1B
  TCNT1  = 0; //initialize counter value to 0
  // set compare match register for 41.75hz increments
  OCR1A = 47903; // = 16E6 / (41.75*8) - 1 (must be <65536)
  TCCR1B |= (1 << WGM12);   // turn on CTC mode
  TCCR1B |= (1 << CS11);    // set prescaler to 8
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
}

Step 2

I attached the timer interrupt to trigger the transmitter with a 10 us pulse

ISR(TIMER1_COMPA_vect)
{   //ISR attached to timer1 that triggers the echo
    digitalWrite(TRIG_PIN_IDX,HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN_IDX, LOW);
}

Step 3

I attached the receiver channel to an external interrupt that would measure the length of the ToF pulse from the receiver.

void handleEcho(void)
{
  bool risingEdge = digitalRead(ECHO_PIN_IDX);
  if (risingEdge) handleEchoRisingEdge();
  else handleEchoFallingEdge();
}

void handleEchoRisingEdge(void)
{
  echoInitialTime = micros();
}

void handleEchoFallingEdge(void)
{
  uint32_t timeOfFlightRoundTrip = micros() - echoInitialTime;
  distanceCm = (0.0344 * timeOfFlightRoundTrip) / 2.0;
}

void setup()
{
  ...
  attachInterrupt(
    digitalPinToInterrupt(ECHO_PIN_IDX),
    handleEcho,
    CHANGE);
  ...
}

Here’s what the actual output looked like…

Scope trace showing the trigger signal delivered the ultrasonic sensor (yellow) and echo signal from the receiver (green). The timer interrupt was used to fire a 10us pulse using a short sleep.

A little better

I wondered if I could do better and remove the sleep from step 2 by setting up PWM (pulse width modulated) output at the specified frequency with a very low duty cycle (10us / (1/41.75Hz) = 4.175E-4). This would in fact completely remove the need for the code in Step 2; no timer interrupt would be needed. I found an enlightening post here. This does indeed work. Here’s the code snippet…

void configureTimer1Pwm(void)
{
  pinMode(TRIG_PIN_IDX, OUTPUT);
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) |_BV(WGM10) | _BV(WGM11);
  TCCR1B = _BV(WGM12) | _BV(WGM13) | _BV(CS11);
  OCR1A = 47903;
  OCR1B = 19;
}

I’ve used timer1 again in “fast-PWM” mode to generate a PWM signal to drive the ultrasonic sensor. The frequency is controlled by the value in OCR1A (and prescaler) set by the clock select (CS) bits in the timer control register (TCCR1B). Effectively a counter counts up until it reaches the compare register A (OCR1A) and the output is switched on and resets. When the counter reaches compare register B, the output is turned off. You can follow the links for details of the calculations and have a look at my scrappy notes here.

I get the same output on the trigger pin as before but without the 10us sleep in ISR(TIMER1_COMPA_vect). In fact, that function is no longer needed. The micro does all the signal generation that is required to drive the ultrasonic sensor. We don’t need any logic in the code anymore. All that’s required is to set up the PWM output correctly. I think that this is a preferable solution because it means that this cyclic task of driving the sensor is done at a low level and is more likely to be jitter-free and more importantly, we don’t block the micro’s execution loop.

Scope trace showing a PWM signal from Arduino timer1 that was used to trigger the ultrasonic sensor.

Example code