diff --git a/README.md b/README.md index 6061e304c9a..ef38bf9f5ad 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # flipperzero-gpioreader -This is a fork of the `gpio` app built into the flipper, with added functionality to read GPIO inputs +This is a fork of the `gpio` app built into the flipper, with added functionality to read GPIO inputs. + +Supports pulling high or low. + +Does not (yet) support analog reads. diff --git a/application.fam b/application.fam new file mode 100644 index 00000000000..365d2491232 --- /dev/null +++ b/application.fam @@ -0,0 +1,12 @@ +App( + appid="gpioreader", + name="GPIO Reader", + apptype=FlipperAppType.EXTERNAL, + entry_point="gpio_app", + cdefines=["APP_GPIOREADER"], + requires=["gui"], + stack_size=1 * 1024, + order=50, + fap_libs=["assets"], + fap_category="GPIO", +) diff --git a/gpio_app.c b/gpio_app.c new file mode 100644 index 00000000000..07a79cb8928 --- /dev/null +++ b/gpio_app.c @@ -0,0 +1,111 @@ +#include "gpio_app_i.h" + +#include +#include + +static bool gpio_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + GpioApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool gpio_app_back_event_callback(void* context) { + furi_assert(context); + GpioApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void gpio_app_tick_event_callback(void* context) { + furi_assert(context); + GpioApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +GpioApp* gpio_app_alloc() { + GpioApp* app = malloc(sizeof(GpioApp)); + + app->gui = furi_record_open(RECORD_GUI); + + app->view_dispatcher = view_dispatcher_alloc(); + app->scene_manager = scene_manager_alloc(&gpio_scene_handlers, app); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, gpio_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, gpio_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, gpio_app_tick_event_callback, 100); + + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + + app->notifications = furi_record_open(RECORD_NOTIFICATION); + + app->var_item_list = variable_item_list_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + GpioAppViewVarItemList, + variable_item_list_get_view(app->var_item_list)); + app->gpio_test = gpio_test_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, GpioAppViewGpioTest, gpio_test_get_view(app->gpio_test)); + app->gpio_reader = gpio_reader_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, GpioAppViewGpioReader, gpio_reader_get_view(app->gpio_reader)); + + app->widget = widget_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, GpioAppViewUsbUartCloseRpc, widget_get_view(app->widget)); + + app->gpio_usb_uart = gpio_usb_uart_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, GpioAppViewUsbUart, gpio_usb_uart_get_view(app->gpio_usb_uart)); + + view_dispatcher_add_view( + app->view_dispatcher, + GpioAppViewUsbUartCfg, + variable_item_list_get_view(app->var_item_list)); + + scene_manager_next_scene(app->scene_manager, GpioSceneStart); + + return app; +} + +void gpio_app_free(GpioApp* app) { + furi_assert(app); + + // Views + view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewVarItemList); + view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewGpioTest); + view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewGpioReader); + view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUart); + view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCfg); + view_dispatcher_remove_view(app->view_dispatcher, GpioAppViewUsbUartCloseRpc); + variable_item_list_free(app->var_item_list); + widget_free(app->widget); + gpio_test_free(app->gpio_test); + gpio_reader_free(app->gpio_reader); + gpio_usb_uart_free(app->gpio_usb_uart); + + // View dispatcher + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + // Close records + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_NOTIFICATION); + + free(app); +} + +int32_t gpio_app(void* p) { + UNUSED(p); + GpioApp* gpio_app = gpio_app_alloc(); + + view_dispatcher_run(gpio_app->view_dispatcher); + + gpio_app_free(gpio_app); + + return 0; +} diff --git a/gpio_app.h b/gpio_app.h new file mode 100644 index 00000000000..156ddc9222d --- /dev/null +++ b/gpio_app.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct GpioApp GpioApp; + +#ifdef __cplusplus +} +#endif diff --git a/gpio_app_i.h b/gpio_app_i.h new file mode 100644 index 00000000000..52f76949d8b --- /dev/null +++ b/gpio_app_i.h @@ -0,0 +1,44 @@ +#pragma once + +#include "gpio_app.h" +#include "gpio_item.h" +#include "scenes/gpio_scene.h" +#include "gpio_custom_event.h" +#include "usb_uart_bridge.h" + +#include +#include +#include +#include +#include +#include +#include +#include "views/gpio_test.h" +#include "views/gpio_reader.h" +#include "views/gpio_usb_uart.h" +#include + +struct GpioApp { + Gui* gui; + NotificationApp* notifications; + ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; + Widget* widget; + + VariableItemList* var_item_list; + VariableItem* var_item_flow; + GpioTest* gpio_test; + GpioReader* gpio_reader; + GpioUsbUart* gpio_usb_uart; + UsbUartBridge* usb_uart_bridge; + UsbUartConfig* usb_uart_cfg; +}; + +typedef enum { + GpioAppViewVarItemList, + GpioAppViewGpioTest, + GpioAppViewGpioReader, + GpioAppViewUsbUart, + GpioAppViewUsbUartCfg, + GpioAppViewUsbUartCloseRpc, +} GpioAppView; diff --git a/gpio_custom_event.h b/gpio_custom_event.h new file mode 100644 index 00000000000..f5648e10c1c --- /dev/null +++ b/gpio_custom_event.h @@ -0,0 +1,14 @@ +#pragma once + +typedef enum { + GpioStartEventOtgOff = 0, + GpioStartEventOtgOn, + GpioStartEventManualControl, + GpioStartEventReader, + GpioStartEventUsbUart, + + GpioCustomEventErrorBack, + + GpioUsbUartEventConfig, + GpioUsbUartEventConfigSet, +} GpioCustomEvent; diff --git a/gpio_item.c b/gpio_item.c new file mode 100644 index 00000000000..f516bd25875 --- /dev/null +++ b/gpio_item.c @@ -0,0 +1,60 @@ +#include "gpio_item.h" + +#include + +typedef struct { + const char* name; + const GpioPin* pin; +} GpioItem; + +static const GpioItem gpio_item[GPIO_ITEM_COUNT] = { + {"1.2: PA7", &gpio_ext_pa7}, + {"1.3: PA6", &gpio_ext_pa6}, + {"1.4: PA4", &gpio_ext_pa4}, + {"1.5: PB3", &gpio_ext_pb3}, + {"1.6: PB2", &gpio_ext_pb2}, + {"1.7: PC3", &gpio_ext_pc3}, + {"2.7: PC1", &gpio_ext_pc1}, + {"2.8: PC0", &gpio_ext_pc0}, +}; + +void gpio_item_configure_pin(uint8_t index, GpioMode mode, GpioPull pull) { + furi_assert(index < GPIO_ITEM_COUNT); + furi_hal_gpio_write(gpio_item[index].pin, false); + furi_hal_gpio_init(gpio_item[index].pin, mode, pull, GpioSpeedVeryHigh); +} + +void gpio_item_configure_all_pins(GpioMode mode) { + GpioPull pull = GpioPullNo; + if(mode == GpioModeInput){ + pull = GpioPullDown; + } + for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { + gpio_item_configure_pin(i, mode, pull); + } +} + +void gpio_item_set_pin(uint8_t index, bool level) { + furi_assert(index < GPIO_ITEM_COUNT); + furi_hal_gpio_write(gpio_item[index].pin, level); +} + +bool gpio_item_get_pin(uint8_t index) { + furi_assert(index < GPIO_ITEM_COUNT); + return furi_hal_gpio_read(gpio_item[index].pin); +} + +void gpio_item_set_all_pins(bool level) { + for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { + gpio_item_set_pin(i, level); + } +} + +const char* gpio_item_get_pin_name(uint8_t index) { + furi_assert(index < GPIO_ITEM_COUNT + 1); + if(index == GPIO_ITEM_COUNT) { + return "ALL"; + } else { + return gpio_item[index].name; + } +} diff --git a/gpio_item.h b/gpio_item.h new file mode 100644 index 00000000000..fe73e385122 --- /dev/null +++ b/gpio_item.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define GPIO_ITEM_COUNT 8 + +void gpio_item_configure_pin(uint8_t index, GpioMode mode, GpioPull pull); + +void gpio_item_configure_all_pins(GpioMode mode); + +void gpio_item_set_pin(uint8_t index, bool level); + +void gpio_item_set_all_pins(bool level); + +const char* gpio_item_get_pin_name(uint8_t index); + +bool gpio_item_get_pin(uint8_t index); diff --git a/scenes/gpio_scene.c b/scenes/gpio_scene.c new file mode 100644 index 00000000000..d5aa4cbe813 --- /dev/null +++ b/scenes/gpio_scene.c @@ -0,0 +1,30 @@ +#include "gpio_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const gpio_scene_on_enter_handlers[])(void*) = { +#include "gpio_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const gpio_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "gpio_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const gpio_scene_on_exit_handlers[])(void* context) = { +#include "gpio_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers gpio_scene_handlers = { + .on_enter_handlers = gpio_scene_on_enter_handlers, + .on_event_handlers = gpio_scene_on_event_handlers, + .on_exit_handlers = gpio_scene_on_exit_handlers, + .scene_num = GpioSceneNum, +}; diff --git a/scenes/gpio_scene.h b/scenes/gpio_scene.h new file mode 100644 index 00000000000..15556c8d548 --- /dev/null +++ b/scenes/gpio_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) GpioScene##id, +typedef enum { +#include "gpio_scene_config.h" + GpioSceneNum, +} GpioScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers gpio_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "gpio_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "gpio_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "gpio_scene_config.h" +#undef ADD_SCENE diff --git a/scenes/gpio_scene_config.h b/scenes/gpio_scene_config.h new file mode 100644 index 00000000000..269c32aaf1f --- /dev/null +++ b/scenes/gpio_scene_config.h @@ -0,0 +1,6 @@ +ADD_SCENE(gpio, start, Start) +ADD_SCENE(gpio, test, Test) +ADD_SCENE(gpio, reader, Reader) +ADD_SCENE(gpio, usb_uart, UsbUart) +ADD_SCENE(gpio, usb_uart_cfg, UsbUartCfg) +ADD_SCENE(gpio, usb_uart_close_rpc, UsbUartCloseRpc) diff --git a/scenes/gpio_scene_reader.c b/scenes/gpio_scene_reader.c new file mode 100644 index 00000000000..5995ff253c7 --- /dev/null +++ b/scenes/gpio_scene_reader.c @@ -0,0 +1,30 @@ +#include "../gpio_app_i.h" + +void gpio_scene_reader_ok_callback(InputType type, void* context) { + furi_assert(context); + GpioApp* app = context; + + if(type == InputTypePress) { + notification_message(app->notifications, &sequence_set_green_255); + } else if(type == InputTypeRelease) { + notification_message(app->notifications, &sequence_reset_green); + } +} + +void gpio_scene_reader_on_enter(void* context) { + GpioApp* app = context; + gpio_item_configure_all_pins(GpioModeInput); + gpio_reader_set_ok_callback(app->gpio_reader, gpio_scene_reader_ok_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewGpioReader); +} + +bool gpio_scene_reader_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void gpio_scene_reader_on_exit(void* context) { + UNUSED(context); + gpio_item_configure_all_pins(GpioModeAnalog); +} diff --git a/scenes/gpio_scene_start.c b/scenes/gpio_scene_start.c new file mode 100644 index 00000000000..71ddd659320 --- /dev/null +++ b/scenes/gpio_scene_start.c @@ -0,0 +1,114 @@ +#include "../gpio_app_i.h" +#include "furi_hal_power.h" +#include "furi_hal_usb.h" +#include + +enum GpioItem { + GpioItemUsbUart, + GpioItemTest, + GpioItemReader, + GpioItemOtg, +}; + +enum GpioOtg { + GpioOtgOff, + GpioOtgOn, + GpioOtgSettingsNum, +}; + +const char* const gpio_otg_text[GpioOtgSettingsNum] = { + "OFF", + "ON", +}; + +static void gpio_scene_start_var_list_enter_callback(void* context, uint32_t index) { + furi_assert(context); + GpioApp* app = context; + if(index == GpioItemTest) { + view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventManualControl); + } else if(index == GpioItemUsbUart) { + view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventUsbUart); + } else if(index == GpioItemReader) { + view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventReader); + } +} + +static void gpio_scene_start_var_list_change_callback(VariableItem* item) { + GpioApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, gpio_otg_text[index]); + if(index == GpioOtgOff) { + view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventOtgOff); + } else if(index == GpioOtgOn) { + view_dispatcher_send_custom_event(app->view_dispatcher, GpioStartEventOtgOn); + } +} + +void gpio_scene_start_on_enter(void* context) { + GpioApp* app = context; + VariableItemList* var_item_list = app->var_item_list; + + VariableItem* item; + variable_item_list_set_enter_callback( + var_item_list, gpio_scene_start_var_list_enter_callback, app); + + variable_item_list_add(var_item_list, "USB-UART Bridge", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "GPIO Manual Control", 0, NULL, NULL); + + variable_item_list_add(var_item_list, "GPIO Manual Read", 0, NULL, NULL); + + item = variable_item_list_add( + var_item_list, + "5V on GPIO", + GpioOtgSettingsNum, + gpio_scene_start_var_list_change_callback, + app); + if(furi_hal_power_is_otg_enabled()) { + variable_item_set_current_value_index(item, GpioOtgOn); + variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOn]); + } else { + variable_item_set_current_value_index(item, GpioOtgOff); + variable_item_set_current_value_text(item, gpio_otg_text[GpioOtgOff]); + } + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioSceneStart)); + + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewVarItemList); +} + +bool gpio_scene_start_on_event(void* context, SceneManagerEvent event) { + GpioApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GpioStartEventOtgOn) { + furi_hal_power_enable_otg(); + } else if(event.event == GpioStartEventOtgOff) { + furi_hal_power_disable_otg(); + } else if(event.event == GpioStartEventManualControl) { + scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemTest); + scene_manager_next_scene(app->scene_manager, GpioSceneTest); + } else if(event.event == GpioStartEventReader) { + scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemReader); + scene_manager_next_scene(app->scene_manager, GpioSceneReader); + } else if(event.event == GpioStartEventUsbUart) { + scene_manager_set_scene_state(app->scene_manager, GpioSceneStart, GpioItemUsbUart); + if(!furi_hal_usb_is_locked()) { + DOLPHIN_DEED(DolphinDeedGpioUartBridge); + scene_manager_next_scene(app->scene_manager, GpioSceneUsbUart); + } else { + scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCloseRpc); + } + } + consumed = true; + } + return consumed; +} + +void gpio_scene_start_on_exit(void* context) { + GpioApp* app = context; + variable_item_list_reset(app->var_item_list); +} diff --git a/scenes/gpio_scene_test.c b/scenes/gpio_scene_test.c new file mode 100644 index 00000000000..b015d80902e --- /dev/null +++ b/scenes/gpio_scene_test.c @@ -0,0 +1,30 @@ +#include "../gpio_app_i.h" + +void gpio_scene_test_ok_callback(InputType type, void* context) { + furi_assert(context); + GpioApp* app = context; + + if(type == InputTypePress) { + notification_message(app->notifications, &sequence_set_green_255); + } else if(type == InputTypeRelease) { + notification_message(app->notifications, &sequence_reset_green); + } +} + +void gpio_scene_test_on_enter(void* context) { + GpioApp* app = context; + gpio_item_configure_all_pins(GpioModeOutputPushPull); + gpio_test_set_ok_callback(app->gpio_test, gpio_scene_test_ok_callback, app); + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewGpioTest); +} + +bool gpio_scene_test_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void gpio_scene_test_on_exit(void* context) { + UNUSED(context); + gpio_item_configure_all_pins(GpioModeAnalog); +} diff --git a/scenes/gpio_scene_usb_uart.c b/scenes/gpio_scene_usb_uart.c new file mode 100644 index 00000000000..aa41aaf9878 --- /dev/null +++ b/scenes/gpio_scene_usb_uart.c @@ -0,0 +1,67 @@ +#include "../gpio_app_i.h" +#include "../usb_uart_bridge.h" + +typedef struct { + UsbUartConfig cfg; + UsbUartState state; +} SceneUsbUartBridge; + +static SceneUsbUartBridge* scene_usb_uart; + +void gpio_scene_usb_uart_callback(GpioCustomEvent event, void* context) { + furi_assert(context); + GpioApp* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, event); +} + +void gpio_scene_usb_uart_on_enter(void* context) { + GpioApp* app = context; + uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUart); + if(prev_state == 0) { + scene_usb_uart = malloc(sizeof(SceneUsbUartBridge)); + scene_usb_uart->cfg.vcp_ch = 0; // TODO: settings load + scene_usb_uart->cfg.uart_ch = 0; + scene_usb_uart->cfg.flow_pins = 0; + scene_usb_uart->cfg.baudrate_mode = 0; + scene_usb_uart->cfg.baudrate = 0; + app->usb_uart_bridge = usb_uart_enable(&scene_usb_uart->cfg); + } + + usb_uart_get_config(app->usb_uart_bridge, &scene_usb_uart->cfg); + usb_uart_get_state(app->usb_uart_bridge, &scene_usb_uart->state); + + gpio_usb_uart_set_callback(app->gpio_usb_uart, gpio_scene_usb_uart_callback, app); + scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 0); + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUart); + notification_message(app->notifications, &sequence_display_backlight_enforce_on); +} + +bool gpio_scene_usb_uart_on_event(void* context, SceneManagerEvent event) { + GpioApp* app = context; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, GpioSceneUsbUart, 1); + scene_manager_next_scene(app->scene_manager, GpioSceneUsbUartCfg); + return true; + } else if(event.type == SceneManagerEventTypeTick) { + uint32_t tx_cnt_last = scene_usb_uart->state.tx_cnt; + uint32_t rx_cnt_last = scene_usb_uart->state.rx_cnt; + usb_uart_get_state(app->usb_uart_bridge, &scene_usb_uart->state); + gpio_usb_uart_update_state( + app->gpio_usb_uart, &scene_usb_uart->cfg, &scene_usb_uart->state); + if(tx_cnt_last != scene_usb_uart->state.tx_cnt) + notification_message(app->notifications, &sequence_blink_blue_10); + if(rx_cnt_last != scene_usb_uart->state.rx_cnt) + notification_message(app->notifications, &sequence_blink_green_10); + } + return false; +} + +void gpio_scene_usb_uart_on_exit(void* context) { + GpioApp* app = context; + uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, GpioSceneUsbUart); + if(prev_state == 0) { + usb_uart_disable(app->usb_uart_bridge); + free(scene_usb_uart); + } + notification_message(app->notifications, &sequence_display_backlight_enforce_auto); +} diff --git a/scenes/gpio_scene_usb_uart_close_rpc.c b/scenes/gpio_scene_usb_uart_close_rpc.c new file mode 100644 index 00000000000..2cb53cab248 --- /dev/null +++ b/scenes/gpio_scene_usb_uart_close_rpc.c @@ -0,0 +1,41 @@ +#include "../gpio_app_i.h" +#include "../gpio_custom_event.h" + +void gpio_scene_usb_uart_close_rpc_on_enter(void* context) { + GpioApp* app = context; + + widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64); + widget_add_string_multiline_element( + app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!"); + widget_add_string_multiline_element( + app->widget, + 3, + 30, + AlignLeft, + AlignTop, + FontSecondary, + "Disconnect from\nPC or phone to\nuse this function."); + + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUartCloseRpc); +} + +bool gpio_scene_usb_uart_close_rpc_on_event(void* context, SceneManagerEvent event) { + GpioApp* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GpioCustomEventErrorBack) { + if(!scene_manager_previous_scene(app->scene_manager)) { + scene_manager_stop(app->scene_manager); + view_dispatcher_stop(app->view_dispatcher); + } + consumed = true; + } + } + return consumed; +} + +void gpio_scene_usb_uart_close_rpc_on_exit(void* context) { + GpioApp* app = context; + widget_reset(app->widget); +} diff --git a/scenes/gpio_scene_usb_uart_config.c b/scenes/gpio_scene_usb_uart_config.c new file mode 100644 index 00000000000..55b04ed67fb --- /dev/null +++ b/scenes/gpio_scene_usb_uart_config.c @@ -0,0 +1,169 @@ +#include "../usb_uart_bridge.h" +#include "../gpio_app_i.h" +#include "furi_hal.h" + +typedef enum { + UsbUartLineIndexVcp, + UsbUartLineIndexBaudrate, + UsbUartLineIndexUart, + UsbUartLineIndexFlow, +} LineIndex; + +static const char* vcp_ch[] = {"0 (CLI)", "1"}; +static const char* uart_ch[] = {"13,14", "15,16"}; +static const char* flow_pins[] = {"None", "2,3", "6,7", "16,15"}; +static const char* baudrate_mode[] = {"Host"}; +static const uint32_t baudrate_list[] = { + 2400, + 9600, + 19200, + 38400, + 57600, + 115200, + 230400, + 460800, + 921600, +}; + +bool gpio_scene_usb_uart_cfg_on_event(void* context, SceneManagerEvent event) { + GpioApp* app = context; + furi_assert(app); + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GpioUsbUartEventConfigSet) { + usb_uart_set_config(app->usb_uart_bridge, app->usb_uart_cfg); + return true; + } + } + return false; +} + +void line_ensure_flow_invariant(GpioApp* app) { + // GPIO pins PC0, PC1 (16,15) are unavailable for RTS/DTR when LPUART is + // selected. This function enforces that invariant by resetting flow_pins + // to None if it is configured to 16,15 when LPUART is selected. + + uint8_t available_flow_pins = app->usb_uart_cfg->uart_ch == FuriHalUartIdLPUART1 ? 3 : 4; + VariableItem* item = app->var_item_flow; + variable_item_set_values_count(item, available_flow_pins); + + if(app->usb_uart_cfg->flow_pins >= available_flow_pins) { + app->usb_uart_cfg->flow_pins = 0; + + variable_item_set_current_value_index(item, app->usb_uart_cfg->flow_pins); + variable_item_set_current_value_text(item, flow_pins[app->usb_uart_cfg->flow_pins]); + } +} + +static void line_vcp_cb(VariableItem* item) { + GpioApp* app = variable_item_get_context(item); + furi_assert(app); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, vcp_ch[index]); + + app->usb_uart_cfg->vcp_ch = index; + view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); +} + +static void line_port_cb(VariableItem* item) { + GpioApp* app = variable_item_get_context(item); + furi_assert(app); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, uart_ch[index]); + + if(index == 0) + app->usb_uart_cfg->uart_ch = FuriHalUartIdUSART1; + else if(index == 1) + app->usb_uart_cfg->uart_ch = FuriHalUartIdLPUART1; + + line_ensure_flow_invariant(app); + view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); +} + +static void line_flow_cb(VariableItem* item) { + GpioApp* app = variable_item_get_context(item); + furi_assert(app); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, flow_pins[index]); + + app->usb_uart_cfg->flow_pins = index; + view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); +} + +static void line_baudrate_cb(VariableItem* item) { + GpioApp* app = variable_item_get_context(item); + furi_assert(app); + uint8_t index = variable_item_get_current_value_index(item); + + char br_text[8]; + + if(index > 0) { + snprintf(br_text, 7, "%lu", baudrate_list[index - 1]); + variable_item_set_current_value_text(item, br_text); + app->usb_uart_cfg->baudrate = baudrate_list[index - 1]; + } else { + variable_item_set_current_value_text(item, baudrate_mode[index]); + app->usb_uart_cfg->baudrate = 0; + } + app->usb_uart_cfg->baudrate_mode = index; + view_dispatcher_send_custom_event(app->view_dispatcher, GpioUsbUartEventConfigSet); +} + +void gpio_scene_usb_uart_cfg_on_enter(void* context) { + GpioApp* app = context; + furi_assert(app); + VariableItemList* var_item_list = app->var_item_list; + + app->usb_uart_cfg = malloc(sizeof(UsbUartConfig)); + usb_uart_get_config(app->usb_uart_bridge, app->usb_uart_cfg); + + VariableItem* item; + char br_text[8]; + + item = variable_item_list_add(var_item_list, "USB Channel", 2, line_vcp_cb, app); + variable_item_set_current_value_index(item, app->usb_uart_cfg->vcp_ch); + variable_item_set_current_value_text(item, vcp_ch[app->usb_uart_cfg->vcp_ch]); + + item = variable_item_list_add( + var_item_list, + "Baudrate", + sizeof(baudrate_list) / sizeof(baudrate_list[0]) + 1, + line_baudrate_cb, + app); + variable_item_set_current_value_index(item, app->usb_uart_cfg->baudrate_mode); + if(app->usb_uart_cfg->baudrate_mode > 0) { + snprintf(br_text, 7, "%lu", baudrate_list[app->usb_uart_cfg->baudrate_mode - 1]); + variable_item_set_current_value_text(item, br_text); + } else { + variable_item_set_current_value_text( + item, baudrate_mode[app->usb_uart_cfg->baudrate_mode]); + } + + item = variable_item_list_add(var_item_list, "UART Pins", 2, line_port_cb, app); + variable_item_set_current_value_index(item, app->usb_uart_cfg->uart_ch); + variable_item_set_current_value_text(item, uart_ch[app->usb_uart_cfg->uart_ch]); + + item = variable_item_list_add( + var_item_list, "RTS/DTR Pins", COUNT_OF(flow_pins), line_flow_cb, app); + variable_item_set_current_value_index(item, app->usb_uart_cfg->flow_pins); + variable_item_set_current_value_text(item, flow_pins[app->usb_uart_cfg->flow_pins]); + app->var_item_flow = item; + line_ensure_flow_invariant(app); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, GpioAppViewUsbUartCfg)); + + view_dispatcher_switch_to_view(app->view_dispatcher, GpioAppViewUsbUartCfg); +} + +void gpio_scene_usb_uart_cfg_on_exit(void* context) { + GpioApp* app = context; + scene_manager_set_scene_state( + app->scene_manager, + GpioAppViewUsbUartCfg, + variable_item_list_get_selected_item_index(app->var_item_list)); + variable_item_list_reset(app->var_item_list); + free(app->usb_uart_cfg); +} diff --git a/usb_uart_bridge.c b/usb_uart_bridge.c new file mode 100644 index 00000000000..1a82dbdc2e5 --- /dev/null +++ b/usb_uart_bridge.c @@ -0,0 +1,375 @@ +#include "usb_uart_bridge.h" +#include "furi_hal.h" +#include +#include "usb_cdc.h" +#include "cli/cli_vcp.h" +#include +#include "cli/cli.h" + +#define USB_CDC_PKT_LEN CDC_DATA_SZ +#define USB_UART_RX_BUF_SIZE (USB_CDC_PKT_LEN * 5) + +#define USB_CDC_BIT_DTR (1 << 0) +#define USB_CDC_BIT_RTS (1 << 1) + +static const GpioPin* flow_pins[][2] = { + {&gpio_ext_pa7, &gpio_ext_pa6}, // 2, 3 + {&gpio_ext_pb2, &gpio_ext_pc3}, // 6, 7 + {&gpio_ext_pc0, &gpio_ext_pc1}, // 16, 15 +}; + +typedef enum { + WorkerEvtStop = (1 << 0), + WorkerEvtRxDone = (1 << 1), + + WorkerEvtTxStop = (1 << 2), + WorkerEvtCdcRx = (1 << 3), + + WorkerEvtCfgChange = (1 << 4), + + WorkerEvtLineCfgSet = (1 << 5), + WorkerEvtCtrlLineSet = (1 << 6), + +} WorkerEvtFlags; + +#define WORKER_ALL_RX_EVENTS \ + (WorkerEvtStop | WorkerEvtRxDone | WorkerEvtCfgChange | WorkerEvtLineCfgSet | \ + WorkerEvtCtrlLineSet) +#define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtCdcRx) + +struct UsbUartBridge { + UsbUartConfig cfg; + UsbUartConfig cfg_new; + + FuriThread* thread; + FuriThread* tx_thread; + + FuriStreamBuffer* rx_stream; + + FuriMutex* usb_mutex; + + FuriSemaphore* tx_sem; + + UsbUartState st; + + FuriApiLock cfg_lock; + + uint8_t rx_buf[USB_CDC_PKT_LEN]; +}; + +static void vcp_on_cdc_tx_complete(void* context); +static void vcp_on_cdc_rx(void* context); +static void vcp_state_callback(void* context, uint8_t state); +static void vcp_on_cdc_control_line(void* context, uint8_t state); +static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config); + +static const CdcCallbacks cdc_cb = { + vcp_on_cdc_tx_complete, + vcp_on_cdc_rx, + vcp_state_callback, + vcp_on_cdc_control_line, + vcp_on_line_config, +}; + +/* USB UART worker */ + +static int32_t usb_uart_tx_thread(void* context); + +static void usb_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void* context) { + UsbUartBridge* usb_uart = (UsbUartBridge*)context; + + if(ev == UartIrqEventRXNE) { + furi_stream_buffer_send(usb_uart->rx_stream, &data, 1, 0); + furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtRxDone); + } +} + +static void usb_uart_vcp_init(UsbUartBridge* usb_uart, uint8_t vcp_ch) { + furi_hal_usb_unlock(); + if(vcp_ch == 0) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_close(cli); + furi_record_close(RECORD_CLI); + furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); + } else { + furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true); + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); + } + furi_hal_cdc_set_callbacks(vcp_ch, (CdcCallbacks*)&cdc_cb, usb_uart); +} + +static void usb_uart_vcp_deinit(UsbUartBridge* usb_uart, uint8_t vcp_ch) { + UNUSED(usb_uart); + furi_hal_cdc_set_callbacks(vcp_ch, NULL, NULL); + if(vcp_ch != 0) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_close(cli); + furi_record_close(RECORD_CLI); + } +} + +static void usb_uart_serial_init(UsbUartBridge* usb_uart, uint8_t uart_ch) { + if(uart_ch == FuriHalUartIdUSART1) { + furi_hal_console_disable(); + } else if(uart_ch == FuriHalUartIdLPUART1) { + furi_hal_uart_init(uart_ch, 115200); + } + furi_hal_uart_set_irq_cb(uart_ch, usb_uart_on_irq_cb, usb_uart); +} + +static void usb_uart_serial_deinit(UsbUartBridge* usb_uart, uint8_t uart_ch) { + UNUSED(usb_uart); + furi_hal_uart_set_irq_cb(uart_ch, NULL, NULL); + if(uart_ch == FuriHalUartIdUSART1) + furi_hal_console_enable(); + else if(uart_ch == FuriHalUartIdLPUART1) + furi_hal_uart_deinit(uart_ch); +} + +static void usb_uart_set_baudrate(UsbUartBridge* usb_uart, uint32_t baudrate) { + if(baudrate != 0) { + furi_hal_uart_set_br(usb_uart->cfg.uart_ch, baudrate); + usb_uart->st.baudrate_cur = baudrate; + } else { + struct usb_cdc_line_coding* line_cfg = + furi_hal_cdc_get_port_settings(usb_uart->cfg.vcp_ch); + if(line_cfg->dwDTERate > 0) { + furi_hal_uart_set_br(usb_uart->cfg.uart_ch, line_cfg->dwDTERate); + usb_uart->st.baudrate_cur = line_cfg->dwDTERate; + } + } +} + +static void usb_uart_update_ctrl_lines(UsbUartBridge* usb_uart) { + if(usb_uart->cfg.flow_pins != 0) { + furi_assert((size_t)(usb_uart->cfg.flow_pins - 1) < COUNT_OF(flow_pins)); + uint8_t state = furi_hal_cdc_get_ctrl_line_state(usb_uart->cfg.vcp_ch); + + furi_hal_gpio_write(flow_pins[usb_uart->cfg.flow_pins - 1][0], !(state & USB_CDC_BIT_RTS)); + furi_hal_gpio_write(flow_pins[usb_uart->cfg.flow_pins - 1][1], !(state & USB_CDC_BIT_DTR)); + } +} + +static int32_t usb_uart_worker(void* context) { + UsbUartBridge* usb_uart = (UsbUartBridge*)context; + + memcpy(&usb_uart->cfg, &usb_uart->cfg_new, sizeof(UsbUartConfig)); + + usb_uart->rx_stream = furi_stream_buffer_alloc(USB_UART_RX_BUF_SIZE, 1); + + usb_uart->tx_sem = furi_semaphore_alloc(1, 1); + usb_uart->usb_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + usb_uart->tx_thread = + furi_thread_alloc_ex("UsbUartTxWorker", 512, usb_uart_tx_thread, usb_uart); + + usb_uart_vcp_init(usb_uart, usb_uart->cfg.vcp_ch); + usb_uart_serial_init(usb_uart, usb_uart->cfg.uart_ch); + usb_uart_set_baudrate(usb_uart, usb_uart->cfg.baudrate); + if(usb_uart->cfg.flow_pins != 0) { + furi_assert((size_t)(usb_uart->cfg.flow_pins - 1) < COUNT_OF(flow_pins)); + furi_hal_gpio_init_simple( + flow_pins[usb_uart->cfg.flow_pins - 1][0], GpioModeOutputPushPull); + furi_hal_gpio_init_simple( + flow_pins[usb_uart->cfg.flow_pins - 1][1], GpioModeOutputPushPull); + usb_uart_update_ctrl_lines(usb_uart); + } + + furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtCdcRx); + + furi_thread_start(usb_uart->tx_thread); + + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever); + furi_check(!(events & FuriFlagError)); + if(events & WorkerEvtStop) break; + if(events & WorkerEvtRxDone) { + size_t len = furi_stream_buffer_receive( + usb_uart->rx_stream, usb_uart->rx_buf, USB_CDC_PKT_LEN, 0); + if(len > 0) { + if(furi_semaphore_acquire(usb_uart->tx_sem, 100) == FuriStatusOk) { + usb_uart->st.rx_cnt += len; + furi_check( + furi_mutex_acquire(usb_uart->usb_mutex, FuriWaitForever) == FuriStatusOk); + furi_hal_cdc_send(usb_uart->cfg.vcp_ch, usb_uart->rx_buf, len); + furi_check(furi_mutex_release(usb_uart->usb_mutex) == FuriStatusOk); + } else { + furi_stream_buffer_reset(usb_uart->rx_stream); + } + } + } + if(events & WorkerEvtCfgChange) { + if(usb_uart->cfg.vcp_ch != usb_uart->cfg_new.vcp_ch) { + furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop); + furi_thread_join(usb_uart->tx_thread); + + usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch); + usb_uart_vcp_init(usb_uart, usb_uart->cfg_new.vcp_ch); + + usb_uart->cfg.vcp_ch = usb_uart->cfg_new.vcp_ch; + furi_thread_start(usb_uart->tx_thread); + events |= WorkerEvtCtrlLineSet; + events |= WorkerEvtLineCfgSet; + } + if(usb_uart->cfg.uart_ch != usb_uart->cfg_new.uart_ch) { + furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop); + furi_thread_join(usb_uart->tx_thread); + + usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch); + usb_uart_serial_init(usb_uart, usb_uart->cfg_new.uart_ch); + + usb_uart->cfg.uart_ch = usb_uart->cfg_new.uart_ch; + usb_uart_set_baudrate(usb_uart, usb_uart->cfg.baudrate); + + furi_thread_start(usb_uart->tx_thread); + } + if(usb_uart->cfg.baudrate != usb_uart->cfg_new.baudrate) { + usb_uart_set_baudrate(usb_uart, usb_uart->cfg_new.baudrate); + usb_uart->cfg.baudrate = usb_uart->cfg_new.baudrate; + } + if(usb_uart->cfg.flow_pins != usb_uart->cfg_new.flow_pins) { + if(usb_uart->cfg.flow_pins != 0) { + furi_hal_gpio_init_simple( + flow_pins[usb_uart->cfg.flow_pins - 1][0], GpioModeAnalog); + furi_hal_gpio_init_simple( + flow_pins[usb_uart->cfg.flow_pins - 1][1], GpioModeAnalog); + } + if(usb_uart->cfg_new.flow_pins != 0) { + furi_assert((size_t)(usb_uart->cfg_new.flow_pins - 1) < COUNT_OF(flow_pins)); + furi_hal_gpio_init_simple( + flow_pins[usb_uart->cfg_new.flow_pins - 1][0], GpioModeOutputPushPull); + furi_hal_gpio_init_simple( + flow_pins[usb_uart->cfg_new.flow_pins - 1][1], GpioModeOutputPushPull); + } + usb_uart->cfg.flow_pins = usb_uart->cfg_new.flow_pins; + events |= WorkerEvtCtrlLineSet; + } + api_lock_unlock(usb_uart->cfg_lock); + } + if(events & WorkerEvtLineCfgSet) { + if(usb_uart->cfg.baudrate == 0) + usb_uart_set_baudrate(usb_uart, usb_uart->cfg.baudrate); + } + if(events & WorkerEvtCtrlLineSet) { + usb_uart_update_ctrl_lines(usb_uart); + } + } + usb_uart_vcp_deinit(usb_uart, usb_uart->cfg.vcp_ch); + usb_uart_serial_deinit(usb_uart, usb_uart->cfg.uart_ch); + + if(usb_uart->cfg.flow_pins != 0) { + furi_hal_gpio_init_simple(flow_pins[usb_uart->cfg.flow_pins - 1][0], GpioModeAnalog); + furi_hal_gpio_init_simple(flow_pins[usb_uart->cfg.flow_pins - 1][1], GpioModeAnalog); + } + + furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtTxStop); + furi_thread_join(usb_uart->tx_thread); + furi_thread_free(usb_uart->tx_thread); + + furi_stream_buffer_free(usb_uart->rx_stream); + furi_mutex_free(usb_uart->usb_mutex); + furi_semaphore_free(usb_uart->tx_sem); + + furi_hal_usb_unlock(); + furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true); + Cli* cli = furi_record_open(RECORD_CLI); + cli_session_open(cli, &cli_vcp); + furi_record_close(RECORD_CLI); + + return 0; +} + +static int32_t usb_uart_tx_thread(void* context) { + UsbUartBridge* usb_uart = (UsbUartBridge*)context; + + uint8_t data[USB_CDC_PKT_LEN]; + while(1) { + uint32_t events = + furi_thread_flags_wait(WORKER_ALL_TX_EVENTS, FuriFlagWaitAny, FuriWaitForever); + furi_check(!(events & FuriFlagError)); + if(events & WorkerEvtTxStop) break; + if(events & WorkerEvtCdcRx) { + furi_check(furi_mutex_acquire(usb_uart->usb_mutex, FuriWaitForever) == FuriStatusOk); + size_t len = furi_hal_cdc_receive(usb_uart->cfg.vcp_ch, data, USB_CDC_PKT_LEN); + furi_check(furi_mutex_release(usb_uart->usb_mutex) == FuriStatusOk); + + if(len > 0) { + usb_uart->st.tx_cnt += len; + furi_hal_uart_tx(usb_uart->cfg.uart_ch, data, len); + } + } + } + return 0; +} + +/* VCP callbacks */ + +static void vcp_on_cdc_tx_complete(void* context) { + UsbUartBridge* usb_uart = (UsbUartBridge*)context; + furi_semaphore_release(usb_uart->tx_sem); +} + +static void vcp_on_cdc_rx(void* context) { + UsbUartBridge* usb_uart = (UsbUartBridge*)context; + furi_thread_flags_set(furi_thread_get_id(usb_uart->tx_thread), WorkerEvtCdcRx); +} + +static void vcp_state_callback(void* context, uint8_t state) { + UNUSED(context); + UNUSED(state); +} + +static void vcp_on_cdc_control_line(void* context, uint8_t state) { + UNUSED(state); + UsbUartBridge* usb_uart = (UsbUartBridge*)context; + furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCtrlLineSet); +} + +static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config) { + UNUSED(config); + UsbUartBridge* usb_uart = (UsbUartBridge*)context; + furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtLineCfgSet); +} + +UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg) { + UsbUartBridge* usb_uart = malloc(sizeof(UsbUartBridge)); + + memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); + + usb_uart->thread = furi_thread_alloc_ex("UsbUartWorker", 1024, usb_uart_worker, usb_uart); + + furi_thread_start(usb_uart->thread); + return usb_uart; +} + +void usb_uart_disable(UsbUartBridge* usb_uart) { + furi_assert(usb_uart); + furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtStop); + furi_thread_join(usb_uart->thread); + furi_thread_free(usb_uart->thread); + free(usb_uart); +} + +void usb_uart_set_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { + furi_assert(usb_uart); + furi_assert(cfg); + usb_uart->cfg_lock = api_lock_alloc_locked(); + memcpy(&(usb_uart->cfg_new), cfg, sizeof(UsbUartConfig)); + furi_thread_flags_set(furi_thread_get_id(usb_uart->thread), WorkerEvtCfgChange); + api_lock_wait_unlock_and_free(usb_uart->cfg_lock); +} + +void usb_uart_get_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg) { + furi_assert(usb_uart); + furi_assert(cfg); + memcpy(cfg, &(usb_uart->cfg_new), sizeof(UsbUartConfig)); +} + +void usb_uart_get_state(UsbUartBridge* usb_uart, UsbUartState* st) { + furi_assert(usb_uart); + furi_assert(st); + memcpy(st, &(usb_uart->st), sizeof(UsbUartState)); +} diff --git a/usb_uart_bridge.h b/usb_uart_bridge.h new file mode 100644 index 00000000000..b456c3cc4e5 --- /dev/null +++ b/usb_uart_bridge.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +typedef struct UsbUartBridge UsbUartBridge; + +typedef struct { + uint8_t vcp_ch; + uint8_t uart_ch; + uint8_t flow_pins; + uint8_t baudrate_mode; + uint32_t baudrate; +} UsbUartConfig; + +typedef struct { + uint32_t rx_cnt; + uint32_t tx_cnt; + uint32_t baudrate_cur; +} UsbUartState; + +UsbUartBridge* usb_uart_enable(UsbUartConfig* cfg); + +void usb_uart_disable(UsbUartBridge* usb_uart); + +void usb_uart_set_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg); + +void usb_uart_get_config(UsbUartBridge* usb_uart, UsbUartConfig* cfg); + +void usb_uart_get_state(UsbUartBridge* usb_uart, UsbUartState* st); diff --git a/views/gpio_reader.c b/views/gpio_reader.c new file mode 100644 index 00000000000..95a425fda1c --- /dev/null +++ b/views/gpio_reader.c @@ -0,0 +1,164 @@ +#include "gpio_reader.h" +#include "../gpio_item.h" + +#include +#include + +struct GpioReader { + View* view; + GpioReaderOkCallback callback; + void* context; +}; + +typedef struct { + uint8_t pin_idx; + bool pullUp[GPIO_ITEM_COUNT]; +} GpioReaderModel; + +static bool gpio_reader_process_ok(GpioReader* gpio_reader, InputEvent* event); +static bool gpio_reader_process_left(GpioReader* gpio_reader); +static bool gpio_reader_process_right(GpioReader* gpio_reader); + +static void gpio_reader_draw_callback(Canvas* canvas, void* _model) { + GpioReaderModel* model = _model; + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "GPIO Reader"); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, 64, 16, AlignCenter, AlignTop, "A7 A6 A4 B3 B2 C3 C1 C0"); + elements_multiline_text_aligned( + canvas, 64, 40, AlignCenter, AlignTop, "Pull Up"); + int charOffset = 10; + for(uint8_t i = 0; i < GPIO_ITEM_COUNT; i++) { + bool high = gpio_item_get_pin(i); + if(high) { + elements_multiline_text_aligned( + canvas, charOffset, 25, AlignCenter, AlignTop, "1"); + } else { + elements_multiline_text_aligned( + canvas, charOffset, 25, AlignCenter, AlignTop, "0"); + } + + if(model->pullUp[i]) { + elements_multiline_text_aligned( + canvas, charOffset, 50, AlignCenter, AlignTop, "1"); + } else { + elements_multiline_text_aligned( + canvas, charOffset, 50, AlignCenter, AlignTop, "0"); + } + if(i == model->pin_idx) { + elements_multiline_text_aligned( + canvas, charOffset, 53, AlignCenter, AlignTop, "_"); + } + + + charOffset += 16; + } + //~ free(charOffset); +} + +static bool gpio_reader_input_callback(InputEvent* event, void* context) { + furi_assert(context); + GpioReader* gpio_reader = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + consumed = gpio_reader_process_right(gpio_reader); + } else if(event->key == InputKeyLeft) { + consumed = gpio_reader_process_left(gpio_reader); + } + } else if(event->key == InputKeyOk) { + consumed = gpio_reader_process_ok(gpio_reader, event); + } + + return consumed; +} + +static bool gpio_reader_process_left(GpioReader* gpio_reader) { + with_view_model( + gpio_reader->view, + GpioReaderModel * model, + { + if(model->pin_idx) { + model->pin_idx--; + } + }, + true); + return true; +} + +static bool gpio_reader_process_right(GpioReader* gpio_reader) { + with_view_model( + gpio_reader->view, + GpioReaderModel * model, + { + if(model->pin_idx < GPIO_ITEM_COUNT-1) { + model->pin_idx++; + } + }, + true); + return true; +} + +static bool gpio_reader_process_ok(GpioReader* gpio_reader, InputEvent* event) { + bool consumed = false; + + with_view_model( + gpio_reader->view, + GpioReaderModel * model, + { + if(event->type == InputTypePress) { + if(model->pullUp[model->pin_idx]){ + gpio_item_configure_pin(model->pin_idx, GpioModeInput, GpioPullDown); + model->pullUp[model->pin_idx] = 0; + consumed = true; + }else{ + gpio_item_configure_pin(model->pin_idx, GpioModeInput, GpioPullUp); + model->pullUp[model->pin_idx] = 1; + consumed = true; + } + } + gpio_reader->callback(event->type, gpio_reader->context); + }, + true); + + return consumed; +} + +GpioReader* gpio_reader_alloc() { + GpioReader* gpio_reader = malloc(sizeof(GpioReader)); + + gpio_reader->view = view_alloc(); + view_allocate_model(gpio_reader->view, ViewModelTypeLocking, sizeof(GpioReaderModel)); + view_set_context(gpio_reader->view, gpio_reader); + view_set_draw_callback(gpio_reader->view, gpio_reader_draw_callback); + view_set_input_callback(gpio_reader->view, gpio_reader_input_callback); + + return gpio_reader; +} + +void gpio_reader_free(GpioReader* gpio_reader) { + furi_assert(gpio_reader); + view_free(gpio_reader->view); + free(gpio_reader); +} + +View* gpio_reader_get_view(GpioReader* gpio_reader) { + furi_assert(gpio_reader); + return gpio_reader->view; +} + +void gpio_reader_set_ok_callback(GpioReader* gpio_reader, GpioReaderOkCallback callback, void* context) { + furi_assert(gpio_reader); + furi_assert(callback); + with_view_model( + gpio_reader->view, + GpioReaderModel * model, + { + UNUSED(model); + gpio_reader->callback = callback; + gpio_reader->context = context; + }, + false); +} diff --git a/views/gpio_reader.h b/views/gpio_reader.h new file mode 100644 index 00000000000..d027d013887 --- /dev/null +++ b/views/gpio_reader.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct GpioReader GpioReader; +typedef void (*GpioReaderOkCallback)(InputType type, void* context); + +GpioReader* gpio_reader_alloc(); + +void gpio_reader_free(GpioReader* gpio_reader); + +View* gpio_reader_get_view(GpioReader* gpio_reader); + +void gpio_reader_set_ok_callback(GpioReader* gpio_reader, GpioReaderOkCallback callback, void* context); diff --git a/views/gpio_test.c b/views/gpio_test.c new file mode 100644 index 00000000000..69dc0f67bfe --- /dev/null +++ b/views/gpio_test.c @@ -0,0 +1,139 @@ +#include "gpio_test.h" +#include "../gpio_item.h" + +#include + +struct GpioTest { + View* view; + GpioTestOkCallback callback; + void* context; +}; + +typedef struct { + uint8_t pin_idx; +} GpioTestModel; + +static bool gpio_test_process_left(GpioTest* gpio_test); +static bool gpio_test_process_right(GpioTest* gpio_test); +static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event); + +static void gpio_test_draw_callback(Canvas* canvas, void* _model) { + GpioTestModel* model = _model; + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_aligned(canvas, 64, 2, AlignCenter, AlignTop, "GPIO Output Mode Test"); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, 64, 16, AlignCenter, AlignTop, "Press < or > to change pin"); + elements_multiline_text_aligned( + canvas, 64, 32, AlignCenter, AlignTop, gpio_item_get_pin_name(model->pin_idx)); +} + +static bool gpio_test_input_callback(InputEvent* event, void* context) { + furi_assert(context); + GpioTest* gpio_test = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyRight) { + consumed = gpio_test_process_right(gpio_test); + } else if(event->key == InputKeyLeft) { + consumed = gpio_test_process_left(gpio_test); + } + } else if(event->key == InputKeyOk) { + consumed = gpio_test_process_ok(gpio_test, event); + } + + return consumed; +} + +static bool gpio_test_process_left(GpioTest* gpio_test) { + with_view_model( + gpio_test->view, + GpioTestModel * model, + { + if(model->pin_idx) { + model->pin_idx--; + } + }, + true); + return true; +} + +static bool gpio_test_process_right(GpioTest* gpio_test) { + with_view_model( + gpio_test->view, + GpioTestModel * model, + { + if(model->pin_idx < GPIO_ITEM_COUNT) { + model->pin_idx++; + } + }, + true); + return true; +} + +static bool gpio_test_process_ok(GpioTest* gpio_test, InputEvent* event) { + bool consumed = false; + + with_view_model( + gpio_test->view, + GpioTestModel * model, + { + if(event->type == InputTypePress) { + if(model->pin_idx < GPIO_ITEM_COUNT) { + gpio_item_set_pin(model->pin_idx, true); + } else { + gpio_item_set_all_pins(true); + } + consumed = true; + } else if(event->type == InputTypeRelease) { + if(model->pin_idx < GPIO_ITEM_COUNT) { + gpio_item_set_pin(model->pin_idx, false); + } else { + gpio_item_set_all_pins(false); + } + consumed = true; + } + gpio_test->callback(event->type, gpio_test->context); + }, + true); + + return consumed; +} + +GpioTest* gpio_test_alloc() { + GpioTest* gpio_test = malloc(sizeof(GpioTest)); + + gpio_test->view = view_alloc(); + view_allocate_model(gpio_test->view, ViewModelTypeLocking, sizeof(GpioTestModel)); + view_set_context(gpio_test->view, gpio_test); + view_set_draw_callback(gpio_test->view, gpio_test_draw_callback); + view_set_input_callback(gpio_test->view, gpio_test_input_callback); + + return gpio_test; +} + +void gpio_test_free(GpioTest* gpio_test) { + furi_assert(gpio_test); + view_free(gpio_test->view); + free(gpio_test); +} + +View* gpio_test_get_view(GpioTest* gpio_test) { + furi_assert(gpio_test); + return gpio_test->view; +} + +void gpio_test_set_ok_callback(GpioTest* gpio_test, GpioTestOkCallback callback, void* context) { + furi_assert(gpio_test); + furi_assert(callback); + with_view_model( + gpio_test->view, + GpioTestModel * model, + { + UNUSED(model); + gpio_test->callback = callback; + gpio_test->context = context; + }, + false); +} diff --git a/views/gpio_test.h b/views/gpio_test.h new file mode 100644 index 00000000000..5cbd11e82cf --- /dev/null +++ b/views/gpio_test.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +typedef struct GpioTest GpioTest; +typedef void (*GpioTestOkCallback)(InputType type, void* context); + +GpioTest* gpio_test_alloc(); + +void gpio_test_free(GpioTest* gpio_test); + +View* gpio_test_get_view(GpioTest* gpio_test); + +void gpio_test_set_ok_callback(GpioTest* gpio_test, GpioTestOkCallback callback, void* context); diff --git a/views/gpio_usb_uart.c b/views/gpio_usb_uart.c new file mode 100644 index 00000000000..c7406d29ba4 --- /dev/null +++ b/views/gpio_usb_uart.c @@ -0,0 +1,161 @@ +#include "../usb_uart_bridge.h" +#include "../gpio_app_i.h" +#include "furi_hal.h" +#include + +struct GpioUsbUart { + View* view; + GpioUsbUartCallback callback; + void* context; +}; + +typedef struct { + uint32_t baudrate; + uint32_t tx_cnt; + uint32_t rx_cnt; + uint8_t vcp_port; + uint8_t tx_pin; + uint8_t rx_pin; + bool tx_active; + bool rx_active; +} GpioUsbUartModel; + +static void gpio_usb_uart_draw_callback(Canvas* canvas, void* _model) { + GpioUsbUartModel* model = _model; + char temp_str[18]; + elements_button_left(canvas, "Config"); + canvas_draw_line(canvas, 2, 10, 125, 10); + canvas_draw_line(canvas, 44, 52, 123, 52); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 2, 9, "USB Serial"); + canvas_draw_str(canvas, 3, 25, "TX:"); + canvas_draw_str(canvas, 3, 42, "RX:"); + + canvas_set_font(canvas, FontSecondary); + snprintf(temp_str, 18, "COM PORT:%u", model->vcp_port); + canvas_draw_str_aligned(canvas, 126, 8, AlignRight, AlignBottom, temp_str); + snprintf(temp_str, 18, "Pin %u", model->tx_pin); + canvas_draw_str(canvas, 22, 25, temp_str); + snprintf(temp_str, 18, "Pin %u", model->rx_pin); + canvas_draw_str(canvas, 22, 42, temp_str); + + if(model->baudrate == 0) + snprintf(temp_str, 18, "Baud: ????"); + else + snprintf(temp_str, 18, "Baud: %lu", model->baudrate); + canvas_draw_str(canvas, 45, 62, temp_str); + + if(model->tx_cnt < 100000000) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 127, 24, AlignRight, AlignBottom, "B."); + canvas_set_font(canvas, FontKeyboard); + snprintf(temp_str, 18, "%lu", model->tx_cnt); + canvas_draw_str_aligned(canvas, 116, 24, AlignRight, AlignBottom, temp_str); + } else { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 127, 24, AlignRight, AlignBottom, "KiB."); + canvas_set_font(canvas, FontKeyboard); + snprintf(temp_str, 18, "%lu", model->tx_cnt / 1024); + canvas_draw_str_aligned(canvas, 111, 24, AlignRight, AlignBottom, temp_str); + } + + if(model->rx_cnt < 100000000) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 127, 41, AlignRight, AlignBottom, "B."); + canvas_set_font(canvas, FontKeyboard); + snprintf(temp_str, 18, "%lu", model->rx_cnt); + canvas_draw_str_aligned(canvas, 116, 41, AlignRight, AlignBottom, temp_str); + } else { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 127, 41, AlignRight, AlignBottom, "KiB."); + canvas_set_font(canvas, FontKeyboard); + snprintf(temp_str, 18, "%lu", model->rx_cnt / 1024); + canvas_draw_str_aligned(canvas, 111, 41, AlignRight, AlignBottom, temp_str); + } + + if(model->tx_active) + canvas_draw_icon(canvas, 48, 14, &I_ArrowUpFilled_14x15); + else + canvas_draw_icon(canvas, 48, 14, &I_ArrowUpEmpty_14x15); + + if(model->rx_active) + canvas_draw_icon(canvas, 48, 34, &I_ArrowDownFilled_14x15); + else + canvas_draw_icon(canvas, 48, 34, &I_ArrowDownEmpty_14x15); +} + +static bool gpio_usb_uart_input_callback(InputEvent* event, void* context) { + furi_assert(context); + GpioUsbUart* usb_uart = context; + bool consumed = false; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft) { + consumed = true; + furi_assert(usb_uart->callback); + usb_uart->callback(GpioUsbUartEventConfig, usb_uart->context); + } + } + + return consumed; +} + +GpioUsbUart* gpio_usb_uart_alloc() { + GpioUsbUart* usb_uart = malloc(sizeof(GpioUsbUart)); + + usb_uart->view = view_alloc(); + view_allocate_model(usb_uart->view, ViewModelTypeLocking, sizeof(GpioUsbUartModel)); + view_set_context(usb_uart->view, usb_uart); + view_set_draw_callback(usb_uart->view, gpio_usb_uart_draw_callback); + view_set_input_callback(usb_uart->view, gpio_usb_uart_input_callback); + + return usb_uart; +} + +void gpio_usb_uart_free(GpioUsbUart* usb_uart) { + furi_assert(usb_uart); + view_free(usb_uart->view); + free(usb_uart); +} + +View* gpio_usb_uart_get_view(GpioUsbUart* usb_uart) { + furi_assert(usb_uart); + return usb_uart->view; +} + +void gpio_usb_uart_set_callback(GpioUsbUart* usb_uart, GpioUsbUartCallback callback, void* context) { + furi_assert(usb_uart); + furi_assert(callback); + + with_view_model( + usb_uart->view, + GpioUsbUartModel * model, + { + UNUSED(model); + usb_uart->callback = callback; + usb_uart->context = context; + }, + false); +} + +void gpio_usb_uart_update_state(GpioUsbUart* instance, UsbUartConfig* cfg, UsbUartState* st) { + furi_assert(instance); + furi_assert(cfg); + furi_assert(st); + + with_view_model( + instance->view, + GpioUsbUartModel * model, + { + model->baudrate = st->baudrate_cur; + model->vcp_port = cfg->vcp_ch; + model->tx_pin = (cfg->uart_ch == 0) ? (13) : (15); + model->rx_pin = (cfg->uart_ch == 0) ? (14) : (16); + model->tx_active = (model->tx_cnt != st->tx_cnt); + model->rx_active = (model->rx_cnt != st->rx_cnt); + model->tx_cnt = st->tx_cnt; + model->rx_cnt = st->rx_cnt; + }, + true); +} diff --git a/views/gpio_usb_uart.h b/views/gpio_usb_uart.h new file mode 100644 index 00000000000..854b51f8d7a --- /dev/null +++ b/views/gpio_usb_uart.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include "../gpio_custom_event.h" +#include "../usb_uart_bridge.h" + +typedef struct GpioUsbUart GpioUsbUart; +typedef void (*GpioUsbUartCallback)(GpioCustomEvent event, void* context); + +GpioUsbUart* gpio_usb_uart_alloc(); + +void gpio_usb_uart_free(GpioUsbUart* usb_uart); + +View* gpio_usb_uart_get_view(GpioUsbUart* usb_uart); + +void gpio_usb_uart_set_callback(GpioUsbUart* usb_uart, GpioUsbUartCallback callback, void* context); + +void gpio_usb_uart_update_state(GpioUsbUart* instance, UsbUartConfig* cfg, UsbUartState* st);