diff --git a/lib/Domain/Gun/Atmega328pHal.hpp b/lib/Domain/Gun/Atmega328pHal.hpp new file mode 100644 index 0000000..c016ee7 --- /dev/null +++ b/lib/Domain/Gun/Atmega328pHal.hpp @@ -0,0 +1,86 @@ +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include + +#define BSP_TICKS_PER_SEC 100 +#define F_CPU 16000000L + +#define LASER_PIN 10 +#define VIBRATOR_PIN 6 +#define BATTERY_VOLTAGE_PIN A3 +#define CHARGING_STATE_PIN A2 + +#define MIN_BAT_VOLTAGE 3000 +#define MAX_BAT_VOLTAGE 4120 + +ISR(TIMER2_COMPA_vect) {} + +class Atmega328pHal : public IGunHal { +public: + ~Atmega328pHal() {} + + Atmega328pHal() { + + pinMode(VIBRATOR_PIN, OUTPUT); + pinMode(LASER_PIN, OUTPUT); + } + /* + * the 'loop' method shall be called each 10ms + * + * Configure timer2 so that an interrupt occurs each 10ms + * and wake up the MCU if it is in sleep mode. + */ + void setupHeartbeat() override { + TCCR2A = (1U << WGM21) | (0U << WGM20); // set Timer2 in CTC mode + TCCR2B = (1U << CS22) | (1U << CS21) | (1U << CS20); // 1/1024 prescaler + ASSR &= ~(1U << AS2); + TIMSK2 = (1U << OCIE2A); // enable TIMER2 compare Interrupt + TCNT2 = 0U; + + // set the output-compare register based on the desired tick frequency + OCR2A = (F_CPU / BSP_TICKS_PER_SEC / 1024U) - 1U; + } + + void laserOn() override { PORTB |= (1 << PB2); } + void laserOff() override { PORTB &= ~(1 << PB2); } + void vibrationOn() override { PORTD |= (1 << PD6); } + void vibrationOff() override { PORTD &= ~(1 << PD6); } + + uint16_t getBatteryVoltageMv() override { + + analogRead(BATTERY_VOLTAGE_PIN); + float rawBatt = analogRead(BATTERY_VOLTAGE_PIN); + rawBatt += analogRead(BATTERY_VOLTAGE_PIN); + rawBatt += analogRead(BATTERY_VOLTAGE_PIN); + rawBatt += analogRead(BATTERY_VOLTAGE_PIN); + rawBatt /= 4; + uint16_t battMv = (5000 / 1023.f) * rawBatt; + + return battMv; + }; + + uint8_t getBatteryVoltagePercent() override { + return map(getBatteryVoltageMv(), MIN_BAT_VOLTAGE, MAX_BAT_VOLTAGE, 0, 100); + } + + virtual bool isCharging() override { + return (digitalRead(CHARGING_STATE_PIN) == HIGH); + } +}; \ No newline at end of file diff --git a/lib/Domain/Gun/Button.cpp b/lib/Domain/Gun/Button.cpp new file mode 100644 index 0000000..93ceeba --- /dev/null +++ b/lib/Domain/Gun/Button.cpp @@ -0,0 +1,18 @@ +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include diff --git a/lib/Domain/Gun.cpp b/lib/Domain/Gun/Button.hpp similarity index 58% rename from lib/Domain/Gun.cpp rename to lib/Domain/Gun/Button.hpp index 40f5832..527c2c3 100644 --- a/lib/Domain/Gun.cpp +++ b/lib/Domain/Gun/Button.hpp @@ -1,39 +1,28 @@ -/* - * - * Copyright (c) 2023 Aurélien Labrosse - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include - -static const uint8_t SHOTS_PER_REARM = 5; - -void Gun::onButton1ShortPress() { - if (availableShots > 0) { - _hal.laserOn(); - _hal.vibrationOn(); - _hal.shortDelay(); - _hal.laserOff(); - _hal.vibrationOff(); - availableShots--; - } - -} - -void Gun::onButton1LongPress() { - _hal.deepSleep(); -} -void Gun::onButton2ShortPress() { availableShots = SHOTS_PER_REARM; } -void Gun::onButton2LongPress() { - _hal.laserOn(); -} +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +class Button : public Contactor { + +public: + Button() : Contactor(), calibrationMode(false) {} + + bool calibrationMode; + void onShortPress() override { shootCount = 0; } + void onLongPress() override { calibrationMode = !calibrationMode; } +}; \ No newline at end of file diff --git a/lib/Domain/Gun/Gun.cpp b/lib/Domain/Gun/Gun.cpp new file mode 100644 index 0000000..539bfed --- /dev/null +++ b/lib/Domain/Gun/Gun.cpp @@ -0,0 +1,143 @@ +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include + +volatile uint16_t hitcount; +uint16_t previoushitcout; +volatile uint16_t shootCount; + +#define TICKS_BETWEEN_UI_UPDATE 10 + +// display API +static void displayBatteryStatus(); +static void displayShootCount(); + +void setup(); +static void setupHeartbeat(); + +// counter for ticks between battery display update +uint16_t updateBatteryDisplayCycleCount; + +Trigger trigger; +Button button; + +volatile Contactor::Event pendingTriggerEvent; +void triggerInterruptHandler() { + if (bit_is_set(PIND, PD2)) { + pendingTriggerEvent = Contactor::Event::Released; + } else { + pendingTriggerEvent = Contactor::Event::Pressed; + } +} + +volatile Contactor::Event pendingButtonEvent; +void buttonInterruptHandler() { + if (bit_is_set(PIND, PD3)) { + pendingButtonEvent = Contactor::Event::Released; + } else { + pendingButtonEvent = Contactor::Event::Pressed; + } +} + +long now = 0; + +void Gun::loop(void) { + + now += 10; // 10ms per loop thanks to timer2 + + trigger.decreaseCycleCount(); + + cli(); + trigger.pendingEvent = pendingTriggerEvent; + button.pendingEvent = pendingButtonEvent; + pendingTriggerEvent = Contactor::Event::NoEvent; + pendingButtonEvent = Contactor::Event::NoEvent; + sei(); + + trigger.processPendingEvent(now); + button.processPendingEvent(now); + + button.checkForLongPress(now); + + if (button.calibrationMode) { + hal->laserOn(); + hal->vibrationOff(); + + } else { + if (trigger.shootCycleCountdown == 0) { + hal->laserOff(); + hal->vibrationOff(); + ui->displayShootCount(shootCount); + } + + if (updateBatteryDisplayCycleCount > 0) { + updateBatteryDisplayCycleCount--; + } + if (updateBatteryDisplayCycleCount == 0) { + displayBatteryStatus(); + updateBatteryDisplayCycleCount = TICKS_BETWEEN_UI_UPDATE; + } + } + + LowPower.idle(SLEEP_15MS, ADC_OFF, TIMER2_ON, TIMER1_ON, TIMER0_ON, SPI_OFF, + USART0_OFF, TWI_OFF); +} + + +void Gun::setup(void) { + +#if defined(SERIAL_OUTPUT) + Serial.begin(115200); + while (!Serial) { + } + + shootCount = 0; + updateBatteryDisplayCycleCount = TICKS_BETWEEN_UI_UPDATE; + + pinMode(LASER_PIN, OUTPUT); + digitalWrite(LASER_PIN, LOW); + + pinMode(OUT2_PIN, OUTPUT); + digitalWrite(OUT2_PIN, LOW); + + pinMode(BATTERY_VOLTAGE_PIN, INPUT); + pinMode(CHARGING_STATE_PIN, INPUT); + + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LOW); + +#if defined(SERIAL_OUTPUT) + Serial.println("Ready!"); +#endif + + ui->displaySplash(); + ui->displayBatteryStatus(); + ui->displayShootCount(); + + pinMode(BUTTON2_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(BUTTON2_PIN), buttonInterruptHandler, + CHANGE); + + pinMode(TRIGGER_PIN, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(TRIGGER_PIN), triggerInterruptHandler, + CHANGE); + cli(); + setupHeartbeat(); + sei(); +} \ No newline at end of file diff --git a/lib/Domain/Gun.hpp b/lib/Domain/Gun/Gun.hpp similarity index 73% rename from lib/Domain/Gun.hpp rename to lib/Domain/Gun/Gun.hpp index 4858b0b..cd6fb5f 100644 --- a/lib/Domain/Gun.hpp +++ b/lib/Domain/Gun/Gun.hpp @@ -16,18 +16,17 @@ */ #pragma once -#include -#include +#include +#include class Gun { - IGunHal &_hal; + IGunHal *hal; + IGunUi *ui; public: - uint8_t availableShots; - Gun(IGunHal &hal) : _hal(hal) {} - void onButton1ShortPress(); - void onButton1LongPress(); - void onButton2ShortPress(); - void onButton2LongPress(); + Gun(IGunHal *hal, IGunUi *ui) : hal(hal), ui(ui) {} + + void loop(); + void setup(); }; \ No newline at end of file diff --git a/lib/Domain/IGunHal.hpp b/lib/Domain/Gun/IGunHal.hpp similarity index 78% rename from lib/Domain/IGunHal.hpp rename to lib/Domain/Gun/IGunHal.hpp index 4891b53..e050679 100644 --- a/lib/Domain/IGunHal.hpp +++ b/lib/Domain/Gun/IGunHal.hpp @@ -16,18 +16,22 @@ */ #pragma once +#include + class IGunHal { public: virtual ~IGunHal() {} - virtual bool isTriggerDown() = 0; - virtual bool isButtonDown() = 0; - virtual void shortDelay() = 0; - virtual void longDelay() = 0; - virtual void ledOn() = 0; - virtual void ledOff() = 0; + + /* + * the 'loop' method shall be called each 10ms + */ + virtual void setupHeartbeat() = 0; + virtual void laserOn() = 0; virtual void laserOff() = 0; virtual void vibrationOn() = 0; virtual void vibrationOff() = 0; - virtual void deepSleep() = 0; + virtual uint16_t getBatteryVoltageMv() = 0; + virtual bool isCharging() = 0; + }; \ No newline at end of file diff --git a/lib/Domain/Gun/IGunUi.hpp b/lib/Domain/Gun/IGunUi.hpp new file mode 100644 index 0000000..1933261 --- /dev/null +++ b/lib/Domain/Gun/IGunUi.hpp @@ -0,0 +1,26 @@ +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +class IGunUi { + +public: + virtual void displaySplash(uint16_t timeoutMs) = 0; + virtual void displayBatteryStatus(uint16_t mv, uint8_t percent) = 0; + virtual void displayChargingStatus(bool isCharging) = 0; + virtual void displayShootCount(uint16_t shootCount) = 0; +}; \ No newline at end of file diff --git a/lib/Domain/Gun/SSD1306Ui.hpp b/lib/Domain/Gun/SSD1306Ui.hpp new file mode 100644 index 0000000..abc6486 --- /dev/null +++ b/lib/Domain/Gun/SSD1306Ui.hpp @@ -0,0 +1,94 @@ +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +#include +#include +#include + +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 32 // OLED display height, in pixels +#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) +// 0x3D for 128x64, 0x3C for 128x32 +#define SCREEN_ADDRESS 0x3C + +static Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + +/** + * @brief UI on a SSD1306 OLED display wired over I²C bus + * + */ +class SSD1306Ui : public IGunUi { +public: + SSD1306Ui() { + + if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { + for (;;) + ; // Don't proceed, loop forever + } + } + void displaySplash(uint16_t timeoutMs) override { + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(1); + display.setFont(&FreeMonoOblique9pt7b); + display.setCursor(9, 11); + display.println("5 in a row"); + display.setCursor(4, 27); + display.println(" # # # # #"); + display.display(); + delay(timeoutMs); + display.clearDisplay(); + } + + void displayBatteryStatus(uint16_t mv, uint8_t percent) override { + display.setFont(NULL); + display.fillRect(79, 0, 59, 24, 0); // clear + display.setCursor(100, 2); + display.print(percent); + display.println("%"); + + // prepare font for next shoot count display + display.setFont(&FreeMonoBold18pt7b); + } + void displayChargingStatus(bool isCharging) override { + display.setCursor(80, 15); + if (isCharging) { + display.println("charging"); + } else { + display.println(" "); + } + } + + void displayShootCount(uint16_t count) override { + display.fillRect(11, 0, 68, 30, 0); // clear + + if (count > 999) + count = 0; + + if (count < 10) { + display.setCursor(15, 25); + } else { + display.setCursor(10, 25); + } + display.setFont(&FreeMonoBold18pt7b); + display.print(count); + display.display(); + } +}; \ No newline at end of file diff --git a/lib/Domain/Gun/Trigger.cpp b/lib/Domain/Gun/Trigger.cpp new file mode 100644 index 0000000..b71006c --- /dev/null +++ b/lib/Domain/Gun/Trigger.cpp @@ -0,0 +1,18 @@ +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include diff --git a/lib/Domain/Gun/Trigger.hpp b/lib/Domain/Gun/Trigger.hpp new file mode 100644 index 0000000..d76a102 --- /dev/null +++ b/lib/Domain/Gun/Trigger.hpp @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2023 Aurélien Labrosse + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +class Trigger : public Contactor { +public: + Trigger() : Contactor(), shootCycleCountdown(0) {} + + // counter for ticks while firing + uint8_t shootCycleCountdown; + + void decreaseCycleCount() { + if (shootCycleCountdown > 0) { + shootCycleCountdown--; + } + } + + void onDown(long now) override { + if (shootCycleCountdown == 0) { + shootCount += 1; + LASER_ON(); + OUT2_ON(); + shootCycleCountdown = SHOOT_DURATION_CYCLE; + } + Contactor::onDown(now); + } +}; diff --git a/src/GunApp.cpp b/src/GunApp.cpp index bbb263c..7e3d12c 100644 --- a/src/GunApp.cpp +++ b/src/GunApp.cpp @@ -16,282 +16,22 @@ */ #include -#include #include #include #include -#define TRIGGER_PIN 2 -#define BUTTON2_PIN 3 +#include +#include -#define LASER_PIN 10 -#define LASER_ON() PORTB |= (1 << PB2); -#define LASER_OFF() PORTB &= ~(1 << PB2); - -#define OUT2_PIN 6 -#define OUT2_ON() PORTD |= (1 << PD6); -#define OUT2_OFF() PORTD &= ~(1 << PD6); - -#define BATTERY_VOLTAGE_PIN A3 -#define CHARGING_STATE_PIN A2 - -#define SHOOT_DURATION_CYCLE 5 -#define MIN_BAT_VOLTAGE 3000 -#define MAX_BAT_VOLTAGE 4120 - -#define SCREEN_WIDTH 128 // OLED display width, in pixels -#define SCREEN_HEIGHT 32 // OLED display height, in pixels -#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) -// 0x3D for 128x64, 0x3C for 128x32 -#define SCREEN_ADDRESS 0x3C - -Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); - -volatile uint16_t hitcount; -uint16_t previoushitcout; -volatile uint16_t shootCount; - -#define BSP_TICKS_PER_SEC 100 -#define F_CPU 16000000L -#define CYCLES_BETWEEN_UI_UPDATE 10 - -// display API -static void displayBatteryStatus(); -static void displayShootCount(); - -void setup(); -static void setupHeartbeat(); - -// counter for ticks between battery display update -uint16_t updateBatteryDisplayCycleCount; - -class Trigger : public Contactor { -public: - Trigger() : Contactor(), shootCycleCountdown(0) {} - - // counter for ticks while firing - uint8_t shootCycleCountdown; - - void decreaseCycleCount() { - if (shootCycleCountdown > 0) { - shootCycleCountdown--; - } - } - - void onDown(long now) override { - if (shootCycleCountdown == 0) { - shootCount += 1; - LASER_ON(); - OUT2_ON(); - shootCycleCountdown = SHOOT_DURATION_CYCLE; - } - Contactor::onDown(now); - } -}; - -class Button : public Contactor { - -public: - Button() : Contactor(), calibrationMode(false) {} - - bool calibrationMode; - void onShortPress() override { shootCount = 0; } - void onLongPress() override { calibrationMode = !calibrationMode; } -}; - -Trigger trigger; -Button button; - -volatile Contactor::Event pendingTriggerEvent; -void triggerInterruptHandler() { - if (bit_is_set(PIND, PD2)) { - pendingTriggerEvent = Contactor::Event::Released; - } else { - pendingTriggerEvent = Contactor::Event::Pressed; - } -} - -volatile Contactor::Event pendingButtonEvent; -void buttonInterruptHandler() { - if (bit_is_set(PIND, PD3)) { - pendingButtonEvent = Contactor::Event::Released; - } else { - pendingButtonEvent = Contactor::Event::Pressed; - } -} +Atmega328pHal hal; +Gun gun(&hal); ISR(TIMER2_COMPA_vect) {} -long now = 0; - -void loop(void) { - - now += 10; // 10ms per loop thanks to timer2 - - trigger.decreaseCycleCount(); - - cli(); - trigger.pendingEvent = pendingTriggerEvent; - button.pendingEvent = pendingButtonEvent; - pendingTriggerEvent = Contactor::Event::NoEvent; - pendingButtonEvent = Contactor::Event::NoEvent; - sei(); - - trigger.processPendingEvent(now); - button.processPendingEvent(now); - - button.checkForLongPress(now); - - if (button.calibrationMode) { - LASER_ON(); - OUT2_OFF(); - - } else { - if (trigger.shootCycleCountdown == 0) { - LASER_OFF(); - OUT2_OFF(); - displayShootCount(); - } - - if (updateBatteryDisplayCycleCount > 0) { - updateBatteryDisplayCycleCount--; - } - if (updateBatteryDisplayCycleCount == 0) { - displayBatteryStatus(); - updateBatteryDisplayCycleCount = CYCLES_BETWEEN_UI_UPDATE; - } - } - - LowPower.idle(SLEEP_15MS, ADC_OFF, TIMER2_ON, TIMER1_ON, TIMER0_ON, SPI_OFF, - USART0_OFF, TWI_OFF); -} - -static void displayShootCount() { - display.fillRect(11, 0, 68, 30, 0); // clear - - if (shootCount > 999) - shootCount = 0; - - if (shootCount < 10) { - display.setCursor(15, 25); - } else { - display.setCursor(10, 25); - } - display.print(shootCount); - display.display(); -} - -static void displayBatteryStatus() { - - analogRead(BATTERY_VOLTAGE_PIN); - float rawBatt = analogRead(BATTERY_VOLTAGE_PIN); - rawBatt += analogRead(BATTERY_VOLTAGE_PIN); - rawBatt += analogRead(BATTERY_VOLTAGE_PIN); - rawBatt += analogRead(BATTERY_VOLTAGE_PIN); - rawBatt /= 4; - - // https://rickkas7.github.io/DisplayGenerator/index.html - - float battMv = (5000 / 1023.f) * rawBatt; - bool isCharging = (digitalRead(CHARGING_STATE_PIN) == HIGH); - -#if defined(SERIAL_OUTPUT) - Serial.print(rawBatt); - Serial.print(';'); - Serial.print(battMv); - Serial.print(';'); - Serial.println(isCharging ? "c" : "n"); - delay(2); -#endif - - uint8_t battPercent = map(battMv, MIN_BAT_VOLTAGE, MAX_BAT_VOLTAGE, 0, 100); - - display.setFont(NULL); - display.fillRect(79, 0, 59, 24, 0); // clear - display.setCursor(100, 2); - display.print(battPercent); - display.println("%"); - if (isCharging) { - display.setCursor(80, 15); - display.println("charging"); - } - - // prepare font for next shoot count display - display.setFont(&FreeMonoBold18pt7b); -} - -static void setupHeartbeat() { - - // set Timer2 in CTC mode, 1/1024 prescaler, start the timer ticking... - TCCR2A = (1U << WGM21) | (0U << WGM20); - TCCR2B = (1U << CS22) | (1U << CS21) | (1U << CS20); // 1/2^10 - ASSR &= ~(1U << AS2); - TIMSK2 = (1U << OCIE2A); // enable TIMER2 compare Interrupt - TCNT2 = 0U; - - // set the output-compare register based on the desired tick frequency - OCR2A = (F_CPU / BSP_TICKS_PER_SEC / 1024U) - 1U; -} +void loop(void) { gun.loop(); } void setup(void) { - -#if defined(SERIAL_OUTPUT) - Serial.begin(115200); - while (!Serial) { - } -#endif - if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { -#if defined(SERIAL_OUTPUT) - Serial.println(F("SSD1306 allocation failed")); -#endif - for (;;) - ; // Don't proceed, loop forever - } - - shootCount = 0; - updateBatteryDisplayCycleCount = TICKS_BETWEEN_UI_UPDATE; - - pinMode(LASER_PIN, OUTPUT); - digitalWrite(LASER_PIN, LOW); - - pinMode(OUT2_PIN, OUTPUT); - digitalWrite(OUT2_PIN, LOW); - - pinMode(BATTERY_VOLTAGE_PIN, INPUT); - pinMode(CHARGING_STATE_PIN, INPUT); - - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LOW); - -#if defined(SERIAL_OUTPUT) - Serial.println("Ready!"); -#endif - - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(1); - display.setFont(&FreeMonoOblique9pt7b); - display.setCursor(9, 11); - display.println("5 in a row"); - display.setCursor(4, 27); - display.println(" # # # # #"); - display.display(); - - delay(2000); - - display.clearDisplay(); - displayBatteryStatus(); - displayShootCount(); - - pinMode(BUTTON2_PIN, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(BUTTON2_PIN), buttonInterruptHandler, - CHANGE); - - pinMode(TRIGGER_PIN, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(TRIGGER_PIN), triggerInterruptHandler, - CHANGE); - cli(); - setupHeartbeat(); - sei(); + hal.setupHeartbeat(); + gun.setup(); } \ No newline at end of file