diff --git a/mouse.cpp b/mouse.cpp index 309e8bd..b9bc1c1 100644 --- a/mouse.cpp +++ b/mouse.cpp @@ -3,28 +3,30 @@ #include "smc_pins.h" extern bool SYSTEM_POWERED; -extern PS2Port Keyboard; +extern PS2KeyboardPort Keyboard; extern PS2Port Mouse; MOUSE_INIT_STATE_T mouse_init_state = OFF; // Mouse initialization state machine -void MouseInitTick() +void MouseTick() { static bool watchdog_armed = false; static uint16_t watchdog_timer = 1023; static MOUSE_INIT_STATE watchdog_expire_state = OFF; - + MOUSE_INIT_STATE_T next_state = mouse_init_state; + if (mouse_init_state != OFF && SYSTEM_POWERED == 0) { mouse_init_state = OFF; watchdog_armed = false; watchdog_timer = 1023; + Mouse.reset(); return; } if (watchdog_armed) { if (watchdog_timer == 0) { - mouse_init_state = watchdog_expire_state; + next_state = watchdog_expire_state; watchdog_armed = false; } else { @@ -41,10 +43,10 @@ void MouseInitTick() { case OFF: if (SYSTEM_POWERED != 0) { - mouse_init_state = POWERUP_BAT_WAIT; + next_state = POWERUP_BAT_WAIT; // If we don't see the mouse respond, jump to sending it a reset. - MOUSE_WATCHDOG(START_RESET); + MOUSE_REARM_WATCHDOG(); } break; @@ -53,11 +55,12 @@ void MouseInitTick() uint8_t b = Mouse.next(); if (b == BAT_OK) { - mouse_init_state = POWERUP_ID_WAIT; - MOUSE_WATCHDOG(START_RESET); // If we don't see the mouse respond, jump to sending it a reset. + next_state = POWERUP_ID_WAIT; + MOUSE_REARM_WATCHDOG(); } else if (b == BAT_FAIL) { - mouse_init_state = FAILED; + next_state = FAILED; } + // Let watchdog send us to START_RESET if we don't get BAT } break; @@ -68,31 +71,31 @@ void MouseInitTick() if (b == MOUSE_ID) { Mouse.sendPS2Command(mouse_command::SET_SAMPLE_RATE); - MOUSE_WATCHDOG(START_RESET); - mouse_init_state = SAMPLERATECMD_ACK_WAIT; + MOUSE_REARM_WATCHDOG(); + next_state = SAMPLERATECMD_ACK_WAIT; } // Watchdog will eventually send us to START_RESET if we don't get MOUSE_ID } break; case PRE_RESET: - mouse_init_state = START_RESET; + next_state = START_RESET; break; case START_RESET: Mouse.flush(); Mouse.sendPS2Command(mouse_command::RESET); MOUSE_WATCHDOG(FAILED); - mouse_init_state = RESET_ACK_WAIT; + next_state = RESET_ACK_WAIT; break; case RESET_ACK_WAIT: if (mstatus == mouse_command::ACK) { Mouse.next(); - MOUSE_WATCHDOG(START_RESET); - mouse_init_state = RESET_BAT_WAIT; + MOUSE_REARM_WATCHDOG(); + next_state = RESET_BAT_WAIT; } else { - mouse_init_state = FAILED; // Assume an error of some sort. + next_state = FAILED; // Assume an error of some sort. } break; @@ -101,10 +104,10 @@ void MouseInitTick() if (Mouse.available()) { uint8_t b = Mouse.next(); if ( b != mouse_command::BAT_OK ) { - mouse_init_state = FAILED; + next_state = FAILED; } else { - MOUSE_WATCHDOG(START_RESET); - mouse_init_state = RESET_ID_WAIT; + MOUSE_REARM_WATCHDOG(); + next_state = RESET_ID_WAIT; } } break; @@ -114,11 +117,11 @@ void MouseInitTick() if (Mouse.available()) { uint8_t b = Mouse.next(); if ( b != mouse_command::MOUSE_ID ) { - mouse_init_state = FAILED; + next_state = FAILED; } else { Mouse.sendPS2Command(mouse_command::SET_SAMPLE_RATE); - MOUSE_WATCHDOG(START_RESET); - mouse_init_state = SAMPLERATECMD_ACK_WAIT; + MOUSE_REARM_WATCHDOG(); + next_state = SAMPLERATECMD_ACK_WAIT; } } break; @@ -126,45 +129,163 @@ void MouseInitTick() case SAMPLERATECMD_ACK_WAIT: // RECEIVE ACK, SEND 20 updates/sec Mouse.next(); if (mstatus != mouse_command::ACK) - mouse_init_state = PRE_RESET; // ?? Try resetting again, I guess. + next_state = PRE_RESET; // ?? Try resetting again, I guess. else { Mouse.sendPS2Command(60); - MOUSE_WATCHDOG(START_RESET); - mouse_init_state = SAMPLERATE_ACK_WAIT; + MOUSE_REARM_WATCHDOG(); + next_state = SAMPLERATE_ACK_WAIT; } break; case SAMPLERATE_ACK_WAIT: // RECEIVE ACK, SEND ENABLE Mouse.next(); if (mstatus != mouse_command::ACK) - mouse_init_state = PRE_RESET; + next_state = PRE_RESET; else { Mouse.sendPS2Command(mouse_command::ENABLE); - MOUSE_WATCHDOG(START_RESET); - mouse_init_state = ENABLE_ACK_WAIT; + MOUSE_REARM_WATCHDOG(); + next_state = ENABLE_ACK_WAIT; } break; case ENABLE_ACK_WAIT: // Receive ACK Mouse.next(); if (mstatus != mouse_command::ACK) { - mouse_init_state = PRE_RESET; + next_state = PRE_RESET; } else { - mouse_init_state = MOUSE_INIT_DONE; - MOUSE_WATCHDOG_DISARM(); + next_state = MOUSE_INIT_DONE; + MOUSE_DISARM_WATCHDOG(); } break; case MOUSE_INIT_DONE: // done - MOUSE_WATCHDOG_DISARM(); - mouse_init_state = MOUSE_READY; + MOUSE_DISARM_WATCHDOG(); + next_state = MOUSE_READY; break; case MOUSE_READY: break; default: + // This is where the FAILED state will end up + break; + } + mouse_init_state = next_state; +} + +uint8_t kbd_init_state = 0; +uint8_t kbd_status_leds = 0; + +void KeyboardTick() +{ + static bool watchdog_armed = false; + static uint16_t watchdog_timer = 255; + static uint8_t watchdog_expire_state = OFF; + uint8_t next_state = kbd_init_state; + static uint8_t blink_counter = 0; + blink_counter++; + + if (kbd_init_state != OFF && SYSTEM_POWERED == 0) + { + kbd_init_state = OFF; + watchdog_armed = false; + watchdog_timer = 255; + return; + } + if (watchdog_armed) { + if (watchdog_timer == 0) { + next_state = watchdog_expire_state; + watchdog_armed = false; + } + else { + watchdog_timer--; + } + } + + PS2_CMD_STATUS kstatus = Keyboard.getCommandStatus(); + if (kstatus == PS2_CMD_STATUS::CMD_PENDING) + { + return; + } + + switch(kbd_init_state) + { + case OFF: + if (SYSTEM_POWERED != 0) { + next_state = POWERUP_BAT_WAIT; + + // If we don't see the mouse respond, jump to sending it a reset. + MOUSE_REARM_WATCHDOG(); + } + break; + + case POWERUP_BAT_WAIT: + if (Keyboard.available()) { + uint8_t b = Keyboard.next(); + if (b == BAT_OK) + { + kbd_status_leds = 0x2; + next_state = KBD_SET_LEDS1; + MOUSE_REARM_WATCHDOG(); + } else if (b == BAT_FAIL) { + next_state = FAILED; + } + // Let watchdog send us to START_RESET if we don't get BAT + } + break; + + case START_RESET: + Keyboard.flush(); + Keyboard.sendPS2Command(mouse_command::RESET); + MOUSE_WATCHDOG(FAILED); + next_state = RESET_ACK_WAIT; + break; + + case RESET_ACK_WAIT: + if (kstatus == mouse_command::ACK) { + Keyboard.next(); + MOUSE_REARM_WATCHDOG(); + next_state = POWERUP_BAT_WAIT; + } else { + next_state = FAILED; // Assume an error of some sort. + } + break; + + + case KBD_SET_LEDS1: + Keyboard.sendPS2Command(SET_STATUS_INDICATORS); + next_state = SET_LEDS1_ACK_WAIT; + MOUSE_REARM_WATCHDOG(); + break; + + case SET_LEDS1_ACK_WAIT: + Keyboard.next(); + if (kstatus != PS2_CMD_STATUS::CMD_ACK) { + next_state = FAILED; + } else { + next_state = SET_LEDS2_ACK_WAIT; + Keyboard.sendPS2Command(kbd_status_leds); + MOUSE_REARM_WATCHDOG(); + } + break; + + case SET_LEDS2_ACK_WAIT: + Keyboard.next(); + if (kstatus != PS2_CMD_STATUS::CMD_ACK) { + next_state = FAILED; + } else { + next_state = KBD_READY; + MOUSE_DISARM_WATCHDOG(); + } + break; + + case KBD_READY: + break; + + default: + // This is where the FAILED state will end up break; } + kbd_init_state = next_state; } diff --git a/mouse.h b/mouse.h index f49ebfb..9d831d2 100644 --- a/mouse.h +++ b/mouse.h @@ -3,15 +3,16 @@ enum mouse_command : uint8_t { - RESET = 0xFF, - ACK = 0xFA, - BAT_OK = 0xAA, - BAT_FAIL = 0xFC, - MOUSE_ID = 0x00, - SET_SAMPLE_RATE = 0xF3, - READ_DEVICE_TYPE = 0xF2, + RESET = 0xFF, // Reset device + ACK = 0xFA, // Acknowledge + BAT_OK = 0xAA, // Basic Acceptance Test passed + BAT_FAIL = 0xFC, // Basic Acceptance Test failed + MOUSE_ID = 0x00, // Mouse identifier + SET_SAMPLE_RATE = 0xF3, // Mouse sample rate command + READ_DEVICE_TYPE = 0xF2, // Request device type SET_RESOLUTION = 0xE8, SET_SCALING = 0xE6, + SET_STATUS_INDICATORS = 0xED, // Set status indicators (keyboard LEDs) ENABLE = 0xF4 }; @@ -31,16 +32,30 @@ typedef enum MOUSE_INIT_STATE : uint8_t { ENABLE_ACK_WAIT, MOUSE_INIT_DONE, MOUSE_READY, + + KBD_SET_LEDS1, + SET_LEDS1_ACK_WAIT, + KBD_SET_LEDS2, + SET_LEDS2_ACK_WAIT, + KBD_READY, FAILED = 255 } MOUSE_INIT_STATE_T; extern MOUSE_INIT_STATE_T mouse_init_state; +extern uint8_t kbd_init_state; + +#define MOUSE_REARM_WATCHDOG() \ + watchdog_armed = true; \ + watchdog_timer = 255; \ + watchdog_expire_state = (MOUSE_INIT_STATE::START_RESET) #define MOUSE_WATCHDOG(x) \ watchdog_armed = true; \ - watchdog_timer = 1023; \ + watchdog_timer = 255; \ watchdog_expire_state = (x) -#define MOUSE_WATCHDOG_DISARM() watchdog_armed = false +#define MOUSE_DISARM_WATCHDOG() watchdog_armed = false +void MouseTick(); +void KeyboardTick(); diff --git a/ps2.h b/ps2.h index 19dd57e..661d217 100644 --- a/ps2.h +++ b/ps2.h @@ -1,5 +1,6 @@ #pragma once #include +#include "mouse.h" #define SCANCODE_TIMEOUT_MS 50 enum PS2_CMD_STATUS : uint8_t { @@ -18,7 +19,7 @@ class PS2Port static_assert((size & (size - 1)) == 0, "Buffer size must be a power of 2"); // size must be a power of 2 static_assert(digitalPinToInterrupt(clkPin) != NOT_AN_INTERRUPT); - private: + protected: volatile uint8_t head; volatile uint8_t tail; volatile uint8_t buffer[size]; @@ -35,23 +36,27 @@ class PS2Port volatile PS2_CMD_STATUS commandStatus = PS2_CMD_STATUS::IDLE; void resetReceiver() { + resetInput(); + outputSize = 0; + timerCountdown = 0; + flush(); + }; + + virtual void resetInput(){ pinMode(datPin, INPUT); pinMode(clkPin, INPUT); curCode = 0; parity = 0; rxBitCount = 0; ps2ddr = 0; - outputSize = 0; - timerCountdown = 0; - flush(); - }; + } public: PS2Port() : head(0), tail(0), curCode(0), parity(0), lastBitMillis(0), rxBitCount(0), ps2ddr(0), timerCountdown(0) - { - resetReceiver(); - }; + { + resetReceiver(); + }; /// @brief Begin processing PS/2 traffic void begin(void(*irqFunc)()) { @@ -73,7 +78,7 @@ class PS2Port if (curMillis >= (lastBitMillis + SCANCODE_TIMEOUT_MS)) { // Haven't heard from device in a while, assume this is a new keycode - resetReceiver(); + resetInput(); } lastBitMillis = curMillis; @@ -111,29 +116,30 @@ class PS2Port // Protocol Error - parity mismatch } - //Hhost to device command response handler - if (commandStatus==PS2_CMD_STATUS::CMD_PENDING){ - if (curCode==PS2_CMD_STATUS::CMD_ERR){ + bool suppress_scancode = false; + //Host to device command response handler + if (commandStatus == PS2_CMD_STATUS::CMD_PENDING) { + if (curCode == PS2_CMD_STATUS::CMD_ERR) { //Command error - Resend - commandStatus=PS2_CMD_STATUS::CMD_ERR; + commandStatus = PS2_CMD_STATUS::CMD_ERR; } - else if (curCode==PS2_CMD_STATUS::CMD_ACK){ - if (outputSize==2){ + else if (curCode == PS2_CMD_STATUS::CMD_ACK) { + if (outputSize == 2) { //Send second byte - sendPS2Command(1, outputBuffer[1]); + sendPS2Command(outputBuffer[1]); + suppress_scancode = true; } - else{ + else { //Command ACK - commandStatus=PS2_CMD_STATUS::CMD_ACK; + commandStatus = PS2_CMD_STATUS::CMD_ACK; } } } - //Update input buffer - byte headNext = (head + 1) & (size - 1); - if (headNext != tail){ - buffer[head] = (byte)(curCode); - head = headNext; + if (!suppress_scancode) + { + //Update input buffer + processByteReceived(curCode); } //Else Ring buffer overrun, drop the incoming code :( DBG_PRINT("keycode: "); @@ -210,7 +216,7 @@ class PS2Port case 10: //ACK - resetReceiver(); //Prepare host to receive device ACK or Resend (error) code + resetInput(); //Prepare host to receive device ACK or Resend (error) code break; } } @@ -221,7 +227,7 @@ class PS2Port }; /// @brief Returns the next available byte from the PS/2 port - uint8_t next() { + virtual uint8_t next() { if (available()) { uint8_t value = buffer[tail]; tail = (tail + 1) & (size - 1); @@ -230,11 +236,10 @@ class PS2Port else { return 0; } - }; + } - void flush() { + virtual void flush() { head = tail = 0; - //lastBitMillis = 0; } void reset() { @@ -306,13 +311,266 @@ class PS2Port parity = 0; } - else{ + else { timerCountdown--; } } - uint8_t count(){ - return (size+head-tail)&(size-1); + uint8_t count() { + return (size + head - tail) & (size - 1); + } + + virtual void processByteReceived(uint8_t value) { + byte headNext = (head + 1) & (size - 1); + if (headNext != tail) { + buffer[head] = (byte)(curCode); + head = headNext; + } + } +}; + +enum PS2_MODIFIER_STATE : uint8_t { + LALT = 1, + LSHIFT = 2, + LCTRL = 4, + RSHIFT = 8, + RALT = 16, + RCTRL = 32, + LWIN = 64, + RWIN = 128 +}; + +template +class PS2KeyboardPort : public PS2Port +{ + protected: + volatile bool buffer_overrun = false; // Set to true on buffer full, and to false when buffer is empty again + volatile uint8_t scancode_state = 0x00; // Tracks the type and byte position of the scan code currently being received (bits 4-7 = scan code type, bits 0-3 = number of bytes) + volatile uint8_t modifier_state = 0x00; // Always tracks modifier key state, even if buffer is full + volatile uint8_t modifier_oldstate = 0x00; // Previous modifier key state, used to compare what's changed during buffer full + volatile bool reset_request = false; + volatile bool nmi_request = false; + uint8_t modifier_codes[8] = {0x11, 0x12, 0x14, 0x59, 0x11, 0x14, 0x1f, 0x27}; // Last byte of modifier key scan codes: LALT, LSHIFT, LCTRL, RSHIFT, RALT, RCTRL, LWIN, RWIN + + public: + + /** + Processes a scan code byte received from the keyboard + */ + void processByteReceived(uint8_t value) { + // If buffer_overrun is set, and if we are not in the middle of receiving a multi-byte scancode (indicated by scancode_state == 0), + // and if the buffer is empty again, we first output modifier key state changes that happened while the buffer was closed, + // and then clear the buffer_overrun flag + if (buffer_overrun && !this->available() && scancode_state == 0x00) { + if (putModifiers()) { + buffer_overrun = false; + } + } + + // Try adding value to buffer. On buffer full: (a) set buffer_overrun, and (b) remove a partial scancode from the head of the buffer + if (!buffer_overrun) { + if (!bufferAdd(value)) { + buffer_overrun = true; // Buffer full + bufferRemovePartialCode(); + } + } + + // Update scancode state; this is always done, also while buffer_overrun is set + updateState(value); + + // buffer_overrun not set means that there are no modifier key state changes to track; set oldstate = state + if (!buffer_overrun) { + modifier_oldstate = modifier_state; + } + } + + /** + A state machine tracking type and length of current scan code + scancode_state bits 0-3 = Number of bytes stored in buffer since last complete scancode + scancode_state bits 4-7 = Type of scancode + */ + void updateState(uint8_t value) { + switch (scancode_state) { + case 0x00: // Start of new scan code + // Update state + if (value == 0xf0) scancode_state = 0x11; // Start of break code + else if (value == 0xab) scancode_state = 0x51; // Start of two byte response to read ID command + else if (value == 0xe0) scancode_state = 0x21; // Start of extended code + else if (value == 0xe1) scancode_state = 0x41; // Start of Pause key code + + // Update modifier key status + else if (value == 0x12) modifier_state |= PS2_MODIFIER_STATE::LSHIFT; + else if (value == 0x59) modifier_state |= PS2_MODIFIER_STATE::RSHIFT; + else if (value == 0x14) modifier_state |= PS2_MODIFIER_STATE::LCTRL; + else if (value == 0x11) modifier_state |= PS2_MODIFIER_STATE::LALT; + + // Check Ctrl+Alt+PrtScr/Restore => NMI + else if (value == 0x84 && isCtrlAltDown()) nmi_request = true; + + break; + + case 0x11: // After 0xf0 (break code) + // Update state + scancode_state = 0x00; + + // Update modifier key status + if (value == 0x12) modifier_state &= ~PS2_MODIFIER_STATE::LSHIFT; + else if (value == 0x59) modifier_state &= ~PS2_MODIFIER_STATE::RSHIFT; + else if (value == 0x14) modifier_state &= ~PS2_MODIFIER_STATE::LCTRL; + else if (value == 0x11) modifier_state &= ~PS2_MODIFIER_STATE::LALT; + + break; + + case 0x21: // After 0xe0 (extended code) + // Update state + if (value == 0xf0) scancode_state = 0x32; // Extended break code + else scancode_state = 0x00; + + // Update modifier key status + if (value == 0x14) modifier_state |= PS2_MODIFIER_STATE::RCTRL; + else if (value == 0x1f) modifier_state |= PS2_MODIFIER_STATE::LWIN; + else if (value == 0x27) modifier_state |= PS2_MODIFIER_STATE::RWIN; + else if (value == 0x11) modifier_state |= PS2_MODIFIER_STATE::RALT; + + // Check Ctrl+Alt+Del => Reset + else if (value == 0x71 && isCtrlAltDown()) reset_request = true; + + break; + + case 0x32: // After 0xe0 0xf0 (extended break code) + // Update state + scancode_state = 0x00; + + // Update modifier key status + if (value == 0x14) modifier_state &= ~PS2_MODIFIER_STATE::RCTRL; + else if (value == 0x1f) modifier_state &= ~PS2_MODIFIER_STATE::LWIN; + else if (value == 0x27) modifier_state &= ~PS2_MODIFIER_STATE::RWIN; + else if (value == 0x11) modifier_state &= ~PS2_MODIFIER_STATE::RALT; + + break; + + case 0x41: // After 0xe1 (pause make code, 8 bytes) + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + scancode_state++; + break; + + case 0x47: + scancode_state = 0x00; + break; + + case 0x51: // After 0xab (two byte response to read ID command) + scancode_state = 0x00; + break; + } + } + + /** + Adds a byte to head of buffer + Returns true if successful, else false (if the buffer was full) + */ + bool bufferAdd(uint8_t value) { + byte headNext = (this->head + 1) & (size - 1); + if (headNext != this->tail) { + this->buffer[this->head] = value; + this->head = headNext; + return true; + } + else { + return false; + } } + /** + Removes partial scan code from head of buffer + */ + void bufferRemovePartialCode() { + if ((scancode_state & 0x0f) > this->count()){ + flush(); + } + else { + this->head = (this->head + size - (scancode_state & 0x0f)) & (size - 1); // scancode_state bits 0-3 = number of bytes stored in buffer since last complete scancode + } + } + + void flush(){ + PS2Port::flush(); + + scancode_state = 0; + modifier_state = 0; + buffer_overrun = false; + nmi_request = false; + reset_request = false; + } + + void resetInput() { + PS2Port::resetInput(); + if (!buffer_overrun) { + bufferRemovePartialCode(); + } + scancode_state = 0; + } + + + /** + Modifier key state changes are tracked when the + buffer is full to avoid "sticky" keys; this function + writes the these changes to the buffer + */ + bool putModifiers() { + for (uint8_t i = 0; i < 8; i++) { + if ((modifier_state & (1 << i)) != (modifier_oldstate & (1 << i))) { + + if (i > 3) { + if (!bufferAdd(0xe0)){ + return false; + } + scancode_state = 0x21; + } + + if (!(modifier_state & (1 << i))) { + if (!bufferAdd(0xf0)){ + bufferRemovePartialCode(); + scancode_state = 0x00; + return false; + } + if (scancode_state == 0x21) scancode_state = 0x32; else scancode_state = 0x11; + } + + if (!bufferAdd(modifier_codes[i])){ + bufferRemovePartialCode(); + scancode_state = 0x00; + return false; + } + + scancode_state = 0x00; + modifier_oldstate = (modifier_oldstate & ~(1 << i)) | (modifier_state & (1 << i)); + } + } + return true; + } + + bool getResetRequest() { + return reset_request; + } + + void ackResetRequest() { + reset_request = false; + } + + bool getNMIRequest() { + return nmi_request; + } + + void ackNMIRequest() { + nmi_request = false; + } + + bool isCtrlAltDown() { + return ((modifier_state & PS2_MODIFIER_STATE::LCTRL) || (modifier_state & PS2_MODIFIER_STATE::RCTRL)) && ((modifier_state & PS2_MODIFIER_STATE::LALT) || (modifier_state & PS2_MODIFIER_STATE::RALT)); + } + }; diff --git a/smc_button.h b/smc_button.h new file mode 100644 index 0000000..74c512c --- /dev/null +++ b/smc_button.h @@ -0,0 +1,79 @@ +#pragma once + +#include "Arduino.h" + +#define BUTTON_STATE_RELEASED 0 +#define BUTTON_STATE_CLICKED 1 +#define BUTTON_STATE_LONG_PRESSED 2 + +#define TICK_TIME_US 10000 +#define BUTTON_LONG_DELAY_MS 1000 +#define BUTTON_DEBOUNCE_DELAY_MS 50 + +#define TICK_TIME_MS (TICK_TIME_US / 1000) +#define BUTTON_LONG_DELAY (BUTTON_LONG_DELAY_MS / TICK_TIME_MS) +#define BUTTON_DEBOUNCE_DELAY (BUTTON_DEBOUNCE_DELAY_MS / TICK_TIME_MS) + +class SmcButton{ + + private: + uint8_t pin; + uint16_t counter=0; + uint8_t state = BUTTON_STATE_RELEASED; + void (*onClick)() = NULL; + void (*onLongPress)() = NULL; + + public: + + SmcButton(uint8_t pinNumber){ + pin = pinNumber; + pinMode(pin, INPUT_PULLUP); + } + + void tick(){ + uint8_t pinValue = digitalRead(pin); + + if (counter > 0){ + counter--; + } + + if (counter > BUTTON_LONG_DELAY-BUTTON_DEBOUNCE_DELAY){ + return; + } + else if (counter == 0 && pinValue==0 && state == BUTTON_STATE_CLICKED){ + //Long press + state = BUTTON_STATE_LONG_PRESSED; + counter = BUTTON_LONG_DELAY; + if (onLongPress!=NULL){ + onLongPress(); + } + } + else{ + //Click + if (pinValue==0 && state == BUTTON_STATE_RELEASED){ + state = BUTTON_STATE_CLICKED; + counter = BUTTON_LONG_DELAY; + } + //Release + else if (pinValue == 1 && state == BUTTON_STATE_CLICKED){ + state = BUTTON_STATE_RELEASED; + counter = BUTTON_LONG_DELAY; + if (onClick!=NULL){ + onClick(); + } + } + else if (pinValue == 1 && state == BUTTON_STATE_LONG_PRESSED){ + state = BUTTON_STATE_RELEASED; + counter = BUTTON_LONG_DELAY; + } + } + } + + void attachClick(void (*callback)()){ + onClick = callback; + } + + void attachDuringLongPress(void (*callback)()){ + onLongPress = callback; + } +}; \ No newline at end of file diff --git a/x16-smc.ino b/x16-smc.ino index 6c7f6b6..86d85cb 100644 --- a/x16-smc.ino +++ b/x16-smc.ino @@ -5,8 +5,9 @@ // Joe Burks //#define ENABLE_NMI_BUT +//#define KBDBUF_FULL_DBG -#include +#include "smc_button.h" #include #include "dbg_supp.h" #include "smc_pins.h" @@ -32,17 +33,19 @@ //Reset & NMI Lines #define RESB_HOLDTIME_MS 500 #define NMI_HOLDTIME_MS 300 +#define RESET_ACTIVE LOW +#define RESET_INACTIVE HIGH //I2C Pins #define I2C_ADDR 0x42 // I2C Device ID -OneButton POW_BUT(POWER_BUTTON_PIN, true, true); -OneButton RES_BUT(RESET_BUTTON_PIN, true, true); +SmcButton POW_BUT(POWER_BUTTON_PIN); +SmcButton RES_BUT(RESET_BUTTON_PIN); #if defined(ENABLE_NMI_BUT) - OneButton NMI_BUT(NMI_BUTTON_PIN, true, true); + SmcButton NMI_BUT(NMI_BUTTON_PIN); #endif -PS2Port Keyboard; +PS2KeyboardPort Keyboard; PS2Port Mouse; void keyboardClockIrq() { @@ -52,17 +55,19 @@ void keyboardClockIrq() { void mouseClockIrq() { Mouse.onFallingClock(); } -void MouseInitTick(); bool SYSTEM_POWERED = 0; // default state - Powered off int I2C_Data[3] = {0, 0, 0}; bool I2C_Active = false; char echo_byte = 0; -void setup() { +void setup() { #if defined(USE_SERIAL_DEBUG) Serial.begin(SERIAL_BPS); #endif +#if defined(KBDBUF_FULL_DBG) + Serial.begin(SERIAL_BPS); +#endif //Setup Timer 1 interrupt to run every 25 us >>> cli(); @@ -126,12 +131,10 @@ void setup() { analogWrite(ACT_LED, 0); pinMode(RESB_PIN,OUTPUT); - digitalWrite(RESB_PIN,LOW); // Hold Reset on statup + digitalWrite(RESB_PIN, RESET_ACTIVE); // Hold Reset on startup -#if defined(ENABLE_NMI_BUT) pinMode(NMIB_PIN,OUTPUT); digitalWrite(NMIB_PIN,HIGH); -#endif // PS/2 host init Keyboard.begin(keyboardClockIrq); @@ -144,7 +147,8 @@ void loop() { #if defined(ENABLE_NMI_BUT) NMI_BUT.tick(); #endif - MouseInitTick(); + MouseTick(); + KeyboardTick(); if ((SYSTEM_POWERED == 1) && (!digitalRead(PWR_OK))) { @@ -153,6 +157,16 @@ void loop() { //error handling? } + if (Keyboard.getResetRequest()) { + Reset_Button_Hold(); + Keyboard.ackResetRequest(); + } + + if (Keyboard.getNMIRequest()) { + Reset_Button_Press(); + Keyboard.ackNMIRequest(); + } + // DEBUG: turn activity LED on if there are keys in the keybuffer delay(10); // Short Delay, required by OneButton if code is short } @@ -174,7 +188,7 @@ void I2C_Receive(int) { int ct=0; while (Wire.available()) { - if (ct<3) { // read first two bytes only + if (ct<3) { // read first three bytes only byte c = Wire.read(); I2C_Data[ct] = c; ct++; @@ -183,7 +197,9 @@ void I2C_Receive(int) { Wire.read(); // eat extra data, should not be sent } } - if (ct == 2 || ct == 3) { + + if ((ct == 2 && I2C_Data[0] != 0x1a) || (ct == 3 && I2C_Data[0] == 0x1a)) { + // Offset 0x1a (Send two-byte command) requires three bytes, the other offsets require two bytes I2C_Process(); // process received cmd } } @@ -246,7 +262,7 @@ void I2C_Send() { // DBG_PRINTLN("I2C_Send"); int nextKey = 0; if (I2C_Data[0] == 0x7) { // 1st Byte : Byte 7 - Keyboard: read next keycode - if (Keyboard.available()) { + if (kbd_init_state == KBD_READY && Keyboard.available()) { nextKey = Keyboard.next(); Wire.write(nextKey); } @@ -289,11 +305,12 @@ void Reset_Button_Hold() { Keyboard.flush(); Mouse.reset(); if (SYSTEM_POWERED == 1) { // Ignore unless Powered On - digitalWrite(RESB_PIN,LOW); // Press RESET + digitalWrite(RESB_PIN, RESET_ACTIVE); // Press RESET delay(RESB_HOLDTIME_MS); - digitalWrite(RESB_PIN,HIGH); + digitalWrite(RESB_PIN, RESET_INACTIVE); analogWrite(ACT_LED, 0); - mouse_init_state = 0; + mouse_init_state = MOUSE_INIT_STATE::START_RESET; + kbd_init_state = MOUSE_INIT_STATE::START_RESET; } } @@ -308,7 +325,7 @@ void Reset_Button_Press() { void PowerOffSeq() { digitalWrite(PWR_ON, HIGH); // Turn off supply SYSTEM_POWERED=0; // Global Power state Off - digitalWrite(RESB_PIN,LOW); + digitalWrite(RESB_PIN, RESET_ACTIVE); delay(RESB_HOLDTIME_MS); // Mostly here to add some delay between presses } @@ -324,9 +341,9 @@ void PowerOnSeq() { // insert error handler, flash activity light & Halt? IE, require hard power off before continue? } else { - SYSTEM_POWERED=1; // Global Power state On delay(RESB_HOLDTIME_MS); // Allow system to stabilize - digitalWrite(RESB_PIN,HIGH); // Release Reset + SYSTEM_POWERED=1; // Global Power state On + digitalWrite(RESB_PIN, RESET_INACTIVE); // Release Reset } }