Practical Arduino Real-Time Control - Part 2

Inconsistent test results meant that I needed to step back and examine how I was using the Arduino. Due to the single threaded nature of the runtime environment, I was forced to use a single loop function to handle all inputs and outputs. I now needed to make the loop execute on a consistent basis in order to provide real-time behaviour.

This caused me to adopt a more sophisticated approach to my Quench control program loop:

  • I redesigned the diagnostic output (Serial.println is not your friend!)
  • I made the loop actions time based
  • I made the loop actions priority based
  • I used direct chip control

Serial.println diagnostic output during a critical time event was not possible as it caused excessive delays which prevented real-time response. Since I was still on the initial strobe control learning curve, I needed the diagnostic information in order to understand my YS50 strobe and the TTL converter controls. A simple solution was to store each piece of diagnostic data into a dedicated variable and output all the diagnostic data at a suitable idle point. This complicated the code, but was workable. Having the slow Serial.println code all fire at a consistent point in time meant there were no timing side effects on time critical code. I was able to reliably dump the diagnostics at the end of each strobe fire cycle after the strobe had recharged. The delay became annoying as the batteries lost power and the recharge time took longer. But it worked.

My initial design copied the Arduino code samples and I read all of the inputs every loop cycle. Reading all of the inputs every cycle introduced extra overhead that slowed down responsiveness (especially when using the standard high level routines - more below). So I altered the design to only read an input when it was required. My first change was to read each input every N loops. This approach made a huge improvement in the responsiveness of the controller. I was no longer wasting time during critical events reading unneeded inputs. But I had a hard time guessing appropriate values for N. Especially since there was several different N's. This concept needed refinement.

Every N loops needed to be replaced with every D microseconds (where each input had a different value of D). The Arduino provided a real-time clock so it was easy to track elapsed time. This required a simple mechanical code transformation. Instead of setting a loop counter to 0, I grabbed the current timestamp. Instead of comparing a loop counter to N, I compared elapsed time to D. This change made the responsiveness of the controller stable and (mostly) predictable.

My next design change was required in order to deal with the single threaded nature of the Arduino. In my past I had relied upon a suitable blend of multiple real-time tasks and interrupt service routines to segregate code for different actions. I now had to pile all of my code into a single Arduino thread. There is a simple rule: higher priority code has to run first. This meant that I needed to introduce a set of flags - 1 per action - and arrange the loop code so that lower priority code was skipped if a higher priority action was active. Only the highest priority active code was executed each loop cycle. For example: if firing of the strobe was in progress, then the code to read the power level setting was skipped. Not elegant, but effective.

My last change was a simple optimization to reduce the overhead of interacting with the Input and Output controls. The Arduino IDE provided a set of portable digital read, digital write and analog read functions. These were "safe" functions that accounted for differences in port hardware across different Arduino boards and also made sure that the ports were set up properly to support the desired operation. This extra portability and safety cost important execution time. I replaced these routines with direct port access commands. Not portable. Not safe. But fast.

I finally had a stable and understandable Arduino based control system. Now all I had to do was learn how to make it control a strobe.