Просмотр исходного кода

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 <mail@s3f.ru>
Vadim Kaushan 5 лет назад
Родитель
Сommit
8c36d65e63

+ 29 - 0
applications/examples/input_dump.c

@@ -0,0 +1,29 @@
+#include "flipper.h"
+#include <stdio.h>
+
+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);
+    }
+}

+ 111 - 0
applications/input/input.c

@@ -0,0 +1,111 @@
+#include <input/input.h>
+#include <input_priv.h>
+#include <stdio.h>
+#include <furi.h>
+
+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);
+    }
+}

+ 40 - 0
applications/input/input.h

@@ -0,0 +1,40 @@
+#ifndef __INPUT_H
+#define __INPUT_H
+
+#include <stdbool.h>
+
+#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 */

+ 10 - 0
applications/startup.h

@@ -15,11 +15,14 @@ void application_blink(void* p);
 void application_uart_write(void* p);
 void application_uart_write(void* p);
 void application_ipc_display(void* p);
 void application_ipc_display(void* p);
 void application_ipc_widget(void* p);
 void application_ipc_widget(void* p);
+void application_input_dump(void* p);
 
 
 void display_u8g2(void* p);
 void display_u8g2(void* p);
 
 
 void u8g2_example(void* p);
 void u8g2_example(void* p);
 
 
+void input_task(void* p);
+
 void coreglitch_demo_0(void* p);
 void coreglitch_demo_0(void* p);
 
 
 const FlipperStartupApp FLIPPER_STARTUP[] = {
 const FlipperStartupApp FLIPPER_STARTUP[] = {
@@ -28,6 +31,10 @@ const FlipperStartupApp FLIPPER_STARTUP[] = {
     {.app = u8g2_example, .name = "u8g2_example"},
     {.app = u8g2_example, .name = "u8g2_example"},
 #endif
 #endif
 
 
+#ifdef USE_INPUT
+    {.app = input_task, .name = "input_task"},
+#endif
+
 // {.app = coreglitch_demo_0, .name = "coreglitch_demo_0"},
 // {.app = coreglitch_demo_0, .name = "coreglitch_demo_0"},
 
 
 #ifdef TEST
 #ifdef TEST
@@ -44,4 +51,7 @@ const FlipperStartupApp FLIPPER_STARTUP[] = {
     {.app = application_ipc_display, .name = "ipc display"},
     {.app = application_ipc_display, .name = "ipc display"},
     {.app = application_ipc_widget, .name = "ipc widget"},
     {.app = application_ipc_widget, .name = "ipc widget"},
 #endif
 #endif
+#ifdef EXAMPLE_INPUT_DUMP
+    {.app = application_input_dump, .name = "input dump"},
+#endif
 };
 };

+ 1 - 0
core/flipper.h

@@ -9,6 +9,7 @@ extern "C" {
 #include "cmsis_os.h"
 #include "cmsis_os.h"
 #include "furi.h"
 #include "furi.h"
 #include "log.h"
 #include "log.h"
+#include "input/input.h"
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }

+ 29 - 0
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 */

+ 1 - 0
target_f2/Inc/main.h

@@ -66,6 +66,7 @@ void register_tim8_callback_ch2(void (*callback)(uint16_t ccr, TimerEvent tim_ev
 /* Private defines -----------------------------------------------------------*/
 /* Private defines -----------------------------------------------------------*/
 #define BUTTON_BACK_Pin GPIO_PIN_13
 #define BUTTON_BACK_Pin GPIO_PIN_13
 #define BUTTON_BACK_GPIO_Port GPIOC
 #define BUTTON_BACK_GPIO_Port GPIOC
+#define BUTTON_BACK_EXTI_IRQn EXTI15_10_IRQn
 #define CHRG_Pin GPIO_PIN_2
 #define CHRG_Pin GPIO_PIN_2
 #define CHRG_GPIO_Port GPIOC
 #define CHRG_GPIO_Port GPIOC
 #define CHRG_EXTI_IRQn EXTI2_IRQn
 #define CHRG_EXTI_IRQn EXTI2_IRQn

+ 1 - 0
target_f2/Inc/stm32l4xx_it.h

@@ -59,6 +59,7 @@ void EXTI1_IRQHandler(void);
 void EXTI2_IRQHandler(void);
 void EXTI2_IRQHandler(void);
 void EXTI4_IRQHandler(void);
 void EXTI4_IRQHandler(void);
 void EXTI9_5_IRQHandler(void);
 void EXTI9_5_IRQHandler(void);
+void EXTI15_10_IRQHandler(void);
 void TIM8_CC_IRQHandler(void);
 void TIM8_CC_IRQHandler(void);
 void OTG_FS_IRQHandler(void);
 void OTG_FS_IRQHandler(void);
 /* USER CODE BEGIN EFP */
 /* USER CODE BEGIN EFP */

+ 13 - 2
target_f2/Makefile

@@ -103,7 +103,8 @@ C_DEFS +=  \
 -DUSE_HAL_DRIVER \
 -DUSE_HAL_DRIVER \
 -DSTM32L476xx \
 -DSTM32L476xx \
 -DBUTON_INVERT=false \
 -DBUTON_INVERT=false \
--DDEBUG_UART=huart1
+-DDEBUG_UART=huart1 \
+-DUSE_INPUT
 
 
 ASM_SOURCES +=  \
 ASM_SOURCES +=  \
 startup_stm32l476xx.s
 startup_stm32l476xx.s
@@ -139,7 +140,8 @@ C_SOURCES += ../lib/u8g2/u8x8_d_st7565.c \
 
 
 # System applications
 # System applications
 
 
-C_SOURCES += ../applications/display-u8g2/display-u8g2.c
+C_SOURCES += ../applications/display-u8g2/display-u8g2.c \
+../applications/input/input.c \
 
 
 # Examples
 # Examples
 
 
@@ -170,6 +172,11 @@ C_SOURCES += ../applications/examples/ipc.c
 C_DEFS += -DEXAMPLE_IPC
 C_DEFS += -DEXAMPLE_IPC
 endif
 endif
 
 
+ifeq ($(EXAMPLE_INPUT_DUMP), 1)
+C_SOURCES += ../applications/examples/input_dump.c
+C_DEFS += -DEXAMPLE_INPUT_DUMP
+endif
+
 # User application
 # User application
 
 
 C_SOURCES += ../applications/coreglitch_demo_0/coreglitch_demo_0.c
 C_SOURCES += ../applications/coreglitch_demo_0/coreglitch_demo_0.c
@@ -303,6 +310,10 @@ example_ipc:
 	EXAMPLE_IPC=1 make
 	EXAMPLE_IPC=1 make
 	rm $(BUILD_DIR)/app.o
 	rm $(BUILD_DIR)/app.o
 
 
+example_input_dump:
+	EXAMPLE_INPUT_DUMP=1 make
+	rm $(BUILD_DIR)/app.o
+
 test:
 test:
 	TEST=1 make
 	TEST=1 make
 	rm $(BUILD_DIR)/app.o
 	rm $(BUILD_DIR)/app.o

+ 5 - 2
target_f2/Src/main.c

@@ -598,7 +598,7 @@ static void MX_GPIO_Init(void) {
 
 
     /*Configure GPIO pin : BUTTON_BACK_Pin */
     /*Configure GPIO pin : BUTTON_BACK_Pin */
     GPIO_InitStruct.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;
     GPIO_InitStruct.Pull = GPIO_PULLDOWN;
     HAL_GPIO_Init(BUTTON_BACK_GPIO_Port, &GPIO_InitStruct);
     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 */
     /*Configure GPIO pin : BUTTON_LEFT_Pin */
     GPIO_InitStruct.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;
     GPIO_InitStruct.Pull = GPIO_PULLDOWN;
     HAL_GPIO_Init(BUTTON_LEFT_GPIO_Port, &GPIO_InitStruct);
     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_SetPriority(EXTI9_5_IRQn, 5, 0);
     HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
     HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
+
+    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 5, 0);
+    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
 }
 }
 
 
 /* USER CODE BEGIN 4 */
 /* USER CODE BEGIN 4 */

+ 13 - 0
target_f2/Src/stm32l4xx_it.c

@@ -237,6 +237,19 @@ void EXTI9_5_IRQHandler(void) {
     /* USER CODE END EXTI9_5_IRQn 1 */
     /* 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 (*tim8_callback_ch2)(uint16_t ccr, TimerEvent tim_event);
 
 
 void register_tim8_callback_ch2(void (*callback)(uint16_t ccr, TimerEvent tim_event)) {
 void register_tim8_callback_ch2(void (*callback)(uint16_t ccr, TimerEvent tim_event)) {

+ 5 - 2
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.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
 NVIC.DebugMonitor_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.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.EXTI1_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true
 NVIC.EXTI2_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
 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).Locked=true
 PB3\ (JTDO-TRACESWO).Mode=TX_Only_Simplex_Unidirect_Master
 PB3\ (JTDO-TRACESWO).Mode=TX_Only_Simplex_Unidirect_Master
 PB3\ (JTDO-TRACESWO).Signal=SPI1_SCK
 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_Label=BUTTON_LEFT
+PB4\ (NJTRST).GPIO_ModeDefaultEXTI=GPIO_MODE_IT_RISING_FALLING
 PB4\ (NJTRST).GPIO_PuPd=GPIO_PULLDOWN
 PB4\ (NJTRST).GPIO_PuPd=GPIO_PULLDOWN
 PB4\ (NJTRST).Locked=true
 PB4\ (NJTRST).Locked=true
 PB4\ (NJTRST).Signal=GPXTI4
 PB4\ (NJTRST).Signal=GPXTI4
@@ -260,8 +262,9 @@ PC11.Signal=SPI3_MISO
 PC12.Locked=true
 PC12.Locked=true
 PC12.Mode=Full_Duplex_Master
 PC12.Mode=Full_Duplex_Master
 PC12.Signal=SPI3_MOSI
 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_Label=BUTTON_BACK
+PC13.GPIO_ModeDefaultEXTI=GPIO_MODE_IT_RISING_FALLING
 PC13.GPIO_PuPd=GPIO_PULLDOWN
 PC13.GPIO_PuPd=GPIO_PULLDOWN
 PC13.Locked=true
 PC13.Locked=true
 PC13.Signal=GPXTI13
 PC13.Signal=GPXTI13

+ 27 - 2
wiki/Testing.md

@@ -1,4 +1,4 @@
-# Bootloader test
+# Bootloader testcase
 
 
 1. `# Clean flash`
 1. `# Clean flash`
 2. `make -C bootloader flash` `# Load bootloader`
 2. `make -C bootloader flash` `# Load bootloader`
@@ -34,4 +34,29 @@
 11. Wait 0.5 s
 11. Wait 0.5 s
 12. `# Expect FW`
 12. `# Expect FW`
     * Expect: uart welcome message
     * Expect: uart welcome message
-    * Expect: USB Flipper CDC
+    * 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)