├── .gitattributes ├── README.md ├── application.fam ├── enc_reader.c ├── enc_reader.h └── images └── enc_reader_logo_10px.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enc reader 2 | 3 | This is a simple app for flipper that can read incremental quadrature encoders and show absolute and relative coordinate. 4 | 5 | ## Pinouts 6 | 7 | - 5V - supply 8 | - A4 - enc A 9 | - A7 - enc B 10 | 11 | ## Configutarion menu 12 | 13 | - Start: run reader 14 | - Resolution: set coordinate showing option (raw pulses or mm) 15 | - 5V supply: set the 5V output state 16 | 17 | ## Main screen 18 | 19 | Shows configuration at top and two position values (absolute and relative). 20 | 21 | 5V output enabled only in this mode. 22 | 23 | ### Controls 24 | 25 | - Left/Back: return to configuration menu 26 | - Ok: set current position as origin 27 | - Right: set all coordinates to 0 -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="enc_reader", 3 | name="Encoder reader", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="enc_reader_app", 6 | cdefines=["ENC_READER"], 7 | requires=[ 8 | "gui", 9 | "notifications", 10 | ], 11 | stack_size=1 * 1024, 12 | order=90, 13 | fap_icon_assets="images", 14 | fap_icon="images/enc_reader_logo_10px.png", 15 | fap_category="GPIO", 16 | ) -------------------------------------------------------------------------------- /enc_reader.c: -------------------------------------------------------------------------------- 1 | #include "enc_reader.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | static void app_change_view(EncApp* app, ViewIndex index); 18 | 19 | /******************************************************************* 20 | * INPUT / OUTPUT FUNCTIONS 21 | *******************************************************************/ 22 | static void enc_reader_app_interrupt_callback(void* context) { 23 | furi_assert(context); 24 | EncApp* app = context; 25 | 26 | if (furi_hal_gpio_read(app->input_pin.b)) app->coordinates.abs++; 27 | else app->coordinates.abs--; 28 | } 29 | 30 | static void enc_reader_setup_gpio_inputs(EncApp* app) { 31 | app->input_pin.a = &gpio_ext_pa4; 32 | app->input_pin.b = &gpio_ext_pa7; 33 | 34 | furi_hal_gpio_init(app->input_pin.a, GpioModeInterruptFall, GpioPullUp, GpioSpeedVeryHigh); 35 | furi_hal_gpio_init(app->input_pin.b, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh); 36 | 37 | furi_hal_gpio_add_int_callback(app->input_pin.a, enc_reader_app_interrupt_callback, app); 38 | furi_hal_gpio_enable_int_callback(app->input_pin.a); 39 | } 40 | 41 | static void enc_reader_update_vbus_state(VbusState state) { 42 | 43 | if (state == VbusON) { 44 | furi_hal_power_enable_otg(); 45 | } else if (state == VbusOFF) { 46 | furi_hal_power_disable_otg(); 47 | } 48 | } 49 | 50 | /******************************************************************* 51 | * SETTINGS VIEW 52 | *******************************************************************/ 53 | static void variable_item_enter_callback(void* context, uint32_t index) { 54 | furi_assert(context); 55 | EncApp* app = context; 56 | if (index == ItemIndexStart) { 57 | //if(index == VbusON) {notification_message(app->notifications, &vOn_led_sequence);} 58 | //else if(index == VbusOFF) {notification_message(app->notifications, &vOff_led_sequence);} 59 | app_change_view(app, ViewIndexMain); 60 | } 61 | } 62 | 63 | static void variable_item_change_callback(VariableItem* item) { 64 | EncApp* app = variable_item_get_context(item); 65 | uint8_t index = variable_item_get_current_value_index(item); 66 | 67 | if (item == app->var_item[ItemIndexResolution]) { 68 | variable_item_set_current_value_text(item, resolution_list[index].text); 69 | 70 | app->resolution = index; 71 | 72 | } else if (item == app->var_item[ItemIndexVbusState]) { 73 | 74 | variable_item_set_current_value_text(item, vbus_state_list[index]); 75 | app->Vbus_state = index; 76 | //enc_reader_update_vbus_state(index); 77 | } 78 | 79 | 80 | } 81 | 82 | static void app_view_settings_alloc(EncApp* app) { 83 | 84 | app->var_item_list = variable_item_list_alloc(); 85 | 86 | variable_item_list_set_enter_callback(app->var_item_list, variable_item_enter_callback, app); 87 | 88 | app->var_item[ItemIndexStart] = variable_item_list_add(app->var_item_list, "Start", 0, NULL, NULL); 89 | app->var_item[ItemIndexResolution] = variable_item_list_add(app->var_item_list, "Resolution", ResolutionMAX, variable_item_change_callback, app); 90 | app->var_item[ItemIndexVbusState] = variable_item_list_add(app->var_item_list, "5V supply", VbusStatesNum, variable_item_change_callback, app); 91 | 92 | variable_item_set_current_value_text(app->var_item[ItemIndexResolution], resolution_list[app->resolution].text); 93 | variable_item_set_current_value_text(app->var_item[ItemIndexVbusState], vbus_state_list[app->Vbus_state]); 94 | } 95 | 96 | static void app_view_settings_free(EncApp* app) { 97 | furi_assert(app); 98 | variable_item_list_free(app->var_item_list); 99 | } 100 | 101 | /******************************************************************* 102 | * MAIN VIEW 103 | *******************************************************************/ 104 | static void enc_reader_app_button_callback(GuiButtonType button_type, InputType input_type, void* context) { 105 | furi_assert(context); 106 | EncApp* app = context; 107 | // Only request the view switch if the user short-presses the Center button. 108 | if (input_type == InputTypeShort) { 109 | if (button_type == GuiButtonTypeLeft) { 110 | app_change_view(app, ViewIndexSettings); 111 | } else if (button_type == GuiButtonTypeCenter) { 112 | app->coordinates.org = app->coordinates.abs; 113 | notification_message(app->notifications, &button_led_sequence); 114 | } else if (button_type == GuiButtonTypeRight) { 115 | app->coordinates.org = 0; 116 | app->coordinates.abs = 0; 117 | notification_message(app->notifications, &button_led_sequence); 118 | } 119 | } 120 | } 121 | 122 | static void app_view_main_alloc(EncApp* app) { 123 | app->widget = widget_alloc(); 124 | } 125 | 126 | static void app_view_main_free(EncApp* app) { 127 | furi_assert(app); 128 | widget_free(app->widget); 129 | } 130 | 131 | /******************************************************************* 132 | * VIEW DISPATCHER FUNCTIONS 133 | *******************************************************************/ 134 | static void app_change_view(EncApp* app, ViewIndex index) { 135 | if (index < ViewIndexMAX) { 136 | app->current_view = index; 137 | enc_reader_update_vbus_state(index == ViewIndexMain ? app->Vbus_state : VbusOFF); 138 | view_dispatcher_switch_to_view(app->view_dispatcher, index); 139 | } 140 | } 141 | 142 | static void enc_reader_app_tick_event_callback(void* context) { 143 | furi_assert(context); 144 | EncApp* app = context; 145 | 146 | if (app->current_view == ViewIndexMain) { 147 | 148 | static const uint8_t screen_width = 128; 149 | static const uint8_t offset_vertical = 2; 150 | static const uint8_t offset_horizontal = 2; 151 | static const uint8_t offset_bottom = 14; 152 | static const uint8_t gap = 16; 153 | 154 | app->coordinates.rel = app->coordinates.abs - app->coordinates.org; 155 | 156 | char string_abs[] = "00000000 "; 157 | char string_rel[] = "00000000 "; 158 | 159 | int32_t asb_pos = app->coordinates.abs / resolution_list[app->resolution].divider; 160 | int32_t rel_pos = app->coordinates.rel / resolution_list[app->resolution].divider; 161 | 162 | snprintf(string_abs, strlen(string_abs), "%8d", (int)asb_pos); 163 | snprintf(string_rel, strlen(string_rel), "%8d", (int)rel_pos); 164 | 165 | widget_reset(app->widget); 166 | 167 | widget_add_frame_element(app->widget, 0, 0, screen_width, 64 - offset_bottom, 2); 168 | 169 | widget_add_string_element(app->widget, offset_horizontal, offset_vertical, AlignLeft, AlignTop, FontSecondary, app->Vbus_state == VbusON ? "5V ON " : "5V OFF"); 170 | widget_add_string_element(app->widget, screen_width - offset_horizontal, offset_vertical, AlignRight, AlignTop, FontSecondary, resolution_list[app->resolution].text); 171 | 172 | widget_add_string_element(app->widget, offset_horizontal, offset_vertical + gap, AlignLeft, AlignTop, FontPrimary, "Abs:"); 173 | widget_add_string_element(app->widget, offset_horizontal, offset_vertical + gap * 2, AlignLeft, AlignTop, FontPrimary, "Rel:"); 174 | 175 | widget_add_string_element(app->widget, screen_width - offset_horizontal, offset_vertical + gap, AlignRight, AlignTop, FontBigNumbers, string_abs); 176 | widget_add_string_element(app->widget, screen_width - offset_horizontal, offset_vertical + gap * 2, AlignRight, AlignTop, FontBigNumbers, string_rel); 177 | 178 | widget_add_button_element(app->widget, GuiButtonTypeLeft, "Config", enc_reader_app_button_callback, app); 179 | widget_add_button_element(app->widget, GuiButtonTypeCenter, "Org", enc_reader_app_button_callback, app); 180 | widget_add_button_element(app->widget, GuiButtonTypeRight, "Reset", enc_reader_app_button_callback, app); 181 | 182 | } 183 | 184 | } 185 | 186 | static bool enc_reader_app_navigation_callback(void* context) { // This function is called when the user has pressed the Back key. 187 | furi_assert(context); 188 | EncApp* app = context; 189 | 190 | if (app->current_view == ViewIndexMain) { 191 | app_change_view(app, ViewIndexSettings); 192 | } else { 193 | view_dispatcher_stop(app->view_dispatcher); 194 | } 195 | return true; 196 | } 197 | 198 | static void app_view_dispatcher_alloc(EncApp* app) { 199 | app->view_dispatcher = view_dispatcher_alloc(); 200 | 201 | view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); 202 | 203 | view_dispatcher_add_view(app->view_dispatcher, ViewIndexMain, widget_get_view(app->widget)); 204 | view_dispatcher_add_view(app->view_dispatcher, ViewIndexSettings, variable_item_list_get_view(app->var_item_list)); 205 | 206 | view_dispatcher_set_tick_event_callback(app->view_dispatcher, enc_reader_app_tick_event_callback, 40); 207 | view_dispatcher_set_navigation_event_callback(app->view_dispatcher, enc_reader_app_navigation_callback); 208 | view_dispatcher_set_event_callback_context(app->view_dispatcher, app); 209 | } 210 | 211 | static void app_view_dispatcher_free(EncApp* app) { 212 | furi_assert(app); 213 | 214 | view_dispatcher_remove_view(app->view_dispatcher, ViewIndexSettings); 215 | view_dispatcher_remove_view(app->view_dispatcher, ViewIndexMain); 216 | 217 | view_dispatcher_free(app->view_dispatcher); 218 | } 219 | 220 | /******************************************************************* 221 | * ALLOC FUNCTIONS 222 | *******************************************************************/ 223 | EncApp* enc_reader_app_alloc() { 224 | EncApp* app = malloc(sizeof(EncApp)); 225 | 226 | app->gui = furi_record_open(RECORD_GUI); 227 | //app->view_port = view_port_alloc(); 228 | //app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); 229 | 230 | enc_reader_setup_gpio_inputs(app); 231 | app_view_settings_alloc(app); 232 | app_view_main_alloc(app); 233 | app_view_dispatcher_alloc(app); 234 | 235 | //gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); 236 | 237 | //view_port_draw_callback_set(app->view_port, enc_reader_app_draw_callback, app); 238 | //view_port_input_callback_set(app->view_port, enc_reader_app_input_callback, app->event_queue); 239 | 240 | app->notifications = furi_record_open(RECORD_NOTIFICATION); 241 | 242 | return app; 243 | } 244 | 245 | void enc_reader_app_free(EncApp* app) { 246 | furi_assert(app); 247 | 248 | app_view_dispatcher_free(app); 249 | app_view_settings_free(app); 250 | app_view_main_free(app); 251 | 252 | 253 | furi_hal_gpio_disable_int_callback(app->input_pin.a); 254 | furi_hal_gpio_remove_int_callback(app->input_pin.a); 255 | 256 | furi_record_close(RECORD_NOTIFICATION); 257 | furi_record_close(RECORD_GUI); 258 | } 259 | 260 | static void enc_reader_app_run(EncApp* app) { 261 | 262 | app->resolution = ResolutionRaw; 263 | app->Vbus_state = VbusOFF; 264 | 265 | app_change_view(app, ViewIndexSettings); 266 | 267 | variable_item_set_current_value_index(app->var_item[ItemIndexResolution], app->resolution); 268 | variable_item_set_current_value_index(app->var_item[ItemIndexVbusState], app->Vbus_state); 269 | 270 | view_dispatcher_run(app->view_dispatcher); 271 | } 272 | 273 | /******************************************************************* 274 | * vvv START HERE vvv 275 | *******************************************************************/ 276 | int32_t enc_reader_app(void *p) { 277 | UNUSED(p); 278 | EncApp* app = enc_reader_app_alloc(); 279 | enc_reader_app_run(app); 280 | furi_hal_power_disable_otg(); 281 | enc_reader_app_free(app); 282 | return 0; 283 | } -------------------------------------------------------------------------------- /enc_reader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | const NotificationSequence button_led_sequence = { 15 | &message_blue_255, 16 | &message_vibro_on, 17 | &message_delay_25, 18 | &message_vibro_off, 19 | &message_delay_250, 20 | &message_blue_0, 21 | NULL, 22 | }; 23 | const NotificationSequence vOn_led_sequence = { 24 | &message_green_255, 25 | &message_vibro_on, 26 | &message_delay_25, 27 | &message_vibro_off, 28 | &message_delay_100, 29 | &message_vibro_on, 30 | &message_delay_25, 31 | &message_vibro_off, 32 | NULL, 33 | }; 34 | const NotificationSequence vOff_led_sequence = { 35 | &message_red_255, 36 | &message_vibro_on, 37 | &message_delay_100, 38 | &message_delay_50, 39 | &message_vibro_off, 40 | NULL, 41 | }; 42 | 43 | // Enumeration of the view indexes. 44 | typedef enum { 45 | ViewIndexSettings, 46 | ViewIndexMain, 47 | ViewIndexMAX, 48 | } ViewIndex; 49 | 50 | // Enumeration of submenu items. 51 | typedef enum { 52 | ItemIndexStart, 53 | ItemIndexResolution, 54 | ItemIndexVbusState, 55 | ItemIndexMAX, 56 | } ItemIndex; 57 | 58 | typedef enum { 59 | ResolutionRaw, 60 | Resolution50, 61 | Resolution100, 62 | Resolution200, 63 | ResolutionMAX, 64 | } ResolutionIndex; 65 | 66 | typedef struct { 67 | int32_t divider; 68 | char* text; 69 | } resolution_t; 70 | 71 | const resolution_t resolution_list[] = { 72 | {1, "Raw"}, 73 | {50, "50pul"}, 74 | {100, "100pul"}, 75 | {200, "200pul"}, 76 | }; 77 | 78 | typedef enum { 79 | VbusOFF, 80 | VbusON, 81 | VbusStatesNum, 82 | } VbusState; 83 | 84 | const char* const vbus_state_list[] = { 85 | "OFF", 86 | "ON", 87 | }; 88 | 89 | typedef enum { 90 | IndexWidgetAbs, 91 | IndexWidgetRel, 92 | IndexWidgetMAX, 93 | } WidgetIndex; 94 | 95 | typedef void (*MainViewOkCallback)(InputType type, void* context); 96 | 97 | 98 | typedef struct EncApp{ 99 | Gui* gui; 100 | NotificationApp* notifications; 101 | 102 | ViewDispatcher* view_dispatcher; 103 | 104 | //View* main_view; 105 | 106 | Widget* widget; 107 | WidgetElement* widget_element[IndexWidgetMAX]; 108 | 109 | VariableItemList* var_item_list; 110 | VariableItem* var_item[ItemIndexMAX]; 111 | 112 | struct { 113 | const GpioPin* a; 114 | const GpioPin* b; 115 | } input_pin; 116 | 117 | struct { 118 | int32_t abs; 119 | int32_t org; 120 | int32_t rel; 121 | } coordinates; 122 | 123 | VbusState Vbus_state; 124 | ResolutionIndex resolution; 125 | ViewIndex current_view; 126 | 127 | } EncApp; -------------------------------------------------------------------------------- /images/enc_reader_logo_10px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Engineegor/Flipper_enc_reader/6f3e8ff73cfd61089ed5034a538bd6b14583f4fe/images/enc_reader_logo_10px.png --------------------------------------------------------------------------------