[FL-2969] FuriHal: add ADC API (#3583)

* Examples: remove unused context
* FuriHal: add simple ADC API
* Examples: add ADC example app
* FuriHal: add extended configuration options for ADC API
* FuriHal: add ADC clock configuration, fix calibration routine for single ended mode, new optimized parameters, documentation.
* FuriHal: add FuriHalAdcChannelTEMPSENSOR sampling time note
* FuriHal: update FuriHalAdcChannelVBAT description.
* FuriHal: use insomnia while ADC is acquired.
* Examples: cleanup example_adc a little bit
This commit is contained in:
あく 2024-04-18 00:17:40 +09:00 committed by GitHub
parent 1a40fae003
commit 70f93a48f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 949 additions and 57 deletions

View File

@ -0,0 +1,9 @@
App(
appid="example_adc",
name="Example: ADC",
apptype=FlipperAppType.EXTERNAL,
entry_point="example_adc_main",
requires=["gui"],
stack_size=1 * 1024,
fap_category="Examples",
)

View File

@ -0,0 +1,176 @@
/**
* @file example_adc.c
* @brief ADC example.
*/
#include <furi.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/elements.h>
#include <input/input.h>
const uint8_t font[] =
"`\2\3\2\3\4\1\2\4\5\11\0\376\6\376\7\377\1M\2\263\3\370 \6\315\364\371\6!\12\315"
"\364\201\260\35\312Q\0\42\11\315tJI\316\13\0#\14\315\264\223dP*\203R'\1$\15\315\264"
"\262A\311\266D\251l\71\0%\15\315\264\7%\61)J\42\345 \0&\14\315\264\263$\13\223\266$"
"\7\1'\10\315\364\201\60\347\10(\10\315\364\32[\313\0)\11\315\64\322b[\35\2*\12\315\264\263"
"(\222j\71\15+\11\315\364I\331\226\23\1,\10\315\364\271\205Y\10-\10\315\364\31t\26\0.\10"
"\315\364\71\346(\0/\14\315\364\221\60\13\263\60\13C\0\60\13\315\264\245Jb)E:\12\61\12\315"
"\364\201Ll\333A\0\62\12\315\264\245bV\33r\20\63\13\315\264\245Z\232D\221\216\2\64\14\315\364"
"\201LJ\242!\313v\20\65\14\315t\207$\134\223(\322Q\0\66\13\315\264\245p\252D\221\216\2\67"
"\12\315t\207\60+\326a\0\70\13\315\264\245\222T\211\42\35\5\71\13\315\264\245J\24\215\221\216\2:"
"\11\315\364i\71!G\1;\12\315\364I\71!\314B\0<\11\315\364\341\254Z\7\1=\12\315\364)"
"C<\344$\0>\11\315\364\301\264V\207\1\77\12\315\264\245Z\35\312a\0@\14\315\264\245J\242$"
"J\272\203\0A\15\315\264\245J\224\14I\224D\71\10B\13\315t\247\312T\211\222\35\5C\12\315\264"
"\245JX\212t\24D\15\315t\247J\224DI\224\354(\0E\14\315t\207$\234\302p\310A\0F"
"\12\315t\207$\234\302:\1G\14\315\264\245J\230(Q\244\243\0H\17\315t\243$J\206$J\242"
"$\312A\0I\11\315\264\267\260m\7\1J\12\315\364\221\260%\212t\24K\14\315t\243\244\244iI"
"T\7\1L\11\315t\303\216C\16\2M\17\315t\243dH\206$J\242$\312A\0N\16\315t\243"
"D\251(Q\22%Q\16\2O\15\315\264\245J\224DI\24\351(\0P\12\315t\247J\224LaN"
"Q\15\315\264\245J\224DI\42\251\61\0R\14\315t\247J\224L\225(\7\1S\13\315\264\245\222\232"
"D\221\216\2T\10\315\264\267\260;\12U\16\315t\243$J\242$J\242HG\1V\15\315t\243$"
"J\242$Jj\71\14W\17\315t\243$J\242dH\206$\312A\0X\15\315t\243$\212\64\251\22"
"\345 \0Y\13\315t\243$Jja\35\6Z\12\315t\207\60k\34r\20[\10\315\264\264\260G\31"
"\134\12\315\264\303\64L\303\64\14]\10\315t\304\276\351\0^\11\315\364\201,\311\271\1_\7\315\364y"
"\35\4`\10\315t\322\234'\0a\14\315\364IK\224$R\222\203\0b\13\315t\303p\252D\311\216"
"\2c\12\315\364IR%\335A\0d\14\315\364\221\60Z\242$\212v\20e\12\315\364I\322\220\244;"
"\10f\12\315\364\221,\333\302:\12g\14\315\364IK\224D\321\30I\0h\14\315t\303p\252DI"
"\224\203\0i\12\315\364\201\34\21k;\10j\12\315\364\201\34\21\273e\0k\13\315t\303J\244%Q"
"\35\4l\10\315\264\305n;\10m\14\315\364)CRQ\22\245\216\1n\13\315\364)%\245\224D\71"
"\10o\12\315\364IR%\212t\24p\13\315\364)S%J\246\60\4q\13\315\364IK\224D\321X"
"\1r\11\315\364)%\245\230\23s\12\315\364I\313\232\354(\0t\13\315\364\201\60\333\302\64\7\1u"
"\15\315\364)Q\22%\211\224\344 \0v\13\315\364)Q\22%\265\34\6w\13\315\364)\25%Q\272"
"\203\0x\12\315\364)Q\244Iu\20y\15\315\364)Q\22%Q\64F\22\0z\12\315\364)CV"
"\33r\20{\12\315\364\212\265\64\254&\0|\7\315\264\302~\7}\12\315t\322\260\232\205\265\14~\11"
"\315\364II;\13\0\177\6\315\364\371\6\0\0\0\4\377\377\0";
#define FONT_HEIGHT (8u)
typedef float (*ValueConverter)(FuriHalAdcHandle* handle, uint16_t value);
typedef struct {
const GpioPinRecord* pin;
float value;
ValueConverter converter;
const char* suffix;
} DataItem;
typedef struct {
size_t count;
DataItem* items;
} Data;
const GpioPinRecord item_vref = {.name = "VREF", .channel = FuriHalAdcChannelVREFINT};
const GpioPinRecord item_temp = {.name = "TEMP", .channel = FuriHalAdcChannelTEMPSENSOR};
const GpioPinRecord item_vbat = {.name = "VBAT", .channel = FuriHalAdcChannelVBAT};
static void app_draw_callback(Canvas* canvas, void* ctx) {
furi_assert(ctx);
Data* data = ctx;
canvas_set_custom_u8g2_font(canvas, font);
char buffer[64];
int32_t x = 0, y = FONT_HEIGHT;
for(size_t i = 0; i < data->count; i++) {
if(i == canvas_height(canvas) / FONT_HEIGHT) {
x = 64;
y = FONT_HEIGHT;
}
snprintf(
buffer,
sizeof(buffer),
"%4s: %4.0f%s\n",
data->items[i].pin->name,
(double)data->items[i].value,
data->items[i].suffix);
canvas_draw_str(canvas, x, y, buffer);
y += FONT_HEIGHT;
}
}
static void app_input_callback(InputEvent* input_event, void* ctx) {
furi_assert(ctx);
FuriMessageQueue* event_queue = ctx;
furi_message_queue_put(event_queue, input_event, FuriWaitForever);
}
int32_t example_adc_main(void* p) {
UNUSED(p);
// Data
Data data = {};
for(size_t i = 0; i < gpio_pins_count; i++) {
if(gpio_pins[i].channel != FuriHalAdcChannelNone) {
data.count++;
}
}
data.count += 3; // Special channels
data.items = malloc(data.count * sizeof(DataItem));
size_t item_pos = 0;
for(size_t i = 0; i < gpio_pins_count; i++) {
if(gpio_pins[i].channel != FuriHalAdcChannelNone) {
furi_hal_gpio_init(gpio_pins[i].pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
data.items[item_pos].pin = &gpio_pins[i];
data.items[item_pos].converter = furi_hal_adc_convert_to_voltage;
data.items[item_pos].suffix = "mV";
item_pos++;
}
}
data.items[item_pos].pin = &item_vref;
data.items[item_pos].converter = furi_hal_adc_convert_vref;
data.items[item_pos].suffix = "mV";
item_pos++;
data.items[item_pos].pin = &item_temp;
data.items[item_pos].converter = furi_hal_adc_convert_temp;
data.items[item_pos].suffix = "C";
item_pos++;
data.items[item_pos].pin = &item_vbat;
data.items[item_pos].converter = furi_hal_adc_convert_vbat;
data.items[item_pos].suffix = "mV";
item_pos++;
furi_assert(item_pos == data.count);
// Alloc message queue
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
// Configure view port
ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, app_draw_callback, &data);
view_port_input_callback_set(view_port, app_input_callback, event_queue);
// Register view port in GUI
Gui* gui = furi_record_open(RECORD_GUI);
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
// Initialize ADC
FuriHalAdcHandle* adc_handle = furi_hal_adc_acquire();
furi_hal_adc_configure(adc_handle);
// Process events
InputEvent event;
bool running = true;
while(running) {
if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
if(event.type == InputTypePress && event.key == InputKeyBack) {
running = false;
}
} else {
for(size_t i = 0; i < data.count; i++) {
data.items[i].value = data.items[i].converter(
adc_handle, furi_hal_adc_read(adc_handle, data.items[i].pin->channel));
}
view_port_update(view_port);
}
}
furi_hal_adc_release(adc_handle);
view_port_enabled_set(view_port, false);
gui_remove_view_port(gui, view_port);
view_port_free(view_port);
furi_message_queue_free(event_queue);
furi_record_close(RECORD_GUI);
free(data.items);
return 0;
}

View File

@ -94,7 +94,7 @@ int32_t example_custom_font_main(void* p) {
// Configure view port // Configure view port
ViewPort* view_port = view_port_alloc(); ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, app_draw_callback, view_port); view_port_draw_callback_set(view_port, app_draw_callback, NULL);
view_port_input_callback_set(view_port, app_input_callback, event_queue); view_port_input_callback_set(view_port, app_input_callback, event_queue);
// Register view port in GUI // Register view port in GUI

View File

@ -39,7 +39,7 @@ int32_t example_images_main(void* p) {
// Configure view port // Configure view port
ViewPort* view_port = view_port_alloc(); ViewPort* view_port = view_port_alloc();
view_port_draw_callback_set(view_port, app_draw_callback, view_port); view_port_draw_callback_set(view_port, app_draw_callback, NULL);
view_port_input_callback_set(view_port, app_input_callback, event_queue); view_port_input_callback_set(view_port, app_input_callback, event_queue);
// Register view port in GUI // Register view port in GUI

View File

@ -1,5 +1,5 @@
entry,status,name,type,params entry,status,name,type,params
Version,+,60.8,, Version,+,61.0,,
Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,, Header,+,applications/services/cli/cli_vcp.h,,
@ -199,6 +199,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,,
Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,,
Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,,
Header,+,targets/furi_hal_include/furi_hal.h,, Header,+,targets/furi_hal_include/furi_hal.h,,
Header,+,targets/furi_hal_include/furi_hal_adc.h,,
Header,+,targets/furi_hal_include/furi_hal_bt.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,,
Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,,
Header,+,targets/furi_hal_include/furi_hal_crypto.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,,
@ -1086,6 +1087,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag*
Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
Function,+,furi_get_tick,uint32_t, Function,+,furi_get_tick,uint32_t,
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime"
Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_init,void,
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_clear_white_list,_Bool,

1 entry status name type params
2 Version + 60.8 61.0
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
199 Header + targets/f7/platform_specific/intrinsic_export.h
200 Header + targets/f7/platform_specific/math_wrapper.h
201 Header + targets/furi_hal_include/furi_hal.h
202 Header + targets/furi_hal_include/furi_hal_adc.h
203 Header + targets/furi_hal_include/furi_hal_bt.h
204 Header + targets/furi_hal_include/furi_hal_cortex.h
205 Header + targets/furi_hal_include/furi_hal_crypto.h
1087 Function + furi_event_flag_set uint32_t FuriEventFlag*, uint32_t
1088 Function + furi_event_flag_wait uint32_t FuriEventFlag*, uint32_t, uint32_t, uint32_t
1089 Function + furi_get_tick uint32_t
1090 Function + furi_hal_adc_acquire FuriHalAdcHandle*
1091 Function + furi_hal_adc_configure void FuriHalAdcHandle*
1092 Function + furi_hal_adc_configure_ex void FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime
1093 Function + furi_hal_adc_convert_temp float FuriHalAdcHandle*, uint16_t
1094 Function + furi_hal_adc_convert_to_voltage float FuriHalAdcHandle*, uint16_t
1095 Function + furi_hal_adc_convert_vbat float FuriHalAdcHandle*, uint16_t
1096 Function + furi_hal_adc_convert_vref float FuriHalAdcHandle*, uint16_t
1097 Function + furi_hal_adc_init void
1098 Function + furi_hal_adc_read uint16_t FuriHalAdcHandle*, FuriHalAdcChannel
1099 Function + furi_hal_adc_release void FuriHalAdcHandle*
1100 Function + furi_hal_bt_change_app FuriHalBleProfileBase* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*
1101 Function + furi_hal_bt_check_profile_type _Bool FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*
1102 Function + furi_hal_bt_clear_white_list _Bool

View File

@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) {
void furi_hal_init(void) { void furi_hal_init(void) {
furi_hal_mpu_init(); furi_hal_mpu_init();
furi_hal_adc_init();
furi_hal_clock_init(); furi_hal_clock_init();
furi_hal_random_init(); furi_hal_random_init();
furi_hal_serial_control_init(); furi_hal_serial_control_init();

View File

@ -68,49 +68,161 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12};
const GpioPinRecord gpio_pins[] = { const GpioPinRecord gpio_pins[] = {
// 5V: 1 // 5V: 1
{.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, {.pin = &gpio_ext_pa7,
{.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, .name = "PA7",
{.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, .channel = FuriHalAdcChannel12,
{.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, .number = 2,
{.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, .debug = false},
{.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, {.pin = &gpio_ext_pa6,
.name = "PA6",
.channel = FuriHalAdcChannel11,
.number = 3,
.debug = false},
{.pin = &gpio_ext_pa4,
.name = "PA4",
.channel = FuriHalAdcChannel9,
.number = 4,
.debug = false},
{.pin = &gpio_ext_pb3,
.name = "PB3",
.channel = FuriHalAdcChannelNone,
.number = 5,
.debug = false},
{.pin = &gpio_ext_pb2,
.name = "PB2",
.channel = FuriHalAdcChannelNone,
.number = 6,
.debug = false},
{.pin = &gpio_ext_pc3,
.name = "PC3",
.channel = FuriHalAdcChannel4,
.number = 7,
.debug = false},
// GND: 8 // GND: 8
// Space // Space
// 3v3: 9 // 3v3: 9
{.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, {.pin = &gpio_swclk,
.name = "PA14",
.channel = FuriHalAdcChannelNone,
.number = 10,
.debug = true},
// GND: 11 // GND: 11
{.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, {.pin = &gpio_swdio,
{.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, .name = "PA13",
{.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, .channel = FuriHalAdcChannelNone,
{.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, .number = 12,
{.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, .debug = true},
{.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, {.pin = &gpio_usart_tx,
.name = "PB6",
.channel = FuriHalAdcChannelNone,
.number = 13,
.debug = true},
{.pin = &gpio_usart_rx,
.name = "PB7",
.channel = FuriHalAdcChannelNone,
.number = 14,
.debug = true},
{.pin = &gpio_ext_pc1,
.name = "PC1",
.channel = FuriHalAdcChannel2,
.number = 15,
.debug = false},
{.pin = &gpio_ext_pc0,
.name = "PC0",
.channel = FuriHalAdcChannel1,
.number = 16,
.debug = false},
{.pin = &gpio_ibutton,
.name = "PB14",
.channel = FuriHalAdcChannelNone,
.number = 17,
.debug = true},
// GND: 18 // GND: 18
// 2nd column // 2nd column
// 5V: 19 // 5V: 19
{.pin = &gpio_ext_pc5, .name = "PC5", .number = 20, .debug = false}, {.pin = &gpio_ext_pc5,
{.pin = &gpio_ext_pc4, .name = "PC4", .number = 21, .debug = false}, .name = "PC5",
{.pin = &gpio_ext_pa5, .name = "PA5", .number = 22, .debug = false}, .channel = FuriHalAdcChannel14,
{.pin = &gpio_ext_pb9, .name = "PB9", .number = 23, .debug = false}, .number = 20,
{.pin = &gpio_ext_pa0, .name = "PA0", .number = 24, .debug = false}, .debug = false},
{.pin = &gpio_ext_pa1, .name = "PA1", .number = 25, .debug = false}, {.pin = &gpio_ext_pc4,
.name = "PC4",
.channel = FuriHalAdcChannel13,
.number = 21,
.debug = false},
{.pin = &gpio_ext_pa5,
.name = "PA5",
.channel = FuriHalAdcChannel10,
.number = 22,
.debug = false},
{.pin = &gpio_ext_pb9,
.name = "PB9",
.channel = FuriHalAdcChannelNone,
.number = 23,
.debug = false},
{.pin = &gpio_ext_pa0,
.name = "PA0",
.channel = FuriHalAdcChannel5,
.number = 24,
.debug = false},
{.pin = &gpio_ext_pa1,
.name = "PA1",
.channel = FuriHalAdcChannel6,
.number = 25,
.debug = false},
// KEY: 26 // KEY: 26
// Space // Space
// 3v3: 27 // 3v3: 27
{.pin = &gpio_ext_pa15, .name = "PA15", .number = 28, .debug = false}, {.pin = &gpio_ext_pa15,
.name = "PA15",
.channel = FuriHalAdcChannelNone,
.number = 28,
.debug = false},
// GND: 29 // GND: 29
{.pin = &gpio_ext_pe4, .name = "PE4", .number = 30, .debug = false}, {.pin = &gpio_ext_pe4,
{.pin = &gpio_ext_pa2, .name = "PA2", .number = 31, .debug = false}, .name = "PE4",
{.pin = &gpio_ext_pb4, .name = "PB4", .number = 32, .debug = false}, .channel = FuriHalAdcChannelNone,
{.pin = &gpio_ext_pb5, .name = "PB5", .number = 33, .debug = false}, .number = 30,
{.pin = &gpio_ext_pd0, .name = "PD0", .number = 34, .debug = false}, .debug = false},
{.pin = &gpio_ext_pb13, .name = "PB13", .number = 35, .debug = false}, {.pin = &gpio_ext_pa2,
.name = "PA2",
.channel = FuriHalAdcChannel7,
.number = 31,
.debug = false},
{.pin = &gpio_ext_pb4,
.name = "PB4",
.channel = FuriHalAdcChannelNone,
.number = 32,
.debug = false},
{.pin = &gpio_ext_pb5,
.name = "PB5",
.channel = FuriHalAdcChannelNone,
.number = 33,
.debug = false},
{.pin = &gpio_ext_pd0,
.name = "PD0",
.channel = FuriHalAdcChannelNone,
.number = 34,
.debug = false},
{.pin = &gpio_ext_pb13,
.name = "PB13",
.channel = FuriHalAdcChannelNone,
.number = 35,
.debug = false},
// GND: 36 // GND: 36
/* Dangerous pins, may damage hardware */ /* Dangerous pins, may damage hardware */
{.pin = &gpio_usart_rx, .name = "PB7", .number = 0, .debug = true}, {.pin = &gpio_usart_rx,
{.pin = &gpio_speaker, .name = "PB8", .number = 0, .debug = true}, .name = "PB7",
.channel = FuriHalAdcChannelNone,
.number = 0,
.debug = true},
{.pin = &gpio_speaker,
.name = "PB8",
.channel = FuriHalAdcChannelNone,
.number = 0,
.debug = true},
}; };
const size_t gpio_pins_count = COUNT_OF(gpio_pins); const size_t gpio_pins_count = COUNT_OF(gpio_pins);

View File

@ -1,9 +1,7 @@
#pragma once #pragma once
#include <furi.h> #include <furi.h>
#include <furi_hal_adc.h>
#include <stm32wbxx.h>
#include <stm32wbxx_ll_gpio.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -41,6 +39,7 @@ typedef struct {
typedef struct { typedef struct {
const GpioPin* pin; const GpioPin* pin;
const char* name; const char* name;
const FuriHalAdcChannel channel;
const uint8_t number; const uint8_t number;
const bool debug; const bool debug;
} GpioPinRecord; } GpioPinRecord;

View File

@ -1,5 +1,5 @@
entry,status,name,type,params entry,status,name,type,params
Version,+,60.8,, Version,+,61.0,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli.h,,
@ -268,6 +268,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,,
Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,,
Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,,
Header,+,targets/furi_hal_include/furi_hal.h,, Header,+,targets/furi_hal_include/furi_hal.h,,
Header,+,targets/furi_hal_include/furi_hal_adc.h,,
Header,+,targets/furi_hal_include/furi_hal_bt.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,,
Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,,
Header,+,targets/furi_hal_include/furi_hal_crypto.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,,
@ -1183,6 +1184,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag*
Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t"
Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t"
Function,+,furi_get_tick,uint32_t, Function,+,furi_get_tick,uint32_t,
Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*,
Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle*
Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime"
Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t"
Function,+,furi_hal_adc_init,void,
Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel"
Function,+,furi_hal_adc_release,void,FuriHalAdcHandle*
Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*"
Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*"
Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_clear_white_list,_Bool,

1 entry status name type params
2 Version + 60.8 61.0
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/cli/cli.h
268 Header + targets/f7/platform_specific/intrinsic_export.h
269 Header + targets/f7/platform_specific/math_wrapper.h
270 Header + targets/furi_hal_include/furi_hal.h
271 Header + targets/furi_hal_include/furi_hal_adc.h
272 Header + targets/furi_hal_include/furi_hal_bt.h
273 Header + targets/furi_hal_include/furi_hal_cortex.h
274 Header + targets/furi_hal_include/furi_hal_crypto.h
1184 Function + furi_event_flag_set uint32_t FuriEventFlag*, uint32_t
1185 Function + furi_event_flag_wait uint32_t FuriEventFlag*, uint32_t, uint32_t, uint32_t
1186 Function + furi_get_tick uint32_t
1187 Function + furi_hal_adc_acquire FuriHalAdcHandle*
1188 Function + furi_hal_adc_configure void FuriHalAdcHandle*
1189 Function + furi_hal_adc_configure_ex void FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime
1190 Function + furi_hal_adc_convert_temp float FuriHalAdcHandle*, uint16_t
1191 Function + furi_hal_adc_convert_to_voltage float FuriHalAdcHandle*, uint16_t
1192 Function + furi_hal_adc_convert_vbat float FuriHalAdcHandle*, uint16_t
1193 Function + furi_hal_adc_convert_vref float FuriHalAdcHandle*, uint16_t
1194 Function + furi_hal_adc_init void
1195 Function + furi_hal_adc_read uint16_t FuriHalAdcHandle*, FuriHalAdcChannel
1196 Function + furi_hal_adc_release void FuriHalAdcHandle*
1197 Function + furi_hal_bt_change_app FuriHalBleProfileBase* const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*
1198 Function + furi_hal_bt_check_profile_type _Bool FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*
1199 Function + furi_hal_bt_clear_white_list _Bool

View File

@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) {
void furi_hal_init(void) { void furi_hal_init(void) {
furi_hal_mpu_init(); furi_hal_mpu_init();
furi_hal_adc_init();
furi_hal_clock_init(); furi_hal_clock_init();
furi_hal_random_init(); furi_hal_random_init();
furi_hal_serial_control_init(); furi_hal_serial_control_init();

View File

@ -0,0 +1,281 @@
#include <furi_hal_adc.h>
#include <furi_hal_bus.h>
#include <furi_hal_cortex.h>
#include <furi_hal_power.h>
#include <furi.h>
#include <stm32wbxx_ll_adc.h>
#include <stm32wbxx_ll_system.h>
struct FuriHalAdcHandle {
ADC_TypeDef* adc;
FuriMutex* mutex;
uint32_t full_scale;
};
static const uint32_t furi_hal_adc_clock[] = {
[FuriHalAdcClockSync16] = LL_ADC_CLOCK_SYNC_PCLK_DIV4,
[FuriHalAdcClockSync32] = LL_ADC_CLOCK_SYNC_PCLK_DIV2,
[FuriHalAdcClockSync64] = LL_ADC_CLOCK_SYNC_PCLK_DIV1,
};
static const uint8_t furi_hal_adc_clock_div[] = {
[FuriHalAdcClockSync16] = 4,
[FuriHalAdcClockSync32] = 2,
[FuriHalAdcClockSync64] = 1,
};
static const uint32_t furi_hal_adc_oversample_ratio[] = {
[FuriHalAdcOversample2] = LL_ADC_OVS_RATIO_2,
[FuriHalAdcOversample4] = LL_ADC_OVS_RATIO_4,
[FuriHalAdcOversample8] = LL_ADC_OVS_RATIO_8,
[FuriHalAdcOversample16] = LL_ADC_OVS_RATIO_16,
[FuriHalAdcOversample32] = LL_ADC_OVS_RATIO_32,
[FuriHalAdcOversample64] = LL_ADC_OVS_RATIO_64,
[FuriHalAdcOversample128] = LL_ADC_OVS_RATIO_128,
[FuriHalAdcOversample256] = LL_ADC_OVS_RATIO_256,
};
static const uint32_t furi_hal_adc_oversample_shift[] = {
[FuriHalAdcOversample2] = LL_ADC_OVS_SHIFT_RIGHT_1,
[FuriHalAdcOversample4] = LL_ADC_OVS_SHIFT_RIGHT_2,
[FuriHalAdcOversample8] = LL_ADC_OVS_SHIFT_RIGHT_3,
[FuriHalAdcOversample16] = LL_ADC_OVS_SHIFT_RIGHT_4,
[FuriHalAdcOversample32] = LL_ADC_OVS_SHIFT_RIGHT_5,
[FuriHalAdcOversample64] = LL_ADC_OVS_SHIFT_RIGHT_6,
[FuriHalAdcOversample128] = LL_ADC_OVS_SHIFT_RIGHT_7,
[FuriHalAdcOversample256] = LL_ADC_OVS_SHIFT_RIGHT_8,
};
static const uint32_t furi_hal_adc_sampling_time[] = {
[FuriHalAdcSamplingtime2_5] = LL_ADC_SAMPLINGTIME_2CYCLES_5,
[FuriHalAdcSamplingtime6_5] = LL_ADC_SAMPLINGTIME_6CYCLES_5,
[FuriHalAdcSamplingtime12_5] = LL_ADC_SAMPLINGTIME_12CYCLES_5,
[FuriHalAdcSamplingtime24_5] = LL_ADC_SAMPLINGTIME_24CYCLES_5,
[FuriHalAdcSamplingtime47_5] = LL_ADC_SAMPLINGTIME_47CYCLES_5,
[FuriHalAdcSamplingtime92_5] = LL_ADC_SAMPLINGTIME_92CYCLES_5,
[FuriHalAdcSamplingtime247_5] = LL_ADC_SAMPLINGTIME_247CYCLES_5,
[FuriHalAdcSamplingtime640_5] = LL_ADC_SAMPLINGTIME_640CYCLES_5,
};
static const uint32_t furi_hal_adc_channel_map[] = {
[FuriHalAdcChannel0] = LL_ADC_CHANNEL_0,
[FuriHalAdcChannel1] = LL_ADC_CHANNEL_1,
[FuriHalAdcChannel2] = LL_ADC_CHANNEL_2,
[FuriHalAdcChannel3] = LL_ADC_CHANNEL_3,
[FuriHalAdcChannel4] = LL_ADC_CHANNEL_4,
[FuriHalAdcChannel5] = LL_ADC_CHANNEL_5,
[FuriHalAdcChannel6] = LL_ADC_CHANNEL_6,
[FuriHalAdcChannel7] = LL_ADC_CHANNEL_7,
[FuriHalAdcChannel8] = LL_ADC_CHANNEL_8,
[FuriHalAdcChannel9] = LL_ADC_CHANNEL_9,
[FuriHalAdcChannel10] = LL_ADC_CHANNEL_10,
[FuriHalAdcChannel11] = LL_ADC_CHANNEL_11,
[FuriHalAdcChannel12] = LL_ADC_CHANNEL_12,
[FuriHalAdcChannel13] = LL_ADC_CHANNEL_13,
[FuriHalAdcChannel14] = LL_ADC_CHANNEL_14,
[FuriHalAdcChannel15] = LL_ADC_CHANNEL_15,
[FuriHalAdcChannel16] = LL_ADC_CHANNEL_16,
[FuriHalAdcChannel17] = LL_ADC_CHANNEL_17,
[FuriHalAdcChannel18] = LL_ADC_CHANNEL_18,
[FuriHalAdcChannelVREFINT] = LL_ADC_CHANNEL_VREFINT,
[FuriHalAdcChannelTEMPSENSOR] = LL_ADC_CHANNEL_TEMPSENSOR,
[FuriHalAdcChannelVBAT] = LL_ADC_CHANNEL_VBAT,
};
static FuriHalAdcHandle* furi_hal_adc_handle = NULL;
void furi_hal_adc_init(void) {
furi_hal_adc_handle = malloc(sizeof(FuriHalAdcHandle));
furi_hal_adc_handle->adc = ADC1;
furi_hal_adc_handle->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
}
FuriHalAdcHandle* furi_hal_adc_acquire(void) {
furi_check(furi_mutex_acquire(furi_hal_adc_handle->mutex, FuriWaitForever) == FuriStatusOk);
furi_hal_power_insomnia_enter();
furi_hal_bus_enable(FuriHalBusADC);
return furi_hal_adc_handle;
}
void furi_hal_adc_release(FuriHalAdcHandle* handle) {
furi_check(handle);
if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC);
LL_VREFBUF_Disable();
LL_VREFBUF_EnableHIZ();
furi_hal_power_insomnia_exit();
furi_check(furi_mutex_release(furi_hal_adc_handle->mutex) == FuriStatusOk);
}
void furi_hal_adc_configure(FuriHalAdcHandle* handle) {
furi_hal_adc_configure_ex(
handle,
FuriHalAdcScale2048,
FuriHalAdcClockSync64,
FuriHalAdcOversample64,
FuriHalAdcSamplingtime247_5);
}
void furi_hal_adc_configure_ex(
FuriHalAdcHandle* handle,
FuriHalAdcScale scale,
FuriHalAdcClock clock,
FuriHalAdcOversample oversample,
FuriHalAdcSamplingTime sampling_time) {
furi_check(handle);
furi_check(scale == FuriHalAdcScale2048 || scale == FuriHalAdcScale2500);
furi_check(clock <= FuriHalAdcClockSync64);
furi_check(oversample <= FuriHalAdcOversampleNone);
furi_check(sampling_time <= FuriHalAdcSamplingtime640_5);
FuriHalCortexTimer timer;
if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC);
uint32_t trim_value = 0;
switch(scale) {
case FuriHalAdcScale2048:
LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE0);
trim_value = LL_VREFBUF_SC0_GetCalibration() & 0x3FU;
handle->full_scale = 2048;
break;
case FuriHalAdcScale2500:
LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE1);
trim_value = LL_VREFBUF_SC1_GetCalibration() & 0x3FU;
handle->full_scale = 2500;
break;
default:
furi_crash();
}
LL_VREFBUF_SetTrimming(trim_value);
LL_VREFBUF_Enable();
LL_VREFBUF_DisableHIZ();
timer = furi_hal_cortex_timer_get(500000); // 500ms to stabilize VREF
while(!LL_VREFBUF_IsVREFReady()) {
furi_check(!furi_hal_cortex_timer_is_expired(timer), "VREF fail");
};
furi_hal_bus_enable(FuriHalBusADC);
// ADC Common config
LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0};
ADC_CommonInitStruct.CommonClock = furi_hal_adc_clock[clock];
furi_check(
LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(handle->adc), &ADC_CommonInitStruct) ==
SUCCESS);
LL_ADC_SetCommonPathInternalCh(
__LL_ADC_COMMON_INSTANCE(handle->adc),
LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_PATH_INTERNAL_TEMPSENSOR |
LL_ADC_PATH_INTERNAL_VBAT);
// ADC config part 1
LL_ADC_InitTypeDef ADC_InitStruct = {0};
ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //-V1048
ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;
furi_check(LL_ADC_Init(handle->adc, &ADC_InitStruct) == SUCCESS);
// ADC config part 2: groups parameters
LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //-V1048
ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE;
ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE;
ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_NONE;
ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
furi_check(LL_ADC_REG_Init(handle->adc, &ADC_REG_InitStruct) == SUCCESS);
// ADC config part 3: sequencer and channels
if(oversample == FuriHalAdcOversampleNone) {
LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_DISABLE);
} else {
LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_GRP_REGULAR_CONTINUED);
LL_ADC_ConfigOverSamplingRatioShift(
handle->adc,
furi_hal_adc_oversample_ratio[oversample],
furi_hal_adc_oversample_shift[oversample]);
}
for(FuriHalAdcChannel channel = FuriHalAdcChannel0; channel < FuriHalAdcChannelNone;
channel++) {
// 47.5 cycles on 64MHz is first meaningful value for internal sources sampling
LL_ADC_SetChannelSamplingTime(
handle->adc,
furi_hal_adc_channel_map[channel],
furi_hal_adc_sampling_time[sampling_time]);
LL_ADC_SetChannelSingleDiff(
handle->adc, furi_hal_adc_channel_map[channel], LL_ADC_SINGLE_ENDED);
}
// Disable ADC deep power down (enabled by default after reset state)
LL_ADC_DisableDeepPowerDown(handle->adc);
// Enable ADC internal voltage regulator
LL_ADC_EnableInternalRegulator(handle->adc);
// Delay for ADC internal voltage regulator stabilization.
timer = furi_hal_cortex_timer_get(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US);
while(!furi_hal_cortex_timer_is_expired(timer))
;
// Run ADC self calibration
LL_ADC_StartCalibration(handle->adc, LL_ADC_SINGLE_ENDED);
// Poll for ADC effectively calibrated
while(LL_ADC_IsCalibrationOnGoing(handle->adc) != 0)
;
// Delay between ADC end of calibration and ADC enable
size_t end =
DWT->CYCCNT + (LL_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES * furi_hal_adc_clock_div[clock]);
while(DWT->CYCCNT < end)
;
// Enable ADC
LL_ADC_ClearFlag_ADRDY(handle->adc);
LL_ADC_Enable(handle->adc);
while(LL_ADC_IsActiveFlag_ADRDY(handle->adc) == 0)
;
}
uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel) {
furi_check(handle);
furi_check(channel <= FuriHalAdcChannelVBAT);
furi_check(LL_ADC_IsEnabled(handle->adc) == 1);
furi_check(LL_ADC_IsDisableOngoing(handle->adc) == 0);
furi_check(LL_ADC_REG_IsConversionOngoing(handle->adc) == 0);
LL_ADC_REG_SetSequencerRanks(
handle->adc, LL_ADC_REG_RANK_1, furi_hal_adc_channel_map[channel]);
LL_ADC_REG_StartConversion(handle->adc);
while(LL_ADC_IsActiveFlag_EOC(handle->adc) == 0)
;
uint16_t value = LL_ADC_REG_ReadConversionData12(handle->adc);
return value;
}
float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value) {
return (float)__LL_ADC_CALC_DATA_TO_VOLTAGE(handle->full_scale, value, LL_ADC_RESOLUTION_12B);
}
float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value) {
UNUSED(handle);
return (float)__LL_ADC_CALC_VREFANALOG_VOLTAGE(value, LL_ADC_RESOLUTION_12B);
}
float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value) {
return (float)__LL_ADC_CALC_TEMPERATURE(handle->full_scale, value, LL_ADC_RESOLUTION_12B);
}
float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value) {
return furi_hal_adc_convert_to_voltage(handle, value) * 3;
}

View File

@ -121,6 +121,7 @@ void furi_hal_clock_init(void) {
LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI); LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI);
LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1); LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1);
LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE);
LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_SYSCLK);
FURI_LOG_I(TAG, "Init OK"); FURI_LOG_I(TAG, "Init OK");
} }

View File

@ -70,28 +70,88 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12};
const GpioPinRecord gpio_pins[] = { const GpioPinRecord gpio_pins[] = {
// 5V: 1 // 5V: 1
{.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, {.pin = &gpio_ext_pa7,
{.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, .name = "PA7",
{.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, .channel = FuriHalAdcChannel12,
{.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, .number = 2,
{.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, .debug = false},
{.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, {.pin = &gpio_ext_pa6,
.name = "PA6",
.channel = FuriHalAdcChannel11,
.number = 3,
.debug = false},
{.pin = &gpio_ext_pa4,
.name = "PA4",
.channel = FuriHalAdcChannel9,
.number = 4,
.debug = false},
{.pin = &gpio_ext_pb3,
.name = "PB3",
.channel = FuriHalAdcChannelNone,
.number = 5,
.debug = false},
{.pin = &gpio_ext_pb2,
.name = "PB2",
.channel = FuriHalAdcChannelNone,
.number = 6,
.debug = false},
{.pin = &gpio_ext_pc3,
.name = "PC3",
.channel = FuriHalAdcChannel4,
.number = 7,
.debug = false},
// GND: 8 // GND: 8
// Space // Space
// 3v3: 9 // 3v3: 9
{.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, {.pin = &gpio_swclk,
.name = "PA14",
.channel = FuriHalAdcChannelNone,
.number = 10,
.debug = true},
// GND: 11 // GND: 11
{.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, {.pin = &gpio_swdio,
{.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, .name = "PA13",
{.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, .channel = FuriHalAdcChannelNone,
{.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, .number = 12,
{.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, .debug = true},
{.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, {.pin = &gpio_usart_tx,
.name = "PB6",
.channel = FuriHalAdcChannelNone,
.number = 13,
.debug = true},
{.pin = &gpio_usart_rx,
.name = "PB7",
.channel = FuriHalAdcChannelNone,
.number = 14,
.debug = true},
{.pin = &gpio_ext_pc1,
.name = "PC1",
.channel = FuriHalAdcChannel2,
.number = 15,
.debug = false},
{.pin = &gpio_ext_pc0,
.name = "PC0",
.channel = FuriHalAdcChannel1,
.number = 16,
.debug = false},
{.pin = &gpio_ibutton,
.name = "PB14",
.channel = FuriHalAdcChannelNone,
.number = 17,
.debug = true},
// GND: 18 // GND: 18
/* Dangerous pins, may damage hardware */ /* Dangerous pins, may damage hardware */
{.pin = &gpio_speaker, .name = "PB8", .debug = true}, {.pin = &gpio_speaker,
{.pin = &gpio_infrared_tx, .name = "PB9", .debug = true}, .name = "PB8",
.channel = FuriHalAdcChannelNone,
.number = 0,
.debug = true},
{.pin = &gpio_infrared_tx,
.name = "PB9",
.channel = FuriHalAdcChannelNone,
.number = 0,
.debug = true},
}; };
const size_t gpio_pins_count = COUNT_OF(gpio_pins); const size_t gpio_pins_count = COUNT_OF(gpio_pins);

View File

@ -1,9 +1,7 @@
#pragma once #pragma once
#include <furi.h> #include <furi.h>
#include <furi_hal_adc.h>
#include <stm32wbxx.h>
#include <stm32wbxx_ll_gpio.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -41,6 +39,7 @@ typedef struct {
typedef struct { typedef struct {
const GpioPin* pin; const GpioPin* pin;
const char* name; const char* name;
const FuriHalAdcChannel channel;
const uint8_t number; const uint8_t number;
const bool debug; const bool debug;
} GpioPinRecord; } GpioPinRecord;
@ -220,10 +219,11 @@ void furi_hal_resources_deinit_early(void);
void furi_hal_resources_init(void); void furi_hal_resources_init(void);
/** /** Get a corresponding external connector pin number for a gpio
* Get a corresponding external connector pin number for a gpio *
* @param gpio GpioPin * @param gpio GpioPin
* @return pin number or -1 if gpio is not on the external connector *
* @return pin number or -1 if gpio is not on the external connector
*/ */
int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio);

View File

@ -12,6 +12,7 @@ struct STOP_EXTERNING_ME {};
#include <furi_hal_cortex.h> #include <furi_hal_cortex.h>
#include <furi_hal_clock.h> #include <furi_hal_clock.h>
#include <furi_hal_adc.h>
#include <furi_hal_bus.h> #include <furi_hal_bus.h>
#include <furi_hal_crypto.h> #include <furi_hal_crypto.h>
#include <furi_hal_debug.h> #include <furi_hal_debug.h>

View File

@ -0,0 +1,229 @@
/**
* @file furi_hal_adc.h
* @brief ADC HAL API
*
* For the sake of simplicity this API implements only small subset
* of what ADC is actually capable of. Feel free to visit Reference
* Manual for STM32WB series and implement any other modes by your
* self.
*
* Couple things to keep in mind:
*
* - ADC resolution is 12 bits, but effective number of bits is ~10 at the best
* and further depends on how you use and configure it.
* - Analog domain is fed from SMPS which is quite noisy.
* - Because of that we use internal on-chip voltage reference for ADC.
* - It's capable of producing 2 voltages: 2.5V and 2.048V. This is the scale
* for your signal.
* - Only single ended mode is available. But you can implement differential one
* by using low level controls directly.
* - No DMA or interrupt API available at this point. But can be implemented
* with low level controls.
*
*
* How to use:
*
* - furi_hal_gpio_init - Configure your pins in `GpioModeAnalog`
* - furi_hal_adc_acquire - acquire ADC handle to work with
* - furi_hal_adc_configure - configure ADC block
* - furi_hal_adc_read - read value
* - furi_hal_adc_release - release ADC handle
*/
#pragma once
#include <furi.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct FuriHalAdcHandle FuriHalAdcHandle;
typedef enum {
FuriHalAdcScale2048, /**< 2.048V scale */
FuriHalAdcScale2500, /**< 2.5V scale */
} FuriHalAdcScale;
typedef enum {
FuriHalAdcClockSync16, /**< 16MHZ, synchronous */
FuriHalAdcClockSync32, /**< 32MHZ, synchronous */
FuriHalAdcClockSync64, /**< 64MHz, synchronous */
} FuriHalAdcClock;
typedef enum {
FuriHalAdcOversample2, /**< ADC will take 2 samples per each value */
FuriHalAdcOversample4, /**< ADC will take 4 samples per each value */
FuriHalAdcOversample8, /**< ADC will take 8 samples per each value */
FuriHalAdcOversample16, /**< ADC will take 16 samples per each value */
FuriHalAdcOversample32, /**< ADC will take 32 samples per each value */
FuriHalAdcOversample64, /**< ADC will take 64 samples per each value */
FuriHalAdcOversample128, /**< ADC will take 128 samples per each value */
FuriHalAdcOversample256, /**< ADC will take 256 samples per each value */
FuriHalAdcOversampleNone, /**< disable oversampling */
} FuriHalAdcOversample;
typedef enum {
FuriHalAdcSamplingtime2_5, /**< Sampling time 2.5 ADC clock */
FuriHalAdcSamplingtime6_5, /**< Sampling time 6.5 ADC clock */
FuriHalAdcSamplingtime12_5, /**< Sampling time 12.5 ADC clock */
FuriHalAdcSamplingtime24_5, /**< Sampling time 24.5 ADC clock */
FuriHalAdcSamplingtime47_5, /**< Sampling time 47.5 ADC clock */
FuriHalAdcSamplingtime92_5, /**< Sampling time 92.5 ADC clock */
FuriHalAdcSamplingtime247_5, /**< Sampling time 247.5 ADC clock */
FuriHalAdcSamplingtime640_5, /**< Sampling time 640.5 ADC clock */
} FuriHalAdcSamplingTime;
typedef enum {
/* Channels 0 - 5 are fast channels */
FuriHalAdcChannel0, /**< Internal channel, see `FuriHalAdcChannelVREFINT`. */
FuriHalAdcChannel1, /**< Channel 1p */
FuriHalAdcChannel2, /**< Channel 2p or 1n */
FuriHalAdcChannel3, /**< Channel 3p or 2n */
FuriHalAdcChannel4, /**< Channel 4p or 3n */
FuriHalAdcChannel5, /**< Channel 5p or 4n */
/* Channels 6 - 18 are slow channels */
FuriHalAdcChannel6, /**< Channel 6p or 5n */
FuriHalAdcChannel7, /**< Channel 7p or 6n */
FuriHalAdcChannel8, /**< Channel 8p or 7n */
FuriHalAdcChannel9, /**< Channel 9p or 8n */
FuriHalAdcChannel10, /**< Channel 10p or 9n */
FuriHalAdcChannel11, /**< Channel 11p or 10n */
FuriHalAdcChannel12, /**< Channel 12p or 11n */
FuriHalAdcChannel13, /**< Channel 13p or 12n */
FuriHalAdcChannel14, /**< Channel 14p or 13n */
FuriHalAdcChannel15, /**< Channel 15p or 14n */
FuriHalAdcChannel16, /**< Channel 16p or 15n */
FuriHalAdcChannel17, /**< Internal channel, see `FuriHalAdcChannelTEMPSENSOR`. */
FuriHalAdcChannel18, /**< Internal channel, see `FuriHalAdcChannelVBAT`. */
/* Special Channels: combines one of the 0-18 channel and additional internal peripherals */
FuriHalAdcChannelVREFINT, /**< Special channel for VREFINT, used for calibration and self test */
FuriHalAdcChannelTEMPSENSOR, /**< Special channel for on-die temperature sensor, requires at least 5us of sampling time */
FuriHalAdcChannelVBAT, /**< Special channel for VBAT/3 voltage, requires at least 12us of sampling time */
/* Special value to indicate that pin is not connected to ADC */
FuriHalAdcChannelNone, /**< No channel */
} FuriHalAdcChannel;
/** Initialize ADC subsystem */
void furi_hal_adc_init(void);
/** Acquire ADC handle
*
* Enables appropriate power and clocking domains
*
* @return FuriHalAdcHandle pointer
*/
FuriHalAdcHandle* furi_hal_adc_acquire(void);
/** Release ADC handle
*
* @param handle The ADC handle
*/
void furi_hal_adc_release(FuriHalAdcHandle* handle);
/** Configure with default parameters and enable ADC
*
* Parameters used:
* - FuriHalAdcScale2048 - 2.048V VREF Scale. Your signal should be in 0 -
* 2.048V range.
* - FuriHalAdcClockSync64 - Clocked from sysclk bus at 64MHz in synchronous
* mode. Fast, no delay on data bus access.
* - FuriHalAdcOversample64 - Going to acquire and average 64 samples. For
* circuits with slowly or not changing signal. Total time per one read:
* (1/64)*(12.5+247.5)*64 = 260us. The best results you'll get if your signal
* will stay on the same level all this time.
* - FuriHalAdcSamplingtime247_5 - Sampling(transfer from source to internal
* sampling capacitor) time is 247.5 ADC clocks: (1/64)*247.5 = 3.8671875us.
* For relatively high impedance circuits.
*
* Also keep your measurement circuit impedance under 10KOhm or oversampling
* results will be compromised. Verify your signal with oscilloscope(you may
* need fast oscilloscope: 200MHz bandwidth, 125MS/s), ensure that signal is not
* distorted by sampling.
*
* Those parameters were optimized for 0 - 2.048 voltage measurement with ~0.1%
* precision. You can get more, but it will require some magic.
*
* @param handle The ADC handle
*/
void furi_hal_adc_configure(FuriHalAdcHandle* handle);
/** Configure with extended parameters and enable ADC
*
* General advice is to start with default parameters, figure out what exactly
* is not working for you and then tune it. Also in some cases changing
* circuit(adding caps, lowering impedance, adding opamp) may be better than changing
* parameters.
*
* @warning In general ADC is a world of magic: make sure that you understand
* how your circuit and ADC works. Then carefully read STM32WB
* series reference manual. Setting incorrect parameters leads to
* very poor results. Also internal channels require special
* settings.
*
* @param handle The ADC handle
* @param[in] scale The ADC voltage scale
* @param[in] clock The ADC clock
* @param[in] oversample The ADC oversample mode
* @param[in] sampling_time The ADC sampling time
*/
void furi_hal_adc_configure_ex(
FuriHalAdcHandle* handle,
FuriHalAdcScale scale,
FuriHalAdcClock clock,
FuriHalAdcOversample oversample,
FuriHalAdcSamplingTime sampling_time);
/** Read single ADC value
*
* @param handle The ADC handle
* @param[in] channel The channel to sample
*
* @return value, 12 bits
*/
uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel);
/** Convert sampled value to voltage
*
* @param handle The ADC handle
* @param[in] value The value acquired with `furi_hal_adc_read`
*
* @return Voltage in mV
*/
float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value);
/** Convert sampled VREFINT value to voltage
*
* @param handle The ADC handle
* @param[in] value The value acquired with `furi_hal_adc_read` for
* `FuriHalAdcChannelVREFINT` channel
*
* @return Voltage in mV
*/
float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value);
/** Convert sampled TEMPSENSOR value to temperature
*
* @param handle The ADC handle
* @param[in] value The value acquired with `furi_hal_adc_read` for
* `FuriHalAdcChannelTEMPSENSOR` channel
*
* @return temperature in degree C
*/
float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value);
/** Convert sampled VBAT value to voltage
*
* @param handle The ADC handle
* @param[in] value The value acquired with `furi_hal_adc_read` for
* `FuriHalAdcChannelVBAT` channel
*
* @return Voltage in mV
*/
float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value);
#ifdef __cplusplus
}
#endif