-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Input_SRV: migration to event_loop #3968
base: dev
Are you sure you want to change the base?
Changes from all commits
8b94888
1a220d5
bbc7081
35ca0ac
0f379ac
a43990c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,64 +2,61 @@ | |
|
||
#include <stdbool.h> | ||
#include <stdint.h> | ||
#include <stdio.h> | ||
#include <furi.h> | ||
#include <cli/cli.h> | ||
#include <furi_hal_gpio.h> | ||
|
||
#define INPUT_DEBOUNCE_TICKS_HALF (INPUT_DEBOUNCE_TICKS / 2) | ||
#define INPUT_PRESS_TICKS 150 | ||
#define INPUT_LONG_PRESS_COUNTS 2 | ||
#define INPUT_THREAD_FLAG_ISR 0x00000001 | ||
#define TAG "Input" | ||
|
||
/** Input pin state */ | ||
typedef struct { | ||
const InputPin* pin; | ||
// State | ||
volatile bool state; | ||
volatile uint8_t debounce; | ||
FuriTimer* press_timer; | ||
FuriPubSub* event_pubsub; | ||
volatile uint8_t press_counter; | ||
volatile uint32_t counter; | ||
} InputPinState; | ||
#define INPUT_SRV_DEBOUNCE_TIMER_TICKS 1 //ms | ||
|
||
/** Input CLI command handler */ | ||
void input_cli(Cli* cli, FuriString* args, void* context); | ||
|
||
// #define INPUT_DEBUG | ||
#define INPUT_SRV_INPUT_LONG_PRESS_TICKS 150 //ms | ||
#define INPUT_SRV_LONG_PRESS_COUNTS 2 | ||
|
||
#define GPIO_Read(input_pin) (furi_hal_gpio_read(input_pin.pin->gpio) ^ (input_pin.pin->inverted)) | ||
|
||
void input_press_timer_callback(void* arg) { | ||
InputPinState* input_pin = arg; | ||
InputEvent event; | ||
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE; | ||
event.sequence_counter = input_pin->counter; | ||
event.key = input_pin->pin->key; | ||
input_pin->press_counter++; | ||
if(input_pin->press_counter == INPUT_LONG_PRESS_COUNTS) { | ||
event.type = InputTypeLong; | ||
furi_pubsub_publish(input_pin->event_pubsub, &event); | ||
} else if(input_pin->press_counter > INPUT_LONG_PRESS_COUNTS) { | ||
input_pin->press_counter--; | ||
event.type = InputTypeRepeat; | ||
furi_pubsub_publish(input_pin->event_pubsub, &event); | ||
} | ||
} | ||
#ifdef INPUT_DEBUG | ||
#define INPUT_LOG(...) FURI_LOG_D(TAG, __VA_ARGS__) | ||
#else | ||
#define INPUT_LOG(...) | ||
#endif | ||
|
||
void input_isr(void* _ctx) { | ||
FuriThreadId thread_id = (FuriThreadId)_ctx; | ||
furi_thread_flags_set(thread_id, INPUT_THREAD_FLAG_ISR); | ||
} | ||
typedef struct { | ||
FuriEventLoopTimer* timer; | ||
FuriPubSub* event_pubsub; | ||
uint32_t sequence_counter; | ||
uint32_t press_counter; | ||
InputKey key; | ||
} InputSrvKeySequence; | ||
|
||
typedef struct { | ||
const InputPin* pin; | ||
uint16_t debounce_count; | ||
bool state; | ||
} InputSrvKeyState; | ||
|
||
typedef struct { | ||
FuriEventLoop* event_loop; | ||
FuriPubSub* event_pubsub; | ||
FuriSemaphore* input_semaphore; | ||
FuriEventLoopTimer* debounce_timer; | ||
InputSrvKeyState* key_state; | ||
InputSrvKeySequence* key_sequence; | ||
uint32_t sequence_counter; | ||
} InputSrv; | ||
|
||
static void input_key_sequence_run( | ||
InputSrvKeySequence* key_sequence, | ||
InputType type, | ||
uint32_t sequence_counter); | ||
|
||
const char* input_get_key_name(InputKey key) { | ||
for(size_t i = 0; i < input_pins_count; i++) { | ||
if(input_pins[i].key == key) { | ||
return input_pins[i].name; | ||
} | ||
} | ||
return "Unknown"; | ||
furi_crash(); | ||
} | ||
|
||
const char* input_get_type_name(InputType type) { | ||
|
@@ -75,95 +72,187 @@ const char* input_get_type_name(InputType type) { | |
case InputTypeRepeat: | ||
return "Repeat"; | ||
default: | ||
return "Unknown"; | ||
furi_crash(); | ||
} | ||
} | ||
|
||
int32_t input_srv(void* p) { | ||
UNUSED(p); | ||
|
||
const FuriThreadId thread_id = furi_thread_get_current_id(); | ||
FuriPubSub* event_pubsub = furi_pubsub_alloc(); | ||
uint32_t counter = 1; | ||
furi_record_create(RECORD_INPUT_EVENTS, event_pubsub); | ||
static void input_isr_key(void* context) { | ||
InputSrv* instance = context; | ||
furi_semaphore_release(instance->input_semaphore); | ||
} | ||
|
||
#ifdef INPUT_DEBUG | ||
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeOutputPushPull); | ||
#endif | ||
static void input_semaphore_callback(FuriEventLoopObject* object, void* context) { | ||
furi_assert(context); | ||
InputSrv* instance = context; | ||
furi_assert(object == instance->input_semaphore); | ||
|
||
#ifdef SRV_CLI | ||
Cli* cli = furi_record_open(RECORD_CLI); | ||
cli_add_command(cli, "input", CliCommandFlagParallelSafe, input_cli, event_pubsub); | ||
#endif | ||
furi_check(furi_semaphore_acquire(instance->input_semaphore, 0) == FuriStatusOk); | ||
|
||
InputPinState pin_states[input_pins_count]; | ||
if(!furi_event_loop_timer_is_running(instance->debounce_timer)) { | ||
furi_event_loop_timer_start(instance->debounce_timer, INPUT_SRV_DEBOUNCE_TIMER_TICKS); | ||
} | ||
} | ||
|
||
static void input_debounce_timer_callback(void* context) { | ||
furi_assert(context); | ||
InputSrv* instance = context; | ||
bool is_changing = false; | ||
for(size_t i = 0; i < input_pins_count; i++) { | ||
furi_hal_gpio_add_int_callback(input_pins[i].gpio, input_isr, thread_id); | ||
pin_states[i].pin = &input_pins[i]; | ||
pin_states[i].state = GPIO_Read(pin_states[i]); | ||
pin_states[i].debounce = INPUT_DEBOUNCE_TICKS_HALF; | ||
pin_states[i].press_timer = | ||
furi_timer_alloc(input_press_timer_callback, FuriTimerTypePeriodic, &pin_states[i]); | ||
pin_states[i].event_pubsub = event_pubsub; | ||
pin_states[i].press_counter = 0; | ||
} | ||
bool state = GPIO_Read(instance->key_state[i]); | ||
|
||
while(1) { | ||
bool is_changing = false; | ||
for(size_t i = 0; i < input_pins_count; i++) { | ||
bool state = GPIO_Read(pin_states[i]); | ||
if(state) { | ||
if(pin_states[i].debounce < INPUT_DEBOUNCE_TICKS) pin_states[i].debounce += 1; | ||
} else { | ||
if(pin_states[i].debounce > 0) pin_states[i].debounce -= 1; | ||
if(state) { | ||
if(instance->key_state[i].debounce_count < INPUT_DEBOUNCE_TICKS) { | ||
instance->key_state[i].debounce_count++; | ||
is_changing = true; | ||
} | ||
} else if(instance->key_state[i].debounce_count > 0) { | ||
instance->key_state[i].debounce_count--; | ||
is_changing = true; | ||
} | ||
|
||
if(pin_states[i].debounce > 0 && pin_states[i].debounce < INPUT_DEBOUNCE_TICKS) { | ||
is_changing = true; | ||
} else if(pin_states[i].state != state) { | ||
pin_states[i].state = state; | ||
|
||
// Common state info | ||
InputEvent event; | ||
event.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE; | ||
event.key = pin_states[i].pin->key; | ||
|
||
// Short / Long / Repeat timer routine | ||
if(state) { | ||
pin_states[i].counter = counter++; | ||
event.sequence_counter = pin_states[i].counter; | ||
furi_timer_start(pin_states[i].press_timer, INPUT_PRESS_TICKS); | ||
} else { | ||
event.sequence_counter = pin_states[i].counter; | ||
furi_timer_stop(pin_states[i].press_timer); | ||
while(furi_timer_is_running(pin_states[i].press_timer)) | ||
furi_delay_tick(1); | ||
if(pin_states[i].press_counter < INPUT_LONG_PRESS_COUNTS) { | ||
event.type = InputTypeShort; | ||
furi_pubsub_publish(event_pubsub, &event); | ||
} | ||
pin_states[i].press_counter = 0; | ||
} | ||
|
||
// Send Press/Release event | ||
event.type = pin_states[i].state ? InputTypePress : InputTypeRelease; | ||
furi_pubsub_publish(event_pubsub, &event); | ||
if(!is_changing && instance->key_state[i].state != state) { | ||
instance->key_state[i].state = state; | ||
|
||
if(state) { | ||
input_key_sequence_run( | ||
&instance->key_sequence[i], InputTypePress, ++instance->sequence_counter); | ||
} else { | ||
input_key_sequence_run(&instance->key_sequence[i], InputTypeRelease, 0); | ||
} | ||
} | ||
} | ||
|
||
if(is_changing) { | ||
#ifdef INPUT_DEBUG | ||
furi_hal_gpio_write(&gpio_ext_pa4, 1); | ||
#endif | ||
furi_delay_tick(1); | ||
} else { | ||
#ifdef INPUT_DEBUG | ||
furi_hal_gpio_write(&gpio_ext_pa4, 0); | ||
#endif | ||
furi_thread_flags_wait(INPUT_THREAD_FLAG_ISR, FuriFlagWaitAny, FuriWaitForever); | ||
if(!is_changing) { | ||
furi_event_loop_timer_stop(instance->debounce_timer); | ||
} | ||
} | ||
|
||
static inline void | ||
input_send(FuriPubSub* pubsub, InputKey key, InputType type, uint32_t sequence_counter) { | ||
InputEvent event = { | ||
.sequence_source = INPUT_SEQUENCE_SOURCE_HARDWARE, | ||
.sequence_counter = sequence_counter, | ||
.key = key, | ||
.type = type, | ||
}; | ||
|
||
furi_pubsub_publish(pubsub, &event); | ||
INPUT_LOG( | ||
"input_send: %s %s %x", | ||
input_get_key_name(event.key), | ||
input_get_type_name(event.type), | ||
event.sequence_counter); | ||
} | ||
|
||
static void input_key_sequence_run( | ||
InputSrvKeySequence* key_sequence, | ||
InputType type, | ||
uint32_t sequence_counter) { | ||
switch(type) { | ||
case InputTypePress: | ||
key_sequence->sequence_counter = sequence_counter; | ||
key_sequence->press_counter = 0; | ||
|
||
furi_assert(!furi_event_loop_timer_is_running(key_sequence->timer)); | ||
furi_event_loop_timer_start(key_sequence->timer, INPUT_SRV_INPUT_LONG_PRESS_TICKS); | ||
|
||
input_send( | ||
key_sequence->event_pubsub, | ||
key_sequence->key, | ||
InputTypePress, | ||
key_sequence->sequence_counter); | ||
|
||
break; | ||
case InputTypeRelease: | ||
if(key_sequence->press_counter < INPUT_SRV_LONG_PRESS_COUNTS) { | ||
input_send( | ||
key_sequence->event_pubsub, | ||
key_sequence->key, | ||
InputTypeShort, | ||
key_sequence->sequence_counter); | ||
} | ||
|
||
furi_assert(furi_event_loop_timer_is_running(key_sequence->timer)); | ||
furi_event_loop_timer_stop(key_sequence->timer); | ||
|
||
input_send( | ||
key_sequence->event_pubsub, | ||
key_sequence->key, | ||
InputTypeRelease, | ||
key_sequence->sequence_counter); | ||
|
||
break; | ||
default: | ||
furi_crash(); | ||
break; | ||
} | ||
} | ||
|
||
static void input_sequence_timer_callback(void* context) { | ||
furi_assert(context); | ||
InputSrvKeySequence* key_sequence = context; | ||
|
||
key_sequence->press_counter++; | ||
|
||
if(key_sequence->press_counter == INPUT_SRV_LONG_PRESS_COUNTS) { | ||
input_send( | ||
key_sequence->event_pubsub, | ||
key_sequence->key, | ||
InputTypeLong, | ||
key_sequence->sequence_counter); | ||
} else if(key_sequence->press_counter > INPUT_SRV_LONG_PRESS_COUNTS) { | ||
input_send( | ||
key_sequence->event_pubsub, | ||
key_sequence->key, | ||
InputTypeRepeat, | ||
key_sequence->sequence_counter); | ||
} | ||
} | ||
|
||
int32_t input_srv(void* p) { | ||
UNUSED(p); | ||
InputSrv* instance = malloc(sizeof(InputSrv)); | ||
instance->event_pubsub = furi_pubsub_alloc(); | ||
furi_record_create(RECORD_INPUT_EVENTS, instance->event_pubsub); | ||
|
||
instance->input_semaphore = furi_semaphore_alloc(1, 0); | ||
instance->event_loop = furi_event_loop_alloc(); | ||
instance->debounce_timer = furi_event_loop_timer_alloc( | ||
instance->event_loop, | ||
input_debounce_timer_callback, | ||
FuriEventLoopTimerTypePeriodic, | ||
instance); | ||
|
||
instance->key_state = malloc(sizeof(InputSrvKeyState) * input_pins_count); | ||
for(size_t i = 0; i < input_pins_count; i++) { | ||
furi_hal_gpio_add_int_callback(input_pins[i].gpio, input_isr_key, instance); | ||
instance->key_state[i].pin = &input_pins[i]; | ||
instance->key_state[i].state = GPIO_Read(instance->key_state[i]); | ||
instance->sequence_counter = 0; | ||
} | ||
|
||
furi_event_loop_subscribe_semaphore( | ||
instance->event_loop, | ||
instance->input_semaphore, | ||
FuriEventLoopEventIn, | ||
input_semaphore_callback, | ||
instance); | ||
|
||
instance->key_sequence = malloc(sizeof(InputSrvKeySequence) * input_pins_count); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All those allocations could easily land on the stack, VLAs come in handy here (copying my remark from the previous PR). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no, if you put everything on the stack and turn on debugging, then the 1 KB stack allocated to the process is sometimes not enough and everything crashes with special effects There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those structures are tiny - so wouldn't this mean that even without them on the stack, 1KB is pushing the limit very close? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all structures on the stack will take about 220 bytes. provided that there are only 6 buttons, when the number of buttons changes, the size of the structure will increase and if the structure is placed on the stack, the implementation ceases to be universal. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. at the expense of 1kb on the stack it is enough even with debugging enabled, without it about 500 bytes are used per process There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case the keys could stay on the heap (although maybe put them on the service heap?) while There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well then what's the point of chopping it into pieces? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently it's chopped into pieces already, and occupies 3 heap allocations - so by moving you save one allocation, which is better than nothing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep in mind that services are allocated in special memory region and it's quite small. Increasing stack size may bring another set of problems There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, in this case the key arrays could stay on the user heap, but the inputsrv is so small there's no reason not to put it on the stack. Before this PR it was just a bunch of local variables. |
||
for(size_t i = 0; i < input_pins_count; i++) { | ||
instance->key_sequence[i].sequence_counter = 0; | ||
instance->key_sequence[i].press_counter = 0; | ||
instance->key_sequence[i].key = input_pins[i].key; | ||
instance->key_sequence[i].timer = furi_event_loop_timer_alloc( | ||
instance->event_loop, | ||
input_sequence_timer_callback, | ||
FuriEventLoopTimerTypePeriodic, | ||
&instance->key_sequence[i]); | ||
instance->key_sequence[i].event_pubsub = instance->event_pubsub; | ||
} | ||
|
||
// Start Input Service | ||
furi_event_loop_run(instance->event_loop); | ||
|
||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interrupts may be missed in some edge cases