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().
-
long led_blink_timer = 0¶
Time remaining in microseconds before the next LED update.
-
const long led_blink_interval = 500000¶
The number of microseconds between LED updates.
-
int led_blink_state = LOW¶
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.
-
int pitch¶
-
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// ================================================================================