diff --git a/applications/plugins/signal_generator/application.fam b/applications/plugins/signal_generator/application.fam new file mode 100644 index 000000000000..7794ee492c69 --- /dev/null +++ b/applications/plugins/signal_generator/application.fam @@ -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", +) diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.c b/applications/plugins/signal_generator/scenes/signal_gen_scene.c new file mode 100644 index 000000000000..29b11ee3f47a --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene.c @@ -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, +}; diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene.h b/applications/plugins/signal_generator/scenes/signal_gen_scene.h new file mode 100644 index 000000000000..c139afa3bf4d --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// 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 diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h b/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h new file mode 100644 index 000000000000..b6c750256697 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(signal_gen, start, Start) +ADD_SCENE(signal_gen, pwm, Pwm) +ADD_SCENE(signal_gen, mco, Mco) diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c new file mode 100644 index 000000000000..632b08c75e44 --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_mco.c @@ -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(); +} diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c new file mode 100644 index 000000000000..f302c023228e --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_pwm.c @@ -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); +} diff --git a/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c b/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c new file mode 100644 index 000000000000..91f6081d84ba --- /dev/null +++ b/applications/plugins/signal_generator/scenes/signal_gen_scene_start.c @@ -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); +} diff --git a/applications/plugins/signal_generator/signal_gen_10px.png b/applications/plugins/signal_generator/signal_gen_10px.png new file mode 100644 index 000000000000..9f6dcc5d0d9a Binary files /dev/null and b/applications/plugins/signal_generator/signal_gen_10px.png differ diff --git a/applications/plugins/signal_generator/signal_gen_app.c b/applications/plugins/signal_generator/signal_gen_app.c new file mode 100644 index 000000000000..ca065d330d1d --- /dev/null +++ b/applications/plugins/signal_generator/signal_gen_app.c @@ -0,0 +1,93 @@ +#include "signal_gen_app_i.h" + +#include +#include + +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; +} diff --git a/applications/plugins/signal_generator/signal_gen_app_i.h b/applications/plugins/signal_generator/signal_gen_app_i.h new file mode 100644 index 000000000000..47c266475b17 --- /dev/null +++ b/applications/plugins/signal_generator/signal_gen_app_i.h @@ -0,0 +1,46 @@ +#pragma once + +#include "scenes/signal_gen_scene.h" + +#include "furi_hal_clock.h" +#include "furi_hal_pwm.h" + +#include +#include +#include +#include +#include +#include +#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; diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.c b/applications/plugins/signal_generator/views/signal_gen_pwm.c new file mode 100644 index 000000000000..00b4ef2674b9 --- /dev/null +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.c @@ -0,0 +1,301 @@ +#include "../signal_gen_app_i.h" +#include "furi_hal.h" +#include + +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); +} diff --git a/applications/plugins/signal_generator/views/signal_gen_pwm.h b/applications/plugins/signal_generator/views/signal_gen_pwm.h new file mode 100644 index 000000000000..986794e7a283 --- /dev/null +++ b/applications/plugins/signal_generator/views/signal_gen_pwm.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#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); diff --git a/assets/icons/Interface/SmallArrowDown_4x7.png b/assets/icons/Interface/SmallArrowDown_4x7.png new file mode 100644 index 000000000000..5c5252b167d2 Binary files /dev/null and b/assets/icons/Interface/SmallArrowDown_4x7.png differ diff --git a/assets/icons/Interface/SmallArrowUp_4x7.png b/assets/icons/Interface/SmallArrowUp_4x7.png new file mode 100644 index 000000000000..886369abc6f1 Binary files /dev/null and b/assets/icons/Interface/SmallArrowUp_4x7.png differ diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index c18780acbe57..18550984d2b2 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,1.12,, +Version,+,1.13,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.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_interrupt.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_spi_config.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_init,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_suspend_tick,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_suppress_charge_enter,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_get,uint32_t, 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_SDcardMounted_11x8,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_Space_65x18,const Icon, Variable,+,I_Tap_reader_36x38,const Icon, diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.c b/firmware/targets/f7/furi_hal/furi_hal_clock.c index a7c9b4d031d2..cf19451ec7ba 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.c +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -236,3 +237,63 @@ void furi_hal_clock_suspend_tick() { void furi_hal_clock_resume_tick() { 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) + ; +} \ No newline at end of file diff --git a/firmware/targets/f7/furi_hal/furi_hal_clock.h b/firmware/targets/f7/furi_hal/furi_hal_clock.h index 6cebb20c626c..5e651bbd3593 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_clock.h +++ b/firmware/targets/f7/furi_hal/furi_hal_clock.h @@ -4,6 +4,33 @@ extern "C" { #endif +#include + +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 */ void furi_hal_clock_init_early(); @@ -25,6 +52,16 @@ void furi_hal_clock_suspend_tick(); /** Continue SysTick counter operation */ 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 } #endif diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.c b/firmware/targets/f7/furi_hal/furi_hal_pwm.c new file mode 100644 index 000000000000..e484808d5ab4 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.c @@ -0,0 +1,138 @@ +#include "furi_hal_pwm.h" +#include +#include + +#include +#include +#include +#include + +#include + +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); + } + } +} diff --git a/firmware/targets/f7/furi_hal/furi_hal_pwm.h b/firmware/targets/f7/furi_hal/furi_hal_pwm.h new file mode 100644 index 000000000000..a8682c5fbb16 --- /dev/null +++ b/firmware/targets/f7/furi_hal/furi_hal_pwm.h @@ -0,0 +1,42 @@ +/** + * @file furi_hal_pwm.h + * PWM contol HAL + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +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