[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
|
||||
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,
|
||||
|
|
@ -1,4 +1,5 @@
|
||||
#include <furi_hal_clock.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <stm32wbxx_ll_pwr.h>
|
||||
@ -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)
|
||||
;
|
||||
}
|
@ -4,6 +4,33 @@
|
||||
extern "C" {
|
||||
#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 */
|
||||
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
|
||||
|
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…
Reference in New Issue
Block a user