[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:
		
							
								
								
									
										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);
 | 
			
		||||
		Reference in New Issue
	
	Block a user