EventBusyBox Arduino Sketch

Note: this sketch is a work-in-progress and only partly tested.

This multi-file sketch is provided to serve as a template for projects which combine responsive sensing and actuation with behavioral logic, implemented using a non-blocking event loop. The actual sketch does nothing specifically useful, but just exercises various sensors and actuators. This is in keeping with the name “Busy Box”, a classic baby toy featuring many individual manipulable widgets on one structure.

Each individual module follows a conventional form: a setup function to initialize the state; a polling function called periodically to update the state; other functions to be called as needed to manipulate and query the state.

The sketch includes the standard setup() and loop() functions, but also a top-level poll() function as an entry point to the polling system. For the polling event loop to work, no function is allowed to ‘block’, meaning no function can capture execution within a long loop or call to delay(). The logic in loop() can implement a blocking wait by using a polling_delay() function which continues to poll while waiting.

Low-level polling functions can implement delays using timer variables to keep track of elapsed time. Sequences must use a state machine approach to keep track of the current sequence position and apply appropriate outputs and transition rules. The modules below provide examples for different approaches to coding within these constraints.

The top-level file EventBusyBox.ino contains just the minimal common code with most of the function calls commented out. To use this template, you will need to identify the specific modules you need for your project, update the hardware pin assignments to match your device, and uncomment the related function calls from setup() and poll(). You may then remove any unnecessary code, i.e. delete unused files and unnecessary comments. Your own behavioral logic is written in loop() as usual, with the caveat that any waiting is performed using polling_delay().

The sketch files may be downloaded in a single archive file as EventBusyBox.zip, or browsed in raw form in the source folder. The individual files are documented below.

EventBusyBox.ino

The main top-level code is in EventBusyBox.ino. This top-level file just contains the minimal common code, with most of the function calls commented out. To use this template, you will need to pick the specific modules you need for your project, update the hardware pin assignments to match, and uncomment the related function calls from setup() and poll(). You may then remove any unnecessary code, i.e. delete unused files and unnecessary comments.

This opt-in approach is intended to discourage the usual problem of examples in which a student isn’t sure which parts are needed and so leaves lots of unnecessary sample code in place even if it interferes with the intended outcome.

The different functional modules are stored in different .ino files which will appear in the Arduino IDE as separate tabs. This keeps related definitions together but without introducing C++ class and objects. Please note that the Arduino system combines multiple .ino files into one unit before compiling, so the code could be combined into a single .ino file if desired without much trouble.

  1/// \file EventBusyBox.ino
  2///
  3/// \copyright Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed
  4/// under the terms of the BSD 3-clause license as included in LICENSE.
  5///
  6/// \brief Omnibus Arduino sketch demonstrating event-loop programming templates (work-in-progress).
  7///
  8/// This multi-file Arduino sketch demonstrates a variety of working examples
  9/// for performing non-blocking event-driven I/O.  It is intended as a starting
 10/// point for projects which combine responsive sensing and actuation with
 11/// behavioral logic.
 12///
 13/// N.B. This is a work in progress and only partly tested.
 14///
 15/// This top-level file just contains the minimal common code, with most of the
 16/// function calls commented out.  To use this template, you will need to pick
 17/// the specific modules you need for your project, update the hardware pin
 18/// assignments to match, and uncomment the related function calls from setup()
 19/// and poll().  You may then remove any unnecessary code, i.e. delete unused
 20/// files and unnecessary comments.
 21///
 22/// This opt-in approach is intended to discourage the usual problem of examples
 23/// in which a student isn't sure which parts are needed and so leaves lots of
 24/// unnecessary sample code in place even if it interferes with the intended
 25/// outcome.
 26///
 27/// The different functional modules are stored in different .ino files which
 28/// will appear in the Arduino IDE as separate tabs.  This keeps related
 29/// definitions together but without introducing C++ class and objects.  Please
 30/// note that the Arduino system combines multiple .ino files into one unit
 31/// before compiling, so the code could be combined into a single .ino file if
 32/// desired without much trouble.
 33
 34// ================================================================================
 35
 36/// Standard Arduino entry point to configure the hardware once after booting
 37/// up.  This runs once after pressing reset or powering up the board.
 38
 39void setup(void)
 40{
 41  // Initialize the Serial port for the user debugging console.  Note the fast baud rate for low latency.
 42  Serial.begin(115200);
 43
 44  // Please uncomment the function calls for the specific modules you wish to use:
 45  setup_LED_blinker();
 46  // setup_melody_player();
 47  // setup_servo_sweeper();
 48  // setup_servo_animation();
 49  // setup_sonar();
 50  // setup_speed_stepper();
 51
 52}
 53// ================================================================================
 54
 55/// Top-level polling entry point for servicing all background I/O and periodic
 56/// processing.  This function should be called repeatedly as fast as possible
 57/// from within loop() to poll program events.  This loop should never be
 58/// allowed to stall or block so that all tasks can be constantly serviced.  In
 59/// particular, the top-level loop should use polling_delay() instead of delay()
 60/// which would otherwise stall all activity.
 61
 62void poll(void)
 63{
 64  /// The timestamp in microseconds from the previous polling cycle, used to
 65  /// compute the interval between updates.
 66  static unsigned long last_update_clock = 0;
 67   
 68  /// The current clock time in microseconds.
 69  unsigned long now = micros();
 70
 71  // Compute the time elapsed in microseconds since the last poll.  This will
 72  // correctly handle wrapround of the 32-bit long time value given the
 73  // properties of twos-complement arithmetic.
 74  unsigned long interval = now - last_update_clock;
 75  last_update_clock = now;
 76
 77  // Begin the polling cycle.  This passes the elapsed time value to each module
 78  // so it can independently evaluate when to poll inputs or update outputs
 79  // using local timer variables.
 80
 81  // Please uncomment the function calls for the specific modules you wish to use:
 82  poll_console_input(interval);
 83  poll_LED_blinker(interval);
 84  // poll_melody_player(interval);
 85  // poll_servo_sweeper(interval);
 86  // poll_servo_animation(interval);
 87  // poll_sonar(interval);
 88  // poll_speed_stepper(interval);
 89}
 90// ================================================================================
 91
 92/// Wait for the given number of milliseconds while servicing background
 93/// activity.  This is a replacement for delay() for use within loop().  Note
 94/// that should not be called from polling functions, only from loop().
 95///
 96///   \param milliseconds number of milliseconds to poll before returning
 97
 98void polling_delay(long milliseconds)
 99{
100  // Record initial clock time in milliseconds.
101  unsigned long then = millis();
102
103  // Keep polling as long as the time remaining is positive.
104  while (milliseconds > 0) {
105
106    // Update all I/O functions.
107    poll();
108
109    // Calculate the elapsed time and subtract it from the delay counter.
110    unsigned long now = millis();    
111    milliseconds -= (now - then);
112    then = now;
113  }
114}
115   
116// ================================================================================
117
118/// Standard Arduino program entry, called repeatedly from the standard library.
119/// This will normally hold your top-level behavior script or operational loop.
120/// However, if this script needs to pause or wait for an event, it should make
121/// sure that the poll() function continues to be called as fast as possible to
122/// service all I/O activity.  In particular, polling_delay() should be used
123/// instead of delay().
124
125void loop(void)
126{
127  // The following sample code will be replaced by your primary logic script.
128  // Whenever the logic needs to wait on an event or delay, it should be sure to
129  // call poll() or polling_delay() to keep checking for events and updating the
130  // I/O state.
131
132  Serial.println("start-of-loop");
133
134  polling_delay(1000);   // Wait for a second.
135  Serial.println("starting-logic");
136
137  // Wait a few times for the LED blinker state to change.
138  for (int i=0; i < 10; i++) {
139    while(!is_LED_blinker_on()) poll(); // wait until the LED blinker reports ON
140    Serial.println("led-now-on");
141
142    while(is_LED_blinker_on()) poll(); // wait until the LED blinker reports OFF
143    Serial.println("led-now-off");
144  }
145  
146  Serial.println("end-of-loop");
147}
148// ================================================================================

console_input.ino

The console input processing code is in console_input.ino. This provides a user console interface for debugging using a host computer and the default default serial port on an Arduino.

static int string_equal(char *str1, char *str2)

Convenience wrapper on strcmp for clarity of code. Returns true if strings are identical.

void parse_console_input(int argc, char *argv[])

Process a console input message. This function should be customized for the particular application to handle whatever user interaction or command input is necessary. The message has already been separated into the argv list of argc strings, each holding one token split on the whitespace.

void poll_console_input(unsigned long elapsed)

Polling function to process messages arriving over the serial port. Each iteration through this polling function processes at most one character. It records the input message line into a buffer while simultaneously dividing it into ‘tokens’ delimited by whitespace. Each token is a string of non-whitespace characters, and might represent either a symbol or an integer. Once a message is complete, parse_console_input() is called.

const int MAX_LINE_LENGTH = 80

The maximum message line length.

const int MAX_TOKENS = 10

The maximum number of tokens in a single message.

  1/// \file console_input.ino
  2/// \brief User console interface for debugging using a host computer and the
  3/// default default serial port on an Arduino.
  4
  5// Copyright (c) 2015-2017, Garth Zeglin. All rights reserved. Licensed under
  6// the terms of the BSD 3-clause license as included in LICENSE.
  7
  8// ================================================================================
  9// Global state variables for the module.
 10
 11/// The maximum message line length.
 12const int MAX_LINE_LENGTH = 80;
 13 
 14/// The maximum number of tokens in a single message.
 15const int MAX_TOKENS = 10;
 16
 17// ================================================================================
 18// Utility functions
 19
 20/// Convenience wrapper on strcmp for clarity of code.  Returns true if strings are identical.
 21static inline int string_equal(char *str1, char *str2) 
 22{
 23  return !strcmp(str1, str2);
 24}
 25
 26// ================================================================================
 27/// Process a console input message.  This function should be customized for the
 28/// particular application to handle whatever user interaction or command input
 29/// is necessary.  The message has already been separated into a list of strings,
 30/// each holding one token split on the whitespace:
 31///   \param argc   number of argument tokens
 32///   \param argv   array of pointers to strings, one per token
 33
 34void parse_console_input(int argc, char *argv[])
 35{
 36  // Interpret the first token as a command symbol.
 37  char *command = argv[0];
 38
 39  // -- process zero-argument commands ---------------------------
 40  if (argc == 1) {
 41    if (string_equal(command, (char *) "report")) {
 42      Serial.println("Status report goes here.");
 43    }
 44    else if (string_equal(command, (char *) "reset")) {
 45      // do something to reset system here...
 46      Serial.println("Reset received.");
 47    }
 48    else if (string_equal(command, (char *) "time")) {
 49      // respond with the millisecond clock
 50      Serial.print("millis ");
 51      Serial.println(millis());
 52    }
 53    else {
 54      Serial.println("Unrecognized command.");
 55    }    
 56  }
 57
 58  // -- process one-argument commands ---------------------------
 59  else if (argc == 2) {
 60    int value = atoi(argv[1]);
 61    if (string_equal(command, (char *) "led")) {
 62      // set the hardware LED output
 63      digitalWrite(LED_BUILTIN, value);
 64    }
 65    else {
 66      Serial.println("Unrecognized command.");
 67    }    
 68  }
 69  else {
 70    Serial.println("Unrecognized command.");
 71  }
 72}
 73
 74// ================================================================================
 75/// Polling function to process messages arriving over the serial port.  Each
 76/// iteration through this polling function processes at most one character.  It
 77/// records the input message line into a buffer while simultaneously dividing it
 78/// into 'tokens' delimited by whitespace.  Each token is a string of
 79/// non-whitespace characters, and might represent either a symbol or an integer.
 80/// Once a message is complete, parse_console_input() is called.
 81
 82void poll_console_input(unsigned long elapsed)
 83{
 84  static char input_buffer[ MAX_LINE_LENGTH ];   // buffer for input characters
 85  static char *argv[MAX_TOKENS];                 // buffer for pointers to tokens
 86  static int chars_in_buffer = 0;  // counter for characters in buffer
 87  static int chars_in_token = 0;   // counter for characters in current partially-received token (the 'open' token)
 88  static int argc = 0;             // counter for tokens in argv
 89  static int error = 0;            // flag for any error condition in the current message
 90
 91  (void) elapsed;  // silence warnings about unused parameter
 92
 93  // Check if at least one byte is available on the serial input.
 94  if (Serial.available()) {
 95    int input = Serial.read();
 96
 97    // If the input is a whitespace character, end any currently open token.
 98    if (isspace(input)) {
 99      if (!error && chars_in_token > 0) {
100	if (chars_in_buffer == MAX_LINE_LENGTH) error = 1;
101	else {
102	  input_buffer[chars_in_buffer++] = 0;  // end the current token
103	  argc++;                               // increase the argument count
104	  chars_in_token = 0;                   // reset the token state
105	}
106      }
107
108      // If the whitespace input is an end-of-line character, then pass the message buffer along for interpretation.
109      if (input == '\r' || input == '\n') {
110
111	// if the message included too many tokens or too many characters, report an error
112	if (error) Serial.println("error: excessive input.");
113
114	// else process any complete message
115	else if (argc > 0) parse_console_input(argc, argv); 
116
117	// reset the full input state
118	error = chars_in_token = chars_in_buffer = argc = 0;                     
119      }
120    }
121
122    // Else the input is a character to store in the buffer at the end of the current token.
123    else {
124      // if beginning a new token
125      if (chars_in_token == 0) {
126
127	// if the token array is full, set an error state
128	if (argc == MAX_TOKENS) error = 1;
129
130	// otherwise save a pointer to the start of the token
131	else argv[ argc ] = &input_buffer[chars_in_buffer];
132      }
133
134      // the save the input and update the counters
135      if (!error) {
136	if (chars_in_buffer == MAX_LINE_LENGTH) error = 1;
137	else {
138	  input_buffer[chars_in_buffer++] = input;
139	  chars_in_token++;
140	}
141      }
142    }
143  }
144}
145// ================================================================================

LED_blinker.ino

A simple LED flashing demo is in LED_blinker.ino.

void setup_LED_blinker(void)

Configuration function to call from setup().

bool is_LED_blinker_on(void)

Status function to report the current LED state.

void poll_LED_blinker(unsigned long interval)

Update function to poll from loop().

Time remaining in microseconds before the next LED update.

The number of microseconds between LED updates.

Current LED output state.

 1/// \file LED_blinker.ino
 2
 3// Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed under the
 4// terms of the BSD 3-clause license as included in LICENSE.
 5
 6/// \brief Onboard LED blinker demo module for EventBusyBox illustrating
 7/// event-loop programming.
 8
 9// ================================================================================
10// Global state variables for the module.
11
12/// Time remaining in microseconds before the next LED update.
13long led_blink_timer = 0;
14
15/// The number of microseconds between LED updates.
16const long led_blink_interval = 500000; // microseconds duration of both ON and OFF intervals
17
18/// Current LED output state.
19int led_blink_state = LOW;
20
21// ================================================================================
22/// Configuration function to call from setup().
23void setup_LED_blinker(void)
24{
25  /// Use the on-board LED (generally D13).
26  pinMode(LED_BUILTIN, OUTPUT);
27}
28// ================================================================================
29
30/// Status function to report the current LED state.
31bool is_LED_blinker_on(void)
32{
33  return (led_blink_state != LOW);
34}  
35
36// ================================================================================
37/// Update function to poll from loop().
38void poll_LED_blinker(unsigned long interval)
39{
40  // Test whether to update the output.
41  led_blink_timer -= interval;
42
43  // The interval has elapsed once the timer variable reaches zero or overruns
44  // into negative values:
45  if (led_blink_timer <= 0) {
46
47    // Reset the timer for the next sampling period.  This approach adds in the
48    // interval value to maintain precise timing despite variation in the
49    // polling time.  E.g. if this sampling point was a little late, the
50    // led_blink_timer value will be negative, the next one will occur a little
51    // sooner, maintaining the overall average interval.
52    led_blink_timer += led_blink_interval;
53
54    // Toggle the hardware output.
55    if (led_blink_state == LOW) led_blink_state = HIGH;
56    else led_blink_state = LOW;
57    digitalWrite(LED_BUILTIN, led_blink_state);
58  }
59}
60// ================================================================================

servo_sweeper.ino

A hobby servo linear interpolation demo is in servo_sweeper.ino. This is aemo of servo linear interpolation for EventBusyBox illustrating event-loop programming. This module moves a hobby servo at an arbitrary linear speed toward a position target.

void start_servo_sweep(int target, float speed)

Asynchronous start function to initiate a linear servo motion. This may be called from another module to initiate a movement from the current servo position to the specified target, emitting servo updates at a constant rate. This function returns immediately, and the actual movement is performed by update_servo_sweeper(). The target parameter specifies the desired angle in degrees, and the speed parameter the desired speed in degrees/sec.

bool is_servo_sweeper_move_done(void)

Return a boolean indication of whether the servo has reached the target position or not. Note this only considers the commanded output; the servo can physically lag behind the target.

void setup_servo_sweeper(void)

Configuration function to call from setup().

void poll_servo_sweeper(unsigned long interval)

Update function to poll from loop().

const int SERVO_PIN = 9

Pin assigment for the servo command output.

const long servo_sweeper_interval = 50000

The number of microseconds between servo updates.

Servo sweeper_servo

Instance of a Servo control object. The Servo C++ class is defined in the Servo library.

long servo_sweeper_timer = 0

Time remaining in microseconds before the next servo update.

float servo_sweeper_angle = 0.0

The most recently commanded servo angle in degrees, stored as a float for finer speed precision.

float servo_sweeper_target = 0

The current servo target. This is stored as a float to match servo_sweeper_angle.

float servo_sweeper_step = 0.0

The current sweep step per update, in degrees.

  1/// \file servo_sweeper.ino
  2/// \brief Demo of servo linear interpolation for EventBusyBox illustrating
  3/// event-loop programming.  This module moves a hobby servo at an arbitrary
  4/// linear speed toward a position target.
  5
  6// \copyright Copyright (c) 2016-2017, Garth Zeglin. All rights
  7// reserved. Licensed under the terms of the BSD 3-clause license as included in
  8// LICENSE.
  9
 10// ================================================================================
 11// Import libraries.
 12#include <Servo.h> 
 13
 14// ================================================================================
 15// Global state variables for the module.
 16
 17/// Pin assigment for the servo command output.
 18const int SERVO_PIN = 9;
 19
 20/// The number of microseconds between servo updates.
 21const long servo_sweeper_interval = 50000;
 22
 23/// Instance of a Servo control object.  The Servo C++ class is defined in the
 24/// Servo library.
 25Servo sweeper_servo;
 26
 27/// Time remaining in microseconds before the next servo update.
 28long servo_sweeper_timer = 0;
 29
 30/// The most recently commanded servo angle in degrees, stored as a float for
 31/// finer speed precision.
 32float servo_sweeper_angle = 0.0;
 33
 34/// The current servo target.  This is stored as a float to match servo_sweeper_angle.
 35float servo_sweeper_target = 0;
 36
 37/// The current sweep step per update, in degrees.
 38float servo_sweeper_step = 0.0;
 39
 40// ================================================================================
 41/// Asynchronous start function to initiate a linear servo motion.  This may be
 42/// called from another module.  to initiate a movement from the current servo
 43/// position to the specified target, emitting servo updates at a constant rate.
 44/// This function returns immediately, and the actual movement is performed by
 45/// update_servo_sweeper().
 46///  \param target  desired angle in degrees
 47///  \param speed   desired speed in degrees/sec
 48
 49void start_servo_sweep(int target, float speed)
 50{
 51  // Save the target position.
 52  servo_sweeper_target = target;
 53
 54  // Compute the size of each step in degrees.  Note the use of float to capture
 55  // fractional precision.  The constant converts speed units from milliseconds
 56  // to seconds:   deg/step = (deg/sec) * (sec/microseconds) * (microseconds/step)
 57  servo_sweeper_step = speed * 0.000001 * servo_sweeper_interval;
 58}  
 59
 60// ================================================================================
 61/// Return a boolean indication of whether the servo has reached the target
 62/// position or not.  Note this only considers the commanded output; the servo
 63/// can physically lag behind the target.
 64bool is_servo_sweeper_move_done(void)
 65{
 66  return (servo_sweeper_angle == servo_sweeper_target);
 67}
 68
 69// ================================================================================
 70/// Configuration function to call from setup().
 71void setup_servo_sweeper(void)
 72{
 73  // Initialize the Servo object to use the given pin for output.
 74  sweeper_servo.attach(SERVO_PIN);
 75}
 76
 77// ================================================================================
 78/// Update function to poll from loop().
 79void poll_servo_sweeper(unsigned long interval)
 80{
 81  // Test whether to update the output.
 82  servo_sweeper_timer -= interval;
 83
 84  // The interval has elapsed once the timer variable reaches zero or overruns
 85  // into negative values:
 86  if (servo_sweeper_timer <= 0) {
 87
 88    // Reset the timer for the next sampling period.
 89    servo_sweeper_timer += servo_sweeper_interval;
 90
 91    // Update the command value.  This correctly handles the case where the servo has already reached the target.
 92    if (servo_sweeper_target >= servo_sweeper_angle) {
 93      // Apply movement in the positive direction.
 94      servo_sweeper_angle += servo_sweeper_step;
 95
 96      // If the movement would exceed the target, reset to the exact value.
 97      if (servo_sweeper_angle > servo_sweeper_target) servo_sweeper_angle = servo_sweeper_target;
 98
 99    } else {
100      // Else apply movement in the negative direction.
101      servo_sweeper_angle -= servo_sweeper_step;
102
103      // If the movement would exceed the target, reset to the exact value.
104      if (servo_sweeper_angle < servo_sweeper_target) servo_sweeper_angle = servo_sweeper_target;
105    }
106
107    // Update the servo with the new position.
108    sweeper_servo.write(servo_sweeper_angle);
109  }
110}
111// ================================================================================

servo_animation.ino

An animation state machine demo using servo_sweeper is in servo_animation.ino. This demo uses linear interpolation to performsa fixed sequence of servo moves over time, using the event-driven non-blocking style of EventBusyBox. The actual servo output uses poll_servo_sweeper().

void servo_animation_transition(int new_state)

Utility function to switch to a new animation state. This provides a hook for common code to execute on every transition.

void start_servo_animation(void)

Asynchronous start function to initiate an animation from another module.

void setup_servo_animation(void)

Configuration function to call from setup().

void poll_servo_animation(unsigned long interval)

Update function to poll from loop().

long servo_animation_timer = 0

Time remaining in microseconds before the next servo update.

const long servo_animation_interval = 100000

The number of microseconds betwen animation updates.

int servo_animation_state = 0

Current state of the animation state machine.

long servo_animation_elapsed = 0

Time elapsed in current state in microseconds.

  1/// \file servo_animation.ino
  2/// \brief Demo of an animation state machine using the servo linear
  3/// interpolation.  This module performs a fixed sequence of servo moves over
  4/// time, using the event-driven non-blocking style of EventBusyBox.  The actual
  5/// servo output uses poll_servo_sweeper().
  6
  7// \copyright Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed
  8// under the terms of the BSD 3-clause license as included in LICENSE.
  9
 10// ================================================================================
 11// Global state variables for the module.
 12
 13/// Time remaining in microseconds before the next servo update.
 14long servo_animation_timer = 0;
 15
 16/// The number of microseconds betwen animation updates.
 17const long servo_animation_interval = 100000;
 18
 19/// Current state of the animation state machine.
 20int servo_animation_state = 0;
 21
 22/// Time elapsed in current state in microseconds.
 23long servo_animation_elapsed = 0;
 24
 25// ================================================================================
 26/// Utility function to switch to a new animation state.  This provides a hook
 27/// for common code to execute on every transition.
 28void servo_animation_transition(int new_state)
 29{
 30  Serial.print("animation entering ");
 31  Serial.println(new_state);
 32  servo_animation_elapsed = 0;
 33  servo_animation_state = new_state;
 34}
 35	
 36// ================================================================================
 37/// Asynchronous start function to initiate an animation from another module.
 38void start_servo_animation(void)
 39{
 40  servo_animation_transition(1);
 41}
 42
 43// ================================================================================
 44/// Configuration function to call from setup().
 45void setup_servo_animation(void)
 46{
 47  // Start one iteration of the sequence as a demo.
 48  start_servo_animation();
 49}
 50
 51// ================================================================================
 52/// Update function to poll from loop().
 53void poll_servo_animation(unsigned long interval)
 54{
 55  // Keep track of the time spent in any given state.
 56  servo_animation_elapsed += interval;
 57  
 58  // Test whether to update the state machine.
 59  servo_animation_timer -= interval;
 60
 61  // The interval has elapsed once the timer variable reaches zero or overruns
 62  // into negative values:
 63  if (servo_animation_timer <= 0) {
 64
 65    // Reset the timer for the next sampling period.
 66    servo_animation_timer += servo_animation_interval;
 67
 68    // Branch to the current state machine state and apply any output changes or
 69    // state updates.
 70
 71    switch(servo_animation_state){
 72
 73    case 0:
 74      // idle, do nothing
 75      break;
 76
 77    case 1:
 78      // Begin the animation by issuing a movement target.
 79      start_servo_sweep(90.0, 45.0);
 80      servo_animation_transition(10);
 81      break;
 82
 83    case 10:
 84      // Wait until the servo sweeper has reached the target before proceeding.
 85      if (is_servo_sweeper_move_done()) {
 86	start_servo_sweep(180.0, 90.0);
 87	servo_animation_transition(20);	
 88      }
 89      break;
 90      
 91    case 20:
 92      if (is_servo_sweeper_move_done()) {
 93	start_servo_sweep(0.0, 45.0);
 94	servo_animation_transition(30);	
 95      }
 96      break;
 97
 98    case 30:
 99      if (is_servo_sweeper_move_done()) {
100	start_servo_sweep(180.0, 30.0);
101	servo_animation_transition(40);	
102      }
103      break;
104
105    case 40:
106      if (is_servo_sweeper_move_done()) {
107	start_servo_sweep(0.0, 30.0);
108	servo_animation_transition(50);	
109      }
110      break;
111      
112    case 50:
113      if (is_servo_sweeper_move_done()) {
114	Serial.println("animation pausing");
115	servo_animation_transition(60);
116      }
117      break;
118      
119    case 60:
120      // Wait until an interval has passed.
121      if (servo_animation_elapsed > 5000000L) {
122	Serial.println("animation pause complete");
123	servo_animation_transition(1);
124      }
125      break;
126      
127    default:
128      Serial.println("animation invalid state");
129      servo_animation_transition(0);
130      break;
131    }
132
133    
134  }
135}
136// ================================================================================

sonar.ino

A sonar sensor input demo is in sonar.ino.

void setup_sonar(void)

Configuration function to call from setup().

void poll_sonar(unsigned long interval)

Update function to poll from loop().

const int TRIG_PIN = 4

The sonar trigger pin output assignment.

const int ECHO_PIN = 5

The sonar echo pin input assignment.

const int MAX_DISTANCE = 450

The rated distance limit of the sensor, in cm.

const long SOUND_SPEED = 34000

A typical speed of sound, specified in cm/sec.

const long TIMEOUT = (2 * MAX_DISTANCE * 1000000) / SOUND_SPEED

Determine the maximum time to wait for an echo. The maximum rated distance is 4.5 meters; if no echo is received within the duration representing this round-trip distance, stop measuring. The timeout is specified in microseconds.

long sonar_timer = 0

Time remaining in microseconds before the next sonar cycle.

const long sonar_interval = 200000

The number of microseconds betwen sonar cycles.

int sonar_distance = 0

The most recent sonar echo distance in centimeters, or -1 if no echo was detected.

 1/// \file sonar.ino
 2/// \brief Sonar sensor demo module for EventBusyBox illustrating event-loop
 3/// programming.
 4
 5// \copyright Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed
 6// under the terms of the BSD 3-clause license as included in LICENSE.
 7
 8// ================================================================================
 9// Global state variables for the module.
10
11/// The sonar trigger pin output assignment.
12const int TRIG_PIN = 4;
13
14/// The sonar echo pin input assignment.
15const int ECHO_PIN = 5;
16
17/// The rated distance limit of the sensor, in cm.
18const int MAX_DISTANCE = 450;
19
20/// A typical speed of sound, specified in cm/sec.
21const long SOUND_SPEED = 34000;
22
23/// Determine the maximum time to wait for an echo. The maximum rated distance is
24/// 4.5 meters; if no echo is received within the duration representing this
25/// round-trip distance, stop measuring.  The timeout is specified in
26/// microseconds.
27const long TIMEOUT = (2 * MAX_DISTANCE * 1000000)/SOUND_SPEED;
28
29/// Time remaining in microseconds before the next sonar cycle.
30long sonar_timer = 0;
31
32/// The number of microseconds betwen sonar cycles.
33const long sonar_interval = 200000;
34
35/// The most recent sonar echo distance in centimeters, or -1 if no echo was detected.
36int sonar_distance = 0;
37
38// ================================================================================
39/// Configuration function to call from setup().
40void setup_sonar(void)
41{
42  // Initialize the trigger pin for output.
43  pinMode(TRIG_PIN, OUTPUT);
44  digitalWrite(TRIG_PIN, LOW);
45  
46  // Initialize the echo pin for input.
47  pinMode(ECHO_PIN, INPUT);
48}
49
50// ================================================================================
51/// Update function to poll from loop().
52void poll_sonar(unsigned long interval)
53{
54  // Test whether to run a measurement cycle.
55  sonar_timer -= interval;
56  
57  if (sonar_timer <= 0) {
58
59    // Reset the timer for the next sampling period.
60    sonar_timer += sonar_interval;
61
62    // Generate a short trigger pulse.
63    digitalWrite(TRIG_PIN, HIGH);
64    delayMicroseconds(10);
65    digitalWrite(TRIG_PIN, LOW);
66
67    // Measure the pulse length in microseconds.
68    long echo_time = pulseIn(ECHO_PIN, HIGH, TIMEOUT);
69
70    // If valid, scale into real-world units.
71    if (echo_time > 0) {
72
73      // Convert to a distance.  Note that the speed of sound is specified in
74      // cm/sec, so the duration is scaled from microsecondst o seconds.  The
75      // factor of 2 accounts for the round-trip doubling the time.
76      sonar_distance = (echo_time * 1e-6 * SOUND_SPEED) / 2;
77      Serial.print("sonar ");
78      Serial.println(sonar_distance);
79
80    } else {
81      sonar_distance = -1;
82      Serial.println("sonar none");
83    }
84  }
85}
86// ================================================================================

melody_player.ino

A music sequence player based on tone() is in melody_player.ino.

void start_melody(void)

Asynchronous start function to initiate playing a melody from another module or reset a currently playing melody.

void setup_melody_player(void)

Configuration function to call from setup().

void poll_melody_player(unsigned long interval)

Update function to poll from loop().

const int speakerPin = 7

Pin output assignment for the speaker.

const long melody_whole_duration = 2000000L

Whole-note duration in microseconds.

bool melody_playing = false

Flag to indicate melody playing is underway.

long melody_player_timer = 0

Time remaining in microseconds before the next note onset.

int melody_player_next_note = 0

Index into the melody note array for the next note to play.

struct melody_note_t

A data structure for specifying each note in the melody by a combination of pitch and duration. A zero pitch and duration specifies the end of the melody. N.B. this uses four bytes per note, not a very efficient use of memory, but adequate for short sequences.

int pitch

Integer frequency value for a musical note.

int duration

Note length as a fraction of the measure tempo, e.g. 1 is a whole note, 4 is a quarter note.

struct melody_note_t melody[] = {{NOTE_C4, 4}, {NOTE_D4, 4}, {NOTE_E4, 4}, {NOTE_F4, 4}, {NOTE_G4, 4}, {NOTE_A4, 4}, {NOTE_B4, 4}, {NOTE_C5, 4}, {NOTE_END, 0}}

A C major scale, one quarter note per pitch, specified using pitch and duration.

  1/// \file melody_player.ino
  2/// \brief Demo melody player module for EventBusyBox illustrating event-loop programming.
  3
  4// \copyright Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed
  5// under the terms of the BSD 3-clause license as included in LICENSE.
  6
  7#include "pitch_table.h"
  8
  9// ================================================================================
 10// Global state variables for the module.
 11
 12/// Pin output assignment for the speaker.
 13const int speakerPin = 7;
 14
 15/// Whole-note duration in microseconds.
 16const long melody_whole_duration = 2000000L;  
 17
 18/// Flag to indicate melody playing is underway.
 19bool melody_playing = false;
 20
 21/// Time remaining in microseconds before the next note onset.
 22long melody_player_timer = 0;
 23
 24/// Index into the melody note array for the next note to play.
 25int melody_player_next_note = 0;
 26  
 27/// A data structure for specifying each note in the melody by a combination of
 28/// pitch and duration.  A zero pitch and duration specifies the end of the melody.
 29/// N.B. this uses four bytes per note, not a very efficient use of memory, but
 30/// adequate for short sequences.
 31struct melody_note_t {
 32  /// Integer frequency value for a musical note.
 33  int pitch;
 34  
 35  /// Note length as a fraction of the measure tempo, e.g. 1 is a whole note, 4
 36  /// is a quarter note.
 37  int duration;
 38};
 39
 40/// A C major scale, one quarter note per pitch, specified using pitch and duration.
 41struct melody_note_t melody[] = {
 42  {NOTE_C4, 4},
 43  {NOTE_D4, 4},
 44  {NOTE_E4, 4},
 45  {NOTE_F4, 4},
 46  {NOTE_G4, 4},
 47  {NOTE_A4, 4},
 48  {NOTE_B4, 4},
 49  {NOTE_C5, 4},
 50  {NOTE_END, 0}
 51};
 52
 53// ================================================================================
 54/// Asynchronous start function to initiate playing a melody from another module
 55/// or reset a currently playing melody.
 56void start_melody(void)
 57{
 58  melody_playing = true;
 59  melody_player_timer = 0;
 60  melody_player_next_note = 0;
 61}
 62  
 63// ================================================================================
 64/// Configuration function to call from setup().
 65void setup_melody_player(void)
 66{
 67  /// Use the speaker pin for tone() output.
 68  pinMode(speakerPin, OUTPUT);
 69
 70  /// For demo purposes, always begin by playing the melody once.
 71  start_melody();
 72}
 73
 74// ================================================================================
 75/// Update function to poll from loop().
 76void poll_melody_player(unsigned long interval)
 77{
 78  if (melody_playing) {
 79    // Test whether to update the output.
 80    melody_player_timer -= interval;
 81
 82    // The interval has elapsed once the timer variable reaches zero or overruns
 83    // into negative values:
 84    if (melody_player_timer <= 0) {
 85
 86      // Fetch the next note and duration.
 87      int pitch    = melody[melody_player_next_note].pitch;
 88      int duration = melody[melody_player_next_note].duration;
 89      
 90      // Test if the melody is done.
 91      if (pitch <= 0) {
 92	noTone(speakerPin);
 93	melody_playing = false;
 94	
 95      } else {
 96	// Calculate the note duration in microseconds and update the timer.
 97	long note_duration = melody_whole_duration / duration;
 98	melody_player_timer += note_duration;
 99
100	// Start generating the next pitch.
101	tone(speakerPin, pitch);
102
103	// Advance to the next note for the next cycle.
104	melody_player_next_note++;
105
106	// Generate a debugging stream.
107	Serial.print("pitch ");
108	Serial.println(pitch);
109      }
110    }
111  }
112}
113// ================================================================================