diff --git a/TimedPin.cpp b/TimedPin.cpp new file mode 100644 index 0000000..f360ba6 --- /dev/null +++ b/TimedPin.cpp @@ -0,0 +1,108 @@ +#include "TimedPin.h" + +/***************************************************************************//** +* @brief Constructor +*******************************************************************************/ +TimedPin::TimedPin(uint8_t pin_id, bool inverted) { + PinId = pin_id; + PinInvert = inverted; + PinMode = TPM_OFF; + OnTime = 0; + OffTime = 0; +} + +void TimedPin::begin() { + pinMode(PinId, OUTPUT); + PinOff(); +} + +void TimedPin::loop() { + uint32_t t = millis(); + int32_t tdif = t - RefTime; + switch (PinMode) { + case TPM_OFF: + break; + case TPM_ON: + break; + + case TPM_BLINK: { + if (PinState) { // pin == on + if (tdif >= OnTime) { // on time expired + RefTime = t; + PinOff(); // toggle pin + } + } else { // pin == off + if (tdif >= OffTime) { // off time expired + RefTime = t; + PinOn(); // toggle pin + } + } + } break; + + case TPM_TIMED_ON: { + if (tdif >= OnTime) { // on time expired + PinOff(); + PinMode = TPM_OFF; + } + } break; + + case TPM_TIMED_OFF: { + if (tdif >= OffTime) { // off time expired + PinOn(); + PinMode = TPM_ON; + } + } break; + + default: { + PinMode = TPM_OFF; + } break; + } +} + +void TimedPin::OffTimed(uint32_t t) { + PinOff(); + PinMode = TPM_TIMED_OFF; + RefTime = millis(); + OffTime = t; +} + +void TimedPin::OnTimed(uint32_t t) { + PinOn(); + PinMode = TPM_TIMED_ON; + RefTime = millis(); + OffTime = t; +} + +void TimedPin::Blink(uint32_t t_on, uint32_t t_off) { + if (PinMode != TPM_BLINK) { + PinMode = TPM_BLINK; + PinOn(); + } + RefTime = millis(); + OnTime = t_on; + OffTime = t_off; +} + +void TimedPin::On() { + PinOn(); + PinMode = TPM_ON; +} + +void TimedPin::Off() { + PinOff(); + PinMode = TPM_OFF; +} + +// ============================================================================= +// Internal functions +// ============================================================================= + +void TimedPin::PinOn() { + PinState = true; + digitalWrite(PinId, PinInvert ? LOW : HIGH); +} + +void TimedPin::PinOff() { + PinState = false; + digitalWrite(PinId, PinInvert ? HIGH : LOW); +} diff --git a/TimedPin.h b/TimedPin.h new file mode 100644 index 0000000..64ab78d --- /dev/null +++ b/TimedPin.h @@ -0,0 +1,42 @@ +#ifndef __TIMED_PIN__H__ +#define __TIMED_PIN__H__ + +#include + +enum TPinModes { + TPM_OFF, // On + TPM_ON, // Off + TPM_BLINK, // Blink + TPM_ASYM, // asymmetric blink + TPM_TIMED_ON, // timed on, then off + TPM_TIMED_OFF // timed off, then on +}; + +class TimedPin { + private: + uint8_t PinId; + TPinModes PinMode; + bool PinState; + bool PinInvert; + uint32_t RefTime; + uint32_t OnTime; + uint32_t OffTime; + uint16_t CycleCnt; + + void PinOn(); + void PinOff(); + + public: + TimedPin(uint8_t pin, bool inverted = false); + void begin(); + void loop(); + void Blink(uint32_t t_on, uint32_t t_off); + void Blink(uint32_t t) { Blink(t, t); } + // void BlinkCycles(uint32_t t_on, uint32_t t_off, uint16_t cycles); + void Off(); + void OffTimed(uint32_t t); + void On(); + void OnTimed(uint32_t t); +}; + +#endif diff --git a/XAirMixerControl.ino b/XAirMixerControl.ino new file mode 100644 index 0000000..06c1c62 --- /dev/null +++ b/XAirMixerControl.ino @@ -0,0 +1,169 @@ +#include +#include "TimedPin.h" +#include "ui.h" + + +/***************************************************************************//** +* @brief Timer class for generate cyclic signals +*******************************************************************************/ +struct Tmr { + int32_t Period; // cycle length (it must be initialized!) + uint32_t TrefMillis; + //--------------------------------------------------------------------------- + bool Check(uint32_t t) { // check with external timebase + int32_t tdif = t - TrefMillis; + if (tdif >= Period) { // period expired + TrefMillis += Period; + return true; + } + return false; + }; + + bool Check() { // check with millisec timebase + uint32_t t = millis(); + int32_t tdif = t - TrefMillis; + if (tdif >= Period) { // period expired + TrefMillis += Period; + return true; + } + return false; + }; +}; + +Tmr Tmr10ms = { .Period = 10 }; +Tmr Tmr100ms = { .Period = 100 }; +Tmr Tmr1s = { .Period = 1000 }; + +/***************************************************************************//** +* @brief Button - port assignment +*******************************************************************************/ +const uint8_t pinBtnInc1 = 8; +const uint8_t pinBtnDec1 = 9; +const uint8_t pinBtnInc2 = 10; +const uint8_t pinBtnDec2 = 11; +const uint8_t pinBtnChOn1 = 12; +const uint8_t pinBtnChOn2 = 13; + +/***************************************************************************//** +* @brief Button codes +*******************************************************************************/ +enum _BTN_CODES { + BTN_INC1 = 0, + BTN_DEC1, + BTN_INC2, + BTN_DEC2, + BTN_CH_ON1, + BTN_CH_ON2, + + BTN_MAX +}; + +/***************************************************************************//** +* @brief LED +*******************************************************************************/ +TimedPin LedBoard(LED_BUILTIN); +TimedPin LedChOn1(2); +TimedPin LedChOn2(3); + + + + +/***************************************************************************//** +* @brief MIDI instance (serial port) +*******************************************************************************/ +MIDI_CREATE_DEFAULT_INSTANCE(); + +// ----------------------------------------------------------------------------- + +// This function will be automatically called when a NoteOn is received. +// It must be a void-returning function with the correct parameters, see documentation here: +// https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks +void handleNoteOn(byte channel, byte pitch, byte velocity) { + // Do whatever you want when a note is pressed. + + // Try to keep your callbacks short (no delays ect) otherwise it would slow down the loop() and have a bad impact on real-time performance. +} + +void handleNoteOff(byte channel, byte pitch, byte velocity) { + // Do something when the note is released. Note that NoteOn messages with 0 velocity are interpreted as NoteOffs. +} + +// ----------------------------------------------------------------------------- + +void setup() { + pinMode(pinBtnInc1, INPUT_PULLUP); + pinMode(pinBtnDec1, INPUT_PULLUP); + pinMode(pinBtnInc2, INPUT_PULLUP); + pinMode(pinBtnDec2, INPUT_PULLUP); + pinMode(pinBtnChOn1, INPUT_PULLUP); + pinMode(pinBtnChOn2, INPUT_PULLUP); + + LedBoard.begin(); + LedChOn1.begin(); + LedChOn2.begin(); + + // Connect the handleNoteOn function to the library, so it is called upon reception of a NoteOn. + MIDI.setHandleNoteOn(handleNoteOn); // Put only the name of the function + MIDI.setHandleNoteOff(handleNoteOff); // Do the same for NoteOffs + MIDI.begin(MIDI_CHANNEL_OMNI); // Initiate MIDI communications, listen to all channels + + UI_Init(); // Start user interface (main state machine) +} + +/***************************************************************************//** +* @brief Main loop +*******************************************************************************/ +void loop() { + MIDI.read(); // Call MIDI.read the fastest you can for real-time performance. + + uint32_t t = millis(); + // execute every 10ms ******************************************************* + if (Tmr10ms.Check(t)) { + static uint8_t BtnPrev = 0xFF; + static uint8_t BtnNew = 0; + static uint8_t BtnRel = 0; + uint8_t btn = (digitalRead(pinBtnInc1) ? (1 << BTN_INC1 ) : 0) + | (digitalRead(pinBtnDec1) ? (1 << BTN_DEC1 ) : 0) + | (digitalRead(pinBtnInc2) ? (1 << BTN_INC2 ) : 0) + | (digitalRead(pinBtnInc2) ? (1 << BTN_DEC2 ) : 0) + | (digitalRead(pinBtnChOn1) ? (1 << BTN_CH_ON1) : 0) + | (digitalRead(pinBtnChOn1) ? (1 << BTN_CH_ON2) : 0); + BtnNew |= (~btn) & BtnPrev; + BtnRel |= btn & (~BtnPrev); + uint8_t mask = 1; + if (BtnNew) { + for (uint_fast8_t i = 0; i < BTN_MAX; i++) { + if (BtnNew & mask) { + BtnNew &= ~mask; // Bit clr + UI_EventProc(EV_UI_KEY_PRESS + i); + break; + } + mask <<= 1; + } + }else if (BtnRel) { // released button + for (uint_fast8_t i = 0; i < BTN_MAX; i++) { + if (BtnRel & mask) { + BtnRel &= ~mask; // Bit clr + UI_EventProc(EV_UI_KEY_REL + i); + break; + } + mask <<= 1; + } + } + UI_EventProc(EV_TIMER_TICK); + UI_EventProc(EV_UI_TICK_10MS); + UI_CheckEvent(); + + LedBoard.loop(); + LedChOn1.loop(); + LedChOn2.loop(); + } + // execute every 100ms ****************************************************** + if (Tmr100ms.Check(t)) { + UI_EventProc(EV_UI_TICK_100MS); + } + // execute every 1s ********************************************************* + if (Tmr1s.Check(t)) { + UI_EventProc(EV_UI_TICK_1S); + } +} diff --git a/statemachine.cpp b/statemachine.cpp new file mode 100644 index 0000000..fbfffb6 --- /dev/null +++ b/statemachine.cpp @@ -0,0 +1,45 @@ +/***************************************************************************//** +* @file statemachine.cpp +* +* Simple finite state machine +* +*******************************************************************************/ + +#include +#include + +#include "statemachine.h" + +void StateMachine(STATE_MACHINE* const me, uint16_t ev) { + me->State(me, ev); // call actual state + if (me->StateNew != NULL) { // new state request + me->StateReturn = me->State; // remeber last state + me->State(me, EV_STATE_EXIT); // closing last state + me->State = me->StateNew; // switching state + me->StateNew = NULL; + me->State(me, EV_STATE_ENTER); // init new state + } +} + +void SM_ST_StateDelayed(STATE_MACHINE* const me, uint16_t event) { + switch (event) { + case EV_TIMER_TICK: { + if (me->Timer_StateDelay) { + me->Timer_StateDelay--; + }else { + SM_SET_STATE(me->StateDelayed); + } + }break; + default: break; + } +} + +void StateMachineInit(STATE_MACHINE* const me, SM_STATE_FUNC* const init_state) { + me->State = init_state; + me->StateNew = NULL; + me->StateDelayed = init_state; + me->StateReturn = init_state; + + me->State(me, EV_STATE_ENTER); // enter into initial state (initial transition) +} + diff --git a/statemachine.h b/statemachine.h new file mode 100644 index 0000000..2bc0535 --- /dev/null +++ b/statemachine.h @@ -0,0 +1,55 @@ +#ifndef __STATEMACHINE_H__ +#define __STATEMACHINE_H__ + +#ifdef __cplusplus +extern "C" { +#endif +#if 0 +} // workaround for CB folding bug +#endif + +#include + + +enum SM_EVENTS { + EV_STATE_ENTER, + EV_STATE_EXIT, + EV_TIMER_TICK, + + EV_USER_FIRST +}; + +#define SM_SET_STATE(st) do { \ + ((STATE_MACHINE*)me)->StateNew = (SM_STATE_FUNC*)(st); \ +} while (0) + +#define SM_SET_STATE_DELAYED(st, delay) do { \ + ((STATE_MACHINE*)me)->StateDelayed = (SM_STATE_FUNC*)(st); \ + ((STATE_MACHINE*)me)->Timer_StateDelay = delay; \ + ((STATE_MACHINE*)me)->StateNew = &SM_ST_StateDelayed; \ +} while (0) + +struct _STATE_MACHINE; +typedef struct _STATE_MACHINE STATE_MACHINE; +typedef void SM_STATE_FUNC(STATE_MACHINE* me, uint16_t event); + +struct _STATE_MACHINE { + SM_STATE_FUNC* State; // actual state + SM_STATE_FUNC* StateNew; // new state + SM_STATE_FUNC* StateDelayed; // auto state switch after delay + SM_STATE_FUNC* StateReturn; // return state for nested states + uint16_t Timer_StateDelay; +}; + +extern void StateMachineInit(STATE_MACHINE* const me, SM_STATE_FUNC* const init_state); +extern void StateMachine(STATE_MACHINE* const me, uint16_t ev); +extern void SM_ST_StateDelayed(STATE_MACHINE* const me, uint16_t event); + +#if 0 +{ // workaround for CB folding bug +#endif +#ifdef __cplusplus +} +#endif + +#endif // __STATEMACHINE_H__ diff --git a/ui.cpp b/ui.cpp new file mode 100644 index 0000000..dc88165 --- /dev/null +++ b/ui.cpp @@ -0,0 +1,157 @@ +/***************************************************************************//** +* @file ui.cpp +*******************************************************************************/ + +#include +#include +#include +#include +#include + +//#include "gitinfo.h" +#include "MIDI.h" +#include "TimedPin.h" +#include "ui.h" + + +#define ELEMCNT(x) (sizeof(x) / sizeof((x)[0])) + + + +// Extern variables ============================================================ +extern TimedPin LedBoard; + +extern MIDI_NAMESPACE::SerialMIDI serialMIDI; +extern MIDI_NAMESPACE::MidiInterface> MIDI; + + +// Typedefs ==================================================================== +struct UI_SM { + STATE_MACHINE sm; + uint32_t Events; + uint16_t Timer; + uint16_t KeyTimer; +}; +typedef void UI_STATE_FUNC(UI_SM* me, uint16_t event); + + +// Local variables ============================================================= +static UI_SM UiSm; +#define DEFINE_MY_OBJECT() UI_SM* const me = &UiSm; + + +const uint16_t ToutProg = 30; // [s] + +// Local function declarations ================================================= +static void UiSt_MixerStartup(UI_SM* me, uint16_t event); +static void UiSt_Home(UI_SM* me, uint16_t event); + + +// Function definitions ======================================================== + + +//u @startuml +//u skinparam defaultTextAlignment left +//u state UserInterface { + +/***************************************************************************//** +* @brief Initialize state machine for user interface +*******************************************************************************/ +void UI_Init() { + DEFINE_MY_OBJECT(); + me->Events = 0; + StateMachineInit(&me->sm, (SM_STATE_FUNC*)&UiSt_MixerStartup); //u [*] -> MixerStartup +} + +/***************************************************************************//** +* @brief Execute event handler +*******************************************************************************/ +void UI_EventProc(uint16_t event) { + DEFINE_MY_OBJECT(); + StateMachine((STATE_MACHINE*)me, event); +} + +/***************************************************************************//** +* @brief Check and execute asynchron event (from event buffer) +*******************************************************************************/ +void UI_CheckEvent() { + DEFINE_MY_OBJECT(); + + if (me->Events) { + uint32_t mask = 1; + uint8_t ev; + for (ev = 0; ev < 32; ev++) { + if (me->Events & mask) { + me->Events &= ~mask; + StateMachine((STATE_MACHINE*)me, ev); + break; + } + mask <<= 1; // shift left + } + } +} + +/***************************************************************************//** +* @brief Send (asynchron) event to state machine to process it later +*******************************************************************************/ +static inline void EventSend(UI_SM* const me, uint16_t event) { + me->Events |= (1 << event); +} + + +// State definitions =========================================================== + + +//u state MixerStartup { +/***************************************************************************//** +* @brief MixerStartup +*******************************************************************************/ +void UiSt_MixerStartup(UI_SM* const me, uint16_t event) { + switch (event) { + case EV_STATE_ENTER: { //u MixerStartup: entry: + me->Timer = 0; + LedBoard.Blink(50, 450); + }break; + case EV_STATE_EXIT: { + }break; + + case EV_UI_TICK_100MS: { + }break; + + case EV_UI_TICK_1S: { + if (++me->Timer == 15) { + SM_SET_STATE(&UiSt_Home); //u MixerStartup -left-> Home : timeout + } + static uint8_t MuteVal = 0; + MIDI.sendControlChange(21, MuteVal, 2); // Send Mute: CH2, 21-26: Aux1-6 + MuteVal = MuteVal ? 0 : 127; + }break; + } +} +//u } + + +//u state Home { +/***************************************************************************//** +* @brief Standby (home) state +*******************************************************************************/ +void UiSt_Home(UI_SM* const me, uint16_t event) { + switch (event) { + case EV_STATE_ENTER: { + LedBoard.Blink(1900, 100); + }break; + case EV_STATE_EXIT: { + }break; + + case EV_UI_TICK_10MS: { + }break; + } +} +//u } + + + + +//u } + +//u @enduml diff --git a/ui.h b/ui.h new file mode 100644 index 0000000..c0a3d75 --- /dev/null +++ b/ui.h @@ -0,0 +1,33 @@ +#ifndef __UI_H__ +#define __UI_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdint.h" +#include "statemachine.h" + + +enum UI_EVENTS { + EV_UI_TICK_10MS = EV_USER_FIRST, + EV_UI_TICK_100MS, + EV_UI_TICK_1S, + EV_UI_KEY_PRESS = 0x0100, + EV_UI_KEY_PRESS_MAX = 0x01FF, + EV_UI_KEY_REL = 0x0200, + EV_UI_KEY_REL_MAX = 0x02FF, +}; + + +extern void UI_Init(); +extern void UI_EventProc(uint16_t event); +extern void UI_EventSend(uint16_t event); +extern void UI_CheckEvent(); + + +#ifdef __cplusplus +} +#endif + +#endif