From 8c36d65e6367189e75d70a196b389e811fb53e14 Mon Sep 17 00:00:00 2001 From: Vadim Kaushan Date: Fri, 2 Oct 2020 09:44:05 +0300 Subject: [PATCH] Input handling and debouncing (#148) * Add input driver and definitions for target_f2 * Add input_dump example * Invert charge input * Fix back and left button configuration * remove input debug * input testing case * move header * lint code Co-authored-by: aanper --- applications/examples/input_dump.c | 29 ++++++++ applications/input/input.c | 111 +++++++++++++++++++++++++++++ applications/input/input.h | 40 +++++++++++ applications/startup.h | 10 +++ core/flipper.h | 1 + target_f2/Inc/input_priv.h | 29 ++++++++ target_f2/Inc/main.h | 1 + target_f2/Inc/stm32l4xx_it.h | 1 + target_f2/Makefile | 15 +++- target_f2/Src/main.c | 7 +- target_f2/Src/stm32l4xx_it.c | 13 ++++ target_f2/flipperzero_f2.ioc | 7 +- wiki/Testing.md | 29 +++++++- 13 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 applications/examples/input_dump.c create mode 100644 applications/input/input.c create mode 100644 applications/input/input.h create mode 100644 target_f2/Inc/input_priv.h diff --git a/applications/examples/input_dump.c b/applications/examples/input_dump.c new file mode 100644 index 00000000..c8f0bed9 --- /dev/null +++ b/applications/examples/input_dump.c @@ -0,0 +1,29 @@ +#include "flipper.h" +#include + +static void state_cb(const void* value, size_t size, void* ctx) { + const InputState* state = value; + + printf("state: %02x\n", *state); +} + +static void event_cb(const void* value, size_t size, void* ctx) { + const InputEvent* event = value; + + printf("event: %02x %s\n", event->input, event->state ? "pressed" : "released"); +} + +void application_input_dump(void* p) { + // TODO try open record and retry on timeout (needs FURI behaviour change) + delay(1000); + + // open record + FuriRecordSubscriber* state_record = + furi_open("input_state", false, false, state_cb, NULL, NULL); + FuriRecordSubscriber* event_record = + furi_open("input_events", false, false, event_cb, NULL, NULL); + + for(;;) { + delay(100); + } +} diff --git a/applications/input/input.c b/applications/input/input.c new file mode 100644 index 00000000..8d0b1b46 --- /dev/null +++ b/applications/input/input.c @@ -0,0 +1,111 @@ +#include +#include +#include +#include + +static volatile bool initialized = false; +static SemaphoreHandle_t event; +static InputState input_state = { + false, +}; + +void input_task(void* p) { + uint32_t state_bits = 0; + StaticSemaphore_t event_semaphore; + uint8_t debounce_counters[INPUT_COUNT]; + + event = xSemaphoreCreateCountingStatic(1, 0, &event_semaphore); + + if(!furi_create("input_state", (void*)&input_state, sizeof(input_state))) { + printf("[input_task] cannot create the input_state record\n"); + furiac_exit(NULL); + } + + FuriRecordSubscriber* input_state_record = + furi_open("input_state", false, false, NULL, NULL, NULL); + if(input_state_record == NULL) { + printf("[input_task] cannot open the input_state record\n"); + furiac_exit(NULL); + } + + if(!furi_create("input_events", NULL, 0)) { + printf("[input_task] cannot create the input_events record\n"); + furiac_exit(NULL); + } + + FuriRecordSubscriber* input_events_record = + furi_open("input_events", false, false, NULL, NULL, NULL); + if(input_events_record == NULL) { + printf("[input_task] cannot open the input_events record\n"); + furiac_exit(NULL); + } + + initialized = true; + + // Force state update + for(uint32_t i = 0; i < INPUT_COUNT; i++) { + debounce_counters[i] = DEBOUNCE_TICKS / 2; + } + + for(;;) { + bool changed = false; + for(uint32_t i = 0; i < INPUT_COUNT; i++) { + bool input_state = app_gpio_read(input_gpio[i]) ^ input_invert[i]; + if(input_state) { + if(debounce_counters[i] < DEBOUNCE_TICKS) { + debounce_counters[i] += 1; + changed = true; + } + } else { + if(debounce_counters[i] > 0) { + debounce_counters[i] -= 1; + changed = true; + } + } + } + + if(!changed) { + uint32_t new_state_bits = 0; + for(uint32_t i = 0; i < INPUT_COUNT; i++) { + if(debounce_counters[i] == DEBOUNCE_TICKS) { + new_state_bits |= (1 << i); + } + } + uint32_t changed_bits = new_state_bits ^ state_bits; + + if(changed_bits != 0) { + // printf("[input] %02x -> %02x\n", state_bits, new_state_bits); + InputState new_state = _BITS2STATE(new_state_bits); + furi_write(input_state_record, &new_state, sizeof(new_state)); + + state_bits = new_state_bits; + + for(uint32_t i = 0; i < INPUT_COUNT; i++) { + if((changed_bits & (1 << i)) != 0) { + bool state = (new_state_bits & (1 << i)) != 0; + InputEvent event = {i, state}; + furi_write(input_events_record, &event, sizeof(event)); + } + } + } + + // Sleep: wait for event + xSemaphoreTake(event, portMAX_DELAY); + } else { + osDelay(1); + } + } +} + +void HAL_GPIO_EXTI_Callback(uint16_t pin) { + if(!initialized) return; + + BaseType_t task_woken = pdFALSE; + + // Ignore the result, as we do not care about repeated event during event processing. + xSemaphoreGiveFromISR(event, &task_woken); + + if(task_woken) { + portYIELD_FROM_ISR(task_woken); + } +} diff --git a/applications/input/input.h b/applications/input/input.h new file mode 100644 index 00000000..8e555c41 --- /dev/null +++ b/applications/input/input.h @@ -0,0 +1,40 @@ +#ifndef __INPUT_H +#define __INPUT_H + +#include + +#define INPUT_COUNT 7 + +typedef enum { + InputUp = 0, + InputDown, + InputRight, + InputLeft, + InputOk, + InputBack, + InputCharging, +} Input; + +typedef struct { + Input input; + bool state; +} InputEvent; + +typedef struct { + bool up : 1; + bool down : 1; + bool right : 1; + bool left : 1; + bool ok : 1; + bool back : 1; + bool charging : 1; +} __attribute__((packed)) InputState; + +#define _BITS2STATE(bits) \ + { \ + .up = (((bits)&0x01) != 0), .down = (((bits)&0x02) != 0), .right = (((bits)&0x04) != 0), \ + .left = (((bits)&0x08) != 0), .ok = (((bits)&0x10) != 0), .back = (((bits)&0x20) != 0), \ + .charging = (((bits)&0x40) != 0) \ + } + +#endif /* __INPUT_H */ diff --git a/applications/startup.h b/applications/startup.h index aa1a0eba..9ff0be7e 100644 --- a/applications/startup.h +++ b/applications/startup.h @@ -15,11 +15,14 @@ void application_blink(void* p); void application_uart_write(void* p); void application_ipc_display(void* p); void application_ipc_widget(void* p); +void application_input_dump(void* p); void display_u8g2(void* p); void u8g2_example(void* p); +void input_task(void* p); + void coreglitch_demo_0(void* p); const FlipperStartupApp FLIPPER_STARTUP[] = { @@ -28,6 +31,10 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { {.app = u8g2_example, .name = "u8g2_example"}, #endif +#ifdef USE_INPUT + {.app = input_task, .name = "input_task"}, +#endif + // {.app = coreglitch_demo_0, .name = "coreglitch_demo_0"}, #ifdef TEST @@ -44,4 +51,7 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { {.app = application_ipc_display, .name = "ipc display"}, {.app = application_ipc_widget, .name = "ipc widget"}, #endif +#ifdef EXAMPLE_INPUT_DUMP + {.app = application_input_dump, .name = "input dump"}, +#endif }; diff --git a/core/flipper.h b/core/flipper.h index b14210b2..a4b0e3f5 100644 --- a/core/flipper.h +++ b/core/flipper.h @@ -9,6 +9,7 @@ extern "C" { #include "cmsis_os.h" #include "furi.h" #include "log.h" +#include "input/input.h" #ifdef __cplusplus } diff --git a/target_f2/Inc/input_priv.h b/target_f2/Inc/input_priv.h new file mode 100644 index 00000000..90fd495e --- /dev/null +++ b/target_f2/Inc/input_priv.h @@ -0,0 +1,29 @@ +#ifndef __INPUT_PRIV_H +#define __INPUT_PRIV_H + +#include "main.h" +#include "flipper_hal.h" + +#define DEBOUNCE_TICKS 10 + +const GpioPin input_gpio[] = { + {BUTTON_UP_GPIO_Port, BUTTON_UP_Pin}, + {BUTTON_DOWN_GPIO_Port, BUTTON_DOWN_Pin}, + {BUTTON_RIGHT_GPIO_Port, BUTTON_RIGHT_Pin}, + {BUTTON_LEFT_GPIO_Port, BUTTON_LEFT_Pin}, + {BUTTON_OK_GPIO_Port, BUTTON_OK_Pin}, + {BUTTON_BACK_GPIO_Port, BUTTON_BACK_Pin}, + {CHRG_GPIO_Port, CHRG_Pin} +}; + +const bool input_invert[] = { + false, // {BUTTON_UP_GPIO_Port, BUTTON_UP_Pin}, + false, // {BUTTON_DOWN_GPIO_Port, BUTTON_DOWN_Pin}, + false, // {BUTTON_RIGHT_GPIO_Port, BUTTON_RIGHT_Pin}, + false, // {BUTTON_LEFT_GPIO_Port, BUTTON_LEFT_Pin}, + false, // {BUTTON_OK_GPIO_Port, BUTTON_OK_Pin}, + false, // {BUTTON_BACK_GPIO_Port, BUTTON_BACK_Pin}, + true, // {CHRG_GPIO_Port, CHRG_Pin} +}; + +#endif /* __INPUT_PRIV_H */ diff --git a/target_f2/Inc/main.h b/target_f2/Inc/main.h index 735552a5..f76c3637 100644 --- a/target_f2/Inc/main.h +++ b/target_f2/Inc/main.h @@ -66,6 +66,7 @@ void register_tim8_callback_ch2(void (*callback)(uint16_t ccr, TimerEvent tim_ev /* Private defines -----------------------------------------------------------*/ #define BUTTON_BACK_Pin GPIO_PIN_13 #define BUTTON_BACK_GPIO_Port GPIOC +#define BUTTON_BACK_EXTI_IRQn EXTI15_10_IRQn #define CHRG_Pin GPIO_PIN_2 #define CHRG_GPIO_Port GPIOC #define CHRG_EXTI_IRQn EXTI2_IRQn diff --git a/target_f2/Inc/stm32l4xx_it.h b/target_f2/Inc/stm32l4xx_it.h index 6f944995..214b2fc5 100644 --- a/target_f2/Inc/stm32l4xx_it.h +++ b/target_f2/Inc/stm32l4xx_it.h @@ -59,6 +59,7 @@ void EXTI1_IRQHandler(void); void EXTI2_IRQHandler(void); void EXTI4_IRQHandler(void); void EXTI9_5_IRQHandler(void); +void EXTI15_10_IRQHandler(void); void TIM8_CC_IRQHandler(void); void OTG_FS_IRQHandler(void); /* USER CODE BEGIN EFP */ diff --git a/target_f2/Makefile b/target_f2/Makefile index a60e6813..56802412 100644 --- a/target_f2/Makefile +++ b/target_f2/Makefile @@ -103,7 +103,8 @@ C_DEFS += \ -DUSE_HAL_DRIVER \ -DSTM32L476xx \ -DBUTON_INVERT=false \ --DDEBUG_UART=huart1 +-DDEBUG_UART=huart1 \ +-DUSE_INPUT ASM_SOURCES += \ startup_stm32l476xx.s @@ -139,7 +140,8 @@ C_SOURCES += ../lib/u8g2/u8x8_d_st7565.c \ # System applications -C_SOURCES += ../applications/display-u8g2/display-u8g2.c +C_SOURCES += ../applications/display-u8g2/display-u8g2.c \ +../applications/input/input.c \ # Examples @@ -170,6 +172,11 @@ C_SOURCES += ../applications/examples/ipc.c C_DEFS += -DEXAMPLE_IPC endif +ifeq ($(EXAMPLE_INPUT_DUMP), 1) +C_SOURCES += ../applications/examples/input_dump.c +C_DEFS += -DEXAMPLE_INPUT_DUMP +endif + # User application C_SOURCES += ../applications/coreglitch_demo_0/coreglitch_demo_0.c @@ -303,6 +310,10 @@ example_ipc: EXAMPLE_IPC=1 make rm $(BUILD_DIR)/app.o +example_input_dump: + EXAMPLE_INPUT_DUMP=1 make + rm $(BUILD_DIR)/app.o + test: TEST=1 make rm $(BUILD_DIR)/app.o diff --git a/target_f2/Src/main.c b/target_f2/Src/main.c index 2d81a510..3c016dbf 100644 --- a/target_f2/Src/main.c +++ b/target_f2/Src/main.c @@ -598,7 +598,7 @@ static void MX_GPIO_Init(void) { /*Configure GPIO pin : BUTTON_BACK_Pin */ GPIO_InitStruct.Pin = BUTTON_BACK_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; + GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING; GPIO_InitStruct.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(BUTTON_BACK_GPIO_Port, &GPIO_InitStruct); @@ -681,7 +681,7 @@ static void MX_GPIO_Init(void) { /*Configure GPIO pin : BUTTON_LEFT_Pin */ GPIO_InitStruct.Pin = BUTTON_LEFT_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; + GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING; GPIO_InitStruct.Pull = GPIO_PULLDOWN; HAL_GPIO_Init(BUTTON_LEFT_GPIO_Port, &GPIO_InitStruct); @@ -706,6 +706,9 @@ static void MX_GPIO_Init(void) { HAL_NVIC_SetPriority(EXTI9_5_IRQn, 5, 0); HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); + + HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); } /* USER CODE BEGIN 4 */ diff --git a/target_f2/Src/stm32l4xx_it.c b/target_f2/Src/stm32l4xx_it.c index 6f86a1a7..86e0782e 100644 --- a/target_f2/Src/stm32l4xx_it.c +++ b/target_f2/Src/stm32l4xx_it.c @@ -237,6 +237,19 @@ void EXTI9_5_IRQHandler(void) { /* USER CODE END EXTI9_5_IRQn 1 */ } +/** + * @brief This function handles EXTI line[15:10] interrupts. + */ +void EXTI15_10_IRQHandler(void) { + /* USER CODE BEGIN EXTI15_10_IRQn 0 */ + + /* USER CODE END EXTI15_10_IRQn 0 */ + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); + /* USER CODE BEGIN EXTI15_10_IRQn 1 */ + + /* USER CODE END EXTI15_10_IRQn 1 */ +} + void (*tim8_callback_ch2)(uint16_t ccr, TimerEvent tim_event); void register_tim8_callback_ch2(void (*callback)(uint16_t ccr, TimerEvent tim_event)) { diff --git a/target_f2/flipperzero_f2.ioc b/target_f2/flipperzero_f2.ioc index 8b165dca..ca781af6 100644 --- a/target_f2/flipperzero_f2.ioc +++ b/target_f2/flipperzero_f2.ioc @@ -100,6 +100,7 @@ MxDb.Version=DB.5.0.40 NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.EXTI0_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true +NVIC.EXTI15_10_IRQn=true\:5\:0\:true\:false\:true\:false\:true\:true NVIC.EXTI1_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true NVIC.EXTI2_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true NVIC.EXTI4_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true @@ -218,8 +219,9 @@ PB2.Signal=GPIO_Analog PB3\ (JTDO-TRACESWO).Locked=true PB3\ (JTDO-TRACESWO).Mode=TX_Only_Simplex_Unidirect_Master PB3\ (JTDO-TRACESWO).Signal=SPI1_SCK -PB4\ (NJTRST).GPIOParameters=GPIO_PuPd,GPIO_Label +PB4\ (NJTRST).GPIOParameters=GPIO_PuPd,GPIO_Label,GPIO_ModeDefaultEXTI PB4\ (NJTRST).GPIO_Label=BUTTON_LEFT +PB4\ (NJTRST).GPIO_ModeDefaultEXTI=GPIO_MODE_IT_RISING_FALLING PB4\ (NJTRST).GPIO_PuPd=GPIO_PULLDOWN PB4\ (NJTRST).Locked=true PB4\ (NJTRST).Signal=GPXTI4 @@ -260,8 +262,9 @@ PC11.Signal=SPI3_MISO PC12.Locked=true PC12.Mode=Full_Duplex_Master PC12.Signal=SPI3_MOSI -PC13.GPIOParameters=GPIO_PuPd,GPIO_Label +PC13.GPIOParameters=GPIO_PuPd,GPIO_Label,GPIO_ModeDefaultEXTI PC13.GPIO_Label=BUTTON_BACK +PC13.GPIO_ModeDefaultEXTI=GPIO_MODE_IT_RISING_FALLING PC13.GPIO_PuPd=GPIO_PULLDOWN PC13.Locked=true PC13.Signal=GPXTI13 diff --git a/wiki/Testing.md b/wiki/Testing.md index 35cc9ee2..0fa012b4 100644 --- a/wiki/Testing.md +++ b/wiki/Testing.md @@ -1,4 +1,4 @@ -# Bootloader test +# Bootloader testcase 1. `# Clean flash` 2. `make -C bootloader flash` `# Load bootloader` @@ -34,4 +34,29 @@ 11. Wait 0.5 s 12. `# Expect FW` * Expect: uart welcome message - * Expect: USB Flipper CDC \ No newline at end of file + * Expect: USB Flipper CDC + +# Input testcase + +1. `docker-compose exec dev make -C target_f2 example_input_dump` +2. Flash +3. For x in ``` +[ + (Up, "00"), + (Down, "01"), + (Right, "02"), + (Left, "03"), + (Ok, "04"), + (Back, "05"), +] +``` + * Press ${x[0]} + * wait 0.05 + * Expect: Uart: "event: ${x[1]} pressed" + * wait 0.05 + * Release ${x[0]} + * wait 0.05 + * Expect: Uart: "event: ${x[1]} released" + * wait 0.05 + +TODO: add debouncing check (multiple press and check there is no multiple events)