[FL-2797] Signal Generator app (#1793)
* Signal Generator app * MCO pin initialization in app * furi_hal_pwm documentation Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									12a6290e91
								
							
						
					
					
						commit
						4241ad24a3
					
				
							
								
								
									
										12
									
								
								applications/plugins/signal_generator/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								applications/plugins/signal_generator/application.fam
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | App( | ||||||
|  |     appid="signal_generator", | ||||||
|  |     name="Signal Generator", | ||||||
|  |     apptype=FlipperAppType.PLUGIN, | ||||||
|  |     entry_point="signal_gen_app", | ||||||
|  |     cdefines=["APP_SIGNAL_GEN"], | ||||||
|  |     requires=["gui"], | ||||||
|  |     stack_size=1 * 1024, | ||||||
|  |     order=50, | ||||||
|  |     fap_icon="signal_gen_10px.png", | ||||||
|  |     fap_category="Tools", | ||||||
|  | ) | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | #include "../signal_gen_app_i.h" | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers array
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, | ||||||
|  | void (*const signal_gen_scene_on_enter_handlers[])(void*) = { | ||||||
|  | #include "signal_gen_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 signal_gen_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { | ||||||
|  | #include "signal_gen_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 signal_gen_scene_on_exit_handlers[])(void* context) = { | ||||||
|  | #include "signal_gen_scene_config.h" | ||||||
|  | }; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | // Initialize scene handlers configuration structure
 | ||||||
|  | const SceneManagerHandlers signal_gen_scene_handlers = { | ||||||
|  |     .on_enter_handlers = signal_gen_scene_on_enter_handlers, | ||||||
|  |     .on_event_handlers = signal_gen_scene_on_event_handlers, | ||||||
|  |     .on_exit_handlers = signal_gen_scene_on_exit_handlers, | ||||||
|  |     .scene_num = SignalGenSceneNum, | ||||||
|  | }; | ||||||
| @ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | 
 | ||||||
|  | // Generate scene id and total number
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) SignalGenScene##id, | ||||||
|  | typedef enum { | ||||||
|  | #include "signal_gen_scene_config.h" | ||||||
|  |     SignalGenSceneNum, | ||||||
|  | } SignalGenScene; | ||||||
|  | #undef ADD_SCENE | ||||||
|  | 
 | ||||||
|  | extern const SceneManagerHandlers signal_gen_scene_handlers; | ||||||
|  | 
 | ||||||
|  | // Generate scene on_enter handlers declaration
 | ||||||
|  | #define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); | ||||||
|  | #include "signal_gen_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 "signal_gen_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 "signal_gen_scene_config.h" | ||||||
|  | #undef ADD_SCENE | ||||||
| @ -0,0 +1,3 @@ | |||||||
|  | ADD_SCENE(signal_gen, start, Start) | ||||||
|  | ADD_SCENE(signal_gen, pwm, Pwm) | ||||||
|  | ADD_SCENE(signal_gen, mco, Mco) | ||||||
| @ -0,0 +1,132 @@ | |||||||
|  | #include "../signal_gen_app_i.h" | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     LineIndexSource, | ||||||
|  |     LineIndexDivision, | ||||||
|  | } LineIndex; | ||||||
|  | 
 | ||||||
|  | static const char* const mco_source_names[] = { | ||||||
|  |     "32768", | ||||||
|  |     "64MHz", | ||||||
|  |     "~100K", | ||||||
|  |     "~200K", | ||||||
|  |     "~400K", | ||||||
|  |     "~800K", | ||||||
|  |     "~1MHz", | ||||||
|  |     "~2MHz", | ||||||
|  |     "~4MHz", | ||||||
|  |     "~8MHz", | ||||||
|  |     "~16MHz", | ||||||
|  |     "~24MHz", | ||||||
|  |     "~32MHz", | ||||||
|  |     "~48MHz", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const FuriHalClockMcoSourceId mco_sources[] = { | ||||||
|  |     FuriHalClockMcoLse, | ||||||
|  |     FuriHalClockMcoSysclk, | ||||||
|  |     FuriHalClockMcoMsi100k, | ||||||
|  |     FuriHalClockMcoMsi200k, | ||||||
|  |     FuriHalClockMcoMsi400k, | ||||||
|  |     FuriHalClockMcoMsi800k, | ||||||
|  |     FuriHalClockMcoMsi1m, | ||||||
|  |     FuriHalClockMcoMsi2m, | ||||||
|  |     FuriHalClockMcoMsi4m, | ||||||
|  |     FuriHalClockMcoMsi8m, | ||||||
|  |     FuriHalClockMcoMsi16m, | ||||||
|  |     FuriHalClockMcoMsi24m, | ||||||
|  |     FuriHalClockMcoMsi32m, | ||||||
|  |     FuriHalClockMcoMsi48m, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const char* const mco_divisor_names[] = { | ||||||
|  |     "1", | ||||||
|  |     "2", | ||||||
|  |     "4", | ||||||
|  |     "8", | ||||||
|  |     "16", | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const FuriHalClockMcoDivisorId mco_divisors[] = { | ||||||
|  |     FuriHalClockMcoDiv1, | ||||||
|  |     FuriHalClockMcoDiv2, | ||||||
|  |     FuriHalClockMcoDiv4, | ||||||
|  |     FuriHalClockMcoDiv8, | ||||||
|  |     FuriHalClockMcoDiv16, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void mco_source_list_change_callback(VariableItem* item) { | ||||||
|  |     SignalGenApp* app = variable_item_get_context(item); | ||||||
|  |     uint8_t index = variable_item_get_current_value_index(item); | ||||||
|  |     variable_item_set_current_value_text(item, mco_source_names[index]); | ||||||
|  | 
 | ||||||
|  |     app->mco_src = mco_sources[index]; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void mco_divisor_list_change_callback(VariableItem* item) { | ||||||
|  |     SignalGenApp* app = variable_item_get_context(item); | ||||||
|  |     uint8_t index = variable_item_get_current_value_index(item); | ||||||
|  |     variable_item_set_current_value_text(item, mco_divisor_names[index]); | ||||||
|  | 
 | ||||||
|  |     app->mco_div = mco_divisors[index]; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenMcoEventUpdate); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_scene_mco_on_enter(void* context) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     VariableItemList* var_item_list = app->var_item_list; | ||||||
|  | 
 | ||||||
|  |     VariableItem* item; | ||||||
|  | 
 | ||||||
|  |     item = variable_item_list_add( | ||||||
|  |         var_item_list, "Source", COUNT_OF(mco_source_names), mco_source_list_change_callback, app); | ||||||
|  |     variable_item_set_current_value_index(item, 0); | ||||||
|  |     variable_item_set_current_value_text(item, mco_source_names[0]); | ||||||
|  | 
 | ||||||
|  |     item = variable_item_list_add( | ||||||
|  |         var_item_list, | ||||||
|  |         "Division", | ||||||
|  |         COUNT_OF(mco_divisor_names), | ||||||
|  |         mco_divisor_list_change_callback, | ||||||
|  |         app); | ||||||
|  |     variable_item_set_current_value_index(item, 0); | ||||||
|  |     variable_item_set_current_value_text(item, mco_divisor_names[0]); | ||||||
|  | 
 | ||||||
|  |     variable_item_list_set_selected_item(var_item_list, LineIndexSource); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewVarItemList); | ||||||
|  | 
 | ||||||
|  |     app->mco_src = FuriHalClockMcoLse; | ||||||
|  |     app->mco_div = FuriHalClockMcoDiv1; | ||||||
|  |     furi_hal_clock_mco_enable(app->mco_src, app->mco_div); | ||||||
|  |     furi_hal_gpio_init_ex( | ||||||
|  |         &gpio_usart_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedVeryHigh, GpioAltFn0MCO); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool signal_gen_scene_mco_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == SignalGenMcoEventUpdate) { | ||||||
|  |             consumed = true; | ||||||
|  |             furi_hal_clock_mco_enable(app->mco_src, app->mco_div); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_scene_mco_on_exit(void* context) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     variable_item_list_reset(app->var_item_list); | ||||||
|  |     furi_hal_gpio_init_ex( | ||||||
|  |         &gpio_usart_tx, | ||||||
|  |         GpioModeAltFunctionPushPull, | ||||||
|  |         GpioPullUp, | ||||||
|  |         GpioSpeedVeryHigh, | ||||||
|  |         GpioAltFn7USART1); | ||||||
|  |     furi_hal_clock_mco_disable(); | ||||||
|  | } | ||||||
| @ -0,0 +1,60 @@ | |||||||
|  | #include "../signal_gen_app_i.h" | ||||||
|  | 
 | ||||||
|  | static const FuriHalPwmOutputId pwm_ch_id[] = { | ||||||
|  |     FuriHalPwmOutputIdTim1PA7, | ||||||
|  |     FuriHalPwmOutputIdLptim2PA4, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #define DEFAULT_FREQ 1000 | ||||||
|  | #define DEFAULT_DUTY 50 | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  |     signal_gen_pwm_callback(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  | 
 | ||||||
|  |     app->pwm_freq = freq; | ||||||
|  |     app->pwm_duty = duty; | ||||||
|  | 
 | ||||||
|  |     if(app->pwm_ch != pwm_ch_id[channel_id]) { | ||||||
|  |         app->pwm_ch_prev = app->pwm_ch; | ||||||
|  |         app->pwm_ch = pwm_ch_id[channel_id]; | ||||||
|  |         view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventChannelChange); | ||||||
|  |     } else { | ||||||
|  |         app->pwm_ch = pwm_ch_id[channel_id]; | ||||||
|  |         view_dispatcher_send_custom_event(app->view_dispatcher, SignalGenPwmEventUpdate); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_scene_pwm_on_enter(void* context) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewPwm); | ||||||
|  | 
 | ||||||
|  |     signal_gen_pwm_set_callback(app->pwm_view, signal_gen_pwm_callback, app); | ||||||
|  | 
 | ||||||
|  |     signal_gen_pwm_set_params(app->pwm_view, 0, DEFAULT_FREQ, DEFAULT_DUTY); | ||||||
|  |     furi_hal_pwm_start(pwm_ch_id[0], DEFAULT_FREQ, DEFAULT_DUTY); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool signal_gen_scene_pwm_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == SignalGenPwmEventUpdate) { | ||||||
|  |             consumed = true; | ||||||
|  |             furi_hal_pwm_set_params(app->pwm_ch, app->pwm_freq, app->pwm_duty); | ||||||
|  |         } else if(event.event == SignalGenPwmEventChannelChange) { | ||||||
|  |             consumed = true; | ||||||
|  |             furi_hal_pwm_stop(app->pwm_ch_prev); | ||||||
|  |             furi_hal_pwm_start(app->pwm_ch, app->pwm_freq, app->pwm_duty); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_scene_pwm_on_exit(void* context) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     variable_item_list_reset(app->var_item_list); | ||||||
|  |     furi_hal_pwm_stop(app->pwm_ch); | ||||||
|  | } | ||||||
| @ -0,0 +1,55 @@ | |||||||
|  | #include "../signal_gen_app_i.h" | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     SubmenuIndexPwm, | ||||||
|  |     SubmenuIndexClockOutput, | ||||||
|  | } SubmenuIndex; | ||||||
|  | 
 | ||||||
|  | void signal_gen_scene_start_submenu_callback(void* context, uint32_t index) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_send_custom_event(app->view_dispatcher, index); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_scene_start_on_enter(void* context) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     Submenu* submenu = app->submenu; | ||||||
|  | 
 | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, "PWM", SubmenuIndexPwm, signal_gen_scene_start_submenu_callback, app); | ||||||
|  |     submenu_add_item( | ||||||
|  |         submenu, | ||||||
|  |         "Clock Output", | ||||||
|  |         SubmenuIndexClockOutput, | ||||||
|  |         signal_gen_scene_start_submenu_callback, | ||||||
|  |         app); | ||||||
|  | 
 | ||||||
|  |     submenu_set_selected_item( | ||||||
|  |         submenu, scene_manager_get_scene_state(app->scene_manager, SignalGenSceneStart)); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(app->view_dispatcher, SignalGenViewSubmenu); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool signal_gen_scene_start_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == SubmenuIndexPwm) { | ||||||
|  |             scene_manager_next_scene(app->scene_manager, SignalGenScenePwm); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexClockOutput) { | ||||||
|  |             scene_manager_next_scene(app->scene_manager, SignalGenSceneMco); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |         scene_manager_set_scene_state(app->scene_manager, SignalGenSceneStart, event.event); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_scene_start_on_exit(void* context) { | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  | 
 | ||||||
|  |     submenu_reset(app->submenu); | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								applications/plugins/signal_generator/signal_gen_10px.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								applications/plugins/signal_generator/signal_gen_10px.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.9 KiB | 
							
								
								
									
										93
									
								
								applications/plugins/signal_generator/signal_gen_app.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								applications/plugins/signal_generator/signal_gen_app.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | #include "signal_gen_app_i.h" | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | #include <furi_hal.h> | ||||||
|  | 
 | ||||||
|  | static bool signal_gen_app_custom_event_callback(void* context, uint32_t event) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     return scene_manager_handle_custom_event(app->scene_manager, event); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool signal_gen_app_back_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     return scene_manager_handle_back_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void signal_gen_app_tick_event_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     SignalGenApp* app = context; | ||||||
|  |     scene_manager_handle_tick_event(app->scene_manager); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SignalGenApp* signal_gen_app_alloc() { | ||||||
|  |     SignalGenApp* app = malloc(sizeof(SignalGenApp)); | ||||||
|  | 
 | ||||||
|  |     app->gui = furi_record_open(RECORD_GUI); | ||||||
|  | 
 | ||||||
|  |     app->view_dispatcher = view_dispatcher_alloc(); | ||||||
|  |     app->scene_manager = scene_manager_alloc(&signal_gen_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, signal_gen_app_custom_event_callback); | ||||||
|  |     view_dispatcher_set_navigation_event_callback( | ||||||
|  |         app->view_dispatcher, signal_gen_app_back_event_callback); | ||||||
|  |     view_dispatcher_set_tick_event_callback( | ||||||
|  |         app->view_dispatcher, signal_gen_app_tick_event_callback, 100); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); | ||||||
|  | 
 | ||||||
|  |     app->var_item_list = variable_item_list_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, | ||||||
|  |         SignalGenViewVarItemList, | ||||||
|  |         variable_item_list_get_view(app->var_item_list)); | ||||||
|  | 
 | ||||||
|  |     app->submenu = submenu_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, SignalGenViewSubmenu, submenu_get_view(app->submenu)); | ||||||
|  | 
 | ||||||
|  |     app->pwm_view = signal_gen_pwm_alloc(); | ||||||
|  |     view_dispatcher_add_view( | ||||||
|  |         app->view_dispatcher, SignalGenViewPwm, signal_gen_pwm_get_view(app->pwm_view)); | ||||||
|  | 
 | ||||||
|  |     scene_manager_next_scene(app->scene_manager, SignalGenSceneStart); | ||||||
|  | 
 | ||||||
|  |     return app; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_app_free(SignalGenApp* app) { | ||||||
|  |     furi_assert(app); | ||||||
|  | 
 | ||||||
|  |     // Views
 | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewVarItemList); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewSubmenu); | ||||||
|  |     view_dispatcher_remove_view(app->view_dispatcher, SignalGenViewPwm); | ||||||
|  | 
 | ||||||
|  |     submenu_free(app->submenu); | ||||||
|  |     variable_item_list_free(app->var_item_list); | ||||||
|  |     signal_gen_pwm_free(app->pwm_view); | ||||||
|  | 
 | ||||||
|  |     // View dispatcher
 | ||||||
|  |     view_dispatcher_free(app->view_dispatcher); | ||||||
|  |     scene_manager_free(app->scene_manager); | ||||||
|  | 
 | ||||||
|  |     // Close records
 | ||||||
|  |     furi_record_close(RECORD_GUI); | ||||||
|  | 
 | ||||||
|  |     free(app); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int32_t signal_gen_app(void* p) { | ||||||
|  |     UNUSED(p); | ||||||
|  |     SignalGenApp* signal_gen_app = signal_gen_app_alloc(); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_run(signal_gen_app->view_dispatcher); | ||||||
|  | 
 | ||||||
|  |     signal_gen_app_free(signal_gen_app); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								applications/plugins/signal_generator/signal_gen_app_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								applications/plugins/signal_generator/signal_gen_app_i.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include "scenes/signal_gen_scene.h" | ||||||
|  | 
 | ||||||
|  | #include "furi_hal_clock.h" | ||||||
|  | #include "furi_hal_pwm.h" | ||||||
|  | 
 | ||||||
|  | #include <gui/gui.h> | ||||||
|  | #include <gui/view_dispatcher.h> | ||||||
|  | #include <gui/scene_manager.h> | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | #include <gui/modules/variable_item_list.h> | ||||||
|  | #include <gui/modules/submenu.h> | ||||||
|  | #include "views/signal_gen_pwm.h" | ||||||
|  | 
 | ||||||
|  | typedef struct SignalGenApp SignalGenApp; | ||||||
|  | 
 | ||||||
|  | struct SignalGenApp { | ||||||
|  |     Gui* gui; | ||||||
|  |     ViewDispatcher* view_dispatcher; | ||||||
|  |     SceneManager* scene_manager; | ||||||
|  | 
 | ||||||
|  |     VariableItemList* var_item_list; | ||||||
|  |     Submenu* submenu; | ||||||
|  |     SignalGenPwm* pwm_view; | ||||||
|  | 
 | ||||||
|  |     FuriHalClockMcoSourceId mco_src; | ||||||
|  |     FuriHalClockMcoDivisorId mco_div; | ||||||
|  | 
 | ||||||
|  |     FuriHalPwmOutputId pwm_ch_prev; | ||||||
|  |     FuriHalPwmOutputId pwm_ch; | ||||||
|  |     uint32_t pwm_freq; | ||||||
|  |     uint8_t pwm_duty; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     SignalGenViewVarItemList, | ||||||
|  |     SignalGenViewSubmenu, | ||||||
|  |     SignalGenViewPwm, | ||||||
|  | } SignalGenAppView; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     SignalGenMcoEventUpdate, | ||||||
|  |     SignalGenPwmEventUpdate, | ||||||
|  |     SignalGenPwmEventChannelChange, | ||||||
|  | } SignalGenCustomEvent; | ||||||
							
								
								
									
										301
									
								
								applications/plugins/signal_generator/views/signal_gen_pwm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								applications/plugins/signal_generator/views/signal_gen_pwm.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,301 @@ | |||||||
|  | #include "../signal_gen_app_i.h" | ||||||
|  | #include "furi_hal.h" | ||||||
|  | #include <gui/elements.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     LineIndexChannel, | ||||||
|  |     LineIndexFrequency, | ||||||
|  |     LineIndexDuty, | ||||||
|  |     LineIndexTotalCount | ||||||
|  | } LineIndex; | ||||||
|  | 
 | ||||||
|  | static const char* const pwm_ch_names[] = {"TIM1(2)", "LPTIM2(4)"}; | ||||||
|  | 
 | ||||||
|  | struct SignalGenPwm { | ||||||
|  |     View* view; | ||||||
|  |     SignalGenPwmViewCallback callback; | ||||||
|  |     void* context; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     LineIndex line_sel; | ||||||
|  |     bool edit_mode; | ||||||
|  |     uint8_t edit_digit; | ||||||
|  | 
 | ||||||
|  |     uint8_t channel_id; | ||||||
|  |     uint32_t freq; | ||||||
|  |     uint8_t duty; | ||||||
|  | 
 | ||||||
|  | } SignalGenPwmViewModel; | ||||||
|  | 
 | ||||||
|  | #define ITEM_H 64 / 3 | ||||||
|  | #define ITEM_W 128 | ||||||
|  | 
 | ||||||
|  | #define VALUE_X 95 | ||||||
|  | #define VALUE_W 55 | ||||||
|  | 
 | ||||||
|  | #define FREQ_VALUE_X 62 | ||||||
|  | #define FREQ_MAX 1000000UL | ||||||
|  | #define FREQ_DIGITS_NB 7 | ||||||
|  | 
 | ||||||
|  | static void pwm_set_config(SignalGenPwm* pwm) { | ||||||
|  |     FuriHalPwmOutputId channel; | ||||||
|  |     uint32_t freq; | ||||||
|  |     uint8_t duty; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         pwm->view, (SignalGenPwmViewModel * model) { | ||||||
|  |             channel = model->channel_id; | ||||||
|  |             freq = model->freq; | ||||||
|  |             duty = model->duty; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     furi_assert(pwm->callback); | ||||||
|  |     pwm->callback(channel, freq, duty, pwm->context); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void pwm_channel_change(SignalGenPwmViewModel* model, InputEvent* event) { | ||||||
|  |     if(event->key == InputKeyLeft) { | ||||||
|  |         if(model->channel_id > 0) { | ||||||
|  |             model->channel_id--; | ||||||
|  |         } | ||||||
|  |     } else if(event->key == InputKeyRight) { | ||||||
|  |         if(model->channel_id < (COUNT_OF(pwm_ch_names) - 1)) { | ||||||
|  |             model->channel_id++; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void pwm_duty_change(SignalGenPwmViewModel* model, InputEvent* event) { | ||||||
|  |     if(event->key == InputKeyLeft) { | ||||||
|  |         if(model->duty > 0) { | ||||||
|  |             model->duty--; | ||||||
|  |         } | ||||||
|  |     } else if(event->key == InputKeyRight) { | ||||||
|  |         if(model->duty < 100) { | ||||||
|  |             model->duty++; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool pwm_freq_edit(SignalGenPwmViewModel* model, InputEvent* event) { | ||||||
|  |     bool consumed = false; | ||||||
|  |     if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { | ||||||
|  |         if(event->key == InputKeyRight) { | ||||||
|  |             if(model->edit_digit > 0) { | ||||||
|  |                 model->edit_digit--; | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event->key == InputKeyLeft) { | ||||||
|  |             if(model->edit_digit < (FREQ_DIGITS_NB - 1)) { | ||||||
|  |                 model->edit_digit++; | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event->key == InputKeyUp) { | ||||||
|  |             uint32_t step = 1; | ||||||
|  |             for(uint8_t i = 0; i < model->edit_digit; i++) { | ||||||
|  |                 step *= 10; | ||||||
|  |             } | ||||||
|  |             if((model->freq + step) < FREQ_MAX) { | ||||||
|  |                 model->freq += step; | ||||||
|  |             } else { | ||||||
|  |                 model->freq = FREQ_MAX; | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event->key == InputKeyDown) { | ||||||
|  |             uint32_t step = 1; | ||||||
|  |             for(uint8_t i = 0; i < model->edit_digit; i++) { | ||||||
|  |                 step *= 10; | ||||||
|  |             } | ||||||
|  |             if(model->freq > (step + 1)) { | ||||||
|  |                 model->freq -= step; | ||||||
|  |             } else { | ||||||
|  |                 model->freq = 1; | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) { | ||||||
|  |     SignalGenPwmViewModel* model = _model; | ||||||
|  |     char* line_label = NULL; | ||||||
|  |     char val_text[16]; | ||||||
|  | 
 | ||||||
|  |     for(uint8_t line = 0; line < LineIndexTotalCount; line++) { | ||||||
|  |         if(line == LineIndexChannel) { | ||||||
|  |             line_label = "PWM Channel"; | ||||||
|  |         } else if(line == LineIndexFrequency) { | ||||||
|  |             line_label = "Frequency"; | ||||||
|  |         } else if(line == LineIndexDuty) { | ||||||
|  |             line_label = "Duty Cycle"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         canvas_set_color(canvas, ColorBlack); | ||||||
|  |         if(line == model->line_sel) { | ||||||
|  |             elements_slightly_rounded_box(canvas, 0, ITEM_H * line + 1, ITEM_W, ITEM_H - 1); | ||||||
|  |             canvas_set_color(canvas, ColorWhite); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         uint8_t text_y = ITEM_H * line + ITEM_H / 2 + 2; | ||||||
|  | 
 | ||||||
|  |         canvas_draw_str_aligned(canvas, 6, text_y, AlignLeft, AlignCenter, line_label); | ||||||
|  | 
 | ||||||
|  |         if(line == LineIndexChannel) { | ||||||
|  |             snprintf(val_text, sizeof(val_text), "%s", pwm_ch_names[model->channel_id]); | ||||||
|  |             canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text); | ||||||
|  |             if(model->channel_id != 0) { | ||||||
|  |                 canvas_draw_str_aligned( | ||||||
|  |                     canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<"); | ||||||
|  |             } | ||||||
|  |             if(model->channel_id != (COUNT_OF(pwm_ch_names) - 1)) { | ||||||
|  |                 canvas_draw_str_aligned( | ||||||
|  |                     canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">"); | ||||||
|  |             } | ||||||
|  |         } else if(line == LineIndexFrequency) { | ||||||
|  |             snprintf(val_text, sizeof(val_text), "%7lu Hz", model->freq); | ||||||
|  |             canvas_set_font(canvas, FontKeyboard); | ||||||
|  |             canvas_draw_str_aligned( | ||||||
|  |                 canvas, FREQ_VALUE_X, text_y, AlignLeft, AlignCenter, val_text); | ||||||
|  |             canvas_set_font(canvas, FontSecondary); | ||||||
|  | 
 | ||||||
|  |             if(model->edit_mode) { | ||||||
|  |                 uint8_t icon_x = (FREQ_VALUE_X - 1) + (FREQ_DIGITS_NB - model->edit_digit - 1) * 6; | ||||||
|  |                 canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_4x7); | ||||||
|  |                 canvas_draw_icon(canvas, icon_x, text_y + 4, &I_SmallArrowDown_4x7); | ||||||
|  |             } | ||||||
|  |         } else if(line == LineIndexDuty) { | ||||||
|  |             snprintf(val_text, sizeof(val_text), "%d%%", model->duty); | ||||||
|  |             canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text); | ||||||
|  |             if(model->duty != 0) { | ||||||
|  |                 canvas_draw_str_aligned( | ||||||
|  |                     canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<"); | ||||||
|  |             } | ||||||
|  |             if(model->duty != 100) { | ||||||
|  |                 canvas_draw_str_aligned( | ||||||
|  |                     canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool signal_gen_pwm_input_callback(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     SignalGenPwm* pwm = context; | ||||||
|  |     bool consumed = false; | ||||||
|  |     bool need_update = false; | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         pwm->view, (SignalGenPwmViewModel * model) { | ||||||
|  |             if(model->edit_mode == false) { | ||||||
|  |                 if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) { | ||||||
|  |                     if(event->key == InputKeyUp) { | ||||||
|  |                         if(model->line_sel == 0) { | ||||||
|  |                             model->line_sel = LineIndexTotalCount - 1; | ||||||
|  |                         } else { | ||||||
|  |                             model->line_sel = | ||||||
|  |                                 CLAMP(model->line_sel - 1, LineIndexTotalCount - 1, 0); | ||||||
|  |                         } | ||||||
|  |                         consumed = true; | ||||||
|  |                     } else if(event->key == InputKeyDown) { | ||||||
|  |                         if(model->line_sel == LineIndexTotalCount - 1) { | ||||||
|  |                             model->line_sel = 0; | ||||||
|  |                         } else { | ||||||
|  |                             model->line_sel = | ||||||
|  |                                 CLAMP(model->line_sel + 1, LineIndexTotalCount - 1, 0); | ||||||
|  |                         } | ||||||
|  |                         consumed = true; | ||||||
|  |                     } else if((event->key == InputKeyLeft) || (event->key == InputKeyRight)) { | ||||||
|  |                         if(model->line_sel == LineIndexChannel) { | ||||||
|  |                             pwm_channel_change(model, event); | ||||||
|  |                             need_update = true; | ||||||
|  |                         } else if(model->line_sel == LineIndexDuty) { | ||||||
|  |                             pwm_duty_change(model, event); | ||||||
|  |                             need_update = true; | ||||||
|  |                         } else if(model->line_sel == LineIndexFrequency) { | ||||||
|  |                             model->edit_mode = true; | ||||||
|  |                         } | ||||||
|  |                         consumed = true; | ||||||
|  |                     } else if(event->key == InputKeyOk) { | ||||||
|  |                         if(model->line_sel == LineIndexFrequency) { | ||||||
|  |                             model->edit_mode = true; | ||||||
|  |                         } | ||||||
|  |                         consumed = true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 if((event->key == InputKeyOk) || (event->key == InputKeyBack)) { | ||||||
|  |                     if(event->type == InputTypeShort) { | ||||||
|  |                         model->edit_mode = false; | ||||||
|  |                         consumed = true; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     if(model->line_sel == LineIndexFrequency) { | ||||||
|  |                         consumed = pwm_freq_edit(model, event); | ||||||
|  |                         need_update = consumed; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     if(need_update) { | ||||||
|  |         pwm_set_config(pwm); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | SignalGenPwm* signal_gen_pwm_alloc() { | ||||||
|  |     SignalGenPwm* pwm = malloc(sizeof(SignalGenPwm)); | ||||||
|  | 
 | ||||||
|  |     pwm->view = view_alloc(); | ||||||
|  |     view_allocate_model(pwm->view, ViewModelTypeLocking, sizeof(SignalGenPwmViewModel)); | ||||||
|  |     view_set_context(pwm->view, pwm); | ||||||
|  |     view_set_draw_callback(pwm->view, signal_gen_pwm_draw_callback); | ||||||
|  |     view_set_input_callback(pwm->view, signal_gen_pwm_input_callback); | ||||||
|  | 
 | ||||||
|  |     return pwm; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_pwm_free(SignalGenPwm* pwm) { | ||||||
|  |     furi_assert(pwm); | ||||||
|  |     view_free(pwm->view); | ||||||
|  |     free(pwm); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | View* signal_gen_pwm_get_view(SignalGenPwm* pwm) { | ||||||
|  |     furi_assert(pwm); | ||||||
|  |     return pwm->view; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_pwm_set_callback( | ||||||
|  |     SignalGenPwm* pwm, | ||||||
|  |     SignalGenPwmViewCallback callback, | ||||||
|  |     void* context) { | ||||||
|  |     furi_assert(pwm); | ||||||
|  |     furi_assert(callback); | ||||||
|  | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         pwm->view, (SignalGenPwmViewModel * model) { | ||||||
|  |             UNUSED(model); | ||||||
|  |             pwm->callback = callback; | ||||||
|  |             pwm->context = context; | ||||||
|  |             return false; | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty) { | ||||||
|  |     with_view_model( | ||||||
|  |         pwm->view, (SignalGenPwmViewModel * model) { | ||||||
|  |             model->channel_id = channel_id; | ||||||
|  |             model->freq = freq; | ||||||
|  |             model->duty = duty; | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     furi_assert(pwm->callback); | ||||||
|  |     pwm->callback(channel_id, freq, duty, pwm->context); | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								applications/plugins/signal_generator/views/signal_gen_pwm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								applications/plugins/signal_generator/views/signal_gen_pwm.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gui/view.h> | ||||||
|  | #include "../signal_gen_app_i.h" | ||||||
|  | 
 | ||||||
|  | typedef struct SignalGenPwm SignalGenPwm; | ||||||
|  | typedef void ( | ||||||
|  |     *SignalGenPwmViewCallback)(uint8_t channel_id, uint32_t freq, uint8_t duty, void* context); | ||||||
|  | 
 | ||||||
|  | SignalGenPwm* signal_gen_pwm_alloc(); | ||||||
|  | 
 | ||||||
|  | void signal_gen_pwm_free(SignalGenPwm* pwm); | ||||||
|  | 
 | ||||||
|  | View* signal_gen_pwm_get_view(SignalGenPwm* pwm); | ||||||
|  | 
 | ||||||
|  | void signal_gen_pwm_set_callback( | ||||||
|  |     SignalGenPwm* pwm, | ||||||
|  |     SignalGenPwmViewCallback callback, | ||||||
|  |     void* context); | ||||||
|  | 
 | ||||||
|  | void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty); | ||||||
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Interface/SmallArrowDown_4x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/icons/Interface/SmallArrowDown_4x7.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 8.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								assets/icons/Interface/SmallArrowUp_4x7.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/icons/Interface/SmallArrowUp_4x7.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 8.4 KiB | 
| @ -1,5 +1,5 @@ | |||||||
| entry,status,name,type,params | entry,status,name,type,params | ||||||
| Version,+,1.12,, | Version,+,1.13,, | ||||||
| Header,+,applications/services/bt/bt_service/bt.h,, | Header,+,applications/services/bt/bt_service/bt.h,, | ||||||
| Header,+,applications/services/cli/cli.h,, | Header,+,applications/services/cli/cli.h,, | ||||||
| Header,+,applications/services/cli/cli_vcp.h,, | Header,+,applications/services/cli/cli_vcp.h,, | ||||||
| @ -42,6 +42,7 @@ Header,+,firmware/targets/f7/furi_hal/furi_hal_i2c_types.h,, | |||||||
| Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, | Header,+,firmware/targets/f7/furi_hal/furi_hal_idle_timer.h,, | ||||||
| Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, | Header,+,firmware/targets/f7/furi_hal/furi_hal_interrupt.h,, | ||||||
| Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, | Header,+,firmware/targets/f7/furi_hal/furi_hal_os.h,, | ||||||
|  | Header,+,firmware/targets/f7/furi_hal/furi_hal_pwm.h,, | ||||||
| Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, | Header,+,firmware/targets/f7/furi_hal/furi_hal_resources.h,, | ||||||
| Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, | Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_config.h,, | ||||||
| Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, | Header,+,firmware/targets/f7/furi_hal/furi_hal_spi_types.h,, | ||||||
| @ -954,6 +955,8 @@ Function,+,furi_hal_cdc_set_callbacks,void,"uint8_t, CdcCallbacks*, void*" | |||||||
| Function,+,furi_hal_clock_deinit_early,void, | Function,+,furi_hal_clock_deinit_early,void, | ||||||
| Function,-,furi_hal_clock_init,void, | Function,-,furi_hal_clock_init,void, | ||||||
| Function,-,furi_hal_clock_init_early,void, | Function,-,furi_hal_clock_init_early,void, | ||||||
|  | Function,+,furi_hal_clock_mco_disable,void, | ||||||
|  | Function,+,furi_hal_clock_mco_enable,void,"FuriHalClockMcoSourceId, FuriHalClockMcoDivisorId" | ||||||
| Function,-,furi_hal_clock_resume_tick,void, | Function,-,furi_hal_clock_resume_tick,void, | ||||||
| Function,-,furi_hal_clock_suspend_tick,void, | Function,-,furi_hal_clock_suspend_tick,void, | ||||||
| Function,-,furi_hal_clock_switch_to_hsi,void, | Function,-,furi_hal_clock_switch_to_hsi,void, | ||||||
| @ -1150,6 +1153,9 @@ Function,+,furi_hal_power_sleep,void, | |||||||
| Function,+,furi_hal_power_sleep_available,_Bool, | Function,+,furi_hal_power_sleep_available,_Bool, | ||||||
| Function,+,furi_hal_power_suppress_charge_enter,void, | Function,+,furi_hal_power_suppress_charge_enter,void, | ||||||
| Function,+,furi_hal_power_suppress_charge_exit,void, | Function,+,furi_hal_power_suppress_charge_exit,void, | ||||||
|  | Function,+,furi_hal_pwm_set_params,void,"FuriHalPwmOutputId, uint32_t, uint8_t" | ||||||
|  | Function,+,furi_hal_pwm_start,void,"FuriHalPwmOutputId, uint32_t, uint8_t" | ||||||
|  | Function,+,furi_hal_pwm_stop,void,FuriHalPwmOutputId | ||||||
| Function,+,furi_hal_random_fill_buf,void,"uint8_t*, uint32_t" | Function,+,furi_hal_random_fill_buf,void,"uint8_t*, uint32_t" | ||||||
| Function,+,furi_hal_random_get,uint32_t, | Function,+,furi_hal_random_get,uint32_t, | ||||||
| Function,+,furi_hal_region_get,const FuriHalRegion*, | Function,+,furi_hal_region_get,const FuriHalRegion*, | ||||||
| @ -2665,6 +2671,8 @@ Variable,+,I_SDQuestion_35x43,const Icon, | |||||||
| Variable,+,I_SDcardFail_11x8,const Icon, | Variable,+,I_SDcardFail_11x8,const Icon, | ||||||
| Variable,+,I_SDcardMounted_11x8,const Icon, | Variable,+,I_SDcardMounted_11x8,const Icon, | ||||||
| Variable,+,I_Scanning_123x52,const Icon, | Variable,+,I_Scanning_123x52,const Icon, | ||||||
|  | Variable,+,I_SmallArrowDown_4x7,const Icon, | ||||||
|  | Variable,+,I_SmallArrowUp_4x7,const Icon, | ||||||
| Variable,+,I_Smile_18x18,const Icon, | Variable,+,I_Smile_18x18,const Icon, | ||||||
| Variable,+,I_Space_65x18,const Icon, | Variable,+,I_Space_65x18,const Icon, | ||||||
| Variable,+,I_Tap_reader_36x38,const Icon, | Variable,+,I_Tap_reader_36x38,const Icon, | ||||||
|  | |||||||
| 
 | 
| @ -1,4 +1,5 @@ | |||||||
| #include <furi_hal_clock.h> | #include <furi_hal_clock.h> | ||||||
|  | #include <furi_hal_resources.h> | ||||||
| #include <furi.h> | #include <furi.h> | ||||||
| 
 | 
 | ||||||
| #include <stm32wbxx_ll_pwr.h> | #include <stm32wbxx_ll_pwr.h> | ||||||
| @ -236,3 +237,63 @@ void furi_hal_clock_suspend_tick() { | |||||||
| void furi_hal_clock_resume_tick() { | void furi_hal_clock_resume_tick() { | ||||||
|     SET_BIT(SysTick->CTRL, SysTick_CTRL_ENABLE_Msk); |     SET_BIT(SysTick->CTRL, SysTick_CTRL_ENABLE_Msk); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div) { | ||||||
|  |     if(source == FuriHalClockMcoLse) { | ||||||
|  |         LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_LSE, div); | ||||||
|  |     } else if(source == FuriHalClockMcoSysclk) { | ||||||
|  |         LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_SYSCLK, div); | ||||||
|  |     } else { | ||||||
|  |         LL_RCC_MSI_Enable(); | ||||||
|  |         while(LL_RCC_MSI_IsReady() != 1) | ||||||
|  |             ; | ||||||
|  |         switch(source) { | ||||||
|  |         case FuriHalClockMcoMsi100k: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_0); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi200k: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_1); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi400k: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_2); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi800k: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_3); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi1m: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_4); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi2m: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_5); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi4m: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_6); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi8m: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_7); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi16m: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_8); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi24m: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_9); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi32m: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_10); | ||||||
|  |             break; | ||||||
|  |         case FuriHalClockMcoMsi48m: | ||||||
|  |             LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_11); | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_MSI, div); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void furi_hal_clock_mco_disable() { | ||||||
|  |     LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_NOCLOCK, FuriHalClockMcoDiv1); | ||||||
|  |     LL_RCC_MSI_Disable(); | ||||||
|  |     while(LL_RCC_MSI_IsReady() != 0) | ||||||
|  |         ; | ||||||
|  | } | ||||||
| @ -4,6 +4,33 @@ | |||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | #include <stm32wbxx_ll_rcc.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     FuriHalClockMcoLse, | ||||||
|  |     FuriHalClockMcoSysclk, | ||||||
|  |     FuriHalClockMcoMsi100k, | ||||||
|  |     FuriHalClockMcoMsi200k, | ||||||
|  |     FuriHalClockMcoMsi400k, | ||||||
|  |     FuriHalClockMcoMsi800k, | ||||||
|  |     FuriHalClockMcoMsi1m, | ||||||
|  |     FuriHalClockMcoMsi2m, | ||||||
|  |     FuriHalClockMcoMsi4m, | ||||||
|  |     FuriHalClockMcoMsi8m, | ||||||
|  |     FuriHalClockMcoMsi16m, | ||||||
|  |     FuriHalClockMcoMsi24m, | ||||||
|  |     FuriHalClockMcoMsi32m, | ||||||
|  |     FuriHalClockMcoMsi48m, | ||||||
|  | } FuriHalClockMcoSourceId; | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     FuriHalClockMcoDiv1 = LL_RCC_MCO1_DIV_1, | ||||||
|  |     FuriHalClockMcoDiv2 = LL_RCC_MCO1_DIV_2, | ||||||
|  |     FuriHalClockMcoDiv4 = LL_RCC_MCO1_DIV_4, | ||||||
|  |     FuriHalClockMcoDiv8 = LL_RCC_MCO1_DIV_8, | ||||||
|  |     FuriHalClockMcoDiv16 = LL_RCC_MCO1_DIV_16, | ||||||
|  | } FuriHalClockMcoDivisorId; | ||||||
|  | 
 | ||||||
| /** Early initialization */ | /** Early initialization */ | ||||||
| void furi_hal_clock_init_early(); | void furi_hal_clock_init_early(); | ||||||
| 
 | 
 | ||||||
| @ -25,6 +52,16 @@ void furi_hal_clock_suspend_tick(); | |||||||
| /** Continue SysTick counter operation */ | /** Continue SysTick counter operation */ | ||||||
| void furi_hal_clock_resume_tick(); | void furi_hal_clock_resume_tick(); | ||||||
| 
 | 
 | ||||||
|  | /** Enable clock output on MCO pin
 | ||||||
|  |  *  | ||||||
|  |  * @param      source  MCO clock source | ||||||
|  |  * @param      div     MCO clock division | ||||||
|  | */ | ||||||
|  | void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div); | ||||||
|  | 
 | ||||||
|  | /** Disable clock output on MCO pin */ | ||||||
|  | void furi_hal_clock_mco_disable(); | ||||||
|  | 
 | ||||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
							
								
								
									
										138
									
								
								firmware/targets/f7/furi_hal/furi_hal_pwm.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								firmware/targets/f7/furi_hal/furi_hal_pwm.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,138 @@ | |||||||
|  | #include "furi_hal_pwm.h" | ||||||
|  | #include <core/check.h> | ||||||
|  | #include <furi_hal_resources.h> | ||||||
|  | 
 | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <stm32wbxx_ll_tim.h> | ||||||
|  | #include <stm32wbxx_ll_lptim.h> | ||||||
|  | #include <stm32wbxx_ll_rcc.h> | ||||||
|  | 
 | ||||||
|  | #include <furi.h> | ||||||
|  | 
 | ||||||
|  | const uint32_t lptim_psc_table[] = { | ||||||
|  |     LL_LPTIM_PRESCALER_DIV1, | ||||||
|  |     LL_LPTIM_PRESCALER_DIV2, | ||||||
|  |     LL_LPTIM_PRESCALER_DIV4, | ||||||
|  |     LL_LPTIM_PRESCALER_DIV8, | ||||||
|  |     LL_LPTIM_PRESCALER_DIV16, | ||||||
|  |     LL_LPTIM_PRESCALER_DIV32, | ||||||
|  |     LL_LPTIM_PRESCALER_DIV64, | ||||||
|  |     LL_LPTIM_PRESCALER_DIV128, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) { | ||||||
|  |     if(channel == FuriHalPwmOutputIdTim1PA7) { | ||||||
|  |         furi_hal_gpio_init_ex( | ||||||
|  |             &gpio_ext_pa7, | ||||||
|  |             GpioModeAltFunctionPushPull, | ||||||
|  |             GpioPullNo, | ||||||
|  |             GpioSpeedVeryHigh, | ||||||
|  |             GpioAltFn1TIM1); | ||||||
|  | 
 | ||||||
|  |         FURI_CRITICAL_ENTER(); | ||||||
|  |         LL_TIM_DeInit(TIM1); | ||||||
|  |         FURI_CRITICAL_EXIT(); | ||||||
|  | 
 | ||||||
|  |         LL_TIM_SetCounterMode(TIM1, LL_TIM_COUNTERMODE_UP); | ||||||
|  |         LL_TIM_SetRepetitionCounter(TIM1, 0); | ||||||
|  |         LL_TIM_SetClockDivision(TIM1, LL_TIM_CLOCKDIVISION_DIV1); | ||||||
|  |         LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL); | ||||||
|  |         LL_TIM_EnableARRPreload(TIM1); | ||||||
|  | 
 | ||||||
|  |         LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1); | ||||||
|  |         LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1); | ||||||
|  |         LL_TIM_OC_SetPolarity(TIM1, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH); | ||||||
|  |         LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH1); | ||||||
|  |         LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N); | ||||||
|  | 
 | ||||||
|  |         LL_TIM_EnableAllOutputs(TIM1); | ||||||
|  | 
 | ||||||
|  |         furi_hal_pwm_set_params(channel, freq, duty); | ||||||
|  | 
 | ||||||
|  |         LL_TIM_EnableCounter(TIM1); | ||||||
|  |     } else if(channel == FuriHalPwmOutputIdLptim2PA4) { | ||||||
|  |         furi_hal_gpio_init_ex( | ||||||
|  |             &gpio_ext_pa4, | ||||||
|  |             GpioModeAltFunctionPushPull, | ||||||
|  |             GpioPullNo, | ||||||
|  |             GpioSpeedVeryHigh, | ||||||
|  |             GpioAltFn14LPTIM2); | ||||||
|  | 
 | ||||||
|  |         FURI_CRITICAL_ENTER(); | ||||||
|  |         LL_LPTIM_DeInit(LPTIM2); | ||||||
|  |         FURI_CRITICAL_EXIT(); | ||||||
|  | 
 | ||||||
|  |         LL_LPTIM_SetUpdateMode(LPTIM2, LL_LPTIM_UPDATE_MODE_ENDOFPERIOD); | ||||||
|  |         LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_PCLK1); | ||||||
|  |         LL_LPTIM_SetClockSource(LPTIM2, LL_LPTIM_CLK_SOURCE_INTERNAL); | ||||||
|  |         LL_LPTIM_ConfigOutput( | ||||||
|  |             LPTIM2, LL_LPTIM_OUTPUT_WAVEFORM_PWM, LL_LPTIM_OUTPUT_POLARITY_INVERSE); | ||||||
|  |         LL_LPTIM_SetCounterMode(LPTIM2, LL_LPTIM_COUNTER_MODE_INTERNAL); | ||||||
|  | 
 | ||||||
|  |         LL_LPTIM_Enable(LPTIM2); | ||||||
|  | 
 | ||||||
|  |         furi_hal_pwm_set_params(channel, freq, duty); | ||||||
|  | 
 | ||||||
|  |         LL_LPTIM_StartCounter(LPTIM2, LL_LPTIM_OPERATING_MODE_CONTINUOUS); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void furi_hal_pwm_stop(FuriHalPwmOutputId channel) { | ||||||
|  |     if(channel == FuriHalPwmOutputIdTim1PA7) { | ||||||
|  |         furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog); | ||||||
|  |         FURI_CRITICAL_ENTER(); | ||||||
|  |         LL_TIM_DeInit(TIM1); | ||||||
|  |         FURI_CRITICAL_EXIT(); | ||||||
|  |     } else if(channel == FuriHalPwmOutputIdLptim2PA4) { | ||||||
|  |         furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog); | ||||||
|  |         FURI_CRITICAL_ENTER(); | ||||||
|  |         LL_LPTIM_DeInit(LPTIM2); | ||||||
|  |         FURI_CRITICAL_EXIT(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) { | ||||||
|  |     furi_assert(freq > 0); | ||||||
|  |     uint32_t freq_div = 64000000LU / freq; | ||||||
|  | 
 | ||||||
|  |     if(channel == FuriHalPwmOutputIdTim1PA7) { | ||||||
|  |         uint32_t prescaler = freq_div / 0x10000LU; | ||||||
|  |         uint32_t period = freq_div / (prescaler + 1); | ||||||
|  |         uint32_t compare = period * duty / 100; | ||||||
|  | 
 | ||||||
|  |         LL_TIM_SetPrescaler(TIM1, prescaler); | ||||||
|  |         LL_TIM_SetAutoReload(TIM1, period - 1); | ||||||
|  |         LL_TIM_OC_SetCompareCH1(TIM1, compare); | ||||||
|  |     } else if(channel == FuriHalPwmOutputIdLptim2PA4) { | ||||||
|  |         uint32_t prescaler = 0; | ||||||
|  |         uint32_t period = 0; | ||||||
|  | 
 | ||||||
|  |         bool clock_lse = false; | ||||||
|  | 
 | ||||||
|  |         do { | ||||||
|  |             period = freq_div / (1 << prescaler); | ||||||
|  |             if(period <= 0xFFFF) { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             prescaler++; | ||||||
|  |             if(prescaler > 7) { | ||||||
|  |                 prescaler = 0; | ||||||
|  |                 clock_lse = true; | ||||||
|  |                 period = 32768LU / freq; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } while(1); | ||||||
|  | 
 | ||||||
|  |         uint32_t compare = period * duty / 100; | ||||||
|  | 
 | ||||||
|  |         LL_LPTIM_SetPrescaler(LPTIM2, lptim_psc_table[prescaler]); | ||||||
|  |         LL_LPTIM_SetAutoReload(LPTIM2, period); | ||||||
|  |         LL_LPTIM_SetCompare(LPTIM2, compare); | ||||||
|  | 
 | ||||||
|  |         if(clock_lse) { | ||||||
|  |             LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_LSE); | ||||||
|  |         } else { | ||||||
|  |             LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_PCLK1); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										42
									
								
								firmware/targets/f7/furi_hal/furi_hal_pwm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								firmware/targets/f7/furi_hal/furi_hal_pwm.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | /**
 | ||||||
|  |  * @file furi_hal_pwm.h | ||||||
|  |  * PWM contol HAL | ||||||
|  |  */ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     FuriHalPwmOutputIdTim1PA7, | ||||||
|  |     FuriHalPwmOutputIdLptim2PA4, | ||||||
|  | } FuriHalPwmOutputId; | ||||||
|  | 
 | ||||||
|  | /** Enable PWM channel and set parameters
 | ||||||
|  |  *  | ||||||
|  |  * @param[in]  channel  PWM channel (FuriHalPwmOutputId) | ||||||
|  |  * @param[in]  freq  Frequency in Hz | ||||||
|  |  * @param[in]  duty  Duty cycle value in % | ||||||
|  | */ | ||||||
|  | void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty); | ||||||
|  | 
 | ||||||
|  | /** Disable PWM channel
 | ||||||
|  |  *  | ||||||
|  |  * @param[in]  channel  PWM channel (FuriHalPwmOutputId) | ||||||
|  | */ | ||||||
|  | void furi_hal_pwm_stop(FuriHalPwmOutputId channel); | ||||||
|  | 
 | ||||||
|  | /** Set PWM channel parameters
 | ||||||
|  |  *  | ||||||
|  |  * @param[in]  channel  PWM channel (FuriHalPwmOutputId) | ||||||
|  |  * @param[in]  freq  Frequency in Hz | ||||||
|  |  * @param[in]  duty  Duty cycle value in % | ||||||
|  | */ | ||||||
|  | void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty); | ||||||
|  | 
 | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Nikolay Minaylov
						Nikolay Minaylov