From 39ed25d1c68ae3befcfe0f1246eb5a5683221bd3 Mon Sep 17 00:00:00 2001 From: Aurelien Labrosse Date: Wed, 15 Nov 2023 17:44:24 +0100 Subject: [PATCH] feat: Add contactor --- README.md | 26 ++++--- lib/Domain/Contactor.cpp | 3 +- lib/Domain/Contactor.hpp | 12 +-- lib/Domain/Gun.hpp | 2 +- lib/Domain/IGunHal.hpp | 4 +- platformio.ini | 10 +-- src/GunApp.cpp | 157 +++++++++++++++++++++++++++------------ 7 files changed, 140 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index e7ca4a4..a9863ca 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Set of applications targetting Atmel/Microchip [ATMega328P](https://www.microchip.com/en-us/product/atmega328p), implementing shooting games. -Using a *gun*, users tries to hit *targets* managed by a *target host*. +Using a _gun_, users tries to hit _targets_ managed by a _target host_. The game is up to four players: yellow, green, red and blue. There are 5 targets. @@ -11,36 +11,38 @@ There are 5 targets. Various games will be implemented. #### Simple shooter + Each player shoot until the five targets are hit. Number of shoots is counted on the gun. Successful hits are counted by the target host and sent to the remote application. ## Functional details + ### target The **target host** application manages player points and turns. It communicates with an Android application implemented on top of [Bluetooth Electronics](https://www.keuwl.com/apps/bluetoothelectronics/) application. It manages the game logic and player points. Various games and modes can be implemented for the host. Each of these could have a specific remote UI. - ### Gun + The **Gun** application manages the gun. Using two buttons, it allows: -| Press | Button 1 | Button 2 | -|-------|------------------|--------------------------------| -| short | shoot | reset shoot count | -| long | N/A | continuous laser (calibration) | +| Press | Button 1 | Button 2 | +| ----- | -------- | ------------------------------ | +| short | shoot | reset shoot count | +| long | N/A | continuous laser (calibration) | Gun application manages following outputs: -* Laser -* vibrator -* 128x32 I²C display + +- Laser +- vibrator +- 128x32 I²C display ## Architecture details Some parts of the projet, implementing logic, can be used on both native and target platform. All that code is represented in the "domain" package below. -Both **target** and **Gun** application are run both native and cross environment. The current -target hardware for both is the [ATMega328p](https://www.microchip.com/en-us/product/atmega328p) chip from Atmel/Microchip. +Current target hardware for all applications is the [ATMega328p](https://www.microchip.com/en-us/product/atmega328p) chip from Atmel/Microchip. To facilitate testing and extension, user interface (Gui) and hardware abstraction (HAL) are provided using interfaces. Implementation exists for native and cross (i.e. testing) environments. -Note: The *BTEGui* implementation can be used in native environment. Based on plain string exchange, if it may be difficult to interpret. +Note: The _BTEGui_ implementation can be used in native environment. Based on plain string exchange, if it may be difficult to interpret. ![Architecture overview](architecture.png) diff --git a/lib/Domain/Contactor.cpp b/lib/Domain/Contactor.cpp index 1d7c8ba..e6a3dd3 100644 --- a/lib/Domain/Contactor.cpp +++ b/lib/Domain/Contactor.cpp @@ -22,11 +22,12 @@ void Contactor::onDown(long now) { downStartTime = now; } void Contactor::onUp(long now) { onShortPress(); checkForLongPress(now); + downStartTime = 0; } void Contactor::checkForLongPress(long now) { if ((downStartTime > 0) && ((now - downStartTime) > LONG_PRESS)) { - downStartTime = 0; onLongPress(); + downStartTime = 0; } } diff --git a/lib/Domain/Contactor.hpp b/lib/Domain/Contactor.hpp index 5211092..6383b8b 100644 --- a/lib/Domain/Contactor.hpp +++ b/lib/Domain/Contactor.hpp @@ -22,12 +22,12 @@ class Contactor { volatile long downStartTime = 0; public: - void checkForLongPress(long now); - void onDown(long now); - void onUp(long now); + virtual void checkForLongPress(long now); + virtual void onDown(long now); + virtual void onUp(long now); virtual ~Contactor() {} - - virtual void onShortPress() = 0; - virtual void onLongPress() = 0; + + virtual void onShortPress(){}; + virtual void onLongPress(){}; }; \ No newline at end of file diff --git a/lib/Domain/Gun.hpp b/lib/Domain/Gun.hpp index cfabb6e..4858b0b 100644 --- a/lib/Domain/Gun.hpp +++ b/lib/Domain/Gun.hpp @@ -25,7 +25,7 @@ class Gun { public: uint8_t availableShots; - Gun(IGunHal &hal) : _hal(hal), availableShots(0) {} + Gun(IGunHal &hal) : _hal(hal) {} void onButton1ShortPress(); void onButton1LongPress(); void onButton2ShortPress(); diff --git a/lib/Domain/IGunHal.hpp b/lib/Domain/IGunHal.hpp index 0f6c27f..4891b53 100644 --- a/lib/Domain/IGunHal.hpp +++ b/lib/Domain/IGunHal.hpp @@ -19,8 +19,8 @@ class IGunHal { public: virtual ~IGunHal() {} - virtual bool isButton1Pressed() = 0; - virtual bool isButton2Pressed() = 0; + virtual bool isTriggerDown() = 0; + virtual bool isButtonDown() = 0; virtual void shortDelay() = 0; virtual void longDelay() = 0; virtual void ledOn() = 0; diff --git a/platformio.ini b/platformio.ini index 4fd07e3..4cd5e49 100755 --- a/platformio.ini +++ b/platformio.ini @@ -18,7 +18,7 @@ lib_deps = # -cavrisp2 # -cdragon_isp # -cavrispmkII -upload_command = avrdude -vv -b57600 -pm328p -c avrisp -Pcom10 $UPLOAD_FLAGS -U flash:w:$SOURCE:i +upload_command = avrdude -vv -b57600 -pm328p -c avrisp -Pcom9 $UPLOAD_FLAGS -U flash:w:$SOURCE:i # upload_protocol=custom # upload_speed=115200 @@ -39,8 +39,8 @@ upload_command = avrdude -vv -b57600 -pm328p -c avrisp -Pcom10 $UPLOAD_FLAGS -U platform=native build_type = release test_ignore = cross/* - build_flags = -DNATIVE - debug_test = noarch/test_gun + build_flags = -DNATIVE -O0 + debug_test = noarch/test_contactor build_src_filter = ${env.src_filter} - - [env:native_debug] @@ -48,9 +48,9 @@ upload_command = avrdude -vv -b57600 -pm328p -c avrisp -Pcom10 $UPLOAD_FLAGS -U fakeit=https://github.com/FabioBatSilva/ArduinoFake.git platform=native build_type = debug - debug_build_flags = -Og -ggdb3 -g3 -DNATIVE -UAVR + debug_build_flags = -Og -ggdb3 -g3 -DNATIVE test_ignore = cross/* - debug_test = noarch/test_gun + debug_test = noarch/test_contactor build_src_filter = ${env.src_filter} - - [env:cross] diff --git a/src/GunApp.cpp b/src/GunApp.cpp index f4bf797..5c2ba66 100644 --- a/src/GunApp.cpp +++ b/src/GunApp.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include #define TRIGGER_PIN 2 @@ -36,8 +38,9 @@ #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) -#define SCREEN_ADDRESS \ - 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 +// 0x3D for 128x64, 0x3C for 128x32 +#define SCREEN_ADDRESS 0x3C + Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); volatile uint16_t hitcount; @@ -48,47 +51,107 @@ volatile uint16_t shootCount; #define F_CPU 16000000L #define TICKS_BETWEEN_UI_UPDATE 1000 // 10 sec -// counter for ticks while firing -volatile uint8_t shootCycleCountdown; +// display API +static void displayBatteryStatus(); +static void displayShootCount(); + +void setup(); +static void setupHeartbeat(); // counter for ticks between battery display update volatile uint16_t updateBatteryDisplayCycleCount; -void shoot() { - // wake up! - if (shootCycleCountdown == 0) { - shootCount += 1; - digitalWrite(LASER_PIN, HIGH); - digitalWrite(OUT2_PIN, HIGH); - shootCycleCountdown = SHOOT_DURATION_CYCLE; +class Trigger : public Contactor { +public: + Trigger() : shootCycleCountdown(0) {} + + // counter for ticks while firing + volatile uint8_t shootCycleCountdown; + + void decreaseCycleCount() { + if (shootCycleCountdown > 0) { + shootCycleCountdown--; + } } -} -void reload() { - // wake up! - shootCount = 0; -} -void setupHeartbeat() { + void onDown(long now) override { - // 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; + Contactor::onDown(now); - // set the output-compare register based on the desired tick frequency - OCR2A = (F_CPU / BSP_TICKS_PER_SEC / 1024U) - 1U; + // do not wait trigger released to shoot + + if (shootCycleCountdown == 0) { + shootCount += 1; + digitalWrite(LASER_PIN, HIGH); + digitalWrite(OUT2_PIN, HIGH); + shootCycleCountdown = SHOOT_DURATION_CYCLE; + } + } +}; + +bool calibrationMode; + +class Button : public Contactor { +public: + void onShortPress() override { shootCount = 0; } + void onLongPress() override { calibrationMode = !calibrationMode; } +}; + +Trigger trigger; +Button button; + +void triggerInterruptHandler() { + long now = millis(); + if (bit_is_set(PIND, PD2)) { + trigger.onUp(now); + } else { + trigger.onDown(now); + } +} + +void buttonInterruptHandler() { + long now = millis(); + if (bit_is_set(PIND, PD3)) { + button.onUp(now); + } else { + button.onDown(now); + } } ISR(TIMER2_COMPA_vect) { - if (shootCycleCountdown > 0) { - shootCycleCountdown--; - } else if (updateBatteryDisplayCycleCount > 0) { + trigger.decreaseCycleCount(); + if (updateBatteryDisplayCycleCount > 0) { updateBatteryDisplayCycleCount--; } } +void loop(void) { + + long now = millis(); + + button.checkForLongPress(now); + + if (calibrationMode) { + digitalWrite(LASER_PIN, HIGH); + digitalWrite(OUT2_PIN, LOW); + + } else { + + if (trigger.shootCycleCountdown == 0) { + digitalWrite(LASER_PIN, LOW); + digitalWrite(OUT2_PIN, LOW); + displayShootCount(); + } + + if (updateBatteryDisplayCycleCount == 0) { + displayBatteryStatus(); + } + } + + LowPower.idle(SLEEP_FOREVER, ADC_OFF, TIMER2_ON, TIMER1_OFF, TIMER0_OFF, + SPI_OFF, USART0_OFF, TWI_OFF); +} + static void displayShootCount() { display.fillRect(11, 0, 68, 30, 0); // clear @@ -146,6 +209,19 @@ static void displayBatteryStatus() { 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 setup(void) { #if defined(SERIAL_OUTPUT) @@ -160,8 +236,9 @@ void setup(void) { for (;;) ; // Don't proceed, loop forever } + shootCount = 0; - shootCycleCountdown = 0; + calibrationMode = false; updateBatteryDisplayCycleCount = TICKS_BETWEEN_UI_UPDATE; pinMode(LASER_PIN, OUTPUT); @@ -197,27 +274,13 @@ void setup(void) { displayShootCount(); pinMode(BUTTON2_PIN, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(BUTTON2_PIN), reload, FALLING); + attachInterrupt(digitalPinToInterrupt(BUTTON2_PIN), buttonInterruptHandler, + CHANGE); pinMode(TRIGGER_PIN, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(TRIGGER_PIN), shoot, FALLING); + attachInterrupt(digitalPinToInterrupt(TRIGGER_PIN), triggerInterruptHandler, + CHANGE); cli(); setupHeartbeat(); sei(); -} - -void loop(void) { - - if (shootCycleCountdown == 0) { - - digitalWrite(LASER_PIN, LOW); - digitalWrite(OUT2_PIN, LOW); - displayShootCount(); - - if (updateBatteryDisplayCycleCount == 0) { - displayBatteryStatus(); - } - } - LowPower.idle(SLEEP_FOREVER, ADC_OFF, TIMER2_ON, TIMER1_OFF, TIMER0_OFF, - SPI_OFF, USART0_OFF, TWI_OFF); } \ No newline at end of file