Add support for encoder knob on MD-UV380
Added: qdec, a header only quadrature encoder library Added: EXTI15_10_IRQHandler to handle the encoder knob Changed: platform_init() for the MD-UV380 now configures the EXTI15_10 interrupt. Changed: platform_getChSelector now returns signed int8 Changed: size of settings_t.valid went from 6 to 7 Without this change, pressing the macro button crashes the radio, I haven't investigated and I don't remember how I found that solution. Changed: meson.build includes the qdec library
This commit is contained in:
parent
b6eab0fde7
commit
e5c5ee90f2
|
|
@ -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 },
|
||||||
|
};
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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
|
||||||
|
|
@ -55,8 +55,12 @@ minmea_src = ['lib/minmea/minmea.c']
|
||||||
|
|
||||||
minmea_inc = ['lib/minmea/include']
|
minmea_inc = ['lib/minmea/include']
|
||||||
|
|
||||||
|
## QDEC, a very simple, header only, quadrature decoding library
|
||||||
|
|
||||||
|
qdec_inc = ['lib/qdec/include']
|
||||||
|
|
||||||
src = openrtx_src + minmea_src
|
src = openrtx_src + minmea_src
|
||||||
inc = openrtx_inc + rtos_inc + minmea_inc
|
inc = openrtx_inc + rtos_inc + minmea_inc + qdec_inc
|
||||||
|
|
||||||
##
|
##
|
||||||
## Definitions
|
## Definitions
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ float platform_getVolumeLevel();
|
||||||
/**
|
/**
|
||||||
* This function reads and returns the current channel selector level.
|
* 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.
|
* This function reads and returns the current PTT status.
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
typedef struct
|
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 brightness;
|
||||||
uint8_t contrast;
|
uint8_t contrast;
|
||||||
int8_t utc_timezone;
|
int8_t utc_timezone;
|
||||||
|
|
|
||||||
|
|
@ -53,14 +53,22 @@ keyboard_t kbd_getKeys()
|
||||||
keyboard_t keys = 0;
|
keyboard_t keys = 0;
|
||||||
|
|
||||||
/* Use absolute position knob to emulate left and right buttons */
|
/* Use absolute position knob to emulate left and right buttons */
|
||||||
static uint8_t old_pos = 0;
|
static int8_t old_pos = 0;
|
||||||
uint8_t new_pos = platform_getChSelector();
|
int8_t new_pos = platform_getChSelector();
|
||||||
if (old_pos != new_pos)
|
if (old_pos != new_pos)
|
||||||
{
|
{
|
||||||
if (new_pos < old_pos)
|
int8_t diff = old_pos - new_pos;
|
||||||
|
if (diff < 0)
|
||||||
keys |= KEY_LEFT;
|
keys |= KEY_LEFT;
|
||||||
else
|
else if (diff > 0)
|
||||||
keys |= KEY_RIGHT;
|
keys |= KEY_RIGHT;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (old_pos < 0)
|
||||||
|
keys |= KEY_LEFT;
|
||||||
|
else
|
||||||
|
keys |= KEY_RIGHT;
|
||||||
|
}
|
||||||
old_pos = new_pos;
|
old_pos = new_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,11 @@
|
||||||
#include <calibInfo_MDx.h>
|
#include <calibInfo_MDx.h>
|
||||||
#include <interfaces/nvmem.h>
|
#include <interfaces/nvmem.h>
|
||||||
#include <interfaces/rtc.h>
|
#include <interfaces/rtc.h>
|
||||||
|
#include <qdec.h>
|
||||||
|
|
||||||
mduv3x0Calib_t calibration;
|
mduv3x0Calib_t calibration;
|
||||||
hwInfo_t hwInfo;
|
hwInfo_t hwInfo;
|
||||||
|
static int8_t knob_pos = 0;
|
||||||
|
|
||||||
#ifdef ENABLE_BKLIGHT_DIMMING
|
#ifdef ENABLE_BKLIGHT_DIMMING
|
||||||
void _Z29TIM1_TRG_COM_TIM11_IRQHandlerv()
|
void _Z29TIM1_TRG_COM_TIM11_IRQHandlerv()
|
||||||
|
|
@ -46,6 +48,42 @@ void _Z29TIM1_TRG_COM_TIM11_IRQHandlerv()
|
||||||
}
|
}
|
||||||
#endif
|
#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()
|
void platform_init()
|
||||||
{
|
{
|
||||||
/* Configure GPIOs */
|
/* Configure GPIOs */
|
||||||
|
|
@ -55,8 +93,19 @@ void platform_init()
|
||||||
gpio_setMode(LCD_BKLIGHT, OUTPUT);
|
gpio_setMode(LCD_BKLIGHT, OUTPUT);
|
||||||
gpio_clearPin(LCD_BKLIGHT);
|
gpio_clearPin(LCD_BKLIGHT);
|
||||||
|
|
||||||
gpio_setMode(CH_SELECTOR_0, INPUT);
|
gpio_setMode(CH_SELECTOR_0, INPUT_PULL_UP);
|
||||||
gpio_setMode(CH_SELECTOR_1, INPUT);
|
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);
|
gpio_setMode(PTT_SW, INPUT_PULL_UP);
|
||||||
|
|
||||||
|
|
@ -150,13 +199,13 @@ float platform_getVolumeLevel()
|
||||||
return adc1_getMeasurement(1);
|
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)
|
* The knob_pos variable is set in the EXTI15_10 interrupt handler
|
||||||
| (gpio_readPin(CH_SELECTOR_1) << 1);
|
* this is safe because interrupt nesting is not allowed.
|
||||||
|
*/
|
||||||
return rsPositions[pos];
|
return knob_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool platform_getPttStatus()
|
bool platform_getPttStatus()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue