7.23.3. Main Source Code¶
The main top-level code is in PinballLogic.ino.
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 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 | /// \file PinballLogic.ino
/// \brief Arduino program demonstrating basic real-time logic for a custom pinball machine.
/// \copyright No copyright, 2016, Garth Zeglin. This file is explicitly placed in the public domain.
/// \details This example is intended as a starting point for developing a
/// custom pinball controller, demonstrating the use of state machines
/// to implement both I/O and game logic.
///
/// This example is written using Arduino C++ conventions:
///
/// 1. state variables are global
/// 2. global C++ objects are statically declared and initialized
/// 3. the sketch is divided into multiple .ino files which form one compilation unit
/// 4. C++ classes in .cpp files are separately compiled
/****************************************************************/
// Forward declarations.
#import "PinballSensor.h"
#import "PopBumper.h"
#import "ToneSpeaker.h"
/****************************************************************/
/**** Hardware pin assignments **********************************/
/****************************************************************/
// Analog input assignments.
const int start_switch_pin = A0;
const int bumper_sensor_pin = A1;
const int drain_sensor_pin = A2;
// Actuator output assignments.
const int LED1_pin = 2; // inverse-logic LED
const int LED2_pin = 3; // inverse-logic LED
const int bumper_pin = 4; // positive-logic solenoid driver
const int speaker_pin = 9; // positive-logic speaker driver
/****************************************************************/
/**** Global variables and constants ****************************/
/****************************************************************/
// The baud rate is the number of bits per second transmitted over the serial port.
const long BAUD_RATE = 115200;
PinballSensor start_switch(start_switch_pin);
PinballSensor bumper_sensor(bumper_sensor_pin);
PinballSensor drain_sensor(drain_sensor_pin);
PopBumper bumper(bumper_pin);
ToneSpeaker speaker(speaker_pin);
// Define the game state variables.
/// Define symbolic values for the main game state machine.
enum state_t { STATE_IDLE, STATE_ATTRACT, STATE_BALL1, STATE_BALL2, STATE_BALL3, STATE_GAME_OVER, NUM_STATES } game_state;
/// A count of the number of microseconds elapsed in the current game state.
long game_state_elapsed;
/// Convenient time constants, in microseconds.
const long FIVE_SECONDS = 5000000;
/// Attract mode melody.
const unsigned char attract_melody[] = {
MIDI_C4, EIGHTH, MIDI_E4, EIGHTH, MIDI_G4, EIGHTH, MIDI_C5, EIGHTH, MIDI_B4, EIGHTH, MIDI_G4, EIGHTH,
MIDI_E4, QUARTER,
MIDI_END
};
/****************************************************************/
/// Update all input state, including periodic sensor sampling, debouncing, and filtering.
void poll_sensor_inputs(unsigned long interval)
{
start_switch.update(interval);
bumper_sensor.update(interval);
drain_sensor.update(interval);
}
/****************************************************************/
/// Update the game state machine, including advancing game state and score and applying modal input-output mappings.
void poll_game_logic(unsigned long interval)
{
game_state_elapsed += interval;
switch(game_state) {
//-----------------------------------------------
case STATE_IDLE:
if (start_switch.isTriggered()) {
game_state_elapsed = 0;
game_state = STATE_BALL1;
send_debug_message("entering BALL1");
}
if (game_state_elapsed > FIVE_SECONDS) {
game_state_elapsed = 0;
game_state = STATE_ATTRACT;
speaker.start_melody(attract_melody);
send_debug_message("entering ATTRACT");
}
break;
//-----------------------------------------------
case STATE_ATTRACT:
if (start_switch.isTriggered()) {
game_state_elapsed = 0;
game_state = STATE_BALL1;
send_debug_message("entering BALL1");
}
if (game_state_elapsed > FIVE_SECONDS) {
game_state_elapsed = 0;
game_state = STATE_IDLE;
send_debug_message("entering IDLE");
}
break;
//-----------------------------------------------
case STATE_BALL1:
if (drain_sensor.isTriggered()) {
game_state_elapsed = 0;
game_state = STATE_BALL2;
send_debug_message("entering BALL2");
}
if (bumper_sensor.isTriggered()) {
bumper.trigger();
}
break;
//-----------------------------------------------
case STATE_BALL2:
if (drain_sensor.isTriggered()) {
game_state_elapsed = 0;
game_state = STATE_BALL3;
send_debug_message("entering BALL3");
}
if (bumper_sensor.isTriggered()) {
bumper.trigger();
}
break;
//-----------------------------------------------
case STATE_BALL3:
if (drain_sensor.isTriggered()) {
game_state_elapsed = 0;
game_state = STATE_GAME_OVER;
send_debug_message("entering GAME_OVER");
}
if (bumper_sensor.isTriggered()) {
bumper.trigger();
}
break;
//-----------------------------------------------
case STATE_GAME_OVER:
if (game_state_elapsed > FIVE_SECONDS) {
game_state_elapsed = 0;
game_state = STATE_IDLE;
send_debug_message("entering IDLE");
}
break;
}
/****************************************************************/
// some simple LED animation based on the game state
switch(game_state) {
case STATE_IDLE:
digitalWrite(LED1_pin, HIGH);
digitalWrite(LED2_pin, HIGH);
break;
case STATE_ATTRACT:
digitalWrite(LED1_pin, (game_state_elapsed % 500000) < 250000);
digitalWrite(LED2_pin, (game_state_elapsed % 300000) < 150000);
break;
case STATE_BALL1:
case STATE_BALL2:
case STATE_BALL3:
if (game_state_elapsed < 1000000) {
// if the state was freshly entered, flash a little
digitalWrite(LED1_pin, (game_state_elapsed % 100000) < 50000);
digitalWrite(LED2_pin, (game_state_elapsed % 100000) < 50000);
} else {
// otherwise flash when the bumper is hit
digitalWrite(LED1_pin, !bumper.isActive());
digitalWrite(LED2_pin, !bumper.isActive());
}
break;
case STATE_GAME_OVER:
digitalWrite(LED1_pin, (game_state_elapsed % 200000) < 100000);
digitalWrite(LED2_pin, (game_state_elapsed % 200000) < 100000);
break;
}
}
/****************************************************************/
/// Update all actuator state, including output pulse timers.
void poll_actuator_outputs(unsigned long interval)
{
bumper.update(interval);
speaker.update(interval);
}
/****************************************************************/
// Debugging functions which can be called from user console input.
void user_print_report()
{
send_debug_message("start of debugging report.");
start_switch.send_debug();
bumper_sensor.send_debug();
drain_sensor.send_debug();
bumper.send_debug();
speaker.send_debug();
send_debug_message("end of debugging report.");
}
void user_reset_game(void)
{
send_debug_message("user game reset.");
game_state = STATE_IDLE;
game_state_elapsed = 0;
}
void user_set_game_state(int value)
{
send_debug_message("user forcing game state.");
// Cast the integer to state_t, which is really just an integer, but
// considered a different kind of integer by the compiler.
if (value >= 0 && value < NUM_STATES) {
game_state = (state_t) value;
game_state_elapsed = 0;
}
}
/****************************************************************/
/**** Standard entry points for Arduino system ******************/
/****************************************************************/
/// Standard Arduino initialization function to configure the system. This
/// function is called once after reset to initialize the program.
void setup()
{
// configure the actuator pins as soon as possible.
pinMode(LED1_pin, OUTPUT);
pinMode(LED2_pin, OUTPUT);
pinMode(bumper_pin, OUTPUT);
pinMode(speaker_pin, OUTPUT);
digitalWrite(LED1_pin, HIGH); // off
digitalWrite(LED2_pin, HIGH); // off
digitalWrite(bumper_pin, LOW); // off
noTone(speaker_pin);
// initialize the Serial port for the user debugging console
Serial.begin( BAUD_RATE );
// send a message as a diagnostic
send_debug_message("wakeup");
}
/****************************************************************/
/// Standard Arduino polling function to handle all I/O and periodic processing.
/// This function is called repeatedly as fast as possible from within the
/// built-in library to poll program events. This loop should never be allowed
/// to stall or block so that all tasks can be constantly serviced.
void loop()
{
// The timestamp in microseconds for the last polling cycle, used to compute
// the exact interval between output updates.
static unsigned long last_update_clock = 0;
// Read the microsecond clock.
unsigned long now = micros();
// Compute the time elapsed 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.
poll_sensor_inputs(interval);
poll_game_logic(interval);
poll_actuator_outputs(interval);
poll_console_input(interval);
}
/****************************************************************/
/****************************************************************/
|