diff --git a/lib/qdec/include/qdec.h b/lib/qdec/include/qdec.h new file mode 100644 index 00000000..9f278b2f --- /dev/null +++ b/lib/qdec/include/qdec.h @@ -0,0 +1,60 @@ +enum QDECODER_EVENT { + QDECODER_EVENT_NONE = 0x00u, + QDECODER_EVENT_CW = 0x80u, + QDECODER_EVENT_CCW = 0x40u, +}; + +// state is internal... needs never be exposed to callers +enum QDECODER_STATE { + QDECODER_STATE_START = 0x00u, + QDECODER_STATE_CW_A = 0x01u, + QDECODER_STATE_CW_B = 0x02u, + QDECODER_STATE_CW_C = 0x03u, + QDECODER_STATE_MID = 0x04u, + QDECODER_STATE_CCW_A = 0x05u, + QDECODER_STATE_CCW_B = 0x06u, + QDECODER_STATE_CCW_C = 0x07u, +}; + +const uint8_t QDECODER_EVENT_BITMASK = 0xC0; // events are the most significant two bits +const uint8_t QDECODER_STATE_BITMASK = 0x07; // states are in the least significant three bits + +const uint8_t HALF_STEP_STATE_TRANSITIONS[8][4] = { + // 0 = start + {QDECODER_STATE_MID, QDECODER_STATE_CW_A, QDECODER_STATE_CCW_A, QDECODER_STATE_START }, + // 1 = CW_A + {QDECODER_EVENT_CW | + QDECODER_STATE_MID, QDECODER_STATE_CW_A, QDECODER_STATE_START, QDECODER_STATE_START }, + // 2 = CW_B (unused in half-step mode) + {QDECODER_STATE_START, QDECODER_STATE_START, QDECODER_STATE_START, QDECODER_STATE_START }, + // 3 = CW_C + {QDECODER_STATE_MID, QDECODER_STATE_MID, QDECODER_STATE_CW_C, QDECODER_STATE_START | QDECODER_EVENT_CW }, + // 4 = mid + {QDECODER_STATE_MID, QDECODER_STATE_CCW_C, QDECODER_STATE_CW_C, QDECODER_STATE_START }, + // 5 = CCW_A + {QDECODER_EVENT_CCW | + QDECODER_STATE_MID, QDECODER_STATE_START, QDECODER_STATE_CCW_A, QDECODER_STATE_START }, + // 6 = CCW_B (unused in half-step mode) + {QDECODER_STATE_START, QDECODER_STATE_START, QDECODER_STATE_START, QDECODER_STATE_START }, + // 7 = CCW_C + {QDECODER_STATE_MID, QDECODER_STATE_CCW_C, QDECODER_STATE_MID, QDECODER_STATE_START | QDECODER_EVENT_CCW}, +}; + +const uint8_t FULL_STEP_STATE_TRANSITIONS[8][4] = { + // 0 = start + {QDECODER_STATE_START, QDECODER_STATE_CW_A, QDECODER_STATE_CCW_A, QDECODER_STATE_START }, + // 1 = CW_A + {QDECODER_STATE_CW_B, QDECODER_STATE_CW_A, QDECODER_STATE_START, QDECODER_STATE_START }, + // 2 = CW_B + {QDECODER_STATE_CW_B, QDECODER_STATE_CW_A, QDECODER_STATE_CW_C, QDECODER_STATE_START }, + // 3 = CW_C + {QDECODER_STATE_CW_B, QDECODER_STATE_START, QDECODER_STATE_CW_C, QDECODER_STATE_START | QDECODER_EVENT_CW }, + // 4 = mid (unused in full step mode ...) + {QDECODER_STATE_START, QDECODER_STATE_START, QDECODER_STATE_START, QDECODER_STATE_START }, + // 5 = CCW_A + {QDECODER_STATE_CCW_B, QDECODER_STATE_START, QDECODER_STATE_CCW_A, QDECODER_STATE_START }, + // 6 = CCW_B + {QDECODER_STATE_CCW_B, QDECODER_STATE_CCW_C, QDECODER_STATE_CCW_A, QDECODER_STATE_START }, + // 7 = CCW_C + {QDECODER_STATE_CCW_B, QDECODER_STATE_CCW_C, QDECODER_STATE_START, QDECODER_STATE_START | QDECODER_EVENT_CCW }, +}; diff --git a/lib/qdec/license.txt b/lib/qdec/license.txt new file mode 100644 index 00000000..77ee82b0 --- /dev/null +++ b/lib/qdec/license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 SimpleHacks ("Simple hacks for a simple life") + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/qdec/readme b/lib/qdec/readme new file mode 100644 index 00000000..0c48d880 --- /dev/null +++ b/lib/qdec/readme @@ -0,0 +1,2 @@ +This is a plain C header library lightly adapted from the SimpleHacks QDEC library. +Find the original source here: https://github.com/SimpleHacks/QDEC diff --git a/meson.build b/meson.build index b4819dfb..4d81a80d 100644 --- a/meson.build +++ b/meson.build @@ -55,8 +55,12 @@ minmea_src = ['lib/minmea/minmea.c'] minmea_inc = ['lib/minmea/include'] +## QDEC, a very simple, header only, quadrature decoding library + +qdec_inc = ['lib/qdec/include'] + src = openrtx_src + minmea_src -inc = openrtx_inc + rtos_inc + minmea_inc +inc = openrtx_inc + rtos_inc + minmea_inc + qdec_inc ## ## Definitions diff --git a/openrtx/include/interfaces/platform.h b/openrtx/include/interfaces/platform.h index b67bcfd1..7df55a4f 100644 --- a/openrtx/include/interfaces/platform.h +++ b/openrtx/include/interfaces/platform.h @@ -101,7 +101,7 @@ float platform_getVolumeLevel(); /** * This function reads and returns the current channel selector level. */ -uint8_t platform_getChSelector(); +int8_t platform_getChSelector(); /** * This function reads and returns the current PTT status. diff --git a/openrtx/include/settings.h b/openrtx/include/settings.h index bdc3c54f..df11bac3 100644 --- a/openrtx/include/settings.h +++ b/openrtx/include/settings.h @@ -23,7 +23,7 @@ typedef struct { - uint8_t valid[6]; // Should contain "OPNRTX" in a valid settings_t + uint8_t valid[7]; // Should contain "OPNRTX" in a valid settings_t uint8_t brightness; uint8_t contrast; int8_t utc_timezone; diff --git a/platform/drivers/keyboard/keyboard_MDx.c b/platform/drivers/keyboard/keyboard_MDx.c index 104d96b2..3ca52263 100644 --- a/platform/drivers/keyboard/keyboard_MDx.c +++ b/platform/drivers/keyboard/keyboard_MDx.c @@ -53,14 +53,22 @@ keyboard_t kbd_getKeys() keyboard_t keys = 0; /* Use absolute position knob to emulate left and right buttons */ - static uint8_t old_pos = 0; - uint8_t new_pos = platform_getChSelector(); + static int8_t old_pos = 0; + int8_t new_pos = platform_getChSelector(); if (old_pos != new_pos) { - if (new_pos < old_pos) + int8_t diff = old_pos - new_pos; + if (diff < 0) keys |= KEY_LEFT; - else + else if (diff > 0) keys |= KEY_RIGHT; + else + { + if (old_pos < 0) + keys |= KEY_LEFT; + else + keys |= KEY_RIGHT; + } old_pos = new_pos; } diff --git a/platform/targets/MD-UV3x0/platform.c b/platform/targets/MD-UV3x0/platform.c index 9cbd7d3b..7d7eb1b4 100644 --- a/platform/targets/MD-UV3x0/platform.c +++ b/platform/targets/MD-UV3x0/platform.c @@ -25,9 +25,11 @@ #include #include #include +#include mduv3x0Calib_t calibration; hwInfo_t hwInfo; +static int8_t knob_pos = 0; #ifdef ENABLE_BKLIGHT_DIMMING void _Z29TIM1_TRG_COM_TIM11_IRQHandlerv() @@ -46,6 +48,42 @@ void _Z29TIM1_TRG_COM_TIM11_IRQHandlerv() } #endif +/* + * Note that this interrupt handler currently assumes only the encoder will + * ever cause this interrupt to fire + */ +void _Z20EXTI15_10_IRQHandlerv() +{ + /* State storage */ + static uint8_t last_state = 0; + + /* Read curent pin state */ + uint8_t pin_state = gpio_readPin(CH_SELECTOR_1)<<1 | gpio_readPin(CH_SELECTOR_0); + /* Look up next state */ + uint8_t next_state = HALF_STEP_STATE_TRANSITIONS[last_state][pin_state]; + /* update state for next call */ + last_state = next_state & QDECODER_STATE_BITMASK; + + /* Mask out events to switch on */ + uint8_t event = next_state & QDECODER_EVENT_BITMASK; + + /* Update file global knob_pos variable */ + switch (event) + { + case QDECODER_EVENT_CW: + knob_pos++; + break; + case QDECODER_EVENT_CCW: + knob_pos--; + break; + default: + break; + } + + /* Clear pin change flags */ + EXTI->PR = EXTI_PR_PR11 | EXTI_PR_PR14; +} + void platform_init() { /* Configure GPIOs */ @@ -55,8 +93,19 @@ void platform_init() gpio_setMode(LCD_BKLIGHT, OUTPUT); gpio_clearPin(LCD_BKLIGHT); - gpio_setMode(CH_SELECTOR_0, INPUT); - gpio_setMode(CH_SELECTOR_1, INPUT); + gpio_setMode(CH_SELECTOR_0, INPUT_PULL_UP); + gpio_setMode(CH_SELECTOR_1, INPUT_PULL_UP); + + EXTI->IMR |= EXTI_IMR_MR11 | EXTI_IMR_MR14; + EXTI->RTSR |= EXTI_RTSR_TR11 | EXTI_RTSR_TR14; + EXTI->FTSR |= EXTI_FTSR_TR11 | EXTI_FTSR_TR14; + + SYSCFG->EXTICR[2] |= SYSCFG_EXTICR3_EXTI11_PB; + SYSCFG->EXTICR[3] |= SYSCFG_EXTICR4_EXTI14_PE; + + NVIC_ClearPendingIRQ(EXTI15_10_IRQn); + NVIC_SetPriority(EXTI15_10_IRQn, 15); + NVIC_EnableIRQ(EXTI15_10_IRQn); gpio_setMode(PTT_SW, INPUT_PULL_UP); @@ -150,13 +199,13 @@ float platform_getVolumeLevel() return adc1_getMeasurement(1); } -uint8_t platform_getChSelector() +int8_t platform_getChSelector() { - static const uint8_t rsPositions[] = { 1, 4, 2, 3}; - int pos = gpio_readPin(CH_SELECTOR_0) - | (gpio_readPin(CH_SELECTOR_1) << 1); - - return rsPositions[pos]; + /* + * The knob_pos variable is set in the EXTI15_10 interrupt handler + * this is safe because interrupt nesting is not allowed. + */ + return knob_pos; } bool platform_getPttStatus()