From 70f93a48f513478e77df3b8bb0af6e472ccfe9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 18 Apr 2024 00:17:40 +0900 Subject: [PATCH] [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 --- .../examples/example_adc/application.fam | 9 + .../examples/example_adc/example_adc.c | 176 +++++++++++ .../example_custom_font/example_custom_font.c | 2 +- .../examples/example_images/example_images.c | 2 +- targets/f18/api_symbols.csv | 13 +- targets/f18/furi_hal/furi_hal.c | 1 + targets/f18/furi_hal/furi_hal_resources.c | 168 +++++++++-- targets/f18/furi_hal/furi_hal_resources.h | 5 +- targets/f7/api_symbols.csv | 13 +- targets/f7/furi_hal/furi_hal.c | 1 + targets/f7/furi_hal/furi_hal_adc.c | 281 ++++++++++++++++++ targets/f7/furi_hal/furi_hal_clock.c | 1 + targets/f7/furi_hal/furi_hal_resources.c | 90 +++++- targets/f7/furi_hal/furi_hal_resources.h | 14 +- targets/furi_hal_include/furi_hal.h | 1 + targets/furi_hal_include/furi_hal_adc.h | 229 ++++++++++++++ 16 files changed, 949 insertions(+), 57 deletions(-) create mode 100644 applications/examples/example_adc/application.fam create mode 100644 applications/examples/example_adc/example_adc.c create mode 100644 targets/f7/furi_hal/furi_hal_adc.c create mode 100644 targets/furi_hal_include/furi_hal_adc.h diff --git a/applications/examples/example_adc/application.fam b/applications/examples/example_adc/application.fam new file mode 100644 index 000000000..68aa2ac4d --- /dev/null +++ b/applications/examples/example_adc/application.fam @@ -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", +) diff --git a/applications/examples/example_adc/example_adc.c b/applications/examples/example_adc/example_adc.c new file mode 100644 index 000000000..53f8d5c12 --- /dev/null +++ b/applications/examples/example_adc/example_adc.c @@ -0,0 +1,176 @@ +/** + * @file example_adc.c + * @brief ADC example. + */ +#include +#include + +#include +#include +#include + +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; +} diff --git a/applications/examples/example_custom_font/example_custom_font.c b/applications/examples/example_custom_font/example_custom_font.c index 405db46e3..a175838e3 100644 --- a/applications/examples/example_custom_font/example_custom_font.c +++ b/applications/examples/example_custom_font/example_custom_font.c @@ -94,7 +94,7 @@ int32_t example_custom_font_main(void* p) { // Configure view port 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); // Register view port in GUI diff --git a/applications/examples/example_images/example_images.c b/applications/examples/example_images/example_images.c index ba9a354f6..60269a81f 100644 --- a/applications/examples/example_images/example_images.c +++ b/applications/examples/example_images/example_images.c @@ -39,7 +39,7 @@ int32_t example_images_main(void* p) { // Configure view port 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); // Register view port in GUI diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 703bcc50c..43500aa1c 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.8,, +Version,+,61.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.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/math_wrapper.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_cortex.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_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, 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_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 6e0a254c1..6cfc939b8 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) { void furi_hal_init(void) { furi_hal_mpu_init(); + furi_hal_adc_init(); furi_hal_clock_init(); furi_hal_random_init(); furi_hal_serial_control_init(); diff --git a/targets/f18/furi_hal/furi_hal_resources.c b/targets/f18/furi_hal/furi_hal_resources.c index 9935d4124..45ca3e6c4 100644 --- a/targets/f18/furi_hal/furi_hal_resources.c +++ b/targets/f18/furi_hal/furi_hal_resources.c @@ -68,49 +68,161 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { // 5V: 1 - {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + {.pin = &gpio_ext_pa7, + .name = "PA7", + .channel = FuriHalAdcChannel12, + .number = 2, + .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 // Space // 3v3: 9 - {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + {.pin = &gpio_swclk, + .name = "PA14", + .channel = FuriHalAdcChannelNone, + .number = 10, + .debug = true}, // GND: 11 - {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, - {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, - {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, - {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, - {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + {.pin = &gpio_swdio, + .name = "PA13", + .channel = FuriHalAdcChannelNone, + .number = 12, + .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 // 2nd column // 5V: 19 - {.pin = &gpio_ext_pc5, .name = "PC5", .number = 20, .debug = false}, - {.pin = &gpio_ext_pc4, .name = "PC4", .number = 21, .debug = false}, - {.pin = &gpio_ext_pa5, .name = "PA5", .number = 22, .debug = false}, - {.pin = &gpio_ext_pb9, .name = "PB9", .number = 23, .debug = false}, - {.pin = &gpio_ext_pa0, .name = "PA0", .number = 24, .debug = false}, - {.pin = &gpio_ext_pa1, .name = "PA1", .number = 25, .debug = false}, + {.pin = &gpio_ext_pc5, + .name = "PC5", + .channel = FuriHalAdcChannel14, + .number = 20, + .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 // Space // 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 - {.pin = &gpio_ext_pe4, .name = "PE4", .number = 30, .debug = false}, - {.pin = &gpio_ext_pa2, .name = "PA2", .number = 31, .debug = false}, - {.pin = &gpio_ext_pb4, .name = "PB4", .number = 32, .debug = false}, - {.pin = &gpio_ext_pb5, .name = "PB5", .number = 33, .debug = false}, - {.pin = &gpio_ext_pd0, .name = "PD0", .number = 34, .debug = false}, - {.pin = &gpio_ext_pb13, .name = "PB13", .number = 35, .debug = false}, + {.pin = &gpio_ext_pe4, + .name = "PE4", + .channel = FuriHalAdcChannelNone, + .number = 30, + .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 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_usart_rx, .name = "PB7", .number = 0, .debug = true}, - {.pin = &gpio_speaker, .name = "PB8", .number = 0, .debug = true}, + {.pin = &gpio_usart_rx, + .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); diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 3d45ad885..8f6173eb9 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -1,9 +1,7 @@ #pragma once #include - -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -41,6 +39,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const FuriHalAdcChannel channel; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c0edf9ffa..dbaadc4a1 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.8,, +Version,+,61.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.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/math_wrapper.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_cortex.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_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, 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_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, diff --git a/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c index 1fe8b241b..7f3959bc3 100644 --- a/targets/f7/furi_hal/furi_hal.c +++ b/targets/f7/furi_hal/furi_hal.c @@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) { void furi_hal_init(void) { furi_hal_mpu_init(); + furi_hal_adc_init(); furi_hal_clock_init(); furi_hal_random_init(); furi_hal_serial_control_init(); diff --git a/targets/f7/furi_hal/furi_hal_adc.c b/targets/f7/furi_hal/furi_hal_adc.c new file mode 100644 index 000000000..7a15f2e54 --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_adc.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include + +#include + +#include +#include + +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; +} diff --git a/targets/f7/furi_hal/furi_hal_clock.c b/targets/f7/furi_hal/furi_hal_clock.c index ad21031fe..9184fa715 100644 --- a/targets/f7/furi_hal/furi_hal_clock.c +++ b/targets/f7/furi_hal/furi_hal_clock.c @@ -121,6 +121,7 @@ void furi_hal_clock_init(void) { LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI); LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1); LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); + LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_SYSCLK); FURI_LOG_I(TAG, "Init OK"); } diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index b98d93769..486c24230 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -70,28 +70,88 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { // 5V: 1 - {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + {.pin = &gpio_ext_pa7, + .name = "PA7", + .channel = FuriHalAdcChannel12, + .number = 2, + .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 // Space // 3v3: 9 - {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + {.pin = &gpio_swclk, + .name = "PA14", + .channel = FuriHalAdcChannelNone, + .number = 10, + .debug = true}, // GND: 11 - {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, - {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, - {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, - {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, - {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + {.pin = &gpio_swdio, + .name = "PA13", + .channel = FuriHalAdcChannelNone, + .number = 12, + .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 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_speaker, .name = "PB8", .debug = true}, - {.pin = &gpio_infrared_tx, .name = "PB9", .debug = true}, + {.pin = &gpio_speaker, + .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); diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index bb01b9bcd..99768654d 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -1,9 +1,7 @@ #pragma once #include - -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -41,6 +39,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const FuriHalAdcChannel channel; const uint8_t number; const bool debug; } GpioPinRecord; @@ -220,10 +219,11 @@ void furi_hal_resources_deinit_early(void); void furi_hal_resources_init(void); -/** - * Get a corresponding external connector pin number for a gpio - * @param gpio GpioPin - * @return pin number or -1 if gpio is not on the external connector +/** Get a corresponding external connector pin number for a gpio + * + * @param gpio GpioPin + * + * @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); diff --git a/targets/furi_hal_include/furi_hal.h b/targets/furi_hal_include/furi_hal.h index 3eaf06da6..719df0275 100644 --- a/targets/furi_hal_include/furi_hal.h +++ b/targets/furi_hal_include/furi_hal.h @@ -12,6 +12,7 @@ struct STOP_EXTERNING_ME {}; #include #include +#include #include #include #include diff --git a/targets/furi_hal_include/furi_hal_adc.h b/targets/furi_hal_include/furi_hal_adc.h new file mode 100644 index 000000000..ecbdad2ca --- /dev/null +++ b/targets/furi_hal_include/furi_hal_adc.h @@ -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 +#include +#include + +#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