main.ino 8 KB
Newer Older
Michael Nelson's avatar
Michael Nelson committed
1 2 3
#include <avr/power.h>

#define MODE_OFF 0
Michael Nelson's avatar
Michael Nelson committed
4
#define DEBUG_WITH_BUILTIN_LED false
Michael Nelson's avatar
Michael Nelson committed
5 6 7 8 9 10 11 12 13 14 15

// Foreground Modes
#define FG_MODE_BLINK_L 1
#define FG_MODE_BLINK_R 2

// Background modes
#define BG_MODE_HAZARDS 1
#define BG_MODE_RUNNING_LIGHTS 2
#define BG_MODE_RUNNING_W_BLINK 3
#define BG_MODE_AUXILARY_LIGHTS 4

16
// Pins 2-6 are for itmes on the handlebars.
Michael Nelson's avatar
Michael Nelson committed
17 18 19 20 21 22 23
// Ethernet coloring plan:
// Brown          - Ground
// Brown striped  - Speaker
// Green          - Left button
// Green striped  - Right button
// Orange         - Left button LED
// Orange striped - Right button LED
24
// Blue           - Left handlebar grip tactile switch
25
// Blue striped   - TODO Right handlebar grip tactile switch
26

Michael Nelson's avatar
Michael Nelson committed
27
// Inputs
28 29
#define PIN_IN_LEFT_UTIL_BUTTON 4
#define PIN_IN_RIGHT_UTIL_BUTTON 5
30 31
#define PIN_IN_SCOOTER_STATUS 2

32
#define PIN_IN_LEFT_GRIP_BUTTON 10
33 34 35

// TODO
#define PIN_IN_RIGHT_GRIP_BUTTON 11
Michael Nelson's avatar
Michael Nelson committed
36 37

// Outputs
38 39
#define PIN_OUT_LEFT_BUTTON_LED 12
#define PIN_OUT_RIGHT_BUTTON_LED 13
40

Michael Nelson's avatar
Michael Nelson committed
41 42 43
#define PIN_OUT_SPEAKER 6
#define PIN_OUT_BLINKER_L 7
#define PIN_OUT_BLINKER_R 8
44
#define PIN_OUT_HORN 9
Michael Nelson's avatar
Michael Nelson committed
45 46

// Config
47
#define BLINK_RATE 250
48
#define RUNNING_LIGHT_BLINK_RATE 920
49
#define HOLD_DOWN_TIME 500
50
#define DEBOUNCE_COOLDOWN_TIME 200
Michael Nelson's avatar
Michael Nelson committed
51 52 53 54 55

// State
int fg_mode = MODE_OFF;
int bg_mode = MODE_OFF;
int blinker_light_state;
Michael Nelson's avatar
Michael Nelson committed
56
int last_fg_mode = MODE_OFF;
57
int last_bg_mode = MODE_OFF;
58
bool hold_waiting = false;
Michael Nelson's avatar
Michael Nelson committed
59

60 61 62 63 64 65 66 67
enum scooter_ui {
  btn_util_left,
  btn_util_right,
  btn_util_both,

  btn_grip_left,
  btn_grip_right,
  btn_grip_both,
68

69 70 71 72 73 74 75 76 77 78
  num_ui_actions
};

// Flags calculated by the program for a single frame of loop as either single push & release
// or a hold. The hold bit is flipped ONCE at the frame exactly at the end of HOLD_DOWN_TIME.
// The user can continue to hold, which increments the input in hold_time, but won't flip
// any bit either "hit" array here.
bool input_single_hit[num_ui_actions];
bool input_hold_hit[num_ui_actions];
int hold_time[num_ui_actions];
Michael Nelson's avatar
Michael Nelson committed
79 80 81

// Time accumulator variables
int cycle_time = 0;
82
int new_input_blocked = 0;
Michael Nelson's avatar
Michael Nelson committed
83 84 85 86

void setup()
{
  // Initialize a serial connection for reporting values to the host
87
  Serial.begin(9600);
Michael Nelson's avatar
Michael Nelson committed
88

89 90
  pinMode(PIN_IN_LEFT_UTIL_BUTTON, INPUT_PULLUP);
  pinMode(PIN_IN_RIGHT_UTIL_BUTTON, INPUT_PULLUP);
91
  pinMode(PIN_IN_LEFT_GRIP_BUTTON, INPUT_PULLUP);
92
  pinMode(PIN_IN_RIGHT_GRIP_BUTTON, INPUT_PULLUP);
93
  pinMode(PIN_IN_SCOOTER_STATUS, INPUT_PULLUP);
Michael Nelson's avatar
Michael Nelson committed
94 95 96 97

  pinMode(PIN_OUT_SPEAKER, OUTPUT);
  pinMode(PIN_OUT_BLINKER_L, OUTPUT);
  pinMode(PIN_OUT_BLINKER_R, OUTPUT);
98 99
  pinMode(PIN_OUT_LEFT_BUTTON_LED, OUTPUT);
  pinMode(PIN_OUT_RIGHT_BUTTON_LED, OUTPUT);
Michael Nelson's avatar
Michael Nelson committed
100 101 102

  /* clock_prescale_set(clock_div_2); */

Michael Nelson's avatar
Michael Nelson committed
103
  /* Serial.println("Started."); */
Michael Nelson's avatar
Michael Nelson committed
104 105
}

106 107 108 109 110
void debounce_subsequent_input()
{
  new_input_blocked = DEBOUNCE_COOLDOWN_TIME;
}

111
void blink_frame(bool left, bool right, int tone_divisor = 2)
Michael Nelson's avatar
Michael Nelson committed
112 113 114 115 116 117 118
{
  if (cycle_time > BLINK_RATE)
  {
    cycle_time = 0;
    blinker_light_state = !blinker_light_state;
  }

119 120
  if (cycle_time == 1 && !blinker_light_state)
    blink_speaker_frame(tone_divisor);
Michael Nelson's avatar
Michael Nelson committed
121

Michael Nelson's avatar
Michael Nelson committed
122
  // Actual light
123 124 125 126 127
  if (left)
  {
    digitalWrite(PIN_OUT_BLINKER_L,       blinker_light_state ? LOW : HIGH);
    digitalWrite(PIN_OUT_LEFT_BUTTON_LED, blinker_light_state ? LOW : HIGH);
  }
Michael Nelson's avatar
Michael Nelson committed
128

129 130 131 132 133
  if (right)
  {
    digitalWrite(PIN_OUT_BLINKER_R,        blinker_light_state ? LOW : HIGH);
    digitalWrite(PIN_OUT_RIGHT_BUTTON_LED, blinker_light_state ? LOW : HIGH);
  }
Michael Nelson's avatar
Michael Nelson committed
134 135 136 137 138 139 140 141

  #if DEBUG_WITH_BUILTIN_LED
  digitalWrite(LED_BUILTIN, blinker_light_state ? LOW : HIGH);
  #endif

  cycle_time++;
}

142 143
void running_light_frame(bool with_blink = false)
{
144
  if (cycle_time >= RUNNING_LIGHT_BLINK_RATE)
145 146
    cycle_time = 0;

147 148
  digitalWrite(PIN_OUT_BLINKER_L,       !(cycle_time % 20) || (with_blink && cycle_time < 70) ? HIGH : LOW);
  digitalWrite(PIN_OUT_BLINKER_R,       !(cycle_time % 20) || (with_blink && cycle_time < 70) ? HIGH : LOW);
149

150
  digitalWrite(PIN_OUT_LEFT_BUTTON_LED, !(cycle_time % 20) || (with_blink && cycle_time < 70) ? HIGH : LOW);
151 152 153 154

  cycle_time++;
}

155 156 157 158 159 160 161 162
void auxiliary_light_frame()
{
  digitalWrite(PIN_OUT_BLINKER_L, HIGH);
  digitalWrite(PIN_OUT_BLINKER_R, HIGH);

  digitalWrite(PIN_OUT_RIGHT_BUTTON_LED, HIGH);
}

163
void blink_speaker_frame(int divisor)
Michael Nelson's avatar
Michael Nelson committed
164
{
165 166 167 168
  /* if (!blinker_light_state && cycle_time < 40) { */
  /*   digitalWrite(PIN_OUT_SPEAKER, !(cycle_time % divisor) ? HIGH : LOW); */
  /* } */
  tone(PIN_OUT_SPEAKER, 2000, 20);
Michael Nelson's avatar
Michael Nelson committed
169 170
}

171
// Each frame, this increments L/R/B hold time variables while the user is holding a button (or both) down.
172 173
// It also checks for the release. In that case it flips on either input_single_* or input_hold_* variables
// to be consumed later in the frame.
Michael Nelson's avatar
Michael Nelson committed
174 175
void read_dual_buttons()
{
176 177 178 179
  for (int i = 0; i < num_ui_actions; i++) {
    input_single_hit[i] = false;
    input_hold_hit[i] = false;
  }
Michael Nelson's avatar
Michael Nelson committed
180

181 182
  if (digitalRead(PIN_IN_LEFT_GRIP_BUTTON) == LOW && digitalRead(PIN_IN_RIGHT_UTIL_BUTTON) == LOW)
    hold_time[btn_grip_both]++;
183
  else if (digitalRead(PIN_IN_LEFT_GRIP_BUTTON) == LOW)
184 185 186 187 188 189 190
    hold_time[btn_grip_left]++;
  else if (digitalRead(PIN_IN_RIGHT_GRIP_BUTTON) == LOW)
    hold_time[btn_grip_right]++;
  else if (digitalRead(PIN_IN_LEFT_UTIL_BUTTON) == LOW)
    hold_time[btn_util_left]++;
  else if (digitalRead(PIN_IN_RIGHT_UTIL_BUTTON) == LOW)
    hold_time[btn_util_right]++;
Michael Nelson's avatar
Michael Nelson committed
191 192
  else
  {
193
    for (int i = 0; i < num_ui_actions; i++)
194
    {
195 196 197 198 199
      if (hold_time[i] && !new_input_blocked && !hold_waiting)
      {
        input_single_hit[i] = true;
        debounce_subsequent_input();
      }
200
    }
201

202 203
    for (int i = 0; i < num_ui_actions; i++)
      hold_time[i] = 0;
204 205 206 207

    if (hold_waiting)
      debounce_subsequent_input();

208 209 210
    hold_waiting = false;
  }

211
  for (int i = 0; i < num_ui_actions; i++)
212
  {
213 214 215 216 217 218
    if (hold_time[i] == HOLD_DOWN_TIME && !new_input_blocked)
    {
      input_hold_hit[i] = true;
      hold_waiting = true;
      debounce_subsequent_input();
    }
Michael Nelson's avatar
Michael Nelson committed
219 220 221 222 223 224 225
  }
}

void set_up_mode()
{
  read_dual_buttons();

226
  if (input_hold_hit[btn_grip_both])
Michael Nelson's avatar
Michael Nelson committed
227
  {
228
    bg_mode = bg_mode == BG_MODE_HAZARDS ? MODE_OFF : BG_MODE_HAZARDS;
Michael Nelson's avatar
Michael Nelson committed
229 230
    fg_mode = MODE_OFF;
  }
231
  else if (input_single_hit[btn_grip_both])
Michael Nelson's avatar
Michael Nelson committed
232 233 234 235 236
  {
    bg_mode = MODE_OFF;
    fg_mode = MODE_OFF;
  }
  // Cycle between running lights
237
  else if (input_hold_hit[btn_grip_left])
Michael Nelson's avatar
Michael Nelson committed
238
  {
239
    if (!bg_mode)
Michael Nelson's avatar
Michael Nelson committed
240
      bg_mode = BG_MODE_RUNNING_LIGHTS;
241 242 243 244
    else if (bg_mode >= BG_MODE_RUNNING_W_BLINK)
      bg_mode = MODE_OFF;
    else
      bg_mode++;
Michael Nelson's avatar
Michael Nelson committed
245 246 247

    fg_mode = MODE_OFF;
  }
248
  else if (input_single_hit[btn_grip_left])
Michael Nelson's avatar
Michael Nelson committed
249
    fg_mode = fg_mode == FG_MODE_BLINK_L ? MODE_OFF : FG_MODE_BLINK_L;
250 251 252 253 254 255 256
  else if (input_hold_hit[btn_util_left])
  {
    bg_mode = bg_mode == BG_MODE_AUXILARY_LIGHTS ? MODE_OFF : BG_MODE_AUXILARY_LIGHTS;
    fg_mode = MODE_OFF;
  }
  else if (input_single_hit[btn_util_left])
    fg_mode = fg_mode == FG_MODE_BLINK_R ? MODE_OFF : FG_MODE_BLINK_R;
Michael Nelson's avatar
Michael Nelson committed
257 258
}

Michael Nelson's avatar
Michael Nelson committed
259 260 261
void reset_all()
{
  cycle_time = 0;
262
  blinker_light_state = 0;
Michael Nelson's avatar
Michael Nelson committed
263 264 265 266 267 268 269 270 271

  #if DEBUG_WITH_BUILTIN_LED
  digitalWrite(LED_BUILTIN, LOW);
  #endif

  digitalWrite(PIN_OUT_BLINKER_L, LOW);
  digitalWrite(PIN_OUT_BLINKER_R, LOW);
  digitalWrite(PIN_OUT_LEFT_BUTTON_LED, LOW);
  digitalWrite(PIN_OUT_RIGHT_BUTTON_LED, LOW);
272
  digitalWrite(PIN_OUT_HORN, LOW);
Michael Nelson's avatar
Michael Nelson committed
273 274
}

275 276 277 278 279
bool scooter_is_off()
{
  return digitalRead(PIN_IN_SCOOTER_STATUS) == LOW;
}

Michael Nelson's avatar
Michael Nelson committed
280 281
void loop()
{
282 283
  if (scooter_is_off())
    return;
284

285
  delay(1);
Michael Nelson's avatar
Michael Nelson committed
286
  set_up_mode();
Michael Nelson's avatar
Michael Nelson committed
287

288
  // Reset all lights going directly from one direction to the next.
289
  if (fg_mode != last_fg_mode || bg_mode != last_bg_mode)
Michael Nelson's avatar
Michael Nelson committed
290 291
    reset_all();

292 293 294 295 296 297
  if (fg_mode == FG_MODE_BLINK_L)
    blink_frame(true, false);
  else if (fg_mode == FG_MODE_BLINK_R)
    blink_frame(false, true);
  else if (bg_mode == BG_MODE_HAZARDS)
    blink_frame(true, true, 3);
298 299 300 301
  else if (bg_mode == BG_MODE_RUNNING_LIGHTS)
    running_light_frame();
  else if (bg_mode == BG_MODE_RUNNING_W_BLINK)
    running_light_frame(true);
302 303
  else if (bg_mode == BG_MODE_AUXILARY_LIGHTS)
    auxiliary_light_frame();
Michael Nelson's avatar
Michael Nelson committed
304
  else
Michael Nelson's avatar
Michael Nelson committed
305
    reset_all();
Michael Nelson's avatar
Michael Nelson committed
306

307 308 309
  // Horn is driven asynchronously from the rest of the blinking logic
  if (hold_time[btn_util_right] > 0)
    digitalWrite(PIN_OUT_HORN, HIGH); // TOOOOOOT
310 311 312
  else
    digitalWrite(PIN_OUT_HORN, LOW);

Michael Nelson's avatar
Michael Nelson committed
313
  last_fg_mode = fg_mode;
314
  last_bg_mode = bg_mode;
315 316 317

  if (new_input_blocked > 0)
    new_input_blocked--;
Michael Nelson's avatar
Michael Nelson committed
318
}