BicycleControl/BicycleControl.ino

579 lines
12 KiB
C++

#include <U8x8lib.h>
#include <Arduino.h>
#include <U8g2lib.h>
#include "Pitches.h"
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
// Software SPI setup:
#define OLED_MOSI 14
#define OLED_CLK 15
#define OLED_DC 16
#define OLED_CS 12
#define OLED_RESET 13
U8X8_SSD1306_128X64_NONAME_4W_SW_SPI screen(OLED_CLK, OLED_MOSI, OLED_CS, OLED_DC, OLED_RESET);
//Global defines
#define BLINKER_DELAY 10000
#define BLINKER_FLASH_ON 500
#define BLINKER_FLASH_OFF 150
//Pin assignments
//Shift register control
#define DS 4
#define STCP 2
#define SHCP 7
#define MR 8
//Shift register pin assignments.
#define LED_blinkerRight 3
#define LED_blinkerLeft 2
#define LED_lampFront 0
#define LED_lampBack 1
//Other pinouts.
#define BUZZER 3
#define LED_indicatorRed 9
#define LED_indicatorGreen 10
#define LED_indicatorBlue 11
//Input pins.
#define INPUT_DELAY 300
#define IN_mode 17
#define IN_left 18
#define IN_right 19
#define MODE_MAX 3
//Global variables
//Shift register buffer.
bool outputBuffer[8] = { 0/*Front lamp*/, 0/*Back lamp*/, 0/*Left blinker*/, 0/*Right blinker*/, 0, 0, 0, 0 };
//-Blinker Variables
bool blinkerRight = 0;
bool blinkerLeft = 0;
unsigned long blinkerRightStart = 0;
unsigned long blinkerLeftStart = 0;
unsigned long blinkerRightLastUpdate = 0;
unsigned long blinkerLeftLastUpdate = 0;
//Input control variables
//Mode control button.
char mode = 0;
bool modePressed, leftPressed, rightPressed = 0;
unsigned long modePressedTime, leftPressedTime, rightPressedTime = 0;
//Music variables.
short BPM, noteDuration;
short* currentSequence;
bool isPlaying = false;
int currentNoteIndex;
unsigned long noteEndTime;
short beep1[] = {
3, 100,
NOTE_A4, 2,
NOTE_E6, 2,
NOTE_C8, 2
};
short skyrim[] = {
104, 120,
NOTE_D5, 8,
NOTE_E5, 8,
NOTE_F5, 8 / 3,
NOTE_F5, 8,
NOTE_F5, 8,
NOTE_G5, 8,
NOTE_A5, 8 / 3,
NOTE_A5, 8,
NOTE_A5, 8,
NOTE_C5, 8,//10
NOTE_G5, 8 / 3,
NOTE_G5, 8,
NOTE_F5, 8,
NOTE_E5, 8,
NOTE_D5, 8 / 3,
NOTE_D5, 8,
NOTE_D5, 8,
NOTE_E5, 8,
NOTE_F5, 8 / 3,
NOTE_F5, 8,//20
NOTE_F5, 8,
NOTE_G5, 8,
NOTE_A5, 8 / 3,
NOTE_A5, 8,
NOTE_A5, 8,
NOTE_C5, 8,
NOTE_D5, 8 / 3,
NOTE_D5, 8,
NOTE_C5, 8,
NOTE_E5, 8,//30
NOTE_D5, 8 / 3,
NOTE_D5, 8,
NOTE_D5, 8,
NOTE_E5, 8,
NOTE_F5, 4,
NOTE_E5, 8,
NOTE_E5, 8,
NOTE_D5, 4,
NOTE_C5, 4,
NOTE_B5, 8,//40
NOTE_B5, 8,
NOTE_A5, 4,
NOTE_G5, 8 / 3,
NOTE_G5, 8,
NOTE_F5, 8,
NOTE_A5, 8,
NOTE_G5, 8 / 3,
NOTE_G5, 4,
NOTE_F5, 16,
NOTE_E5, 16,//50
NOTE_F5, 4,
NOTE_F5, 16,
NOTE_E5, 16,
NOTE_F5, 4,
NOTE_F5, 16,
NOTE_E5, 16,
NOTE_G5, 8,
NOTE_F5, 8,
NOTE_E5, 8,
NOTE_D5, 4,//60
NOTE_D5, 16,
NOTE_C5, 16,
NOTE_D5, 4,
NOTE_D5, 16,
NOTE_C5, 16,
NOTE_D5, 4,
NOTE_C5, 16,
NOTE_D5, 16,
NOTE_E5, 8,
NOTE_F5, 8,//70
NOTE_C5, 8,
NOTE_D5, 4,
NOTE_D5, 16,
NOTE_E5, 16,
NOTE_F5, 4,
NOTE_F5, 16,
NOTE_G5, 16,
NOTE_A5, 4,
NOTE_E5, 16,
NOTE_F5, 16,//80
NOTE_G5, 8,
NOTE_F5, 8,
NOTE_E5, 8,
NOTE_D5, 4,
NOTE_D5, 16,
NOTE_C5, 16,
NOTE_D5, 4,
NOTE_D5, 16,
NOTE_C5, 16,
NOTE_D5, 4,//90
NOTE_C5, 16,
NOTE_D5, 16,
NOTE_E5, 8,
NOTE_F5, 8,
NOTE_C5, 8,
NOTE_D5, 8 / 3,
NOTE_D5, 4,
NOTE_REST, 8,
NOTE_D5, 4,
NOTE_REST, 8,//100
NOTE_D5, 4,
NOTE_REST, 8,
NOTE_D5, 4,
NOTE_REST, 8
};
//Song variables.
short songCount = 2;
short currentSong = 0;
short* songs[] = {
skyrim,
beep1,
};
String songNames[] = {
"Skyrim",
"Beep1",
};
//-------------------|
//GRAPHICS FUNCTIONS:|
//-------------------|
//Shortcut for writing text to the screen.
void writeText(const char* text, int x, int y) {
screen.setCursor(x, y);
screen.print(text);
}
//Draws the template of the main screen onto the display.
void drawMainScreen() {
screen.clear();
screen.setInverseFont(0);
screen.setFont(u8x8_font_amstrad_cpc_extended_f);
writeText("Bicycle Control", 0, 0);
screen.setFont(u8x8_font_artossans8_r);
writeText("+-+-+-+-+------+", 0, 1);
writeText("|L|R|F|B|Mode:0|", 0, 2);
writeText("+-+-+-+-+------+", 0, 3);
writeText("|Song: |", 0, 4);
writeText("+--------------+", 0, 5);
}
//Draws the blinker indicators to the screen.
void drawBlinkerStatus() {
screen.setFont(u8x8_font_artossans8_r);
screen.setInverseFont(blinkerLeft);
writeText("L", 1, 2);
screen.setInverseFont(blinkerRight);
writeText("R", 3, 2);
}
//Draws the front lamp indicator.
void drawFrontLampStatus() {
screen.setFont(u8x8_font_artossans8_r);
screen.setInverseFont(outputBuffer[LED_lampFront]);
writeText("F", 5, 2);
}
//Draws the back lamp indicator.
void drawBackLampStatus() {
screen.setFont(u8x8_font_artossans8_r);
screen.setInverseFont(outputBuffer[LED_lampBack]);
writeText("B", 7, 2);
}
//Draws the current mode number on the screen.
void drawModeStatus() {
screen.setFont(u8x8_font_artossans8_r);
screen.setInverseFont(0);
screen.setCursor(14, 2);
screen.print((int)mode);
}
void drawSongStatus() {
screen.setFont(u8x8_font_artossans8_r);
screen.setInverseFont(0);
screen.setCursor(6, 4);
screen.print(" ");
screen.setCursor(6, 4);
screen.print(songNames[currentSong]);
}
//-------------------|
//STARTUP FUNCTIONS: |
//-------------------|
//Initialize the OLED screen.
void startupScreen() {
screen.begin();
screen.setFont(u8x8_font_amstrad_cpc_extended_r);
}
//Initialize peripherals.
void startupPeripherals() {
//Shift register outputs.
pinMode(DS, OUTPUT);
pinMode(STCP, OUTPUT);
pinMode(SHCP, OUTPUT);
pinMode(MR, OUTPUT);
digitalWrite(MR, HIGH);
//Other outputs.
pinMode(BUZZER, OUTPUT);
pinMode(LED_indicatorRed, OUTPUT);
pinMode(LED_indicatorGreen, OUTPUT);
pinMode(LED_indicatorBlue, OUTPUT);
//Inputs.
pinMode(IN_mode, INPUT);
pinMode(IN_left, INPUT);
pinMode(IN_right, INPUT);
}
//-------------------|
//UPDATE FUNCTIONS: |
//-------------------|
//Updates the display with blinker status.
void updateBlinkerStatus() {
bool needsRefresh = 0;
unsigned long currentTime = millis();
//Check if the blinkers must shut off.
if (blinkerLeft) {
//Check if we should try blinking the light.
if (currentTime - blinkerLeftStart < BLINKER_DELAY) {
if (outputBuffer[LED_blinkerLeft]) {
//Test if the blinker needs to be flashed off.
if (currentTime - blinkerLeftLastUpdate > BLINKER_FLASH_ON) {
setPin(LED_blinkerLeft, 0);
blinkerLeftLastUpdate = currentTime;
}
}
else {
//Test if the blinker needs to be flashed on.
if (currentTime - blinkerLeftLastUpdate > BLINKER_FLASH_OFF) {
setPin(LED_blinkerLeft, 1);
blinkerLeftLastUpdate = currentTime;
}
}
}
else {
//Try to shut down the blinker.
setPin(LED_blinkerLeft, 0);
blinkerLeft = 0;
needsRefresh = 1;
}
}
if (blinkerRight) {
//Check if we should try blinking the light.
if (currentTime - blinkerRightStart < BLINKER_DELAY) {
if (outputBuffer[LED_blinkerRight]) {
//Test if the blinker needs to be flashed off.
if (currentTime - blinkerRightLastUpdate > BLINKER_FLASH_ON) {
setPin(LED_blinkerRight, 0);
blinkerRightLastUpdate = currentTime;
}
}
else {
//Test if the blinker needs to be flashed on.
if (currentTime - blinkerRightLastUpdate > BLINKER_FLASH_OFF) {
setPin(LED_blinkerRight, 1);
blinkerRightLastUpdate = currentTime;
}
}
}
else {
//Try to shut down the blinker.
setPin(LED_blinkerRight, 0);
blinkerRight = 0;
needsRefresh = 1;
}
}
//Only if a change has been made, update the display.
if (needsRefresh) drawBlinkerStatus();
}
//Checks for user button presses, and processes input.
void updateInputs() {
unsigned long currentTime = millis();
if (!modePressed && digitalRead(IN_mode)) {
//Mode button was pressed.
modePressed = 1;
modePressedTime = currentTime;
if (mode == MODE_MAX) {
mode = 0;
}
else {
mode++;
}
drawModeStatus();
//Serial.println("Mode pressed.");
}
if (!leftPressed && digitalRead(IN_left)) {
//Left button was pressed.
leftPressed = 1;
leftPressedTime = currentTime;
switch (mode) {
case 0:
setFrontLamp(!outputBuffer[LED_lampFront]);
break;
case 1:
if (!blinkerLeft) activateBlinker(0);
break;
case 2:
if (currentSong == 0) {
currentSong = songCount - 1;
}
else {
currentSong--;
}
drawSongStatus();
break;
case 3:
isPlaying = false;
break;
}
//Serial.println("Left pressed.");
}
if (!rightPressed && digitalRead(IN_right)) {
//Right button was pressed.
rightPressed = 1;
rightPressedTime = currentTime;
switch (mode) {
case 0:
setBackLamp(!outputBuffer[LED_lampBack]);
break;
case 1:
if (!blinkerRight) activateBlinker(1);
break;
case 2:
if (currentSong == songCount - 1) {
currentSong = 0;
} else {
currentSong++;
}
drawSongStatus();
break;
case 3:
playSequence(songs[currentSong]);
break;
}
//Serial.println("Right pressed.");
}
//Remove restrictions on time delays.
if (currentTime - modePressedTime > INPUT_DELAY) {
modePressed = 0;
}
if (currentTime - leftPressedTime > INPUT_DELAY) {
leftPressed = 0;
}
if (currentTime - rightPressedTime > INPUT_DELAY) {
rightPressed = 0;
}
}
//Updates the music player, and potentially quits the music.
void updateSoundPlay() {
if (isPlaying && (noteEndTime < millis()) && currentNoteIndex < currentSequence[0]) {
//It is necessary to play the next note.
int realNoteIndex = currentNoteIndex * 2 + 2;
int realDuration = noteDuration / currentSequence[currentNoteIndex * 2 + 3];
tone(BUZZER, currentSequence[realNoteIndex], realDuration);
noteEndTime = millis() + realDuration;
currentNoteIndex++;
}
if (currentNoteIndex == currentSequence[0]) {
isPlaying = false;
}
}
//Updates all modules.
void updateAll() {
updateBlinkerStatus();
updateInputs();
updateSoundPlay();
}
//-------------------|
//STATE CHANGE FUNC: |
//-------------------|
//Loads 8 bits into the register.
void loadRegister(bool values[8]) {
for (int i = 7; i >= 0; --i) {
digitalWrite(DS, values[i]);
digitalWrite(SHCP, HIGH);
delayMicroseconds(1);
digitalWrite(SHCP, LOW);
}
digitalWrite(STCP, HIGH);
delayMicroseconds(1);
digitalWrite(STCP, LOW);
}
//Sets a pin on the register and reloads the bits.
void setPin(byte index, bool value) {
outputBuffer[index] = value;
loadRegister(outputBuffer);
}
//Activates a blinker for BLINK_DELAY milliseconds, until it is shut off by the updateBlinkStatus. 0 = Left, 1 = Right
void activateBlinker(bool side) {
unsigned long currentTime = millis();
if (side) {
setPin(LED_blinkerRight, 1);
blinkerRight = 1;
blinkerRightStart = currentTime;
blinkerRightLastUpdate = currentTime;
Serial.println("Activated Right blinker.");
}
else {
setPin(LED_blinkerLeft, 1);
blinkerLeft = 1;
blinkerLeftStart = currentTime;
blinkerLeftLastUpdate = currentTime;
Serial.println("Activated Left blinker.");
}
drawBlinkerStatus();
}
//Controls the front lamp.
void setFrontLamp(bool value) {
setPin(LED_lampFront, value);
drawFrontLampStatus();
}
//Controls the back lamp.
void setBackLamp(bool value) {
setPin(LED_lampBack, value);
drawBackLampStatus();
}
//Sets the color of the indicator LED.
void setIndicatorColor(byte red, byte green, byte blue) {
analogWrite(LED_indicatorRed, red);
analogWrite(LED_indicatorGreen, green);
analogWrite(LED_indicatorBlue, blue);
}
//-------------------|
//MUSIC FUNCTIONS: |
//-------------------|
void setBPM(short newBPM) {
BPM = newBPM;
noteDuration = 240000 / BPM;
}
//Begins playing a new sequence of the form: {Length, BPM, note 1, time 1, note 2, time 2, ...}
void playSequence(short* sequence) {
currentSequence = sequence;
setBPM(sequence[1]);
currentNoteIndex = 0;
noteEndTime = 0;
isPlaying = true;
}
//-------------------|
//ERROR CHECKING: |
//-------------------|
void testOutput(short pinNumber, const char* name) {
screen.clear();
writeText(name, 0, 0);
digitalWrite(pinNumber, HIGH);
delay(500);
digitalWrite(pinNumber, LOW);
}
//Visually tests each output.
void testOutputs() {
screen.clear();
writeText("Outputs test...", 0, 0);
delay(1000);
testOutput(LED_blinkerLeft, "Left blinker");
testOutput(LED_blinkerRight, "Right blinker");
testOutput(BUZZER, "Sound");
testOutput(LED_indicatorRed, "Indic. Red");
testOutput(LED_indicatorGreen, "Indic. Green");
testOutput(LED_indicatorBlue, "Indic. Blue");
testOutput(LED_lampFront, "Front lamp");
testOutput(LED_lampBack, "Back lamp");
}
//-------------------|
//ARDUINO RUNTIME: |
//-------------------|
void setup() {
Serial.begin(9600);
//Startup all the pins.
startupPeripherals();
startupScreen();
drawMainScreen();
drawSongStatus();
updateAll();
}
void loop() {
updateBlinkerStatus();
updateInputs();
updateSoundPlay();
}