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.
Omnibus Arduino sketch demonstrating event-loop programming templates (work-in-progress).
This multi-file Arduino sketch demonstrates a variety of working examples for performing non-blocking event-driven I/O. It is intended as a starting point for projects which combine responsive sensing and actuation with behavioral logic.
- Copyright
- Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed under the terms of the BSD 3-clause license as included in LICENSE.
N.B. This is a work in progress and only partly tested.
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.
Functions
-
void
setup
(void)¶ Standard Arduino entry point to configure the hardware once after booting up. This runs once after pressing reset or powering up the board.
-
void
poll
(void)¶ Top-level polling entry point for servicing all background I/O and periodic processing. This function should be called repeatedly as fast as possible from within loop() to poll program events. This loop should never be allowed to stall or block so that all tasks can be constantly serviced. In particular, the top-level loop should use polling_delay() instead of delay() which would otherwise stall all activity.
-
void
polling_delay
(long milliseconds)¶ Wait for the given number of milliseconds while servicing background activity. This is a replacement for delay() for use within loop(). Note that should not be called from polling functions, only from loop().
- Parameters
milliseconds
: number of milliseconds to poll before returning
-
void
loop
(void)¶ Standard Arduino program entry, called repeatedly from the standard library. This will normally hold your top-level behavior script or operational loop. However, if this script needs to pause or wait for an event, it should make sure that the poll() function continues to be called as fast as possible to service all I/O activity. In particular, polling_delay() should be used instead of delay().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | /// \file EventBusyBox.ino
///
/// \copyright Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed
/// under the terms of the BSD 3-clause license as included in LICENSE.
///
/// \brief Omnibus Arduino sketch demonstrating event-loop programming templates (work-in-progress).
///
/// \details This multi-file Arduino sketch demonstrates a variety of working
/// examples for performing non-blocking event-driven I/O. It is intended as a
/// starting point for projects which combine responsive sensing and actuation
/// with behavioral logic.
///
/// N.B. This is a work in progress and only partly tested.
///
/// 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.
// ================================================================================
/// Standard Arduino entry point to configure the hardware once after booting
/// up. This runs once after pressing reset or powering up the board.
void setup(void)
{
// Initialize the Serial port for the user debugging console. Note the fast baud rate for low latency.
Serial.begin(115200);
// Please uncomment the function calls for the specific modules you wish to use:
setup_LED_blinker();
// setup_melody_player();
// setup_servo_sweeper();
// setup_servo_animation();
// setup_sonar();
// setup_speed_stepper();
}
// ================================================================================
/// Top-level polling entry point for servicing all background I/O and periodic
/// processing. This function should be called repeatedly as fast as possible
/// from within loop() to poll program events. This loop should never be
/// allowed to stall or block so that all tasks can be constantly serviced. In
/// particular, the top-level loop should use polling_delay() instead of delay()
/// which would otherwise stall all activity.
void poll(void)
{
/// The timestamp in microseconds from the previous polling cycle, used to
/// compute the interval between updates.
static unsigned long last_update_clock = 0;
/// The current clock time in microseconds.
unsigned long now = micros();
// Compute the time elapsed in microseconds since the last poll. This will
// correctly handle wrapround of the 32-bit long time value given the
// properties of twos-complement arithmetic.
unsigned long interval = now - last_update_clock;
last_update_clock = now;
// Begin the polling cycle. This passes the elapsed time value to each module
// so it can independently evaluate when to poll inputs or update outputs
// using local timer variables.
// Please uncomment the function calls for the specific modules you wish to use:
poll_console_input(interval);
poll_LED_blinker(interval);
// poll_melody_player(interval);
// poll_servo_sweeper(interval);
// poll_servo_animation(interval);
// poll_sonar(interval);
// poll_speed_stepper(interval);
}
// ================================================================================
/// Wait for the given number of milliseconds while servicing background
/// activity. This is a replacement for delay() for use within loop(). Note
/// that should not be called from polling functions, only from loop().
///
/// \param milliseconds number of milliseconds to poll before returning
void polling_delay(long milliseconds)
{
// Record initial clock time in milliseconds.
unsigned long then = millis();
// Keep polling as long as the time remaining is positive.
while (milliseconds > 0) {
// Update all I/O functions.
poll();
// Calculate the elapsed time and subtract it from the delay counter.
unsigned long now = millis();
milliseconds -= (now - then);
then = now;
}
}
// ================================================================================
/// Standard Arduino program entry, called repeatedly from the standard library.
/// This will normally hold your top-level behavior script or operational loop.
/// However, if this script needs to pause or wait for an event, it should make
/// sure that the poll() function continues to be called as fast as possible to
/// service all I/O activity. In particular, polling_delay() should be used
/// instead of delay().
void loop(void)
{
// The following sample code will be replaced by your primary logic script.
// Whenever the logic needs to wait on an event or delay, it should be sure to
// call poll() or polling_delay() to keep checking for events and updating the
// I/O state.
Serial.println("start-of-loop");
polling_delay(1000); // Wait for a second.
Serial.println("starting-logic");
// Wait a few times for the LED blinker state to change.
for (int i=0; i < 10; i++) {
while(!is_LED_blinker_on()) poll(); // wait until the LED blinker reports ON
Serial.println("led-now-on");
while(is_LED_blinker_on()) poll(); // wait until the LED blinker reports OFF
Serial.println("led-now-off");
}
Serial.println("end-of-loop");
}
// ================================================================================
|
console_input.ino¶
The console input processing code is in console_input.ino.
User console interface for debugging using a host computer and the default default serial port on an Arduino.
Functions
-
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 a list of strings, each holding one token split on the whitespace:
- Parameters
argc
: number of argument tokensargv
: array of pointers to strings, one per token
-
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.
Variables
-
const int
MAX_LINE_LENGTH
¶ The maximum message line length.
-
const int
MAX_TOKENS
¶ The maximum number of tokens in a single message.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | /// \file console_input.ino
/// \brief User console interface for debugging using a host computer and the
/// default default serial port on an Arduino.
// Copyright (c) 2015-2017, Garth Zeglin. All rights reserved. Licensed under
// the terms of the BSD 3-clause license as included in LICENSE.
// ================================================================================
// Global state variables for the module.
/// The maximum message line length.
const int MAX_LINE_LENGTH = 80;
/// The maximum number of tokens in a single message.
const int MAX_TOKENS = 10;
// ================================================================================
// Utility functions
/// Convenience wrapper on strcmp for clarity of code. Returns true if strings are identical.
static inline int string_equal(char *str1, char *str2)
{
return !strcmp(str1, str2);
}
// ================================================================================
/// 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 a list of strings,
/// each holding one token split on the whitespace:
/// \param argc number of argument tokens
/// \param argv array of pointers to strings, one per token
void parse_console_input(int argc, char *argv[])
{
// Interpret the first token as a command symbol.
char *command = argv[0];
// -- process zero-argument commands ---------------------------
if (argc == 1) {
if (string_equal(command, (char *) "report")) {
Serial.println("Status report goes here.");
}
else if (string_equal(command, (char *) "reset")) {
// do something to reset system here...
Serial.println("Reset received.");
}
else if (string_equal(command, (char *) "time")) {
// respond with the millisecond clock
Serial.print("millis ");
Serial.println(millis());
}
else {
Serial.println("Unrecognized command.");
}
}
// -- process one-argument commands ---------------------------
else if (argc == 2) {
int value = atoi(argv[1]);
if (string_equal(command, (char *) "led")) {
// set the hardware LED output
digitalWrite(LED_BUILTIN, value);
}
else {
Serial.println("Unrecognized command.");
}
}
else {
Serial.println("Unrecognized command.");
}
}
// ================================================================================
/// 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.
void poll_console_input(unsigned long elapsed)
{
static char input_buffer[ MAX_LINE_LENGTH ]; // buffer for input characters
static char *argv[MAX_TOKENS]; // buffer for pointers to tokens
static int chars_in_buffer = 0; // counter for characters in buffer
static int chars_in_token = 0; // counter for characters in current partially-received token (the 'open' token)
static int argc = 0; // counter for tokens in argv
static int error = 0; // flag for any error condition in the current message
(void) elapsed; // silence warnings about unused parameter
// Check if at least one byte is available on the serial input.
if (Serial.available()) {
int input = Serial.read();
// If the input is a whitespace character, end any currently open token.
if (isspace(input)) {
if (!error && chars_in_token > 0) {
if (chars_in_buffer == MAX_LINE_LENGTH) error = 1;
else {
input_buffer[chars_in_buffer++] = 0; // end the current token
argc++; // increase the argument count
chars_in_token = 0; // reset the token state
}
}
// If the whitespace input is an end-of-line character, then pass the message buffer along for interpretation.
if (input == '\r' || input == '\n') {
// if the message included too many tokens or too many characters, report an error
if (error) Serial.println("error: excessive input.");
// else process any complete message
else if (argc > 0) parse_console_input(argc, argv);
// reset the full input state
error = chars_in_token = chars_in_buffer = argc = 0;
}
}
// Else the input is a character to store in the buffer at the end of the current token.
else {
// if beginning a new token
if (chars_in_token == 0) {
// if the token array is full, set an error state
if (argc == MAX_TOKENS) error = 1;
// otherwise save a pointer to the start of the token
else argv[ argc ] = &input_buffer[chars_in_buffer];
}
// the save the input and update the counters
if (!error) {
if (chars_in_buffer == MAX_LINE_LENGTH) error = 1;
else {
input_buffer[chars_in_buffer++] = input;
chars_in_token++;
}
}
}
}
}
// ================================================================================
|
LED_blinker.ino¶
A simple LED flashing demo is in LED_blinker.ino.
Functions
-
bool
is_LED_blinker_on
(void)¶ Status function to report the current LED state.
Variables
-
long
led_blink_timer
¶ Onboard LED blinker demo module for EventBusyBox illustrating event-loop programming.
Time remaining in microseconds before the next LED update.
-
const long
led_blink_interval
¶ The number of microseconds between LED updates.
-
int
led_blink_state
¶ Current LED output state.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | /// \file LED_blinker.ino
// Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed under the
// terms of the BSD 3-clause license as included in LICENSE.
/// \brief Onboard LED blinker demo module for EventBusyBox illustrating
/// event-loop programming.
// ================================================================================
// Global state variables for the module.
/// Time remaining in microseconds before the next LED update.
long led_blink_timer = 0;
/// The number of microseconds between LED updates.
const long led_blink_interval = 500000; // microseconds duration of both ON and OFF intervals
/// Current LED output state.
int led_blink_state = LOW;
// ================================================================================
/// Configuration function to call from setup().
void setup_LED_blinker(void)
{
/// Use the on-board LED (generally D13).
pinMode(LED_BUILTIN, OUTPUT);
}
// ================================================================================
/// Status function to report the current LED state.
bool is_LED_blinker_on(void)
{
return (led_blink_state != LOW);
}
// ================================================================================
/// Update function to poll from loop().
void poll_LED_blinker(unsigned long interval)
{
// Test whether to update the output.
led_blink_timer -= interval;
// The interval has elapsed once the timer variable reaches zero or overruns
// into negative values:
if (led_blink_timer <= 0) {
// Reset the timer for the next sampling period. This approach adds in the
// interval value to maintain precise timing despite variation in the
// polling time. E.g. if this sampling point was a little late, the
// led_blink_timer value will be negative, the next one will occur a little
// sooner, maintaining the overall average interval.
led_blink_timer += led_blink_interval;
// Toggle the hardware output.
if (led_blink_state == LOW) led_blink_state = HIGH;
else led_blink_state = LOW;
digitalWrite(LED_BUILTIN, led_blink_state);
}
}
// ================================================================================
|
servo_sweeper.ino¶
A hobby servo linear interpolation demo is in servo_sweeper.ino.
Demo 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.
Functions
-
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().
- Parameters
target
: desired angle in degreesspeed
: 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.
Variables
-
const int
SERVO_PIN
¶ Pin assigment for the servo command output.
-
const long
servo_sweeper_interval
¶ 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
¶ Time remaining in microseconds before the next servo update.
-
float
servo_sweeper_angle
¶ The most recently commanded servo angle in degrees, stored as a float for finer speed precision.
-
float
servo_sweeper_target
¶ The current servo target. This is stored as a float to match servo_sweeper_angle.
-
float
servo_sweeper_step
¶ The current sweep step per update, in degrees.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | /// \file servo_sweeper.ino
/// \brief Demo 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.
// \copyright Copyright (c) 2016-2017, Garth Zeglin. All rights
// reserved. Licensed under the terms of the BSD 3-clause license as included in
// LICENSE.
// ================================================================================
// Import libraries.
#include <Servo.h>
// ================================================================================
// Global state variables for the module.
/// Pin assigment for the servo command output.
const int SERVO_PIN = 9;
/// The number of microseconds between servo updates.
const long servo_sweeper_interval = 50000;
/// Instance of a Servo control object. The Servo C++ class is defined in the
/// Servo library.
Servo sweeper_servo;
/// Time remaining in microseconds before the next servo update.
long servo_sweeper_timer = 0;
/// The most recently commanded servo angle in degrees, stored as a float for
/// finer speed precision.
float servo_sweeper_angle = 0.0;
/// The current servo target. This is stored as a float to match servo_sweeper_angle.
float servo_sweeper_target = 0;
/// The current sweep step per update, in degrees.
float servo_sweeper_step = 0.0;
// ================================================================================
/// 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().
/// \param target desired angle in degrees
/// \param speed desired speed in degrees/sec
void start_servo_sweep(int target, float speed)
{
// Save the target position.
servo_sweeper_target = target;
// Compute the size of each step in degrees. Note the use of float to capture
// fractional precision. The constant converts speed units from milliseconds
// to seconds: deg/step = (deg/sec) * (sec/microseconds) * (microseconds/step)
servo_sweeper_step = speed * 0.000001 * servo_sweeper_interval;
}
// ================================================================================
/// 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.
bool is_servo_sweeper_move_done(void)
{
return (servo_sweeper_angle == servo_sweeper_target);
}
// ================================================================================
/// Configuration function to call from setup().
void setup_servo_sweeper(void)
{
// Initialize the Servo object to use the given pin for output.
sweeper_servo.attach(SERVO_PIN);
}
// ================================================================================
/// Update function to poll from loop().
void poll_servo_sweeper(unsigned long interval)
{
// Test whether to update the output.
servo_sweeper_timer -= interval;
// The interval has elapsed once the timer variable reaches zero or overruns
// into negative values:
if (servo_sweeper_timer <= 0) {
// Reset the timer for the next sampling period.
servo_sweeper_timer += servo_sweeper_interval;
// Update the command value. This correctly handles the case where the servo has already reached the target.
if (servo_sweeper_target >= servo_sweeper_angle) {
// Apply movement in the positive direction.
servo_sweeper_angle += servo_sweeper_step;
// If the movement would exceed the target, reset to the exact value.
if (servo_sweeper_angle > servo_sweeper_target) servo_sweeper_angle = servo_sweeper_target;
} else {
// Else apply movement in the negative direction.
servo_sweeper_angle -= servo_sweeper_step;
// If the movement would exceed the target, reset to the exact value.
if (servo_sweeper_angle < servo_sweeper_target) servo_sweeper_angle = servo_sweeper_target;
}
// Update the servo with the new position.
sweeper_servo.write(servo_sweeper_angle);
}
}
// ================================================================================
|
servo_animation.ino¶
An animation state machine demo using servo_sweeper is in servo_animation.ino.
Demo of an animation state machine using the servo linear interpolation. This module performs a fixed sequence of servo moves over time, using the event-driven non-blocking style of EventBusyBox. The actual servo output uses poll_servo_sweeper().
Functions
-
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.
Variables
-
long
servo_animation_timer
¶ Time remaining in microseconds before the next servo update.
-
const long
servo_animation_interval
¶ The number of microseconds betwen animation updates.
-
int
servo_animation_state
¶ Current state of the animation state machine.
-
long
servo_animation_elapsed
¶ Time elapsed in current state in microseconds.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | /// \file servo_animation.ino
/// \brief Demo of an animation state machine using the servo linear
/// interpolation. This module performs a fixed sequence of servo moves over
/// time, using the event-driven non-blocking style of EventBusyBox. The actual
/// servo output uses poll_servo_sweeper().
// \copyright Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed
// under the terms of the BSD 3-clause license as included in LICENSE.
// ================================================================================
// Global state variables for the module.
/// Time remaining in microseconds before the next servo update.
long servo_animation_timer = 0;
/// The number of microseconds betwen animation updates.
const long servo_animation_interval = 100000;
/// Current state of the animation state machine.
int servo_animation_state = 0;
/// Time elapsed in current state in microseconds.
long servo_animation_elapsed = 0;
// ================================================================================
/// Utility function to switch to a new animation state. This provides a hook
/// for common code to execute on every transition.
void servo_animation_transition(int new_state)
{
Serial.print("animation entering ");
Serial.println(new_state);
servo_animation_elapsed = 0;
servo_animation_state = new_state;
}
// ================================================================================
/// Asynchronous start function to initiate an animation from another module.
void start_servo_animation(void)
{
servo_animation_transition(1);
}
// ================================================================================
/// Configuration function to call from setup().
void setup_servo_animation(void)
{
// Start one iteration of the sequence as a demo.
start_servo_animation();
}
// ================================================================================
/// Update function to poll from loop().
void poll_servo_animation(unsigned long interval)
{
// Keep track of the time spent in any given state.
servo_animation_elapsed += interval;
// Test whether to update the state machine.
servo_animation_timer -= interval;
// The interval has elapsed once the timer variable reaches zero or overruns
// into negative values:
if (servo_animation_timer <= 0) {
// Reset the timer for the next sampling period.
servo_animation_timer += servo_animation_interval;
// Branch to the current state machine state and apply any output changes or
// state updates.
switch(servo_animation_state){
case 0:
// idle, do nothing
break;
case 1:
// Begin the animation by issuing a movement target.
start_servo_sweep(90.0, 45.0);
servo_animation_transition(10);
break;
case 10:
// Wait until the servo sweeper has reached the target before proceeding.
if (is_servo_sweeper_move_done()) {
start_servo_sweep(180.0, 90.0);
servo_animation_transition(20);
}
break;
case 20:
if (is_servo_sweeper_move_done()) {
start_servo_sweep(0.0, 45.0);
servo_animation_transition(30);
}
break;
case 30:
if (is_servo_sweeper_move_done()) {
start_servo_sweep(180.0, 30.0);
servo_animation_transition(40);
}
break;
case 40:
if (is_servo_sweeper_move_done()) {
start_servo_sweep(0.0, 30.0);
servo_animation_transition(50);
}
break;
case 50:
if (is_servo_sweeper_move_done()) {
Serial.println("animation pausing");
servo_animation_transition(60);
}
break;
case 60:
// Wait until an interval has passed.
if (servo_animation_elapsed > 5000000L) {
Serial.println("animation pause complete");
servo_animation_transition(1);
}
break;
default:
Serial.println("animation invalid state");
servo_animation_transition(0);
break;
}
}
}
// ================================================================================
|
sonar.ino¶
A sonar sensor input demo is in sonar.ino.
Sonar sensor demo module for EventBusyBox illustrating event-loop programming.
Functions
Variables
-
const int
TRIG_PIN
¶ The sonar trigger pin output assignment.
-
const int
ECHO_PIN
¶ The sonar echo pin input assignment.
-
const int
MAX_DISTANCE
¶ The rated distance limit of the sensor, in cm.
-
const long
SOUND_SPEED
¶ A typical speed of sound, specified in cm/sec.
-
const long
TIMEOUT
¶ 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
¶ Time remaining in microseconds before the next sonar cycle.
-
const long
sonar_interval
¶ The number of microseconds betwen sonar cycles.
-
int
sonar_distance
¶ The most recent sonar echo distance in centimeters, or -1 if no echo was detected.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | /// \file sonar.ino
/// \brief Sonar sensor demo module for EventBusyBox illustrating event-loop
/// programming.
// \copyright Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed
// under the terms of the BSD 3-clause license as included in LICENSE.
// ================================================================================
// Global state variables for the module.
/// The sonar trigger pin output assignment.
const int TRIG_PIN = 4;
/// The sonar echo pin input assignment.
const int ECHO_PIN = 5;
/// The rated distance limit of the sensor, in cm.
const int MAX_DISTANCE = 450;
/// A typical speed of sound, specified in cm/sec.
const long SOUND_SPEED = 34000;
/// 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.
const long TIMEOUT = (2 * MAX_DISTANCE * 1000000)/SOUND_SPEED;
/// Time remaining in microseconds before the next sonar cycle.
long sonar_timer = 0;
/// The number of microseconds betwen sonar cycles.
const long sonar_interval = 200000;
/// The most recent sonar echo distance in centimeters, or -1 if no echo was detected.
int sonar_distance = 0;
// ================================================================================
/// Configuration function to call from setup().
void setup_sonar(void)
{
// Initialize the trigger pin for output.
pinMode(TRIG_PIN, OUTPUT);
digitalWrite(TRIG_PIN, LOW);
// Initialize the echo pin for input.
pinMode(ECHO_PIN, INPUT);
}
// ================================================================================
/// Update function to poll from loop().
void poll_sonar(unsigned long interval)
{
// Test whether to run a measurement cycle.
sonar_timer -= interval;
if (sonar_timer <= 0) {
// Reset the timer for the next sampling period.
sonar_timer += sonar_interval;
// Generate a short trigger pulse.
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Measure the pulse length in microseconds.
long echo_time = pulseIn(ECHO_PIN, HIGH, TIMEOUT);
// If valid, scale into real-world units.
if (echo_time > 0) {
// Convert to a distance. Note that the speed of sound is specified in
// cm/sec, so the duration is scaled from microsecondst o seconds. The
// factor of 2 accounts for the round-trip doubling the time.
sonar_distance = (echo_time * 1e-6 * SOUND_SPEED) / 2;
Serial.print("sonar ");
Serial.println(sonar_distance);
} else {
sonar_distance = -1;
Serial.println("sonar none");
}
}
}
// ================================================================================
|
melody_player.ino¶
A music sequence player based on tone() is in melody_player.ino.
Demo melody player module for EventBusyBox illustrating event-loop programming.
Functions
-
void
start_melody
(void)¶ Asynchronous start function to initiate playing a melody from another module or reset a currently playing melody.
Variables
-
const int
speakerPin
¶ Pin output assignment for the speaker.
-
const long
melody_whole_duration
¶ Whole-note duration in microseconds.
-
bool
melody_playing
¶ Flag to indicate melody playing is underway.
-
long
melody_player_timer
¶ Time remaining in microseconds before the next note onset.
-
int
melody_player_next_note
¶ Index into the melody note array for the next note to play.
-
struct melody_note_t
melody
[]¶ A C major scale, one quarter note per pitch, specified using pitch and duration.
-
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | /// \file melody_player.ino
/// \brief Demo melody player module for EventBusyBox illustrating event-loop programming.
// \copyright Copyright (c) 2017, Garth Zeglin. All rights reserved. Licensed
// under the terms of the BSD 3-clause license as included in LICENSE.
#include "pitch_table.h"
// ================================================================================
// Global state variables for the module.
/// Pin output assignment for the speaker.
const int speakerPin = 7;
/// Whole-note duration in microseconds.
const long melody_whole_duration = 2000000L;
/// Flag to indicate melody playing is underway.
bool melody_playing = false;
/// Time remaining in microseconds before the next note onset.
long melody_player_timer = 0;
/// Index into the melody note array for the next note to play.
int melody_player_next_note = 0;
/// 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.
struct melody_note_t {
/// Integer frequency value for a musical note.
int pitch;
/// Note length as a fraction of the measure tempo, e.g. 1 is a whole note, 4
/// is a quarter note.
int duration;
};
/// A C major scale, one quarter note per pitch, specified using pitch and duration.
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},
{0,0}
};
// ================================================================================
/// Asynchronous start function to initiate playing a melody from another module
/// or reset a currently playing melody.
void start_melody(void)
{
melody_playing = true;
melody_player_timer = 0;
melody_player_next_note = 0;
}
// ================================================================================
/// Configuration function to call from setup().
void setup_melody_player(void)
{
/// Use the speaker pin for tone() output.
pinMode(speakerPin, OUTPUT);
/// For demo purposes, always begin by playing the melody once.
start_melody();
}
// ================================================================================
/// Update function to poll from loop().
void poll_melody_player(unsigned long interval)
{
if (melody_playing) {
// Test whether to update the output.
melody_player_timer -= interval;
// The interval has elapsed once the timer variable reaches zero or overruns
// into negative values:
if (melody_player_timer <= 0) {
// Fetch the next note and duration.
int pitch = melody[melody_player_next_note].pitch;
int duration = melody[melody_player_next_note].duration;
// Test if the melody is done.
if (pitch <= 0) {
noTone(speakerPin);
melody_playing = false;
} else {
// Calculate the note duration in microseconds and update the timer.
long note_duration = melody_whole_duration / duration;
melody_player_timer += note_duration;
// Start generating the next pitch.
tone(speakerPin, pitch);
// Advance to the next note for the next cycle.
melody_player_next_note++;
// Generate a debugging stream.
Serial.print("pitch ");
Serial.println(pitch);
}
}
}
}
// ================================================================================
|