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>
This commit is contained in:
		
							parent
							
								
									ed76f702b1
								
							
						
					
					
						commit
						8c36d65e63
					
				
							
								
								
									
										29
									
								
								applications/examples/input_dump.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								applications/examples/input_dump.c
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								applications/input/input.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								applications/input/input.c
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								applications/input/input.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								applications/input/input.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 */ | ||||
| @ -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 | ||||
| }; | ||||
|  | ||||
| @ -9,6 +9,7 @@ extern "C" { | ||||
| #include "cmsis_os.h" | ||||
| #include "furi.h" | ||||
| #include "log.h" | ||||
| #include "input/input.h" | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
|  | ||||
							
								
								
									
										29
									
								
								target_f2/Inc/input_priv.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								target_f2/Inc/input_priv.h
									
									
									
									
									
										Normal file
									
								
							| @ -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 */ | ||||
| @ -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 | ||||
|  | ||||
| @ -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 */ | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 */ | ||||
|  | ||||
| @ -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)) { | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| # Bootloader test | ||||
| # Bootloader testcase | ||||
| 
 | ||||
| 1. `# Clean flash` | ||||
| 2. `make -C bootloader flash` `# Load bootloader` | ||||
| @ -35,3 +35,28 @@ | ||||
| 12. `# Expect FW` | ||||
|     * Expect: uart welcome message | ||||
|     * 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) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Vadim Kaushan
						Vadim Kaushan