mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
[FL-3629] fbt: reworked assets & resources handling (#3160)
* fbt: reworking targets & assets handling WIP * fbt: dist fixes * fbt: moved SD card resources to owning apps * unit_tests: moved resources to app folder * github: updated unit_tests paths * github: packaging fixes * unit_tests: fixes * fbt: assets: internal cleanup * fbt: reworked assets handling * github: unit_tests: reintroducing fixes * minor cleanup * fbt: naming changes to reflect private nature of scons tools * fbt: resources: fixed dist archive paths * docs: updated paths * docs: updated more paths * docs: included "resources" parameter in app manifest docs; updated assets readme * updated gitignore for assets * github: updated action versions * unit_tests: restored timeout; scripts: assets: logging changes * gh: don't upload desktop animations for unit test run Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
22
targets/ReadMe.md
Normal file
22
targets/ReadMe.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Flipper firmware
|
||||
|
||||
What does it do?
|
||||
|
||||
- [x] RTOS
|
||||
- [x] FuriHAL
|
||||
- [x] FuriCore
|
||||
- [x] Services
|
||||
- [x] Applications
|
||||
|
||||
# Targets
|
||||
|
||||
| Name | Firmware Address | Reset Combo | DFU Combo |
|
||||
|-----------|-------------------|-----------------------|-----------------------|
|
||||
| f7 | 0x08000000 | L+Back, release both | L+Back, release Back |
|
||||
|
||||
Also, there is a "hardware" ST bootloader combo available even on a bricked or empty device: L+Ok+Back, release Back, Left.
|
||||
Target independent code and headers in `target/include` folders. More details in `documentation/KeyCombo.md`
|
||||
|
||||
# Building
|
||||
|
||||
Check out `documentation/fbt.md` on how to build and flash firmware.
|
||||
21
targets/SConscript
Normal file
21
targets/SConscript
Normal file
@@ -0,0 +1,21 @@
|
||||
Import("env")
|
||||
|
||||
env.Append(
|
||||
LINT_SOURCES=[Dir(".")],
|
||||
)
|
||||
|
||||
libenv = env.Clone(FW_LIB_NAME="flipper${TARGET_HW}")
|
||||
libenv.Append(
|
||||
CPPPATH=[
|
||||
"#/lib/stm32wb_copro/wpan/interface/patterns/ble_thread/tl",
|
||||
]
|
||||
)
|
||||
libenv.ApplyLibFlags()
|
||||
|
||||
|
||||
lib = libenv.StaticLibrary(
|
||||
"${FW_LIB_NAME}",
|
||||
env.get("TARGET_CFG").gatherSources(),
|
||||
)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
Return("lib")
|
||||
2786
targets/f18/api_symbols.csv
Normal file
2786
targets/f18/api_symbols.csv
Normal file
File diff suppressed because it is too large
Load Diff
67
targets/f18/furi_hal/furi_hal.c
Normal file
67
targets/f18/furi_hal/furi_hal.c
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_mpu.h>
|
||||
#include <furi_hal_memory.h>
|
||||
|
||||
#include <stm32wbxx_ll_cortex.h>
|
||||
|
||||
#define TAG "FuriHal"
|
||||
|
||||
void furi_hal_init_early() {
|
||||
furi_hal_cortex_init_early();
|
||||
furi_hal_clock_init_early();
|
||||
furi_hal_bus_init_early();
|
||||
furi_hal_dma_init_early();
|
||||
furi_hal_resources_init_early();
|
||||
furi_hal_os_init();
|
||||
furi_hal_spi_config_init_early();
|
||||
furi_hal_i2c_init_early();
|
||||
furi_hal_light_init();
|
||||
furi_hal_rtc_init_early();
|
||||
}
|
||||
|
||||
void furi_hal_deinit_early() {
|
||||
furi_hal_rtc_deinit_early();
|
||||
furi_hal_i2c_deinit_early();
|
||||
furi_hal_spi_config_deinit_early();
|
||||
furi_hal_resources_deinit_early();
|
||||
furi_hal_dma_deinit_early();
|
||||
furi_hal_bus_deinit_early();
|
||||
furi_hal_clock_deinit_early();
|
||||
}
|
||||
|
||||
void furi_hal_init() {
|
||||
furi_hal_mpu_init();
|
||||
furi_hal_clock_init();
|
||||
furi_hal_random_init();
|
||||
furi_hal_console_init();
|
||||
furi_hal_rtc_init();
|
||||
furi_hal_interrupt_init();
|
||||
furi_hal_flash_init();
|
||||
furi_hal_resources_init();
|
||||
furi_hal_version_init();
|
||||
furi_hal_spi_config_init();
|
||||
furi_hal_spi_dma_init();
|
||||
furi_hal_speaker_init();
|
||||
furi_hal_crypto_init();
|
||||
furi_hal_i2c_init();
|
||||
furi_hal_power_init();
|
||||
furi_hal_light_init();
|
||||
furi_hal_bt_init();
|
||||
furi_hal_memory_init();
|
||||
|
||||
#ifndef FURI_RAM_EXEC
|
||||
furi_hal_usb_init();
|
||||
furi_hal_vibro_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
void furi_hal_switch(void* address) {
|
||||
__set_BASEPRI(0);
|
||||
asm volatile("ldr r3, [%0] \n"
|
||||
"msr msp, r3 \n"
|
||||
"ldr r3, [%1] \n"
|
||||
"mov pc, r3 \n"
|
||||
:
|
||||
: "r"(address), "r"(address + 0x4)
|
||||
: "r3");
|
||||
}
|
||||
149
targets/f18/furi_hal/furi_hal_power_config.c
Normal file
149
targets/f18/furi_hal/furi_hal_power_config.c
Normal file
@@ -0,0 +1,149 @@
|
||||
#include <bq27220_data_memory.h>
|
||||
|
||||
const BQ27220DMGaugingConfig furi_hal_power_gauge_data_memory_gauging_config = {
|
||||
.CCT = 1,
|
||||
.CSYNC = 0,
|
||||
.EDV_CMP = 0,
|
||||
.SC = 1,
|
||||
.FIXED_EDV0 = 1,
|
||||
.FCC_LIM = 1,
|
||||
.FC_FOR_VDQ = 1,
|
||||
.IGNORE_SD = 1,
|
||||
.SME0 = 0,
|
||||
};
|
||||
|
||||
const BQ27220DMData furi_hal_power_gauge_data_memory[] = {
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1GaugingConfig,
|
||||
.type = BQ27220DMTypePtr16,
|
||||
.value.u32 = (uint32_t)&furi_hal_power_gauge_data_memory_gauging_config,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1FullChargeCapacity,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 1300,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1DesignCapacity,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 1300,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1EMF,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3679,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1C0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 430,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1R0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 334,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1T0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 4626,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1R1,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 408,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1TC,
|
||||
.type = BQ27220DMTypeU8,
|
||||
.value.u8 = 11,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1C1,
|
||||
.type = BQ27220DMTypeU8,
|
||||
.value.u8 = 0,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 4044,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD10,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3905,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD20,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3807,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD30,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3718,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD40,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3642,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD50,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3585,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD60,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3546,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD70,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3514,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD80,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3477,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD90,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3411,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD100,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3299,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1EDV0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3300,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1EDV1,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3321,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1EDV2,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3355,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressCalibrationCurrentDeadband,
|
||||
.type = BQ27220DMTypeU8,
|
||||
.value.u8 = 1,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressConfigurationPowerSleepCurrent,
|
||||
.type = BQ27220DMTypeI16,
|
||||
.value.i16 = 1,
|
||||
},
|
||||
{
|
||||
.type = BQ27220DMTypeEnd,
|
||||
},
|
||||
};
|
||||
244
targets/f18/furi_hal/furi_hal_resources.c
Normal file
244
targets/f18/furi_hal/furi_hal_resources.c
Normal file
@@ -0,0 +1,244 @@
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_bus.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
#include <stm32wbxx_ll_pwr.h>
|
||||
|
||||
#define TAG "FuriHalResources"
|
||||
|
||||
const GpioPin gpio_swdio = {.port = GPIOA, .pin = LL_GPIO_PIN_13};
|
||||
const GpioPin gpio_swclk = {.port = GPIOA, .pin = LL_GPIO_PIN_14};
|
||||
|
||||
const GpioPin gpio_vibro = {.port = GPIOA, .pin = LL_GPIO_PIN_8};
|
||||
const GpioPin gpio_ibutton = {.port = GPIOB, .pin = LL_GPIO_PIN_14};
|
||||
|
||||
const GpioPin gpio_display_cs = {.port = GPIOC, .pin = LL_GPIO_PIN_11};
|
||||
const GpioPin gpio_display_rst_n = {.port = GPIOB, .pin = LL_GPIO_PIN_0};
|
||||
const GpioPin gpio_display_di = {.port = GPIOB, .pin = LL_GPIO_PIN_1};
|
||||
const GpioPin gpio_sdcard_cs = {.port = GPIOC, .pin = LL_GPIO_PIN_12};
|
||||
const GpioPin gpio_sdcard_cd = {.port = GPIOC, .pin = LL_GPIO_PIN_10};
|
||||
|
||||
const GpioPin gpio_button_up = {.port = GPIOB, .pin = LL_GPIO_PIN_10};
|
||||
const GpioPin gpio_button_down = {.port = GPIOC, .pin = LL_GPIO_PIN_6};
|
||||
const GpioPin gpio_button_right = {.port = GPIOB, .pin = LL_GPIO_PIN_12};
|
||||
const GpioPin gpio_button_left = {.port = GPIOB, .pin = LL_GPIO_PIN_11};
|
||||
const GpioPin gpio_button_ok = {.port = GPIOH, .pin = LL_GPIO_PIN_3};
|
||||
const GpioPin gpio_button_back = {.port = GPIOC, .pin = LL_GPIO_PIN_13};
|
||||
|
||||
const GpioPin gpio_spi_d_miso = {.port = GPIOC, .pin = LL_GPIO_PIN_2};
|
||||
const GpioPin gpio_spi_d_mosi = {.port = GPIOB, .pin = LL_GPIO_PIN_15};
|
||||
const GpioPin gpio_spi_d_sck = {.port = GPIOD, .pin = LL_GPIO_PIN_1};
|
||||
|
||||
const GpioPin gpio_ext_pc0 = {.port = GPIOC, .pin = LL_GPIO_PIN_0};
|
||||
const GpioPin gpio_ext_pc1 = {.port = GPIOC, .pin = LL_GPIO_PIN_1};
|
||||
const GpioPin gpio_ext_pc3 = {.port = GPIOC, .pin = LL_GPIO_PIN_3};
|
||||
const GpioPin gpio_ext_pb2 = {.port = GPIOB, .pin = LL_GPIO_PIN_2};
|
||||
const GpioPin gpio_ext_pb3 = {.port = GPIOB, .pin = LL_GPIO_PIN_3};
|
||||
const GpioPin gpio_ext_pa4 = {.port = GPIOA, .pin = LL_GPIO_PIN_4};
|
||||
const GpioPin gpio_ext_pa6 = {.port = GPIOA, .pin = LL_GPIO_PIN_6};
|
||||
const GpioPin gpio_ext_pa7 = {.port = GPIOA, .pin = LL_GPIO_PIN_7};
|
||||
|
||||
const GpioPin gpio_ext_pc5 = {.port = GPIOC, .pin = LL_GPIO_PIN_5};
|
||||
const GpioPin gpio_ext_pc4 = {.port = GPIOC, .pin = LL_GPIO_PIN_4};
|
||||
const GpioPin gpio_ext_pa5 = {.port = GPIOA, .pin = LL_GPIO_PIN_5};
|
||||
const GpioPin gpio_ext_pb9 = {.port = GPIOB, .pin = LL_GPIO_PIN_9};
|
||||
const GpioPin gpio_ext_pa0 = {.port = GPIOA, .pin = LL_GPIO_PIN_0};
|
||||
const GpioPin gpio_ext_pa1 = {.port = GPIOA, .pin = LL_GPIO_PIN_1};
|
||||
const GpioPin gpio_ext_pa15 = {.port = GPIOA, .pin = LL_GPIO_PIN_15};
|
||||
const GpioPin gpio_ext_pe4 = {.port = GPIOE, .pin = LL_GPIO_PIN_4};
|
||||
const GpioPin gpio_ext_pa2 = {.port = GPIOA, .pin = LL_GPIO_PIN_2};
|
||||
const GpioPin gpio_ext_pb4 = {.port = GPIOB, .pin = LL_GPIO_PIN_4};
|
||||
const GpioPin gpio_ext_pb5 = {.port = GPIOB, .pin = LL_GPIO_PIN_5};
|
||||
const GpioPin gpio_ext_pd0 = {.port = GPIOD, .pin = LL_GPIO_PIN_0};
|
||||
const GpioPin gpio_ext_pb13 = {.port = GPIOB, .pin = LL_GPIO_PIN_13};
|
||||
|
||||
const GpioPin gpio_usart_tx = {.port = GPIOB, .pin = LL_GPIO_PIN_6};
|
||||
const GpioPin gpio_usart_rx = {.port = GPIOB, .pin = LL_GPIO_PIN_7};
|
||||
|
||||
const GpioPin gpio_i2c_power_sda = {.port = GPIOA, .pin = LL_GPIO_PIN_10};
|
||||
const GpioPin gpio_i2c_power_scl = {.port = GPIOA, .pin = LL_GPIO_PIN_9};
|
||||
|
||||
const GpioPin gpio_speaker = {.port = GPIOB, .pin = LL_GPIO_PIN_8};
|
||||
|
||||
const GpioPin gpio_periph_power = {.port = GPIOA, .pin = LL_GPIO_PIN_3};
|
||||
|
||||
const GpioPin gpio_usb_dm = {.port = GPIOA, .pin = LL_GPIO_PIN_11};
|
||||
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},
|
||||
// GND: 8
|
||||
// Space
|
||||
// 3v3: 9
|
||||
{.pin = &gpio_swclk, .name = "PA14", .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},
|
||||
// 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},
|
||||
// KEY: 26
|
||||
// Space
|
||||
// 3v3: 27
|
||||
{.pin = &gpio_ext_pa15, .name = "PA15", .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},
|
||||
// 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},
|
||||
};
|
||||
|
||||
const size_t gpio_pins_count = COUNT_OF(gpio_pins);
|
||||
|
||||
const InputPin input_pins[] = {
|
||||
{.gpio = &gpio_button_up, .key = InputKeyUp, .inverted = true, .name = "Up"},
|
||||
{.gpio = &gpio_button_down, .key = InputKeyDown, .inverted = true, .name = "Down"},
|
||||
{.gpio = &gpio_button_right, .key = InputKeyRight, .inverted = true, .name = "Right"},
|
||||
{.gpio = &gpio_button_left, .key = InputKeyLeft, .inverted = true, .name = "Left"},
|
||||
{.gpio = &gpio_button_ok, .key = InputKeyOk, .inverted = false, .name = "OK"},
|
||||
{.gpio = &gpio_button_back, .key = InputKeyBack, .inverted = true, .name = "Back"},
|
||||
};
|
||||
|
||||
const size_t input_pins_count = COUNT_OF(input_pins);
|
||||
|
||||
static void furi_hal_resources_init_input_pins(GpioMode mode) {
|
||||
for(size_t i = 0; i < input_pins_count; i++) {
|
||||
furi_hal_gpio_init(
|
||||
input_pins[i].gpio,
|
||||
mode,
|
||||
(input_pins[i].inverted) ? GpioPullUp : GpioPullDown,
|
||||
GpioSpeedLow);
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_hal_resources_init_gpio_pins(GpioMode mode) {
|
||||
for(size_t i = 0; i < gpio_pins_count; i++) {
|
||||
if(!gpio_pins[i].debug) {
|
||||
furi_hal_gpio_init(gpio_pins[i].pin, mode, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_resources_init_early() {
|
||||
furi_hal_bus_enable(FuriHalBusGPIOA);
|
||||
furi_hal_bus_enable(FuriHalBusGPIOB);
|
||||
furi_hal_bus_enable(FuriHalBusGPIOC);
|
||||
furi_hal_bus_enable(FuriHalBusGPIOD);
|
||||
furi_hal_bus_enable(FuriHalBusGPIOE);
|
||||
furi_hal_bus_enable(FuriHalBusGPIOH);
|
||||
|
||||
furi_hal_resources_init_input_pins(GpioModeInput);
|
||||
|
||||
// Explicit, surviving reset, pulls
|
||||
LL_PWR_EnablePUPDCfg();
|
||||
LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_A, LL_PWR_GPIO_BIT_8); // gpio_vibro
|
||||
LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_8); // gpio_speaker
|
||||
|
||||
// SD Card stepdown control
|
||||
furi_hal_gpio_write(&gpio_periph_power, 1);
|
||||
furi_hal_gpio_init(&gpio_periph_power, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
// Display pins
|
||||
furi_hal_gpio_write(&gpio_display_rst_n, 0);
|
||||
furi_hal_gpio_init_simple(&gpio_display_rst_n, GpioModeOutputPushPull);
|
||||
LL_PWR_EnableGPIOPullUp(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_0); // gpio_display_rst_n
|
||||
furi_hal_gpio_write(&gpio_display_di, 0);
|
||||
furi_hal_gpio_init_simple(&gpio_display_di, GpioModeOutputPushPull);
|
||||
LL_PWR_EnableGPIOPullDown(LL_PWR_GPIO_B, LL_PWR_GPIO_BIT_1); // gpio_display_di
|
||||
|
||||
// Hard reset USB
|
||||
furi_hal_gpio_write(&gpio_usb_dm, 1);
|
||||
furi_hal_gpio_write(&gpio_usb_dp, 1);
|
||||
furi_hal_gpio_init_simple(&gpio_usb_dm, GpioModeOutputOpenDrain);
|
||||
furi_hal_gpio_init_simple(&gpio_usb_dp, GpioModeOutputOpenDrain);
|
||||
furi_hal_gpio_write(&gpio_usb_dm, 0);
|
||||
furi_hal_gpio_write(&gpio_usb_dp, 0);
|
||||
furi_delay_us(5); // Device Driven disconnect: 2.5us + extra to compensate cables
|
||||
furi_hal_gpio_write(&gpio_usb_dm, 1);
|
||||
furi_hal_gpio_write(&gpio_usb_dp, 1);
|
||||
furi_hal_gpio_init_simple(&gpio_usb_dm, GpioModeAnalog);
|
||||
furi_hal_gpio_init_simple(&gpio_usb_dp, GpioModeAnalog);
|
||||
furi_hal_gpio_write(&gpio_usb_dm, 0);
|
||||
furi_hal_gpio_write(&gpio_usb_dp, 0);
|
||||
|
||||
// External header pins
|
||||
furi_hal_resources_init_gpio_pins(GpioModeAnalog);
|
||||
}
|
||||
|
||||
void furi_hal_resources_deinit_early() {
|
||||
furi_hal_resources_init_input_pins(GpioModeAnalog);
|
||||
furi_hal_bus_disable(FuriHalBusGPIOA);
|
||||
furi_hal_bus_disable(FuriHalBusGPIOB);
|
||||
furi_hal_bus_disable(FuriHalBusGPIOC);
|
||||
furi_hal_bus_disable(FuriHalBusGPIOD);
|
||||
furi_hal_bus_disable(FuriHalBusGPIOE);
|
||||
furi_hal_bus_disable(FuriHalBusGPIOH);
|
||||
}
|
||||
|
||||
void furi_hal_resources_init() {
|
||||
// Button pins
|
||||
furi_hal_resources_init_input_pins(GpioModeInterruptRiseFall);
|
||||
|
||||
// SD pins
|
||||
furi_hal_gpio_init(&gpio_sdcard_cd, GpioModeInput, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_write(&gpio_sdcard_cd, 0);
|
||||
|
||||
furi_hal_gpio_init(&gpio_ibutton, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
|
||||
NVIC_EnableIRQ(EXTI0_IRQn);
|
||||
|
||||
NVIC_SetPriority(EXTI1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
|
||||
NVIC_EnableIRQ(EXTI1_IRQn);
|
||||
|
||||
NVIC_SetPriority(EXTI2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
|
||||
NVIC_EnableIRQ(EXTI2_IRQn);
|
||||
|
||||
NVIC_SetPriority(EXTI3_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
|
||||
NVIC_EnableIRQ(EXTI3_IRQn);
|
||||
|
||||
NVIC_SetPriority(EXTI4_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
|
||||
NVIC_EnableIRQ(EXTI4_IRQn);
|
||||
|
||||
NVIC_SetPriority(EXTI9_5_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
|
||||
NVIC_EnableIRQ(EXTI9_5_IRQn);
|
||||
|
||||
NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
|
||||
NVIC_EnableIRQ(EXTI15_10_IRQn);
|
||||
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio) {
|
||||
for(size_t i = 0; i < gpio_pins_count; i++) {
|
||||
if(gpio_pins[i].pin == gpio) {
|
||||
return gpio_pins[i].number;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
127
targets/f18/furi_hal/furi_hal_resources.h
Normal file
127
targets/f18/furi_hal/furi_hal_resources.h
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include <stm32wbxx.h>
|
||||
#include <stm32wbxx_ll_gpio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Input Related Constants */
|
||||
#define INPUT_DEBOUNCE_TICKS 4
|
||||
|
||||
/* Input Keys */
|
||||
typedef enum {
|
||||
InputKeyUp,
|
||||
InputKeyDown,
|
||||
InputKeyRight,
|
||||
InputKeyLeft,
|
||||
InputKeyOk,
|
||||
InputKeyBack,
|
||||
InputKeyMAX, /**< Special value */
|
||||
} InputKey;
|
||||
|
||||
/* Light */
|
||||
typedef enum {
|
||||
LightRed = (1 << 0),
|
||||
LightGreen = (1 << 1),
|
||||
LightBlue = (1 << 2),
|
||||
LightBacklight = (1 << 3),
|
||||
} Light;
|
||||
|
||||
typedef struct {
|
||||
const GpioPin* gpio;
|
||||
const InputKey key;
|
||||
const bool inverted;
|
||||
const char* name;
|
||||
} InputPin;
|
||||
|
||||
typedef struct {
|
||||
const GpioPin* pin;
|
||||
const char* name;
|
||||
const uint8_t number;
|
||||
const bool debug;
|
||||
} GpioPinRecord;
|
||||
|
||||
extern const InputPin input_pins[];
|
||||
extern const size_t input_pins_count;
|
||||
|
||||
extern const GpioPinRecord gpio_pins[];
|
||||
extern const size_t gpio_pins_count;
|
||||
|
||||
extern const GpioPin gpio_swdio;
|
||||
extern const GpioPin gpio_swclk;
|
||||
|
||||
extern const GpioPin gpio_vibro;
|
||||
extern const GpioPin gpio_ibutton;
|
||||
|
||||
extern const GpioPin gpio_display_cs;
|
||||
extern const GpioPin gpio_display_rst_n;
|
||||
extern const GpioPin gpio_display_di;
|
||||
extern const GpioPin gpio_sdcard_cs;
|
||||
extern const GpioPin gpio_sdcard_cd;
|
||||
|
||||
extern const GpioPin gpio_button_up;
|
||||
extern const GpioPin gpio_button_down;
|
||||
extern const GpioPin gpio_button_right;
|
||||
extern const GpioPin gpio_button_left;
|
||||
extern const GpioPin gpio_button_ok;
|
||||
extern const GpioPin gpio_button_back;
|
||||
|
||||
extern const GpioPin gpio_spi_d_miso;
|
||||
extern const GpioPin gpio_spi_d_mosi;
|
||||
extern const GpioPin gpio_spi_d_sck;
|
||||
|
||||
extern const GpioPin gpio_ext_pc0;
|
||||
extern const GpioPin gpio_ext_pc1;
|
||||
extern const GpioPin gpio_ext_pc3;
|
||||
extern const GpioPin gpio_ext_pb2;
|
||||
extern const GpioPin gpio_ext_pb3;
|
||||
extern const GpioPin gpio_ext_pa4;
|
||||
extern const GpioPin gpio_ext_pa6;
|
||||
extern const GpioPin gpio_ext_pa7;
|
||||
|
||||
extern const GpioPin gpio_ext_pc5;
|
||||
extern const GpioPin gpio_ext_pc4;
|
||||
extern const GpioPin gpio_ext_pa5;
|
||||
extern const GpioPin gpio_ext_pb9;
|
||||
extern const GpioPin gpio_ext_pa0;
|
||||
extern const GpioPin gpio_ext_pa1;
|
||||
extern const GpioPin gpio_ext_pa15;
|
||||
extern const GpioPin gpio_ext_pe4;
|
||||
extern const GpioPin gpio_ext_pa2;
|
||||
extern const GpioPin gpio_ext_pb4;
|
||||
extern const GpioPin gpio_ext_pb5;
|
||||
extern const GpioPin gpio_ext_pd0;
|
||||
extern const GpioPin gpio_ext_pb13;
|
||||
|
||||
extern const GpioPin gpio_usart_tx;
|
||||
extern const GpioPin gpio_usart_rx;
|
||||
extern const GpioPin gpio_i2c_power_sda;
|
||||
extern const GpioPin gpio_i2c_power_scl;
|
||||
|
||||
extern const GpioPin gpio_speaker;
|
||||
|
||||
extern const GpioPin gpio_periph_power;
|
||||
|
||||
extern const GpioPin gpio_usb_dm;
|
||||
extern const GpioPin gpio_usb_dp;
|
||||
|
||||
void furi_hal_resources_init_early();
|
||||
|
||||
void furi_hal_resources_deinit_early();
|
||||
|
||||
void furi_hal_resources_init();
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
356
targets/f18/furi_hal/furi_hal_spi_config.c
Normal file
356
targets/f18/furi_hal/furi_hal_spi_config.c
Normal file
@@ -0,0 +1,356 @@
|
||||
#include <furi_hal_spi_config.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_bus.h>
|
||||
#include <furi_hal_spi.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "FuriHalSpiConfig"
|
||||
|
||||
/* SPI Presets */
|
||||
|
||||
const LL_SPI_InitTypeDef furi_hal_spi_preset_2edge_low_8m = {
|
||||
.Mode = LL_SPI_MODE_MASTER,
|
||||
.TransferDirection = LL_SPI_FULL_DUPLEX,
|
||||
.DataWidth = LL_SPI_DATAWIDTH_8BIT,
|
||||
.ClockPolarity = LL_SPI_POLARITY_LOW,
|
||||
.ClockPhase = LL_SPI_PHASE_2EDGE,
|
||||
.NSS = LL_SPI_NSS_SOFT,
|
||||
.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8,
|
||||
.BitOrder = LL_SPI_MSB_FIRST,
|
||||
.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE,
|
||||
.CRCPoly = 7,
|
||||
};
|
||||
|
||||
const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_8m = {
|
||||
.Mode = LL_SPI_MODE_MASTER,
|
||||
.TransferDirection = LL_SPI_FULL_DUPLEX,
|
||||
.DataWidth = LL_SPI_DATAWIDTH_8BIT,
|
||||
.ClockPolarity = LL_SPI_POLARITY_LOW,
|
||||
.ClockPhase = LL_SPI_PHASE_1EDGE,
|
||||
.NSS = LL_SPI_NSS_SOFT,
|
||||
.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8,
|
||||
.BitOrder = LL_SPI_MSB_FIRST,
|
||||
.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE,
|
||||
.CRCPoly = 7,
|
||||
};
|
||||
|
||||
const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_4m = {
|
||||
.Mode = LL_SPI_MODE_MASTER,
|
||||
.TransferDirection = LL_SPI_FULL_DUPLEX,
|
||||
.DataWidth = LL_SPI_DATAWIDTH_8BIT,
|
||||
.ClockPolarity = LL_SPI_POLARITY_LOW,
|
||||
.ClockPhase = LL_SPI_PHASE_1EDGE,
|
||||
.NSS = LL_SPI_NSS_SOFT,
|
||||
.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV16,
|
||||
.BitOrder = LL_SPI_MSB_FIRST,
|
||||
.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE,
|
||||
.CRCPoly = 7,
|
||||
};
|
||||
|
||||
const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_16m = {
|
||||
.Mode = LL_SPI_MODE_MASTER,
|
||||
.TransferDirection = LL_SPI_FULL_DUPLEX,
|
||||
.DataWidth = LL_SPI_DATAWIDTH_8BIT,
|
||||
.ClockPolarity = LL_SPI_POLARITY_LOW,
|
||||
.ClockPhase = LL_SPI_PHASE_1EDGE,
|
||||
.NSS = LL_SPI_NSS_SOFT,
|
||||
.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV2,
|
||||
.BitOrder = LL_SPI_MSB_FIRST,
|
||||
.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE,
|
||||
.CRCPoly = 7,
|
||||
};
|
||||
|
||||
const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_2m = {
|
||||
.Mode = LL_SPI_MODE_MASTER,
|
||||
.TransferDirection = LL_SPI_FULL_DUPLEX,
|
||||
.DataWidth = LL_SPI_DATAWIDTH_8BIT,
|
||||
.ClockPolarity = LL_SPI_POLARITY_LOW,
|
||||
.ClockPhase = LL_SPI_PHASE_1EDGE,
|
||||
.NSS = LL_SPI_NSS_SOFT,
|
||||
.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV32,
|
||||
.BitOrder = LL_SPI_MSB_FIRST,
|
||||
.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE,
|
||||
.CRCPoly = 7,
|
||||
};
|
||||
|
||||
/* SPI Buses */
|
||||
|
||||
FuriMutex* furi_hal_spi_bus_r_mutex = NULL;
|
||||
|
||||
void furi_hal_spi_config_init_early() {
|
||||
furi_hal_spi_bus_init(&furi_hal_spi_bus_d);
|
||||
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_display);
|
||||
}
|
||||
|
||||
void furi_hal_spi_config_deinit_early() {
|
||||
furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_display);
|
||||
furi_hal_spi_bus_deinit(&furi_hal_spi_bus_d);
|
||||
}
|
||||
|
||||
void furi_hal_spi_config_init() {
|
||||
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_fast);
|
||||
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_sd_slow);
|
||||
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
static void furi_hal_spi_bus_r_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) {
|
||||
if(event == FuriHalSpiBusEventInit) {
|
||||
furi_hal_spi_bus_r_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
bus->current_handle = NULL;
|
||||
} else if(event == FuriHalSpiBusEventDeinit) {
|
||||
furi_mutex_free(furi_hal_spi_bus_r_mutex);
|
||||
} else if(event == FuriHalSpiBusEventLock) {
|
||||
furi_check(furi_mutex_acquire(furi_hal_spi_bus_r_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
} else if(event == FuriHalSpiBusEventUnlock) {
|
||||
furi_check(furi_mutex_release(furi_hal_spi_bus_r_mutex) == FuriStatusOk);
|
||||
} else if(event == FuriHalSpiBusEventActivate) {
|
||||
furi_hal_bus_enable(FuriHalBusSPI1);
|
||||
} else if(event == FuriHalSpiBusEventDeactivate) {
|
||||
furi_hal_bus_disable(FuriHalBusSPI1);
|
||||
}
|
||||
}
|
||||
|
||||
FuriHalSpiBus furi_hal_spi_bus_r = {
|
||||
.spi = SPI1,
|
||||
.callback = furi_hal_spi_bus_r_event_callback,
|
||||
};
|
||||
|
||||
FuriMutex* furi_hal_spi_bus_d_mutex = NULL;
|
||||
|
||||
static void furi_hal_spi_bus_d_event_callback(FuriHalSpiBus* bus, FuriHalSpiBusEvent event) {
|
||||
if(event == FuriHalSpiBusEventInit) {
|
||||
furi_hal_spi_bus_d_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
bus->current_handle = NULL;
|
||||
} else if(event == FuriHalSpiBusEventDeinit) {
|
||||
furi_mutex_free(furi_hal_spi_bus_d_mutex);
|
||||
} else if(event == FuriHalSpiBusEventLock) {
|
||||
furi_check(furi_mutex_acquire(furi_hal_spi_bus_d_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
} else if(event == FuriHalSpiBusEventUnlock) {
|
||||
furi_check(furi_mutex_release(furi_hal_spi_bus_d_mutex) == FuriStatusOk);
|
||||
} else if(event == FuriHalSpiBusEventActivate) {
|
||||
furi_hal_bus_enable(FuriHalBusSPI2);
|
||||
} else if(event == FuriHalSpiBusEventDeactivate) {
|
||||
furi_hal_bus_disable(FuriHalBusSPI2);
|
||||
}
|
||||
}
|
||||
|
||||
FuriHalSpiBus furi_hal_spi_bus_d = {
|
||||
.spi = SPI2,
|
||||
.callback = furi_hal_spi_bus_d_event_callback,
|
||||
};
|
||||
|
||||
/* SPI Bus Handles */
|
||||
|
||||
inline static void furi_hal_spi_bus_r_handle_event_callback(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
FuriHalSpiBusHandleEvent event,
|
||||
const LL_SPI_InitTypeDef* preset) {
|
||||
if(event == FuriHalSpiBusHandleEventInit) {
|
||||
furi_hal_gpio_write(handle->cs, true);
|
||||
furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
} else if(event == FuriHalSpiBusHandleEventDeinit) {
|
||||
furi_hal_gpio_write(handle->cs, true);
|
||||
furi_hal_gpio_init(handle->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
} else if(event == FuriHalSpiBusHandleEventActivate) {
|
||||
LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset);
|
||||
LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER);
|
||||
LL_SPI_Enable(handle->bus->spi);
|
||||
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->miso,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI1);
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->mosi,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI1);
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->sck,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI1);
|
||||
|
||||
furi_hal_gpio_write(handle->cs, false);
|
||||
} else if(event == FuriHalSpiBusHandleEventDeactivate) {
|
||||
furi_hal_gpio_write(handle->cs, true);
|
||||
|
||||
furi_hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
LL_SPI_Disable(handle->bus->spi);
|
||||
}
|
||||
}
|
||||
|
||||
inline static void furi_hal_spi_bus_nfc_handle_event_callback(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
FuriHalSpiBusHandleEvent event,
|
||||
const LL_SPI_InitTypeDef* preset) {
|
||||
if(event == FuriHalSpiBusHandleEventInit) {
|
||||
// Configure GPIOs in normal SPI mode
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->miso,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI1);
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->mosi,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI1);
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->sck,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI1);
|
||||
furi_hal_gpio_write(handle->cs, true);
|
||||
furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
} else if(event == FuriHalSpiBusHandleEventDeinit) {
|
||||
// Configure GPIOs for st25r3916 Transparent mode
|
||||
furi_hal_gpio_init(handle->sck, GpioModeInput, GpioPullUp, GpioSpeedLow);
|
||||
furi_hal_gpio_init(handle->miso, GpioModeInput, GpioPullUp, GpioSpeedLow);
|
||||
furi_hal_gpio_init(handle->cs, GpioModeInput, GpioPullUp, GpioSpeedLow);
|
||||
furi_hal_gpio_write(handle->mosi, false);
|
||||
furi_hal_gpio_init(handle->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
} else if(event == FuriHalSpiBusHandleEventActivate) {
|
||||
LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset);
|
||||
LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER);
|
||||
LL_SPI_Enable(handle->bus->spi);
|
||||
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->miso,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI1);
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->mosi,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI1);
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->sck,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI1);
|
||||
|
||||
} else if(event == FuriHalSpiBusHandleEventDeactivate) {
|
||||
furi_hal_gpio_init(handle->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(handle->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
furi_hal_gpio_init(handle->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
|
||||
LL_SPI_Disable(handle->bus->spi);
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_hal_spi_bus_handle_external_event_callback(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
FuriHalSpiBusHandleEvent event) {
|
||||
furi_hal_spi_bus_r_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m);
|
||||
}
|
||||
|
||||
FuriHalSpiBusHandle furi_hal_spi_bus_handle_external = {
|
||||
.bus = &furi_hal_spi_bus_r,
|
||||
.callback = furi_hal_spi_bus_handle_external_event_callback,
|
||||
.miso = &gpio_ext_pa6,
|
||||
.mosi = &gpio_ext_pa7,
|
||||
.sck = &gpio_ext_pb3,
|
||||
.cs = &gpio_ext_pa4,
|
||||
};
|
||||
|
||||
inline static void furi_hal_spi_bus_d_handle_event_callback(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
FuriHalSpiBusHandleEvent event,
|
||||
const LL_SPI_InitTypeDef* preset) {
|
||||
if(event == FuriHalSpiBusHandleEventInit) {
|
||||
furi_hal_gpio_write(handle->cs, true);
|
||||
furi_hal_gpio_init(handle->cs, GpioModeOutputPushPull, GpioPullUp, GpioSpeedVeryHigh);
|
||||
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->miso,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI2);
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->mosi,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI2);
|
||||
furi_hal_gpio_init_ex(
|
||||
handle->sck,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn5SPI2);
|
||||
|
||||
} else if(event == FuriHalSpiBusHandleEventDeinit) {
|
||||
furi_hal_gpio_write(handle->cs, true);
|
||||
furi_hal_gpio_init(handle->cs, GpioModeAnalog, GpioPullUp, GpioSpeedLow);
|
||||
} else if(event == FuriHalSpiBusHandleEventActivate) {
|
||||
LL_SPI_Init(handle->bus->spi, (LL_SPI_InitTypeDef*)preset);
|
||||
LL_SPI_SetRxFIFOThreshold(handle->bus->spi, LL_SPI_RX_FIFO_TH_QUARTER);
|
||||
LL_SPI_Enable(handle->bus->spi);
|
||||
furi_hal_gpio_write(handle->cs, false);
|
||||
} else if(event == FuriHalSpiBusHandleEventDeactivate) {
|
||||
furi_hal_gpio_write(handle->cs, true);
|
||||
LL_SPI_Disable(handle->bus->spi);
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_hal_spi_bus_handle_display_event_callback(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
FuriHalSpiBusHandleEvent event) {
|
||||
furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_4m);
|
||||
}
|
||||
|
||||
FuriHalSpiBusHandle furi_hal_spi_bus_handle_display = {
|
||||
.bus = &furi_hal_spi_bus_d,
|
||||
.callback = furi_hal_spi_bus_handle_display_event_callback,
|
||||
.miso = &gpio_spi_d_miso,
|
||||
.mosi = &gpio_spi_d_mosi,
|
||||
.sck = &gpio_spi_d_sck,
|
||||
.cs = &gpio_display_cs,
|
||||
};
|
||||
|
||||
static void furi_hal_spi_bus_handle_sd_fast_event_callback(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
FuriHalSpiBusHandleEvent event) {
|
||||
furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_16m);
|
||||
}
|
||||
|
||||
FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast = {
|
||||
.bus = &furi_hal_spi_bus_d,
|
||||
.callback = furi_hal_spi_bus_handle_sd_fast_event_callback,
|
||||
.miso = &gpio_spi_d_miso,
|
||||
.mosi = &gpio_spi_d_mosi,
|
||||
.sck = &gpio_spi_d_sck,
|
||||
.cs = &gpio_sdcard_cs,
|
||||
};
|
||||
|
||||
static void furi_hal_spi_bus_handle_sd_slow_event_callback(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
FuriHalSpiBusHandleEvent event) {
|
||||
furi_hal_spi_bus_d_handle_event_callback(handle, event, &furi_hal_spi_preset_1edge_low_2m);
|
||||
}
|
||||
|
||||
FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow = {
|
||||
.bus = &furi_hal_spi_bus_d,
|
||||
.callback = furi_hal_spi_bus_handle_sd_slow_event_callback,
|
||||
.miso = &gpio_spi_d_miso,
|
||||
.mosi = &gpio_spi_d_mosi,
|
||||
.sck = &gpio_spi_d_sck,
|
||||
.cs = &gpio_sdcard_cs,
|
||||
};
|
||||
55
targets/f18/furi_hal/furi_hal_spi_config.h
Normal file
55
targets/f18/furi_hal/furi_hal_spi_config.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal_spi_types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Preset for ST25R916 */
|
||||
extern const LL_SPI_InitTypeDef furi_hal_spi_preset_2edge_low_8m;
|
||||
|
||||
/** Preset for CC1101 */
|
||||
extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_8m;
|
||||
|
||||
/** Preset for ST7567 (Display) */
|
||||
extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_4m;
|
||||
|
||||
/** Preset for SdCard in fast mode */
|
||||
extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_16m;
|
||||
|
||||
/** Preset for SdCard in slow mode */
|
||||
extern const LL_SPI_InitTypeDef furi_hal_spi_preset_1edge_low_2m;
|
||||
|
||||
/** Furi Hal Spi Bus R (External) */
|
||||
extern FuriHalSpiBus furi_hal_spi_bus_r;
|
||||
|
||||
/** Furi Hal Spi Bus D (Display, SdCard) */
|
||||
extern FuriHalSpiBus furi_hal_spi_bus_d;
|
||||
|
||||
/** External on `furi_hal_spi_bus_r`
|
||||
* Preset: `furi_hal_spi_preset_1edge_low_2m`
|
||||
*
|
||||
* miso: pa6
|
||||
* mosi: pa7
|
||||
* sck: pb3
|
||||
* cs: pa4 (software controlled)
|
||||
*
|
||||
* @warning not initialized by default, call `furi_hal_spi_bus_handle_init` to initialize
|
||||
* Bus pins are floating on inactive state, CS high after initialization
|
||||
*
|
||||
*/
|
||||
extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_external;
|
||||
|
||||
/** ST7567(Display) on `furi_hal_spi_bus_d` */
|
||||
extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_display;
|
||||
|
||||
/** SdCard in fast mode on `furi_hal_spi_bus_d` */
|
||||
extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_fast;
|
||||
|
||||
/** SdCard in slow mode on `furi_hal_spi_bus_d` */
|
||||
extern FuriHalSpiBusHandle furi_hal_spi_bus_handle_sd_slow;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
1
targets/f18/furi_hal/furi_hal_target_hw.h
Normal file
1
targets/f18/furi_hal/furi_hal_target_hw.h
Normal file
@@ -0,0 +1 @@
|
||||
#pragma once
|
||||
25
targets/f18/furi_hal/furi_hal_version_device.c
Normal file
25
targets/f18/furi_hal/furi_hal_version_device.c
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <furi_hal_version.h>
|
||||
|
||||
bool furi_hal_version_do_i_belong_here() {
|
||||
return (furi_hal_version_get_hw_target() == 18) || (furi_hal_version_get_hw_target() == 0);
|
||||
}
|
||||
|
||||
const char* furi_hal_version_get_model_name() {
|
||||
return "Flipper Nano";
|
||||
}
|
||||
|
||||
const char* furi_hal_version_get_model_code() {
|
||||
return "FN.1";
|
||||
}
|
||||
|
||||
const char* furi_hal_version_get_fcc_id() {
|
||||
return "Pending";
|
||||
}
|
||||
|
||||
const char* furi_hal_version_get_ic_id() {
|
||||
return "Pending";
|
||||
}
|
||||
|
||||
const char* furi_hal_version_get_mic_id() {
|
||||
return "Pending";
|
||||
}
|
||||
66
targets/f18/target.json
Normal file
66
targets/f18/target.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"inherit": "7",
|
||||
"include_paths": [
|
||||
"furi_hal"
|
||||
],
|
||||
"sdk_header_paths": [
|
||||
"../furi_hal_include",
|
||||
"furi_hal",
|
||||
"platform_specific"
|
||||
],
|
||||
"sdk_symbols": "api_symbols.csv",
|
||||
"linker_dependencies": [
|
||||
"print",
|
||||
"flipper18",
|
||||
"furi",
|
||||
"freertos",
|
||||
"stm32wb",
|
||||
"hwdrivers",
|
||||
"fatfs",
|
||||
"littlefs",
|
||||
"flipperformat",
|
||||
"toolbox",
|
||||
"digital_signal",
|
||||
"signal_reader",
|
||||
"microtar",
|
||||
"usb_stm32",
|
||||
"appframe",
|
||||
"assets",
|
||||
"one_wire",
|
||||
"music_worker",
|
||||
"misc",
|
||||
"flipper_application",
|
||||
"flipperformat",
|
||||
"toolbox",
|
||||
"flipper18"
|
||||
|
||||
],
|
||||
"excluded_sources": [
|
||||
"furi_hal_infrared.c",
|
||||
"furi_hal_nfc.c",
|
||||
"furi_hal_nfc_timer.c",
|
||||
"furi_hal_nfc_irq.c",
|
||||
"furi_hal_nfc_event.c",
|
||||
"furi_hal_nfc_iso15693.c",
|
||||
"furi_hal_nfc_iso14443a.c",
|
||||
"furi_hal_nfc_iso14443b.c",
|
||||
"furi_hal_nfc_felica.c",
|
||||
"furi_hal_rfid.c",
|
||||
"furi_hal_subghz.c"
|
||||
],
|
||||
"excluded_headers": [
|
||||
"furi_hal_infrared.h",
|
||||
"furi_hal_nfc.h",
|
||||
"furi_hal_rfid.h",
|
||||
"furi_hal_subghz.h",
|
||||
"furi_hal_ibutton.h",
|
||||
"furi_hal_subghz_configs.h"
|
||||
],
|
||||
"excluded_modules": [
|
||||
"nfc",
|
||||
"lfrfid",
|
||||
"subghz",
|
||||
"ibutton",
|
||||
"infrared"
|
||||
]
|
||||
}
|
||||
3534
targets/f7/api_symbols.csv
Normal file
3534
targets/f7/api_symbols.csv
Normal file
File diff suppressed because it is too large
Load Diff
54
targets/f7/application_ext.ld
Normal file
54
targets/f7/application_ext.ld
Normal file
@@ -0,0 +1,54 @@
|
||||
FORCE_COMMON_ALLOCATION
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.text 0x00000000 : ALIGN(4)
|
||||
{
|
||||
*(.text)
|
||||
*(.stub)
|
||||
*(.text*)
|
||||
*(.text.*)
|
||||
*(.text._*)
|
||||
|
||||
KEEP (*(.init))
|
||||
KEEP (*(.fini))
|
||||
}
|
||||
|
||||
.rodata :
|
||||
{
|
||||
*(.rodata)
|
||||
*(.rodata1)
|
||||
*(.rodata.*)
|
||||
}
|
||||
|
||||
.data :
|
||||
{
|
||||
*(.data)
|
||||
*(.data1)
|
||||
*(.data.*)
|
||||
}
|
||||
|
||||
|
||||
.bss :
|
||||
{
|
||||
*(.bss)
|
||||
*(.bss*)
|
||||
*(.sbss)
|
||||
*(.sbss*)
|
||||
*(COMMON)
|
||||
}
|
||||
|
||||
.ARM.attributes :
|
||||
{
|
||||
*(.ARM.attributes)
|
||||
*(.ARM.attributes.*)
|
||||
}
|
||||
|
||||
/DISCARD/ :
|
||||
{
|
||||
*(.comment)
|
||||
*(.comment.*)
|
||||
*(.llvmbc)
|
||||
*(.llvmcmd)
|
||||
}
|
||||
}
|
||||
12
targets/f7/ble_glue/app_common.h
Normal file
12
targets/f7/ble_glue/app_common.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <core/common_defines.h>
|
||||
#include <tl.h>
|
||||
|
||||
#include "app_conf.h"
|
||||
202
targets/f7/ble_glue/app_conf.h
Normal file
202
targets/f7/ble_glue/app_conf.h
Normal file
@@ -0,0 +1,202 @@
|
||||
#pragma once
|
||||
|
||||
#include <ble/core/ble_defs.h>
|
||||
|
||||
#define CFG_TX_POWER (0x19) /* +0dBm */
|
||||
|
||||
#define CFG_IDENTITY_ADDRESS GAP_PUBLIC_ADDR
|
||||
|
||||
/**
|
||||
* Define IO Authentication
|
||||
*/
|
||||
#define CFG_USED_FIXED_PIN USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN
|
||||
#define CFG_ENCRYPTION_KEY_SIZE_MAX (16)
|
||||
#define CFG_ENCRYPTION_KEY_SIZE_MIN (8)
|
||||
|
||||
/**
|
||||
* Define IO capabilities
|
||||
*/
|
||||
#define CFG_IO_CAPABILITY IO_CAP_DISPLAY_YES_NO
|
||||
|
||||
/**
|
||||
* Define MITM modes
|
||||
*/
|
||||
#define CFG_MITM_PROTECTION MITM_PROTECTION_REQUIRED
|
||||
|
||||
/**
|
||||
* Define Secure Connections Support
|
||||
*/
|
||||
#define CFG_SC_SUPPORT SC_PAIRING_OPTIONAL
|
||||
|
||||
/**
|
||||
* Define PHY
|
||||
*/
|
||||
#define ALL_PHYS_PREFERENCE 0x00
|
||||
#define RX_2M_PREFERRED 0x02
|
||||
#define TX_2M_PREFERRED 0x02
|
||||
#define TX_1M 0x01
|
||||
#define TX_2M 0x02
|
||||
#define RX_1M 0x01
|
||||
#define RX_2M 0x02
|
||||
|
||||
/******************************************************************************
|
||||
* BLE Stack
|
||||
******************************************************************************/
|
||||
/**
|
||||
* Maximum number of simultaneous connections that the device will support.
|
||||
* Valid values are from 1 to 8
|
||||
*/
|
||||
#define CFG_BLE_NUM_LINK 1
|
||||
|
||||
/**
|
||||
* Maximum number of Services that can be stored in the GATT database.
|
||||
* Note that the GAP and GATT services are automatically added so this parameter should be 2 plus the number of user services
|
||||
*/
|
||||
#define CFG_BLE_NUM_GATT_SERVICES 8
|
||||
|
||||
/**
|
||||
* Maximum number of Attributes
|
||||
* (i.e. the number of characteristic + the number of characteristic values + the number of descriptors, excluding the services)
|
||||
* that can be stored in the GATT database.
|
||||
* Note that certain characteristics and relative descriptors are added automatically during device initialization
|
||||
* so this parameters should be 9 plus the number of user Attributes
|
||||
*/
|
||||
#define CFG_BLE_NUM_GATT_ATTRIBUTES 68
|
||||
|
||||
/**
|
||||
* Maximum supported ATT_MTU size
|
||||
*/
|
||||
#define CFG_BLE_MAX_ATT_MTU (256 + 128 + 16 + 8 + 4 + 2)
|
||||
|
||||
/**
|
||||
* Size of the storage area for Attribute values
|
||||
* This value depends on the number of attributes used by application. In particular the sum of the following quantities (in octets) should be made for each attribute:
|
||||
* - attribute value length
|
||||
* - 5, if UUID is 16 bit; 19, if UUID is 128 bit
|
||||
* - 2, if server configuration descriptor is used
|
||||
* - 2*DTM_NUM_LINK, if client configuration descriptor is used
|
||||
* - 2, if extended properties is used
|
||||
* The total amount of memory needed is the sum of the above quantities for each attribute.
|
||||
*/
|
||||
#define CFG_BLE_ATT_VALUE_ARRAY_SIZE (1344)
|
||||
|
||||
/**
|
||||
* Prepare Write List size in terms of number of packet
|
||||
*/
|
||||
#define CFG_BLE_PREPARE_WRITE_LIST_SIZE BLE_PREP_WRITE_X_ATT(CFG_BLE_MAX_ATT_MTU)
|
||||
|
||||
/**
|
||||
* Number of allocated memory blocks
|
||||
*/
|
||||
#define CFG_BLE_MBLOCK_COUNT \
|
||||
(BLE_MBLOCKS_CALC(CFG_BLE_PREPARE_WRITE_LIST_SIZE, CFG_BLE_MAX_ATT_MTU, CFG_BLE_NUM_LINK))
|
||||
|
||||
/**
|
||||
* Enable or disable the Extended Packet length feature. Valid values are 0 or 1.
|
||||
*/
|
||||
#define CFG_BLE_DATA_LENGTH_EXTENSION 1
|
||||
|
||||
/**
|
||||
* Sleep clock accuracy in Slave mode (ppm value)
|
||||
*/
|
||||
#define CFG_BLE_SLAVE_SCA 500
|
||||
|
||||
/**
|
||||
* Sleep clock accuracy in Master mode
|
||||
* 0 : 251 ppm to 500 ppm
|
||||
* 1 : 151 ppm to 250 ppm
|
||||
* 2 : 101 ppm to 150 ppm
|
||||
* 3 : 76 ppm to 100 ppm
|
||||
* 4 : 51 ppm to 75 ppm
|
||||
* 5 : 31 ppm to 50 ppm
|
||||
* 6 : 21 ppm to 30 ppm
|
||||
* 7 : 0 ppm to 20 ppm
|
||||
*/
|
||||
#define CFG_BLE_MASTER_SCA 0
|
||||
|
||||
/**
|
||||
* Source for the low speed clock for RF wake-up
|
||||
* 1 : external high speed crystal HSE/32/32
|
||||
* 0 : external low speed crystal ( no calibration )
|
||||
*/
|
||||
#define CFG_BLE_LSE_SOURCE \
|
||||
SHCI_C2_BLE_INIT_CFG_BLE_LS_CLK_LSE | SHCI_C2_BLE_INIT_CFG_BLE_LS_OTHER_DEV | \
|
||||
SHCI_C2_BLE_INIT_CFG_BLE_LS_CALIB
|
||||
|
||||
/**
|
||||
* Start up time of the high speed (16 or 32 MHz) crystal oscillator in units of 625/256 us (~2.44 us)
|
||||
*/
|
||||
#define CFG_BLE_HSE_STARTUP_TIME 0x148
|
||||
|
||||
/**
|
||||
* Maximum duration of the connection event when the device is in Slave mode in units of 625/256 us (~2.44 us)
|
||||
*/
|
||||
#define CFG_BLE_MAX_CONN_EVENT_LENGTH (0xFFFFFFFF)
|
||||
|
||||
/**
|
||||
* Viterbi Mode
|
||||
* 1 : enabled
|
||||
* 0 : disabled
|
||||
*/
|
||||
#define CFG_BLE_VITERBI_MODE 1
|
||||
|
||||
/**
|
||||
* BLE stack Options flags to be configured with:
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_LL_ONLY
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_LL_HOST
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_NO_SVC_CHANGE_DESC
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_WITH_SVC_CHANGE_DESC
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_DEVICE_NAME_RO
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_DEVICE_NAME_RW
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_NO_EXT_ADV
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_CS_ALGO2
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_NO_CS_ALGO2
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_POWER_CLASS_1
|
||||
* - SHCI_C2_BLE_INIT_OPTIONS_POWER_CLASS_2_3
|
||||
* which are used to set following configuration bits:
|
||||
* (bit 0): 1: LL only
|
||||
* 0: LL + host
|
||||
* (bit 1): 1: no service change desc.
|
||||
* 0: with service change desc.
|
||||
* (bit 2): 1: device name Read-Only
|
||||
* 0: device name R/W
|
||||
* (bit 3): 1: extended advertizing supported [NOT SUPPORTED]
|
||||
* 0: extended advertizing not supported [NOT SUPPORTED]
|
||||
* (bit 4): 1: CS Algo #2 supported
|
||||
* 0: CS Algo #2 not supported
|
||||
* (bit 7): 1: LE Power Class 1
|
||||
* 0: LE Power Class 2-3
|
||||
* other bits: reserved (shall be set to 0)
|
||||
*/
|
||||
#define CFG_BLE_OPTIONS \
|
||||
(SHCI_C2_BLE_INIT_OPTIONS_LL_HOST | SHCI_C2_BLE_INIT_OPTIONS_WITH_SVC_CHANGE_DESC | \
|
||||
SHCI_C2_BLE_INIT_OPTIONS_DEVICE_NAME_RO | SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV | \
|
||||
SHCI_C2_BLE_INIT_OPTIONS_CS_ALGO2 | SHCI_C2_BLE_INIT_OPTIONS_POWER_CLASS_2_3)
|
||||
|
||||
/**
|
||||
* Queue length of BLE Event
|
||||
* This parameter defines the number of asynchronous events that can be stored in the HCI layer before
|
||||
* being reported to the application. When a command is sent to the BLE core coprocessor, the HCI layer
|
||||
* is waiting for the event with the Num_HCI_Command_Packets set to 1. The receive queue shall be large
|
||||
* enough to store all asynchronous events received in between.
|
||||
* When CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE is set to 27, this allow to store three 255 bytes long asynchronous events
|
||||
* between the HCI command and its event.
|
||||
* This parameter depends on the value given to CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE. When the queue size is to small,
|
||||
* the system may hang if the queue is full with asynchronous events and the HCI layer is still waiting
|
||||
* for a CC/CS event, In that case, the notification TL_BLE_HCI_ToNot() is called to indicate
|
||||
* to the application a HCI command did not receive its command event within 30s (Default HCI Timeout).
|
||||
*/
|
||||
#define CFG_TLBLE_EVT_QUEUE_LENGTH 5
|
||||
/**
|
||||
* This parameter should be set to fit most events received by the HCI layer. It defines the buffer size of each element
|
||||
* allocated in the queue of received events and can be used to optimize the amount of RAM allocated by the Memory Manager.
|
||||
* It should not exceed 255 which is the maximum HCI packet payload size (a greater value is a lost of memory as it will
|
||||
* never be used)
|
||||
* With the current wireless firmware implementation, this parameter shall be kept to 255
|
||||
*
|
||||
*/
|
||||
#define CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE \
|
||||
255 /**< Set to 255 with the memory manager and the mailbox */
|
||||
|
||||
#define TL_BLE_EVENT_FRAME_SIZE (TL_EVT_HDR_SIZE + CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE)
|
||||
253
targets/f7/ble_glue/app_debug.c
Normal file
253
targets/f7/ble_glue/app_debug.c
Normal file
@@ -0,0 +1,253 @@
|
||||
#include "app_common.h"
|
||||
#include "app_debug.h"
|
||||
#include <interface/patterns/ble_thread/tl/tl.h>
|
||||
#include <interface/patterns/ble_thread/tl/mbox_def.h>
|
||||
#include <interface/patterns/ble_thread/shci/shci.h>
|
||||
#include <utilities/dbg_trace.h>
|
||||
#include <utilities/utilities_common.h>
|
||||
|
||||
#include "stm32wbxx_ll_bus.h"
|
||||
#include "stm32wbxx_ll_pwr.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
typedef PACKED_STRUCT {
|
||||
GPIO_TypeDef* port;
|
||||
uint16_t pin;
|
||||
uint8_t enable;
|
||||
uint8_t reserved;
|
||||
}
|
||||
APPD_GpioConfig_t;
|
||||
|
||||
#define GPIO_NBR_OF_RF_SIGNALS 9
|
||||
#define GPIO_CFG_NBR_OF_FEATURES 34
|
||||
#define NBR_OF_TRACES_CONFIG_PARAMETERS 4
|
||||
#define NBR_OF_GENERAL_CONFIG_PARAMETERS 4
|
||||
|
||||
/**
|
||||
* THIS SHALL BE SET TO A VALUE DIFFERENT FROM 0 ONLY ON REQUEST FROM ST SUPPORT
|
||||
*/
|
||||
#define BLE_DTB_CFG 0
|
||||
// #define BLE_DTB_CFG 7
|
||||
#define SYS_DBG_CFG1 (SHCI_C2_DEBUG_OPTIONS_IPCORE_LP | SHCI_C2_DEBUG_OPTIONS_CPU2_STOP_EN)
|
||||
|
||||
/* Private variables ---------------------------------------------------------*/
|
||||
PLACE_IN_SECTION("MB_MEM2")
|
||||
ALIGN(4) static SHCI_C2_DEBUG_TracesConfig_t APPD_TracesConfig = {0, 0, 0, 0};
|
||||
PLACE_IN_SECTION("MB_MEM2")
|
||||
ALIGN(4)
|
||||
static SHCI_C2_DEBUG_GeneralConfig_t APPD_GeneralConfig =
|
||||
{BLE_DTB_CFG, SYS_DBG_CFG1, {0, 0}, 0, 0, 0, 0, 0};
|
||||
|
||||
/**
|
||||
* THE DEBUG ON GPIO FOR CPU2 IS INTENDED TO BE USED ONLY ON REQUEST FROM ST SUPPORT
|
||||
* It provides timing information on the CPU2 activity.
|
||||
* All configuration of (port, pin) is supported for each features and can be selected by the user
|
||||
* depending on the availability
|
||||
*/
|
||||
static const APPD_GpioConfig_t aGpioConfigList[GPIO_CFG_NBR_OF_FEATURES] = {
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_ISR - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_7, 1, 0}, /* BLE_STACK_TICK - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_CMD_PROCESS - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_ACL_DATA_PROCESS - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* SYS_CMD_PROCESS - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* RNG_PROCESS - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVM_PROCESS - Set on Entry / Reset on Exit */
|
||||
{GPIOB, LL_GPIO_PIN_3, 1, 0}, /* IPCC_GENERAL - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_BLE_CMD_RX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_BLE_EVT_TX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_BLE_ACL_DATA_RX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_SYS_CMD_RX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_SYS_EVT_TX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_CLI_CMD_RX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_OT_CMD_RX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_OT_ACK_TX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_CLI_ACK_TX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_MEM_MANAGER_RX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* IPCC_TRACES_TX - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_6, 1, 0}, /* HARD_FAULT - Set on Entry / Reset on Exit */
|
||||
/* From v1.1.1 */
|
||||
{GPIOC, LL_GPIO_PIN_1, 1, 0}, /* IP_CORE_LP_STATUS - Set on Entry / Reset on Exit */
|
||||
/* From v1.2.0 */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* END_OF_CONNECTION_EVENT - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* TIMER_SERVER_CALLBACK - Toggle on Entry */
|
||||
{GPIOA, LL_GPIO_PIN_4, 1, 0}, /* PES_ACTIVITY - Set on Entry / Reset on Exit */
|
||||
{GPIOC, LL_GPIO_PIN_0, 1, 0}, /* MB_BLE_SEND_EVT - Set on Entry / Reset on Exit */
|
||||
/* From v1.3.0 */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_NO_DELAY - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* BLE_STACK_STORE_NVM_CB - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVMA_WRITE_ONGOING - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVMA_WRITE_COMPLETE - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVMA_CLEANUP - Set on Entry / Reset on Exit */
|
||||
/* From v1.4.0 */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* NVMA_START - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* FLASH_EOP - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* FLASH_WRITE - Set on Entry / Reset on Exit */
|
||||
{GPIOA, LL_GPIO_PIN_0, 0, 0}, /* FLASH_ERASE - Set on Entry / Reset on Exit */
|
||||
};
|
||||
|
||||
/**
|
||||
* THE DEBUG ON GPIO FOR CPU2 IS INTENDED TO BE USED ONLY ON REQUEST FROM ST SUPPORT
|
||||
* This table is relevant only for BLE
|
||||
* It provides timing information on BLE RF activity.
|
||||
* New signals may be allocated at any location when requested by ST
|
||||
* The GPIO allocated to each signal depend on the BLE_DTB_CFG value and cannot be changed
|
||||
*/
|
||||
#if(BLE_DTB_CFG == 7)
|
||||
static const APPD_GpioConfig_t aRfConfigList[GPIO_NBR_OF_RF_SIGNALS] = {
|
||||
{GPIOB, LL_GPIO_PIN_2, 0, 0}, /* DTB10 - Tx/Rx SPI */
|
||||
{GPIOB, LL_GPIO_PIN_7, 0, 0}, /* DTB11 - Tx/Tx SPI Clk */
|
||||
{GPIOA, LL_GPIO_PIN_8, 0, 0}, /* DTB12 - Tx/Rx Ready & SPI Select */
|
||||
{GPIOA, LL_GPIO_PIN_9, 0, 0}, /* DTB13 - Tx/Rx Start */
|
||||
{GPIOA, LL_GPIO_PIN_10, 0, 0}, /* DTB14 - FSM0 */
|
||||
{GPIOA, LL_GPIO_PIN_11, 0, 0}, /* DTB15 - FSM1 */
|
||||
{GPIOB, LL_GPIO_PIN_8, 0, 0}, /* DTB16 - FSM2 */
|
||||
{GPIOB, LL_GPIO_PIN_11, 0, 0}, /* DTB17 - FSM3 */
|
||||
{GPIOB, LL_GPIO_PIN_10, 0, 0}, /* DTB18 - FSM4 */
|
||||
};
|
||||
#endif
|
||||
|
||||
static void APPD_SetCPU2GpioConfig(void);
|
||||
static void APPD_BleDtbCfg(void);
|
||||
|
||||
void APPD_Init() {
|
||||
APPD_SetCPU2GpioConfig();
|
||||
APPD_BleDtbCfg();
|
||||
}
|
||||
|
||||
void APPD_EnableCPU2(void) {
|
||||
SHCI_C2_DEBUG_Init_Cmd_Packet_t DebugCmdPacket = {
|
||||
{{0, 0, 0}}, /**< Does not need to be initialized */
|
||||
{(uint8_t*)aGpioConfigList,
|
||||
(uint8_t*)&APPD_TracesConfig,
|
||||
(uint8_t*)&APPD_GeneralConfig,
|
||||
GPIO_CFG_NBR_OF_FEATURES,
|
||||
NBR_OF_TRACES_CONFIG_PARAMETERS,
|
||||
NBR_OF_GENERAL_CONFIG_PARAMETERS}};
|
||||
|
||||
/**< Traces channel initialization */
|
||||
TL_TRACES_Init();
|
||||
|
||||
/** GPIO DEBUG Initialization */
|
||||
SHCI_C2_DEBUG_Init(&DebugCmdPacket);
|
||||
|
||||
// We don't need External Power Amplifier
|
||||
// LL_GPIO_InitTypeDef gpio_config;
|
||||
// gpio_config.Pull = GPIO_NOPULL;
|
||||
// gpio_config.Mode = GPIO_MODE_OUTPUT_PP;
|
||||
// gpio_config.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
||||
// gpio_config.Pin = LL_GPIO_PIN_3;
|
||||
// HAL_GPIO_Init(GPIOC, &gpio_config);
|
||||
// SHCI_C2_ExtpaConfig((uint32_t)GPIOC, LL_GPIO_PIN_3, EXT_PA_ENABLED_LOW, EXT_PA_ENABLED);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void APPD_SetCPU2GpioConfig(void) {
|
||||
LL_GPIO_InitTypeDef gpio_config = {0};
|
||||
uint8_t local_loop;
|
||||
uint16_t gpioa_pin_list;
|
||||
uint16_t gpiob_pin_list;
|
||||
uint16_t gpioc_pin_list;
|
||||
|
||||
gpioa_pin_list = 0;
|
||||
gpiob_pin_list = 0;
|
||||
gpioc_pin_list = 0;
|
||||
|
||||
for(local_loop = 0; local_loop < GPIO_CFG_NBR_OF_FEATURES; local_loop++) {
|
||||
if(aGpioConfigList[local_loop].enable != 0) {
|
||||
switch((uint32_t)aGpioConfigList[local_loop].port) {
|
||||
case(uint32_t)GPIOA:
|
||||
gpioa_pin_list |= aGpioConfigList[local_loop].pin;
|
||||
break;
|
||||
|
||||
case(uint32_t)GPIOB:
|
||||
gpiob_pin_list |= aGpioConfigList[local_loop].pin;
|
||||
break;
|
||||
|
||||
case(uint32_t)GPIOC:
|
||||
gpioc_pin_list |= aGpioConfigList[local_loop].pin;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gpio_config.Mode = LL_GPIO_MODE_OUTPUT;
|
||||
gpio_config.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
|
||||
gpio_config.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
|
||||
gpio_config.Pull = LL_GPIO_PULL_NO;
|
||||
|
||||
// Never disable SWD, why would you?
|
||||
// gpio_config.Pin = LL_GPIO_PIN_15 | LL_GPIO_PIN_14 | LL_GPIO_PIN_13;
|
||||
// LL_GPIO_Init(GPIOA, &gpio_config);
|
||||
|
||||
if(gpioa_pin_list != 0) {
|
||||
gpio_config.Pin = gpioa_pin_list;
|
||||
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOA);
|
||||
LL_GPIO_Init(GPIOA, &gpio_config);
|
||||
LL_GPIO_ResetOutputPin(GPIOA, gpioa_pin_list);
|
||||
}
|
||||
|
||||
if(gpiob_pin_list != 0) {
|
||||
gpio_config.Pin = gpiob_pin_list;
|
||||
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOB);
|
||||
LL_GPIO_Init(GPIOB, &gpio_config);
|
||||
LL_GPIO_ResetOutputPin(GPIOB, gpiob_pin_list);
|
||||
}
|
||||
|
||||
if(gpioc_pin_list != 0) {
|
||||
gpio_config.Pin = gpioc_pin_list;
|
||||
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOC);
|
||||
LL_GPIO_Init(GPIOC, &gpio_config);
|
||||
LL_GPIO_ResetOutputPin(GPIOC, gpioc_pin_list);
|
||||
}
|
||||
}
|
||||
|
||||
static void APPD_BleDtbCfg(void) {
|
||||
#if(BLE_DTB_CFG != 0)
|
||||
LL_GPIO_InitTypeDef gpio_config = {0};
|
||||
uint8_t local_loop;
|
||||
uint16_t gpioa_pin_list;
|
||||
uint16_t gpiob_pin_list;
|
||||
|
||||
gpioa_pin_list = 0;
|
||||
gpiob_pin_list = 0;
|
||||
|
||||
for(local_loop = 0; local_loop < GPIO_NBR_OF_RF_SIGNALS; local_loop++) {
|
||||
if(aRfConfigList[local_loop].enable != 0) {
|
||||
switch((uint32_t)aRfConfigList[local_loop].port) {
|
||||
case(uint32_t)GPIOA:
|
||||
gpioa_pin_list |= aRfConfigList[local_loop].pin;
|
||||
break;
|
||||
case(uint32_t)GPIOB:
|
||||
gpiob_pin_list |= aRfConfigList[local_loop].pin;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gpio_config.Mode = LL_GPIO_MODE_ALTERNATE;
|
||||
gpio_config.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
|
||||
gpio_config.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
|
||||
gpio_config.Pull = LL_GPIO_PULL_NO;
|
||||
gpio_config.Alternate = LL_GPIO_AF_6;
|
||||
gpio_config.Pin = LL_GPIO_PIN_15 | LL_GPIO_PIN_14 | LL_GPIO_PIN_13;
|
||||
|
||||
if(gpioa_pin_list != 0) {
|
||||
gpio_config.Pin = gpioa_pin_list;
|
||||
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOA);
|
||||
LL_GPIO_Init(GPIOA, &gpio_config);
|
||||
}
|
||||
|
||||
if(gpiob_pin_list != 0) {
|
||||
gpio_config.Pin = gpiob_pin_list;
|
||||
LL_C2_AHB2_GRP1_EnableClock(LL_C2_AHB2_GRP1_PERIPH_GPIOB);
|
||||
LL_GPIO_Init(GPIOB, &gpio_config);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
12
targets/f7/ble_glue/app_debug.h
Normal file
12
targets/f7/ble_glue/app_debug.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void APPD_Init(void);
|
||||
void APPD_EnableCPU2(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
194
targets/f7/ble_glue/ble_app.c
Normal file
194
targets/f7/ble_glue/ble_app.c
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "ble_app.h"
|
||||
|
||||
#include <ble/ble.h>
|
||||
#include <interface/patterns/ble_thread/tl/hci_tl.h>
|
||||
#include <interface/patterns/ble_thread/shci/shci.h>
|
||||
#include "gap.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "Bt"
|
||||
|
||||
#define BLE_APP_FLAG_HCI_EVENT (1UL << 0)
|
||||
#define BLE_APP_FLAG_KILL_THREAD (1UL << 1)
|
||||
#define BLE_APP_FLAG_ALL (BLE_APP_FLAG_HCI_EVENT | BLE_APP_FLAG_KILL_THREAD)
|
||||
|
||||
PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer;
|
||||
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE];
|
||||
|
||||
_Static_assert(
|
||||
sizeof(SHCI_C2_Ble_Init_Cmd_Packet_t) == 58,
|
||||
"Ble stack config structure size mismatch (check new config options - last updated for v.1.17.3)");
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* hci_mtx;
|
||||
FuriSemaphore* hci_sem;
|
||||
FuriThread* thread;
|
||||
} BleApp;
|
||||
|
||||
static BleApp* ble_app = NULL;
|
||||
|
||||
static int32_t ble_app_hci_thread(void* context);
|
||||
static void ble_app_hci_event_handler(void* pPayload);
|
||||
static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status);
|
||||
|
||||
static const HCI_TL_HciInitConf_t hci_tl_config = {
|
||||
.p_cmdbuffer = (uint8_t*)&ble_app_cmd_buffer,
|
||||
.StatusNotCallBack = ble_app_hci_status_not_handler,
|
||||
};
|
||||
|
||||
static const SHCI_C2_CONFIG_Cmd_Param_t config_param = {
|
||||
.PayloadCmdSize = SHCI_C2_CONFIG_PAYLOAD_CMD_SIZE,
|
||||
.Config1 = SHCI_C2_CONFIG_CONFIG1_BIT0_BLE_NVM_DATA_TO_SRAM,
|
||||
.BleNvmRamAddress = (uint32_t)ble_app_nvm,
|
||||
.EvtMask1 = SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE,
|
||||
};
|
||||
|
||||
static const SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = {
|
||||
.Header = {{0, 0, 0}}, // Header unused
|
||||
.Param = {
|
||||
.pBleBufferAddress = 0, // pBleBufferAddress not used
|
||||
.BleBufferSize = 0, // BleBufferSize not used
|
||||
.NumAttrRecord = CFG_BLE_NUM_GATT_ATTRIBUTES,
|
||||
.NumAttrServ = CFG_BLE_NUM_GATT_SERVICES,
|
||||
.AttrValueArrSize = CFG_BLE_ATT_VALUE_ARRAY_SIZE,
|
||||
.NumOfLinks = CFG_BLE_NUM_LINK,
|
||||
.ExtendedPacketLengthEnable = CFG_BLE_DATA_LENGTH_EXTENSION,
|
||||
.PrWriteListSize = CFG_BLE_PREPARE_WRITE_LIST_SIZE,
|
||||
.MblockCount = CFG_BLE_MBLOCK_COUNT,
|
||||
.AttMtu = CFG_BLE_MAX_ATT_MTU,
|
||||
.SlaveSca = CFG_BLE_SLAVE_SCA,
|
||||
.MasterSca = CFG_BLE_MASTER_SCA,
|
||||
.LsSource = CFG_BLE_LSE_SOURCE,
|
||||
.MaxConnEventLength = CFG_BLE_MAX_CONN_EVENT_LENGTH,
|
||||
.HsStartupTime = CFG_BLE_HSE_STARTUP_TIME,
|
||||
.ViterbiEnable = CFG_BLE_VITERBI_MODE,
|
||||
.Options = CFG_BLE_OPTIONS,
|
||||
.HwVersion = 0,
|
||||
.max_coc_initiator_nbr = 32,
|
||||
.min_tx_power = 0,
|
||||
.max_tx_power = 0,
|
||||
.rx_model_config = 1,
|
||||
/* New stack (13.3->15.0) */
|
||||
.max_adv_set_nbr = 1, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set
|
||||
.max_adv_data_len = 1650, // Only used if SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV is set
|
||||
.tx_path_compens = 0, // RF TX Path Compensation, * 0.1 dB
|
||||
.rx_path_compens = 0, // RF RX Path Compensation, * 0.1 dB
|
||||
.ble_core_version = SHCI_C2_BLE_INIT_BLE_CORE_5_4,
|
||||
/*15.0->17.0*/
|
||||
.Options_extension = SHCI_C2_BLE_INIT_OPTIONS_ENHANCED_ATT_NOTSUPPORTED |
|
||||
SHCI_C2_BLE_INIT_OPTIONS_APPEARANCE_READONLY,
|
||||
}};
|
||||
|
||||
bool ble_app_init() {
|
||||
SHCI_CmdStatus_t status;
|
||||
ble_app = malloc(sizeof(BleApp));
|
||||
// Allocate semafore and mutex for ble command buffer access
|
||||
ble_app->hci_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
ble_app->hci_sem = furi_semaphore_alloc(1, 0);
|
||||
// HCI transport layer thread to handle user asynch events
|
||||
ble_app->thread = furi_thread_alloc_ex("BleHciDriver", 1024, ble_app_hci_thread, ble_app);
|
||||
furi_thread_start(ble_app->thread);
|
||||
|
||||
// Initialize Ble Transport Layer
|
||||
hci_init(ble_app_hci_event_handler, (void*)&hci_tl_config);
|
||||
|
||||
// Configure NVM store for pairing data
|
||||
status = SHCI_C2_Config((SHCI_C2_CONFIG_Cmd_Param_t*)&config_param);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to configure 2nd core: %d", status);
|
||||
}
|
||||
|
||||
// Start ble stack on 2nd core
|
||||
status = SHCI_C2_BLE_Init((SHCI_C2_Ble_Init_Cmd_Packet_t*)&ble_init_cmd_packet);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to start ble stack: %d", status);
|
||||
}
|
||||
return status == SHCI_Success;
|
||||
}
|
||||
|
||||
void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size) {
|
||||
*addr = (uint8_t*)ble_app_nvm;
|
||||
*size = sizeof(ble_app_nvm);
|
||||
}
|
||||
|
||||
void ble_app_thread_stop() {
|
||||
if(ble_app) {
|
||||
FuriThreadId thread_id = furi_thread_get_id(ble_app->thread);
|
||||
furi_assert(thread_id);
|
||||
furi_thread_flags_set(thread_id, BLE_APP_FLAG_KILL_THREAD);
|
||||
furi_thread_join(ble_app->thread);
|
||||
furi_thread_free(ble_app->thread);
|
||||
// Free resources
|
||||
furi_mutex_free(ble_app->hci_mtx);
|
||||
furi_semaphore_free(ble_app->hci_sem);
|
||||
free(ble_app);
|
||||
ble_app = NULL;
|
||||
memset(&ble_app_cmd_buffer, 0, sizeof(ble_app_cmd_buffer));
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t ble_app_hci_thread(void* arg) {
|
||||
UNUSED(arg);
|
||||
uint32_t flags = 0;
|
||||
|
||||
while(1) {
|
||||
flags = furi_thread_flags_wait(BLE_APP_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
|
||||
if(flags & BLE_APP_FLAG_KILL_THREAD) {
|
||||
break;
|
||||
}
|
||||
if(flags & BLE_APP_FLAG_HCI_EVENT) {
|
||||
hci_user_evt_proc();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Called by WPAN lib
|
||||
void hci_notify_asynch_evt(void* pdata) {
|
||||
UNUSED(pdata);
|
||||
furi_check(ble_app);
|
||||
FuriThreadId thread_id = furi_thread_get_id(ble_app->thread);
|
||||
furi_assert(thread_id);
|
||||
furi_thread_flags_set(thread_id, BLE_APP_FLAG_HCI_EVENT);
|
||||
}
|
||||
|
||||
void hci_cmd_resp_release(uint32_t flag) {
|
||||
UNUSED(flag);
|
||||
furi_check(ble_app);
|
||||
furi_check(furi_semaphore_release(ble_app->hci_sem) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void hci_cmd_resp_wait(uint32_t timeout) {
|
||||
furi_check(ble_app);
|
||||
furi_check(furi_semaphore_acquire(ble_app->hci_sem, timeout) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static void ble_app_hci_event_handler(void* pPayload) {
|
||||
SVCCTL_UserEvtFlowStatus_t svctl_return_status;
|
||||
tHCI_UserEvtRxParam* pParam = (tHCI_UserEvtRxParam*)pPayload;
|
||||
|
||||
furi_check(ble_app);
|
||||
svctl_return_status = SVCCTL_UserEvtRx((void*)&(pParam->pckt->evtserial));
|
||||
if(svctl_return_status != SVCCTL_UserEvtFlowDisable) {
|
||||
pParam->status = HCI_TL_UserEventFlow_Enable;
|
||||
} else {
|
||||
pParam->status = HCI_TL_UserEventFlow_Disable;
|
||||
}
|
||||
}
|
||||
|
||||
static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status) {
|
||||
if(status == HCI_TL_CmdBusy) {
|
||||
furi_hal_power_insomnia_enter();
|
||||
furi_mutex_acquire(ble_app->hci_mtx, FuriWaitForever);
|
||||
} else if(status == HCI_TL_CmdAvailable) {
|
||||
furi_mutex_release(ble_app->hci_mtx);
|
||||
furi_hal_power_insomnia_exit();
|
||||
}
|
||||
}
|
||||
|
||||
void SVCCTL_ResumeUserEventFlow(void) {
|
||||
hci_resume_flow();
|
||||
}
|
||||
16
targets/f7/ble_glue/ble_app.h
Normal file
16
targets/f7/ble_glue/ble_app.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool ble_app_init();
|
||||
void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size);
|
||||
void ble_app_thread_stop();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
14
targets/f7/ble_glue/ble_conf.h
Normal file
14
targets/f7/ble_glue/ble_conf.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_conf.h"
|
||||
|
||||
/**
|
||||
* There is one handler per service enabled
|
||||
* Note: There is no handler for the Device Information Service
|
||||
*
|
||||
* This shall take into account all registered handlers
|
||||
* (from either the provided services or the custom services)
|
||||
*/
|
||||
#define BLE_CFG_SVC_MAX_NBR_CB 7
|
||||
|
||||
#define BLE_CFG_CLT_MAX_NBR_CB 0
|
||||
99
targets/f7/ble_glue/ble_const.h
Normal file
99
targets/f7/ble_glue/ble_const.h
Normal file
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <ble/core/ble_std.h>
|
||||
#include <ble/core/ble_defs.h>
|
||||
#include "osal.h"
|
||||
#include "compiler.h"
|
||||
|
||||
/* Default BLE variant */
|
||||
#ifndef BASIC_FEATURES
|
||||
#define BASIC_FEATURES 0
|
||||
#endif
|
||||
#ifndef SLAVE_ONLY
|
||||
#define SLAVE_ONLY 0
|
||||
#endif
|
||||
#ifndef LL_ONLY
|
||||
#define LL_ONLY 0
|
||||
#endif
|
||||
#ifndef LL_ONLY_BASIC
|
||||
#define LL_ONLY_BASIC 0
|
||||
#endif
|
||||
#ifndef BEACON_ONLY
|
||||
#define BEACON_ONLY 0
|
||||
#endif
|
||||
|
||||
/* Size of command/events buffers:
|
||||
*
|
||||
* To change the size of commands and events parameters used in the
|
||||
* auto-generated files, you need to update 2 defines:
|
||||
*
|
||||
* - BLE_CMD_MAX_PARAM_LEN
|
||||
* - BLE_EVT_MAX_PARAM_LEN
|
||||
*
|
||||
* These 2 defines are set below with default values and can be changed.
|
||||
*
|
||||
* To compute the value to support a characteristic of 512 bytes for a specific
|
||||
* command or an event, you need to look in "ble_types.h".
|
||||
*
|
||||
* Here are 2 examples, one with a command and one with an event:
|
||||
*
|
||||
* - aci_gatt_update_char_value_ext_cp0
|
||||
* ----------------------------------
|
||||
*
|
||||
* we have in the structure:
|
||||
*
|
||||
* uint8_t Value[(BLE_CMD_MAX_PARAM_LEN- 12)/sizeof(uint8_t)];
|
||||
*
|
||||
* so to support a 512 byte value, we need to have
|
||||
*
|
||||
* BLE_CMD_MAX_PARAM_LEN at least equal to: 512 + 12 = 524
|
||||
*
|
||||
* - aci_gatt_read_handle_value_rp0
|
||||
* ------------------------------
|
||||
*
|
||||
* we have in the structure:
|
||||
*
|
||||
* uint8_t Value[((BLE_EVT_MAX_PARAM_LEN - 3) - 5)/sizeof(uint8_t)];
|
||||
*
|
||||
* so to support a 512 byte value, we need to have
|
||||
*
|
||||
* BLE_EVT_MAX_PARAM_LEN at least equal to: 512 + 3 + 5 = 520
|
||||
*
|
||||
* If you need several events or commands with 512-size values, you need to
|
||||
* take the maximum values for BLE_EVT_MAX_PARAM_LEN and BLE_CMD_MAX_PARAM_LEN.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Maximum parameter size of BLE commands.
|
||||
* Change this value if needed. */
|
||||
#define BLE_CMD_MAX_PARAM_LEN HCI_COMMAND_MAX_PARAM_LEN
|
||||
|
||||
/* Maximum parameter size of BLE responses/events.
|
||||
* Change this value if needed. */
|
||||
#define BLE_EVT_MAX_PARAM_LEN HCI_EVENT_MAX_PARAM_LEN
|
||||
|
||||
/* Callback function to send command and receive response */
|
||||
struct hci_request {
|
||||
uint16_t ogf;
|
||||
uint16_t ocf;
|
||||
int event;
|
||||
void* cparam;
|
||||
int clen;
|
||||
void* rparam;
|
||||
int rlen;
|
||||
};
|
||||
extern int hci_send_req(struct hci_request* req, uint8_t async);
|
||||
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#endif
|
||||
1
targets/f7/ble_glue/ble_dbg_conf.h
Normal file
1
targets/f7/ble_glue/ble_dbg_conf.h
Normal file
@@ -0,0 +1 @@
|
||||
#pragma once
|
||||
481
targets/f7/ble_glue/ble_glue.c
Normal file
481
targets/f7/ble_glue/ble_glue.c
Normal file
@@ -0,0 +1,481 @@
|
||||
#include "ble_glue.h"
|
||||
#include "app_common.h"
|
||||
#include "ble_app.h"
|
||||
#include <ble/ble.h>
|
||||
#include <hci_tl.h>
|
||||
|
||||
#include <interface/patterns/ble_thread/tl/tl.h>
|
||||
#include <interface/patterns/ble_thread/shci/shci.h>
|
||||
#include <interface/patterns/ble_thread/tl/shci_tl.h>
|
||||
#include "app_debug.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define TAG "Core2"
|
||||
|
||||
#define BLE_GLUE_FLAG_SHCI_EVENT (1UL << 0)
|
||||
#define BLE_GLUE_FLAG_KILL_THREAD (1UL << 1)
|
||||
#define BLE_GLUE_FLAG_ALL (BLE_GLUE_FLAG_SHCI_EVENT | BLE_GLUE_FLAG_KILL_THREAD)
|
||||
|
||||
#define POOL_SIZE \
|
||||
(CFG_TLBLE_EVT_QUEUE_LENGTH * 4U * \
|
||||
DIVC((sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE), 4U))
|
||||
|
||||
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_event_pool[POOL_SIZE];
|
||||
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t ble_glue_system_cmd_buff;
|
||||
PLACE_IN_SECTION("MB_MEM2")
|
||||
ALIGN(4)
|
||||
static uint8_t ble_glue_system_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U];
|
||||
PLACE_IN_SECTION("MB_MEM2")
|
||||
ALIGN(4)
|
||||
static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255];
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* shci_mtx;
|
||||
FuriThread* thread;
|
||||
BleGlueStatus status;
|
||||
BleGlueKeyStorageChangedCallback callback;
|
||||
BleGlueC2Info c2_info;
|
||||
void* context;
|
||||
} BleGlue;
|
||||
|
||||
static BleGlue* ble_glue = NULL;
|
||||
|
||||
static int32_t ble_glue_shci_thread(void* argument);
|
||||
static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status);
|
||||
static void ble_glue_sys_user_event_callback(void* pPayload);
|
||||
|
||||
void ble_glue_set_key_storage_changed_callback(
|
||||
BleGlueKeyStorageChangedCallback callback,
|
||||
void* context) {
|
||||
furi_assert(ble_glue);
|
||||
furi_assert(callback);
|
||||
ble_glue->callback = callback;
|
||||
ble_glue->context = context;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/* TL hook to catch hardfaults */
|
||||
|
||||
int32_t ble_glue_TL_SYS_SendCmd(uint8_t* buffer, uint16_t size) {
|
||||
if(furi_hal_bt_get_hardfault_info()) {
|
||||
furi_crash("ST(R) Copro(R) HardFault");
|
||||
}
|
||||
|
||||
return TL_SYS_SendCmd(buffer, size);
|
||||
}
|
||||
|
||||
void shci_register_io_bus(tSHciIO* fops) {
|
||||
/* Register IO bus services */
|
||||
fops->Init = TL_SYS_Init;
|
||||
fops->Send = ble_glue_TL_SYS_SendCmd;
|
||||
}
|
||||
|
||||
static int32_t ble_glue_TL_BLE_SendCmd(uint8_t* buffer, uint16_t size) {
|
||||
if(furi_hal_bt_get_hardfault_info()) {
|
||||
furi_crash("ST(R) Copro(R) HardFault");
|
||||
}
|
||||
|
||||
return TL_BLE_SendCmd(buffer, size);
|
||||
}
|
||||
|
||||
void hci_register_io_bus(tHciIO* fops) {
|
||||
/* Register IO bus services */
|
||||
fops->Init = TL_BLE_Init;
|
||||
fops->Send = ble_glue_TL_BLE_SendCmd;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void ble_glue_init() {
|
||||
ble_glue = malloc(sizeof(BleGlue));
|
||||
ble_glue->status = BleGlueStatusStartup;
|
||||
|
||||
#ifdef BLE_GLUE_DEBUG
|
||||
APPD_Init();
|
||||
#endif
|
||||
|
||||
// Initialize all transport layers
|
||||
TL_MM_Config_t tl_mm_config;
|
||||
SHCI_TL_HciInitConf_t SHci_Tl_Init_Conf;
|
||||
// Reference table initialization
|
||||
TL_Init();
|
||||
|
||||
ble_glue->shci_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
// FreeRTOS system task creation
|
||||
ble_glue->thread = furi_thread_alloc_ex("BleShciDriver", 1024, ble_glue_shci_thread, ble_glue);
|
||||
furi_thread_start(ble_glue->thread);
|
||||
|
||||
// System channel initialization
|
||||
SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_system_cmd_buff;
|
||||
SHci_Tl_Init_Conf.StatusNotCallBack = ble_glue_sys_status_not_callback;
|
||||
shci_init(ble_glue_sys_user_event_callback, (void*)&SHci_Tl_Init_Conf);
|
||||
|
||||
/**< Memory Manager channel initialization */
|
||||
tl_mm_config.p_BleSpareEvtBuffer = ble_glue_ble_spare_event_buff;
|
||||
tl_mm_config.p_SystemSpareEvtBuffer = ble_glue_system_spare_event_buff;
|
||||
tl_mm_config.p_AsynchEvtPool = ble_glue_event_pool;
|
||||
tl_mm_config.AsynchEvtPoolSize = POOL_SIZE;
|
||||
TL_MM_Init(&tl_mm_config);
|
||||
TL_Enable();
|
||||
|
||||
/*
|
||||
* From now, the application is waiting for the ready event ( VS_HCI_C2_Ready )
|
||||
* received on the system channel before starting the Stack
|
||||
* This system event is received with ble_glue_sys_user_event_callback()
|
||||
*/
|
||||
}
|
||||
|
||||
const BleGlueC2Info* ble_glue_get_c2_info() {
|
||||
return &ble_glue->c2_info;
|
||||
}
|
||||
|
||||
BleGlueStatus ble_glue_get_c2_status() {
|
||||
return ble_glue->status;
|
||||
}
|
||||
|
||||
static const char* ble_glue_get_reltype_str(const uint8_t reltype) {
|
||||
static char relcode[3] = {0};
|
||||
switch(reltype) {
|
||||
case INFO_STACK_TYPE_BLE_FULL:
|
||||
return "F";
|
||||
case INFO_STACK_TYPE_BLE_HCI:
|
||||
return "H";
|
||||
case INFO_STACK_TYPE_BLE_LIGHT:
|
||||
return "L";
|
||||
case INFO_STACK_TYPE_BLE_BEACON:
|
||||
return "Be";
|
||||
case INFO_STACK_TYPE_BLE_BASIC:
|
||||
return "Ba";
|
||||
case INFO_STACK_TYPE_BLE_FULL_EXT_ADV:
|
||||
return "F+";
|
||||
case INFO_STACK_TYPE_BLE_HCI_EXT_ADV:
|
||||
return "H+";
|
||||
default:
|
||||
snprintf(relcode, sizeof(relcode), "%X", reltype);
|
||||
return relcode;
|
||||
}
|
||||
}
|
||||
|
||||
static void ble_glue_update_c2_fw_info() {
|
||||
WirelessFwInfo_t wireless_info;
|
||||
SHCI_GetWirelessFwInfo(&wireless_info);
|
||||
BleGlueC2Info* local_info = &ble_glue->c2_info;
|
||||
|
||||
local_info->VersionMajor = wireless_info.VersionMajor;
|
||||
local_info->VersionMinor = wireless_info.VersionMinor;
|
||||
local_info->VersionSub = wireless_info.VersionSub;
|
||||
local_info->VersionBranch = wireless_info.VersionBranch;
|
||||
local_info->VersionReleaseType = wireless_info.VersionReleaseType;
|
||||
|
||||
local_info->MemorySizeSram2B = wireless_info.MemorySizeSram2B;
|
||||
local_info->MemorySizeSram2A = wireless_info.MemorySizeSram2A;
|
||||
local_info->MemorySizeSram1 = wireless_info.MemorySizeSram1;
|
||||
local_info->MemorySizeFlash = wireless_info.MemorySizeFlash;
|
||||
|
||||
local_info->StackType = wireless_info.StackType;
|
||||
snprintf(
|
||||
local_info->StackTypeString,
|
||||
BLE_GLUE_MAX_VERSION_STRING_LEN,
|
||||
"%d.%d.%d:%s",
|
||||
local_info->VersionMajor,
|
||||
local_info->VersionMinor,
|
||||
local_info->VersionSub,
|
||||
ble_glue_get_reltype_str(local_info->StackType));
|
||||
|
||||
local_info->FusVersionMajor = wireless_info.FusVersionMajor;
|
||||
local_info->FusVersionMinor = wireless_info.FusVersionMinor;
|
||||
local_info->FusVersionSub = wireless_info.FusVersionSub;
|
||||
local_info->FusMemorySizeSram2B = wireless_info.FusMemorySizeSram2B;
|
||||
local_info->FusMemorySizeSram2A = wireless_info.FusMemorySizeSram2A;
|
||||
local_info->FusMemorySizeFlash = wireless_info.FusMemorySizeFlash;
|
||||
}
|
||||
|
||||
static void ble_glue_dump_stack_info() {
|
||||
const BleGlueC2Info* c2_info = &ble_glue->c2_info;
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Core2: FUS: %d.%d.%d, mem %d/%d, flash %d pages",
|
||||
c2_info->FusVersionMajor,
|
||||
c2_info->FusVersionMinor,
|
||||
c2_info->FusVersionSub,
|
||||
c2_info->FusMemorySizeSram2B,
|
||||
c2_info->FusMemorySizeSram2A,
|
||||
c2_info->FusMemorySizeFlash);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Core2: Stack: %d.%d.%d, branch %d, reltype %d, stacktype %d, flash %d pages",
|
||||
c2_info->VersionMajor,
|
||||
c2_info->VersionMinor,
|
||||
c2_info->VersionSub,
|
||||
c2_info->VersionBranch,
|
||||
c2_info->VersionReleaseType,
|
||||
c2_info->StackType,
|
||||
c2_info->MemorySizeFlash);
|
||||
}
|
||||
|
||||
bool ble_glue_wait_for_c2_start(int32_t timeout) {
|
||||
bool started = false;
|
||||
|
||||
do {
|
||||
started = ble_glue->status == BleGlueStatusC2Started;
|
||||
if(!started) {
|
||||
timeout--;
|
||||
furi_delay_tick(1);
|
||||
}
|
||||
} while(!started && (timeout > 0));
|
||||
|
||||
if(started) {
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"C2 boot completed, mode: %s",
|
||||
ble_glue->c2_info.mode == BleGlueC2ModeFUS ? "FUS" : "Stack");
|
||||
ble_glue_update_c2_fw_info();
|
||||
ble_glue_dump_stack_info();
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "C2 startup failed");
|
||||
ble_glue->status = BleGlueStatusBroken;
|
||||
}
|
||||
|
||||
return started;
|
||||
}
|
||||
|
||||
bool ble_glue_start() {
|
||||
furi_assert(ble_glue);
|
||||
|
||||
if(ble_glue->status != BleGlueStatusC2Started) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
if(ble_app_init()) {
|
||||
FURI_LOG_I(TAG, "Radio stack started");
|
||||
ble_glue->status = BleGlueStatusRadioStackRunning;
|
||||
ret = true;
|
||||
if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) {
|
||||
FURI_LOG_I(TAG, "Flash activity control switched to SEM7");
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to switch flash activity control to SEM7");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Radio stack startup failed");
|
||||
ble_glue->status = BleGlueStatusRadioStackMissing;
|
||||
ble_app_thread_stop();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ble_glue_is_alive() {
|
||||
if(!ble_glue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ble_glue->status >= BleGlueStatusC2Started;
|
||||
}
|
||||
|
||||
bool ble_glue_is_radio_stack_ready() {
|
||||
if(!ble_glue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ble_glue->status == BleGlueStatusRadioStackRunning;
|
||||
}
|
||||
|
||||
BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) {
|
||||
furi_check(desired_mode > BleGlueC2ModeUnknown);
|
||||
|
||||
if(desired_mode == ble_glue->c2_info.mode) {
|
||||
return BleGlueCommandResultOK;
|
||||
}
|
||||
|
||||
if((ble_glue->c2_info.mode == BleGlueC2ModeFUS) && (desired_mode == BleGlueC2ModeStack)) {
|
||||
if((ble_glue->c2_info.VersionMajor == 0) && (ble_glue->c2_info.VersionMinor == 0)) {
|
||||
FURI_LOG_W(TAG, "Stack isn't installed!");
|
||||
return BleGlueCommandResultError;
|
||||
}
|
||||
SHCI_CmdStatus_t status = SHCI_C2_FUS_StartWs();
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to start Radio Stack with status: %02X", status);
|
||||
return BleGlueCommandResultError;
|
||||
}
|
||||
return BleGlueCommandResultRestartPending;
|
||||
}
|
||||
if((ble_glue->c2_info.mode == BleGlueC2ModeStack) && (desired_mode == BleGlueC2ModeFUS)) {
|
||||
SHCI_FUS_GetState_ErrorCode_t error_code = 0;
|
||||
uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code);
|
||||
FURI_LOG_D(TAG, "FUS state: %X, error = %x", fus_state, error_code);
|
||||
if(fus_state == SHCI_FUS_CMD_NOT_SUPPORTED) {
|
||||
// Second call to SHCI_C2_FUS_GetState() restarts whole MCU & boots FUS
|
||||
fus_state = SHCI_C2_FUS_GetState(&error_code);
|
||||
FURI_LOG_D(TAG, "FUS state#2: %X, error = %x", fus_state, error_code);
|
||||
return BleGlueCommandResultRestartPending;
|
||||
}
|
||||
return BleGlueCommandResultOK;
|
||||
}
|
||||
|
||||
return BleGlueCommandResultError;
|
||||
}
|
||||
|
||||
static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) {
|
||||
switch(status) {
|
||||
case SHCI_TL_CmdBusy:
|
||||
furi_mutex_acquire(ble_glue->shci_mtx, FuriWaitForever);
|
||||
break;
|
||||
case SHCI_TL_CmdAvailable:
|
||||
furi_mutex_release(ble_glue->shci_mtx);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The type of the payload for a system user event is tSHCI_UserEvtRxParam
|
||||
* When the system event is both :
|
||||
* - a ready event (subevtcode = SHCI_SUB_EVT_CODE_READY)
|
||||
* - reported by the FUS (sysevt_ready_rsp == FUS_FW_RUNNING)
|
||||
* The buffer shall not be released
|
||||
* ( eg ((tSHCI_UserEvtRxParam*)pPayload)->status shall be set to SHCI_TL_UserEventFlow_Disable )
|
||||
* When the status is not filled, the buffer is released by default
|
||||
*/
|
||||
static void ble_glue_sys_user_event_callback(void* pPayload) {
|
||||
UNUSED(pPayload);
|
||||
|
||||
#ifdef BLE_GLUE_DEBUG
|
||||
APPD_EnableCPU2();
|
||||
#endif
|
||||
|
||||
TL_AsynchEvt_t* p_sys_event =
|
||||
(TL_AsynchEvt_t*)(((tSHCI_UserEvtRxParam*)pPayload)->pckt->evtserial.evt.payload);
|
||||
|
||||
if(p_sys_event->subevtcode == SHCI_SUB_EVT_CODE_READY) {
|
||||
FURI_LOG_I(TAG, "Core2 started");
|
||||
SHCI_C2_Ready_Evt_t* p_c2_ready_evt = (SHCI_C2_Ready_Evt_t*)p_sys_event->payload;
|
||||
if(p_c2_ready_evt->sysevt_ready_rsp == WIRELESS_FW_RUNNING) {
|
||||
ble_glue->c2_info.mode = BleGlueC2ModeStack;
|
||||
} else if(p_c2_ready_evt->sysevt_ready_rsp == FUS_FW_RUNNING) {
|
||||
ble_glue->c2_info.mode = BleGlueC2ModeFUS;
|
||||
}
|
||||
|
||||
ble_glue->status = BleGlueStatusC2Started;
|
||||
} else if(p_sys_event->subevtcode == SHCI_SUB_EVT_ERROR_NOTIF) {
|
||||
FURI_LOG_E(TAG, "Error during initialization");
|
||||
} else if(p_sys_event->subevtcode == SHCI_SUB_EVT_BLE_NVM_RAM_UPDATE) {
|
||||
SHCI_C2_BleNvmRamUpdate_Evt_t* p_sys_ble_nvm_ram_update_event =
|
||||
(SHCI_C2_BleNvmRamUpdate_Evt_t*)p_sys_event->payload;
|
||||
if(ble_glue->callback) {
|
||||
ble_glue->callback(
|
||||
(uint8_t*)p_sys_ble_nvm_ram_update_event->StartAddress,
|
||||
p_sys_ble_nvm_ram_update_event->Size,
|
||||
ble_glue->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ble_glue_clear_shared_memory() {
|
||||
memset(ble_glue_event_pool, 0, sizeof(ble_glue_event_pool));
|
||||
memset(&ble_glue_system_cmd_buff, 0, sizeof(ble_glue_system_cmd_buff));
|
||||
memset(ble_glue_system_spare_event_buff, 0, sizeof(ble_glue_system_spare_event_buff));
|
||||
memset(ble_glue_ble_spare_event_buff, 0, sizeof(ble_glue_ble_spare_event_buff));
|
||||
}
|
||||
|
||||
void ble_glue_thread_stop() {
|
||||
if(ble_glue) {
|
||||
FuriThreadId thread_id = furi_thread_get_id(ble_glue->thread);
|
||||
furi_assert(thread_id);
|
||||
furi_thread_flags_set(thread_id, BLE_GLUE_FLAG_KILL_THREAD);
|
||||
furi_thread_join(ble_glue->thread);
|
||||
furi_thread_free(ble_glue->thread);
|
||||
// Free resources
|
||||
furi_mutex_free(ble_glue->shci_mtx);
|
||||
ble_glue_clear_shared_memory();
|
||||
free(ble_glue);
|
||||
ble_glue = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap functions
|
||||
static int32_t ble_glue_shci_thread(void* context) {
|
||||
UNUSED(context);
|
||||
uint32_t flags = 0;
|
||||
|
||||
while(true) {
|
||||
flags = furi_thread_flags_wait(BLE_GLUE_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever);
|
||||
if(flags & BLE_GLUE_FLAG_SHCI_EVENT) {
|
||||
shci_user_evt_proc();
|
||||
}
|
||||
if(flags & BLE_GLUE_FLAG_KILL_THREAD) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void shci_notify_asynch_evt(void* pdata) {
|
||||
UNUSED(pdata);
|
||||
if(ble_glue) {
|
||||
FuriThreadId thread_id = furi_thread_get_id(ble_glue->thread);
|
||||
furi_assert(thread_id);
|
||||
furi_thread_flags_set(thread_id, BLE_GLUE_FLAG_SHCI_EVENT);
|
||||
}
|
||||
}
|
||||
|
||||
bool ble_glue_reinit_c2() {
|
||||
return SHCI_C2_Reinit() == SHCI_Success;
|
||||
}
|
||||
|
||||
BleGlueCommandResult ble_glue_fus_stack_delete() {
|
||||
FURI_LOG_I(TAG, "Erasing stack");
|
||||
SHCI_CmdStatus_t erase_stat = SHCI_C2_FUS_FwDelete();
|
||||
FURI_LOG_I(TAG, "Cmd res = %x", erase_stat);
|
||||
if(erase_stat == SHCI_Success) {
|
||||
return BleGlueCommandResultOperationOngoing;
|
||||
}
|
||||
ble_glue_fus_get_status();
|
||||
return BleGlueCommandResultError;
|
||||
}
|
||||
|
||||
BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr) {
|
||||
FURI_LOG_I(TAG, "Installing stack");
|
||||
SHCI_CmdStatus_t write_stat = SHCI_C2_FUS_FwUpgrade(src_addr, dst_addr);
|
||||
FURI_LOG_I(TAG, "Cmd res = %x", write_stat);
|
||||
if(write_stat == SHCI_Success) {
|
||||
return BleGlueCommandResultOperationOngoing;
|
||||
}
|
||||
ble_glue_fus_get_status();
|
||||
return BleGlueCommandResultError;
|
||||
}
|
||||
|
||||
BleGlueCommandResult ble_glue_fus_get_status() {
|
||||
furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS);
|
||||
SHCI_FUS_GetState_ErrorCode_t error_code = 0;
|
||||
uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code);
|
||||
FURI_LOG_I(TAG, "FUS state: %x, error: %x", fus_state, error_code);
|
||||
if((error_code != 0) || (fus_state == FUS_STATE_VALUE_ERROR)) {
|
||||
return BleGlueCommandResultError;
|
||||
} else if(
|
||||
(fus_state >= FUS_STATE_VALUE_FW_UPGRD_ONGOING) &&
|
||||
(fus_state <= FUS_STATE_VALUE_SERVICE_ONGOING_END)) {
|
||||
return BleGlueCommandResultOperationOngoing;
|
||||
}
|
||||
return BleGlueCommandResultOK;
|
||||
}
|
||||
|
||||
BleGlueCommandResult ble_glue_fus_wait_operation() {
|
||||
furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS);
|
||||
|
||||
while(true) {
|
||||
BleGlueCommandResult fus_status = ble_glue_fus_get_status();
|
||||
if(fus_status == BleGlueCommandResultOperationOngoing) {
|
||||
furi_delay_ms(20);
|
||||
} else if(fus_status == BleGlueCommandResultError) {
|
||||
return BleGlueCommandResultError;
|
||||
} else {
|
||||
return BleGlueCommandResultOK;
|
||||
}
|
||||
}
|
||||
}
|
||||
126
targets/f7/ble_glue/ble_glue.h
Normal file
126
targets/f7/ble_glue/ble_glue.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
BleGlueC2ModeUnknown = 0,
|
||||
BleGlueC2ModeFUS,
|
||||
BleGlueC2ModeStack,
|
||||
} BleGlueC2Mode;
|
||||
|
||||
#define BLE_GLUE_MAX_VERSION_STRING_LEN 20
|
||||
typedef struct {
|
||||
BleGlueC2Mode mode;
|
||||
/**
|
||||
* Wireless Info
|
||||
*/
|
||||
uint8_t VersionMajor;
|
||||
uint8_t VersionMinor;
|
||||
uint8_t VersionSub;
|
||||
uint8_t VersionBranch;
|
||||
uint8_t VersionReleaseType;
|
||||
uint8_t MemorySizeSram2B; /*< Multiple of 1K */
|
||||
uint8_t MemorySizeSram2A; /*< Multiple of 1K */
|
||||
uint8_t MemorySizeSram1; /*< Multiple of 1K */
|
||||
uint8_t MemorySizeFlash; /*< Multiple of 4K */
|
||||
uint8_t StackType;
|
||||
char StackTypeString[BLE_GLUE_MAX_VERSION_STRING_LEN];
|
||||
/**
|
||||
* Fus Info
|
||||
*/
|
||||
uint8_t FusVersionMajor;
|
||||
uint8_t FusVersionMinor;
|
||||
uint8_t FusVersionSub;
|
||||
uint8_t FusMemorySizeSram2B; /*< Multiple of 1K */
|
||||
uint8_t FusMemorySizeSram2A; /*< Multiple of 1K */
|
||||
uint8_t FusMemorySizeFlash; /*< Multiple of 4K */
|
||||
} BleGlueC2Info;
|
||||
|
||||
typedef enum {
|
||||
// Stage 1: core2 startup and FUS
|
||||
BleGlueStatusStartup,
|
||||
BleGlueStatusBroken,
|
||||
BleGlueStatusC2Started,
|
||||
// Stage 2: radio stack
|
||||
BleGlueStatusRadioStackRunning,
|
||||
BleGlueStatusRadioStackMissing
|
||||
} BleGlueStatus;
|
||||
|
||||
typedef void (
|
||||
*BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context);
|
||||
|
||||
/** Initialize start core2 and initialize transport */
|
||||
void ble_glue_init();
|
||||
|
||||
/** Start Core2 Radio stack
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool ble_glue_start();
|
||||
|
||||
/** Is core2 alive and at least FUS is running
|
||||
*
|
||||
* @return true if core2 is alive
|
||||
*/
|
||||
bool ble_glue_is_alive();
|
||||
|
||||
/** Waits for C2 to reports its mode to callback
|
||||
*
|
||||
* @return true if it reported before reaching timeout
|
||||
*/
|
||||
bool ble_glue_wait_for_c2_start(int32_t timeout);
|
||||
|
||||
BleGlueStatus ble_glue_get_c2_status();
|
||||
|
||||
const BleGlueC2Info* ble_glue_get_c2_info();
|
||||
|
||||
/** Is core2 radio stack present and ready
|
||||
*
|
||||
* @return true if present and ready
|
||||
*/
|
||||
bool ble_glue_is_radio_stack_ready();
|
||||
|
||||
/** Set callback for NVM in RAM changes
|
||||
*
|
||||
* @param[in] callback The callback to call on NVM change
|
||||
* @param context The context for callback
|
||||
*/
|
||||
void ble_glue_set_key_storage_changed_callback(
|
||||
BleGlueKeyStorageChangedCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Stop SHCI thread */
|
||||
void ble_glue_thread_stop();
|
||||
|
||||
bool ble_glue_reinit_c2();
|
||||
|
||||
typedef enum {
|
||||
BleGlueCommandResultUnknown,
|
||||
BleGlueCommandResultOK,
|
||||
BleGlueCommandResultError,
|
||||
BleGlueCommandResultRestartPending,
|
||||
BleGlueCommandResultOperationOngoing,
|
||||
} BleGlueCommandResult;
|
||||
|
||||
/** Restart MCU to launch radio stack firmware if necessary
|
||||
*
|
||||
* @return true on radio stack start command
|
||||
*/
|
||||
BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode mode);
|
||||
|
||||
BleGlueCommandResult ble_glue_fus_stack_delete();
|
||||
|
||||
BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr);
|
||||
|
||||
BleGlueCommandResult ble_glue_fus_get_status();
|
||||
|
||||
BleGlueCommandResult ble_glue_fus_wait_operation();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
138
targets/f7/ble_glue/compiler.h
Normal file
138
targets/f7/ble_glue/compiler.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __PACKED_STRUCT
|
||||
#define __PACKED_STRUCT PACKED(struct)
|
||||
#endif
|
||||
|
||||
#ifndef __PACKED_UNION
|
||||
#define __PACKED_UNION PACKED(union)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief This is the section dedicated to IAR toolchain
|
||||
*/
|
||||
#if defined(__ICCARM__) || defined(__IAR_SYSTEMS_ASM__)
|
||||
|
||||
#ifndef __WEAK
|
||||
#define __WEAK __weak
|
||||
#endif
|
||||
|
||||
#define QUOTE_(a) #a
|
||||
|
||||
/**
|
||||
* @brief PACKED
|
||||
* Use the PACKED macro for variables that needs to be packed.
|
||||
* Usage: PACKED(struct) myStruct_s
|
||||
* PACKED(union) myStruct_s
|
||||
*/
|
||||
#define PACKED(decl) __packed decl
|
||||
|
||||
/**
|
||||
* @brief SECTION
|
||||
* Use the SECTION macro to assign data or code in a specific section.
|
||||
* Usage: SECTION(".my_section")
|
||||
*/
|
||||
#define SECTION(name) _Pragma(QUOTE_(location = name))
|
||||
|
||||
/**
|
||||
* @brief ALIGN_DEF
|
||||
* Use the ALIGN_DEF macro to specify the alignment of a variable.
|
||||
* Usage: ALIGN_DEF(4)
|
||||
*/
|
||||
#define ALIGN_DEF(v) _Pragma(QUOTE_(data_alignment = v))
|
||||
|
||||
/**
|
||||
* @brief NO_INIT
|
||||
* Use the NO_INIT macro to declare a not initialized variable.
|
||||
* Usage: NO_INIT(int my_no_init_var)
|
||||
* Usage: NO_INIT(uint16_t my_no_init_array[10])
|
||||
*/
|
||||
#define NO_INIT(var) __no_init var
|
||||
|
||||
/**
|
||||
* @brief This is the section dedicated to GNU toolchain
|
||||
*/
|
||||
#else
|
||||
#ifdef __GNUC__
|
||||
|
||||
#ifndef __WEAK
|
||||
#define __WEAK __attribute__((weak))
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief PACKED
|
||||
* Use the PACKED macro for variables that needs to be packed.
|
||||
* Usage: PACKED(struct) myStruct_s
|
||||
* PACKED(union) myStruct_s
|
||||
*/
|
||||
#define PACKED(decl) decl __attribute__((packed))
|
||||
|
||||
/**
|
||||
* @brief SECTION
|
||||
* Use the SECTION macro to assign data or code in a specific section.
|
||||
* Usage: SECTION(".my_section")
|
||||
*/
|
||||
#define SECTION(name) __attribute__((section(name)))
|
||||
|
||||
/**
|
||||
* @brief ALIGN_DEF
|
||||
* Use the ALIGN_DEF macro to specify the alignment of a variable.
|
||||
* Usage: ALIGN_DEF(4)
|
||||
*/
|
||||
#define ALIGN_DEF(N) __attribute__((aligned(N)))
|
||||
|
||||
/**
|
||||
* @brief NO_INIT
|
||||
* Use the NO_INIT macro to declare a not initialized variable.
|
||||
* Usage: NO_INIT(int my_no_init_var)
|
||||
* Usage: NO_INIT(uint16_t my_no_init_array[10])
|
||||
*/
|
||||
#define NO_INIT(var) var __attribute__((section(".noinit")))
|
||||
|
||||
/**
|
||||
* @brief This is the section dedicated to Keil toolchain
|
||||
*/
|
||||
#else
|
||||
#ifdef __CC_ARM
|
||||
|
||||
#ifndef __WEAK
|
||||
#define __WEAK __weak
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief PACKED
|
||||
* Use the PACKED macro for variables that needs to be packed.
|
||||
* Usage: PACKED(struct) myStruct_s
|
||||
* PACKED(union) myStruct_s
|
||||
*/
|
||||
#define PACKED(decl) decl __attribute__((packed))
|
||||
|
||||
/**
|
||||
* @brief SECTION
|
||||
* Use the SECTION macro to assign data or code in a specific section.
|
||||
* Usage: SECTION(".my_section")
|
||||
*/
|
||||
#define SECTION(name) __attribute__((section(name)))
|
||||
|
||||
/**
|
||||
* @brief ALIGN_DEF
|
||||
* Use the ALIGN_DEF macro to specify the alignment of a variable.
|
||||
* Usage: ALIGN_DEF(4)
|
||||
*/
|
||||
#define ALIGN_DEF(N) __attribute__((aligned(N)))
|
||||
|
||||
/**
|
||||
* @brief NO_INIT
|
||||
* Use the NO_INIT macro to declare a not initialized variable.
|
||||
* Usage: NO_INIT(int my_no_init_var)
|
||||
* Usage: NO_INIT(uint16_t my_no_init_array[10])
|
||||
*/
|
||||
#define NO_INIT(var) var __attribute__((section("NoInit")))
|
||||
|
||||
#else
|
||||
|
||||
#error Neither ICCARM, CC ARM nor GNUC C detected. Define your macros.
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
567
targets/f7/ble_glue/gap.c
Normal file
567
targets/f7/ble_glue/gap.c
Normal file
@@ -0,0 +1,567 @@
|
||||
#include "gap.h"
|
||||
|
||||
#include "app_common.h"
|
||||
#include <ble/ble.h>
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "BtGap"
|
||||
|
||||
#define FAST_ADV_TIMEOUT 30000
|
||||
#define INITIAL_ADV_TIMEOUT 60000
|
||||
|
||||
#define GAP_INTERVAL_TO_MS(x) (uint16_t)((x)*1.25)
|
||||
|
||||
typedef struct {
|
||||
uint16_t gap_svc_handle;
|
||||
uint16_t dev_name_char_handle;
|
||||
uint16_t appearance_char_handle;
|
||||
uint16_t connection_handle;
|
||||
uint8_t adv_svc_uuid_len;
|
||||
uint8_t adv_svc_uuid[20];
|
||||
char* adv_name;
|
||||
} GapSvc;
|
||||
|
||||
typedef struct {
|
||||
GapSvc service;
|
||||
GapConfig* config;
|
||||
GapConnectionParams connection_params;
|
||||
GapState state;
|
||||
FuriMutex* state_mutex;
|
||||
GapEventCallback on_event_cb;
|
||||
void* context;
|
||||
FuriTimer* advertise_timer;
|
||||
FuriThread* thread;
|
||||
FuriMessageQueue* command_queue;
|
||||
bool enable_adv;
|
||||
} Gap;
|
||||
|
||||
typedef enum {
|
||||
GapCommandAdvFast,
|
||||
GapCommandAdvLowPower,
|
||||
GapCommandAdvStop,
|
||||
GapCommandKillThread,
|
||||
} GapCommand;
|
||||
|
||||
// Identity root key
|
||||
static const uint8_t gap_irk[16] =
|
||||
{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0};
|
||||
// Encryption root key
|
||||
static const uint8_t gap_erk[16] =
|
||||
{0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21};
|
||||
|
||||
static Gap* gap = NULL;
|
||||
|
||||
static void gap_advertise_start(GapState new_state);
|
||||
static int32_t gap_app(void* context);
|
||||
|
||||
static void gap_verify_connection_parameters(Gap* gap) {
|
||||
furi_assert(gap);
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Connection parameters: Connection Interval: %d (%d ms), Slave Latency: %d, Supervision Timeout: %d",
|
||||
gap->connection_params.conn_interval,
|
||||
GAP_INTERVAL_TO_MS(gap->connection_params.conn_interval),
|
||||
gap->connection_params.slave_latency,
|
||||
gap->connection_params.supervisor_timeout);
|
||||
|
||||
// Send connection parameters request update if necessary
|
||||
GapConnectionParamsRequest* params = &gap->config->conn_param;
|
||||
if(params->conn_int_min > gap->connection_params.conn_interval ||
|
||||
params->conn_int_max < gap->connection_params.conn_interval) {
|
||||
FURI_LOG_W(TAG, "Unsupported connection interval. Request connection parameters update");
|
||||
if(aci_l2cap_connection_parameter_update_req(
|
||||
gap->service.connection_handle,
|
||||
params->conn_int_min,
|
||||
params->conn_int_max,
|
||||
gap->connection_params.slave_latency,
|
||||
gap->connection_params.supervisor_timeout)) {
|
||||
FURI_LOG_E(TAG, "Failed to request connection parameters update");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) {
|
||||
hci_event_pckt* event_pckt;
|
||||
evt_le_meta_event* meta_evt;
|
||||
evt_blecore_aci* blue_evt;
|
||||
hci_le_phy_update_complete_event_rp0* evt_le_phy_update_complete;
|
||||
uint8_t tx_phy;
|
||||
uint8_t rx_phy;
|
||||
tBleStatus ret = BLE_STATUS_INVALID_PARAMS;
|
||||
|
||||
event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data;
|
||||
|
||||
if(gap) {
|
||||
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
|
||||
}
|
||||
switch(event_pckt->evt) {
|
||||
case HCI_DISCONNECTION_COMPLETE_EVT_CODE: {
|
||||
hci_disconnection_complete_event_rp0* disconnection_complete_event =
|
||||
(hci_disconnection_complete_event_rp0*)event_pckt->data;
|
||||
if(disconnection_complete_event->Connection_Handle == gap->service.connection_handle) {
|
||||
gap->service.connection_handle = 0;
|
||||
gap->state = GapStateIdle;
|
||||
FURI_LOG_I(
|
||||
TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason);
|
||||
}
|
||||
// Enterprise sleep
|
||||
furi_delay_us(666 + 666);
|
||||
if(gap->enable_adv) {
|
||||
// Restart advertising
|
||||
gap_advertise_start(GapStateAdvFast);
|
||||
}
|
||||
GapEvent event = {.type = GapEventTypeDisconnected};
|
||||
gap->on_event_cb(event, gap->context);
|
||||
} break;
|
||||
|
||||
case HCI_LE_META_EVT_CODE:
|
||||
meta_evt = (evt_le_meta_event*)event_pckt->data;
|
||||
switch(meta_evt->subevent) {
|
||||
case HCI_LE_CONNECTION_UPDATE_COMPLETE_SUBEVT_CODE: {
|
||||
hci_le_connection_update_complete_event_rp0* event =
|
||||
(hci_le_connection_update_complete_event_rp0*)meta_evt->data;
|
||||
gap->connection_params.conn_interval = event->Conn_Interval;
|
||||
gap->connection_params.slave_latency = event->Conn_Latency;
|
||||
gap->connection_params.supervisor_timeout = event->Supervision_Timeout;
|
||||
FURI_LOG_I(TAG, "Connection parameters event complete");
|
||||
gap_verify_connection_parameters(gap);
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_LE_PHY_UPDATE_COMPLETE_SUBEVT_CODE:
|
||||
evt_le_phy_update_complete = (hci_le_phy_update_complete_event_rp0*)meta_evt->data;
|
||||
if(evt_le_phy_update_complete->Status) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Update PHY failed, status %d", evt_le_phy_update_complete->Status);
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Update PHY succeed");
|
||||
}
|
||||
ret = hci_le_read_phy(gap->service.connection_handle, &tx_phy, &rx_phy);
|
||||
if(ret) {
|
||||
FURI_LOG_E(TAG, "Read PHY failed, status: %d", ret);
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "PHY Params TX = %d, RX = %d ", tx_phy, rx_phy);
|
||||
}
|
||||
break;
|
||||
|
||||
case HCI_LE_CONNECTION_COMPLETE_SUBEVT_CODE: {
|
||||
hci_le_connection_complete_event_rp0* event =
|
||||
(hci_le_connection_complete_event_rp0*)meta_evt->data;
|
||||
gap->connection_params.conn_interval = event->Conn_Interval;
|
||||
gap->connection_params.slave_latency = event->Conn_Latency;
|
||||
gap->connection_params.supervisor_timeout = event->Supervision_Timeout;
|
||||
|
||||
// Stop advertising as connection completed
|
||||
furi_timer_stop(gap->advertise_timer);
|
||||
|
||||
// Update connection status and handle
|
||||
gap->state = GapStateConnected;
|
||||
gap->service.connection_handle = event->Connection_Handle;
|
||||
|
||||
gap_verify_connection_parameters(gap);
|
||||
// Start pairing by sending security request
|
||||
aci_gap_slave_security_req(event->Connection_Handle);
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE:
|
||||
blue_evt = (evt_blecore_aci*)event_pckt->data;
|
||||
switch(blue_evt->ecode) {
|
||||
aci_gap_pairing_complete_event_rp0* pairing_complete;
|
||||
|
||||
case ACI_GAP_LIMITED_DISCOVERABLE_VSEVT_CODE:
|
||||
FURI_LOG_I(TAG, "Limited discoverable event");
|
||||
break;
|
||||
|
||||
case ACI_GAP_PASS_KEY_REQ_VSEVT_CODE: {
|
||||
// Generate random PIN code
|
||||
uint32_t pin = rand() % 999999; //-V1064
|
||||
aci_gap_pass_key_resp(gap->service.connection_handle, pin);
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock)) {
|
||||
FURI_LOG_I(TAG, "Pass key request event. Pin: ******");
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Pass key request event. Pin: %06ld", pin);
|
||||
}
|
||||
GapEvent event = {.type = GapEventTypePinCodeShow, .data.pin_code = pin};
|
||||
gap->on_event_cb(event, gap->context);
|
||||
} break;
|
||||
|
||||
case ACI_ATT_EXCHANGE_MTU_RESP_VSEVT_CODE: {
|
||||
aci_att_exchange_mtu_resp_event_rp0* pr = (void*)blue_evt->data;
|
||||
FURI_LOG_I(TAG, "Rx MTU size: %d", pr->Server_RX_MTU);
|
||||
// Set maximum packet size given header size is 3 bytes
|
||||
GapEvent event = {
|
||||
.type = GapEventTypeUpdateMTU, .data.max_packet_size = pr->Server_RX_MTU - 3};
|
||||
gap->on_event_cb(event, gap->context);
|
||||
} break;
|
||||
|
||||
case ACI_GAP_AUTHORIZATION_REQ_VSEVT_CODE:
|
||||
FURI_LOG_D(TAG, "Authorization request event");
|
||||
break;
|
||||
|
||||
case ACI_GAP_SLAVE_SECURITY_INITIATED_VSEVT_CODE:
|
||||
FURI_LOG_D(TAG, "Slave security initiated");
|
||||
break;
|
||||
|
||||
case ACI_GAP_BOND_LOST_VSEVT_CODE:
|
||||
FURI_LOG_D(TAG, "Bond lost event. Start rebonding");
|
||||
aci_gap_allow_rebond(gap->service.connection_handle);
|
||||
break;
|
||||
|
||||
case ACI_GAP_ADDR_NOT_RESOLVED_VSEVT_CODE:
|
||||
FURI_LOG_D(TAG, "Address not resolved event");
|
||||
break;
|
||||
|
||||
case ACI_GAP_KEYPRESS_NOTIFICATION_VSEVT_CODE:
|
||||
FURI_LOG_D(TAG, "Key press notification event");
|
||||
break;
|
||||
|
||||
case ACI_GAP_NUMERIC_COMPARISON_VALUE_VSEVT_CODE: {
|
||||
uint32_t pin =
|
||||
((aci_gap_numeric_comparison_value_event_rp0*)(blue_evt->data))->Numeric_Value;
|
||||
FURI_LOG_I(TAG, "Verify numeric comparison: %06lu", pin);
|
||||
GapEvent event = {.type = GapEventTypePinCodeVerify, .data.pin_code = pin};
|
||||
bool result = gap->on_event_cb(event, gap->context);
|
||||
aci_gap_numeric_comparison_value_confirm_yesno(gap->service.connection_handle, result);
|
||||
break;
|
||||
}
|
||||
|
||||
case ACI_GAP_PAIRING_COMPLETE_VSEVT_CODE:
|
||||
pairing_complete = (aci_gap_pairing_complete_event_rp0*)blue_evt->data;
|
||||
if(pairing_complete->Status) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Pairing failed with status: %d. Terminating connection",
|
||||
pairing_complete->Status);
|
||||
aci_gap_terminate(gap->service.connection_handle, 5);
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Pairing complete");
|
||||
GapEvent event = {.type = GapEventTypeConnected};
|
||||
gap->on_event_cb(event, gap->context); //-V595
|
||||
}
|
||||
break;
|
||||
|
||||
case ACI_L2CAP_CONNECTION_UPDATE_RESP_VSEVT_CODE:
|
||||
FURI_LOG_D(TAG, "Procedure complete event");
|
||||
break;
|
||||
|
||||
case ACI_L2CAP_CONNECTION_UPDATE_REQ_VSEVT_CODE: {
|
||||
uint16_t result =
|
||||
((aci_l2cap_connection_update_resp_event_rp0*)(blue_evt->data))->Result;
|
||||
if(result == 0) {
|
||||
FURI_LOG_D(TAG, "Connection parameters accepted");
|
||||
} else if(result == 1) {
|
||||
FURI_LOG_D(TAG, "Connection parameters denied");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(gap) {
|
||||
furi_mutex_release(gap->state_mutex);
|
||||
}
|
||||
return SVCCTL_UserEvtFlowEnable;
|
||||
}
|
||||
|
||||
static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) {
|
||||
if(uid_len == 2) {
|
||||
gap->service.adv_svc_uuid[0] = AD_TYPE_16_BIT_SERV_UUID;
|
||||
} else if(uid_len == 4) {
|
||||
gap->service.adv_svc_uuid[0] = AD_TYPE_32_BIT_SERV_UUID;
|
||||
} else if(uid_len == 16) {
|
||||
gap->service.adv_svc_uuid[0] = AD_TYPE_128_BIT_SERV_UUID_CMPLT_LIST;
|
||||
}
|
||||
memcpy(&gap->service.adv_svc_uuid[gap->service.adv_svc_uuid_len], uid, uid_len);
|
||||
gap->service.adv_svc_uuid_len += uid_len;
|
||||
}
|
||||
|
||||
static void gap_init_svc(Gap* gap) {
|
||||
tBleStatus status;
|
||||
uint32_t srd_bd_addr[2];
|
||||
|
||||
// Configure mac address
|
||||
aci_hal_write_config_data(
|
||||
CONFIG_DATA_PUBADDR_OFFSET, CONFIG_DATA_PUBADDR_LEN, gap->config->mac_address);
|
||||
|
||||
/* Static random Address
|
||||
* The two upper bits shall be set to 1
|
||||
* The lowest 32bits is read from the UDN to differentiate between devices
|
||||
* The RNG may be used to provide a random number on each power on
|
||||
*/
|
||||
srd_bd_addr[1] = 0x0000ED6E;
|
||||
srd_bd_addr[0] = LL_FLASH_GetUDN();
|
||||
aci_hal_write_config_data(
|
||||
CONFIG_DATA_RANDOM_ADDRESS_OFFSET, CONFIG_DATA_RANDOM_ADDRESS_LEN, (uint8_t*)srd_bd_addr);
|
||||
// Set Identity root key used to derive LTK and CSRK
|
||||
aci_hal_write_config_data(CONFIG_DATA_IR_OFFSET, CONFIG_DATA_IR_LEN, (uint8_t*)gap_irk);
|
||||
// Set Encryption root key used to derive LTK and CSRK
|
||||
aci_hal_write_config_data(CONFIG_DATA_ER_OFFSET, CONFIG_DATA_ER_LEN, (uint8_t*)gap_erk);
|
||||
// Set TX Power to 0 dBm
|
||||
aci_hal_set_tx_power_level(1, 0x19);
|
||||
// Initialize GATT interface
|
||||
aci_gatt_init();
|
||||
// Initialize GAP interface
|
||||
// Skip fist symbol AD_TYPE_COMPLETE_LOCAL_NAME
|
||||
char* name = gap->service.adv_name + 1;
|
||||
aci_gap_init(
|
||||
GAP_PERIPHERAL_ROLE,
|
||||
0,
|
||||
strlen(name),
|
||||
&gap->service.gap_svc_handle,
|
||||
&gap->service.dev_name_char_handle,
|
||||
&gap->service.appearance_char_handle);
|
||||
|
||||
// Set GAP characteristics
|
||||
status = aci_gatt_update_char_value(
|
||||
gap->service.gap_svc_handle,
|
||||
gap->service.dev_name_char_handle,
|
||||
0,
|
||||
strlen(name),
|
||||
(uint8_t*)name);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed updating name characteristic: %d", status);
|
||||
}
|
||||
|
||||
uint8_t gap_appearence_char_uuid[2] = {
|
||||
gap->config->appearance_char & 0xff, gap->config->appearance_char >> 8};
|
||||
status = aci_gatt_update_char_value(
|
||||
gap->service.gap_svc_handle,
|
||||
gap->service.appearance_char_handle,
|
||||
0,
|
||||
2,
|
||||
gap_appearence_char_uuid);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed updating appearence characteristic: %d", status);
|
||||
}
|
||||
// Set default PHY
|
||||
hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED);
|
||||
// Set I/O capability
|
||||
bool keypress_supported = false;
|
||||
if(gap->config->pairing_method == GapPairingPinCodeShow) {
|
||||
aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY);
|
||||
} else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) {
|
||||
aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO);
|
||||
keypress_supported = true;
|
||||
}
|
||||
// Setup authentication
|
||||
aci_gap_set_authentication_requirement(
|
||||
gap->config->bonding_mode,
|
||||
CFG_MITM_PROTECTION,
|
||||
CFG_SC_SUPPORT,
|
||||
keypress_supported,
|
||||
CFG_ENCRYPTION_KEY_SIZE_MIN,
|
||||
CFG_ENCRYPTION_KEY_SIZE_MAX,
|
||||
CFG_USED_FIXED_PIN,
|
||||
0,
|
||||
CFG_IDENTITY_ADDRESS);
|
||||
// Configure whitelist
|
||||
aci_gap_configure_whitelist();
|
||||
}
|
||||
|
||||
static void gap_advertise_start(GapState new_state) {
|
||||
tBleStatus status;
|
||||
uint16_t min_interval;
|
||||
uint16_t max_interval;
|
||||
|
||||
if(new_state == GapStateAdvFast) {
|
||||
min_interval = 0x80; // 80 ms
|
||||
max_interval = 0xa0; // 100 ms
|
||||
} else {
|
||||
min_interval = 0x0640; // 1 s
|
||||
max_interval = 0x0fa0; // 2.5 s
|
||||
}
|
||||
// Stop advertising timer
|
||||
furi_timer_stop(gap->advertise_timer);
|
||||
|
||||
if((new_state == GapStateAdvLowPower) &&
|
||||
((gap->state == GapStateAdvFast) || (gap->state == GapStateAdvLowPower))) {
|
||||
// Stop advertising
|
||||
status = aci_gap_set_non_discoverable();
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "set_non_discoverable failed %d", status);
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "set_non_discoverable success");
|
||||
}
|
||||
}
|
||||
// Configure advertising
|
||||
status = aci_gap_set_discoverable(
|
||||
ADV_IND,
|
||||
min_interval,
|
||||
max_interval,
|
||||
CFG_IDENTITY_ADDRESS,
|
||||
0,
|
||||
strlen(gap->service.adv_name),
|
||||
(uint8_t*)gap->service.adv_name,
|
||||
gap->service.adv_svc_uuid_len,
|
||||
gap->service.adv_svc_uuid,
|
||||
0,
|
||||
0);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "set_discoverable failed %d", status);
|
||||
}
|
||||
gap->state = new_state;
|
||||
GapEvent event = {.type = GapEventTypeStartAdvertising};
|
||||
gap->on_event_cb(event, gap->context);
|
||||
furi_timer_start(gap->advertise_timer, INITIAL_ADV_TIMEOUT);
|
||||
}
|
||||
|
||||
static void gap_advertise_stop() {
|
||||
tBleStatus ret;
|
||||
if(gap->state > GapStateIdle) {
|
||||
if(gap->state == GapStateConnected) {
|
||||
// Terminate connection
|
||||
ret = aci_gap_terminate(gap->service.connection_handle, 0x13);
|
||||
if(ret != BLE_STATUS_SUCCESS) {
|
||||
FURI_LOG_E(TAG, "terminate failed %d", ret);
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "terminate success");
|
||||
}
|
||||
}
|
||||
// Stop advertising
|
||||
furi_timer_stop(gap->advertise_timer);
|
||||
ret = aci_gap_set_non_discoverable();
|
||||
if(ret != BLE_STATUS_SUCCESS) {
|
||||
FURI_LOG_E(TAG, "set_non_discoverable failed %d", ret);
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "set_non_discoverable success");
|
||||
}
|
||||
gap->state = GapStateIdle;
|
||||
}
|
||||
GapEvent event = {.type = GapEventTypeStopAdvertising};
|
||||
gap->on_event_cb(event, gap->context);
|
||||
}
|
||||
|
||||
void gap_start_advertising() {
|
||||
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
|
||||
if(gap->state == GapStateIdle) {
|
||||
gap->state = GapStateStartingAdv;
|
||||
FURI_LOG_I(TAG, "Start advertising");
|
||||
gap->enable_adv = true;
|
||||
GapCommand command = GapCommandAdvFast;
|
||||
furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk);
|
||||
}
|
||||
furi_mutex_release(gap->state_mutex);
|
||||
}
|
||||
|
||||
void gap_stop_advertising() {
|
||||
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
|
||||
if(gap->state > GapStateIdle) {
|
||||
FURI_LOG_I(TAG, "Stop advertising");
|
||||
gap->enable_adv = false;
|
||||
GapCommand command = GapCommandAdvStop;
|
||||
furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk);
|
||||
}
|
||||
furi_mutex_release(gap->state_mutex);
|
||||
}
|
||||
|
||||
static void gap_advetise_timer_callback(void* context) {
|
||||
UNUSED(context);
|
||||
GapCommand command = GapCommandAdvLowPower;
|
||||
furi_check(furi_message_queue_put(gap->command_queue, &command, 0) == FuriStatusOk);
|
||||
}
|
||||
|
||||
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) {
|
||||
if(!ble_glue_is_radio_stack_ready()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
gap = malloc(sizeof(Gap));
|
||||
gap->config = config;
|
||||
// Create advertising timer
|
||||
gap->advertise_timer = furi_timer_alloc(gap_advetise_timer_callback, FuriTimerTypeOnce, NULL);
|
||||
// Initialization of GATT & GAP layer
|
||||
gap->service.adv_name = config->adv_name;
|
||||
gap_init_svc(gap);
|
||||
// Initialization of the BLE Services
|
||||
SVCCTL_Init();
|
||||
// Initialization of the GAP state
|
||||
gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
gap->state = GapStateIdle;
|
||||
gap->service.connection_handle = 0xFFFF;
|
||||
gap->enable_adv = true;
|
||||
|
||||
// Thread configuration
|
||||
gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap);
|
||||
furi_thread_start(gap->thread);
|
||||
|
||||
// Command queue allocation
|
||||
gap->command_queue = furi_message_queue_alloc(8, sizeof(GapCommand));
|
||||
|
||||
uint8_t adv_service_uid[2];
|
||||
gap->service.adv_svc_uuid_len = 1;
|
||||
adv_service_uid[0] = gap->config->adv_service_uuid & 0xff;
|
||||
adv_service_uid[1] = gap->config->adv_service_uuid >> 8;
|
||||
set_advertisment_service_uid(adv_service_uid, sizeof(adv_service_uid));
|
||||
|
||||
// Set callback
|
||||
gap->on_event_cb = on_event_cb;
|
||||
gap->context = context;
|
||||
return true;
|
||||
}
|
||||
|
||||
GapState gap_get_state() {
|
||||
GapState state;
|
||||
if(gap) {
|
||||
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
|
||||
state = gap->state;
|
||||
furi_mutex_release(gap->state_mutex);
|
||||
} else {
|
||||
state = GapStateUninitialized;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
void gap_thread_stop() {
|
||||
if(gap) {
|
||||
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
|
||||
gap->enable_adv = false;
|
||||
GapCommand command = GapCommandKillThread;
|
||||
furi_message_queue_put(gap->command_queue, &command, FuriWaitForever);
|
||||
furi_mutex_release(gap->state_mutex);
|
||||
furi_thread_join(gap->thread);
|
||||
furi_thread_free(gap->thread);
|
||||
// Free resources
|
||||
furi_mutex_free(gap->state_mutex);
|
||||
furi_message_queue_free(gap->command_queue);
|
||||
furi_timer_stop(gap->advertise_timer);
|
||||
while(xTimerIsTimerActive(gap->advertise_timer) == pdTRUE) furi_delay_tick(1);
|
||||
furi_timer_free(gap->advertise_timer);
|
||||
free(gap);
|
||||
gap = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t gap_app(void* context) {
|
||||
UNUSED(context);
|
||||
GapCommand command;
|
||||
while(1) {
|
||||
FuriStatus status = furi_message_queue_get(gap->command_queue, &command, FuriWaitForever);
|
||||
if(status != FuriStatusOk) {
|
||||
FURI_LOG_E(TAG, "Message queue get error: %d", status);
|
||||
continue;
|
||||
}
|
||||
furi_mutex_acquire(gap->state_mutex, FuriWaitForever);
|
||||
if(command == GapCommandKillThread) {
|
||||
break;
|
||||
}
|
||||
if(command == GapCommandAdvFast) {
|
||||
gap_advertise_start(GapStateAdvFast);
|
||||
} else if(command == GapCommandAdvLowPower) {
|
||||
gap_advertise_start(GapStateAdvLowPower);
|
||||
} else if(command == GapCommandAdvStop) {
|
||||
gap_advertise_stop();
|
||||
}
|
||||
furi_mutex_release(gap->state_mutex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
86
targets/f7/ble_glue/gap.h
Normal file
86
targets/f7/ble_glue/gap.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <furi_hal_version.h>
|
||||
|
||||
#define GAP_MAC_ADDR_SIZE (6)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
GapEventTypeConnected,
|
||||
GapEventTypeDisconnected,
|
||||
GapEventTypeStartAdvertising,
|
||||
GapEventTypeStopAdvertising,
|
||||
GapEventTypePinCodeShow,
|
||||
GapEventTypePinCodeVerify,
|
||||
GapEventTypeUpdateMTU,
|
||||
} GapEventType;
|
||||
|
||||
typedef union {
|
||||
uint32_t pin_code;
|
||||
uint16_t max_packet_size;
|
||||
} GapEventData;
|
||||
|
||||
typedef struct {
|
||||
GapEventType type;
|
||||
GapEventData data;
|
||||
} GapEvent;
|
||||
|
||||
typedef bool (*GapEventCallback)(GapEvent event, void* context);
|
||||
|
||||
typedef enum {
|
||||
GapStateUninitialized,
|
||||
GapStateIdle,
|
||||
GapStateStartingAdv,
|
||||
GapStateAdvFast,
|
||||
GapStateAdvLowPower,
|
||||
GapStateConnected,
|
||||
} GapState;
|
||||
|
||||
typedef enum {
|
||||
GapPairingNone,
|
||||
GapPairingPinCodeShow,
|
||||
GapPairingPinCodeVerifyYesNo,
|
||||
} GapPairing;
|
||||
|
||||
typedef struct {
|
||||
uint16_t conn_interval;
|
||||
uint16_t slave_latency;
|
||||
uint16_t supervisor_timeout;
|
||||
} GapConnectionParams;
|
||||
|
||||
typedef struct {
|
||||
uint16_t conn_int_min;
|
||||
uint16_t conn_int_max;
|
||||
uint16_t slave_latency;
|
||||
uint16_t supervisor_timeout;
|
||||
} GapConnectionParamsRequest;
|
||||
|
||||
typedef struct {
|
||||
uint16_t adv_service_uuid;
|
||||
uint16_t appearance_char;
|
||||
bool bonding_mode;
|
||||
GapPairing pairing_method;
|
||||
uint8_t mac_address[GAP_MAC_ADDR_SIZE];
|
||||
char adv_name[FURI_HAL_VERSION_DEVICE_NAME_LENGTH];
|
||||
GapConnectionParamsRequest conn_param;
|
||||
} GapConfig;
|
||||
|
||||
bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context);
|
||||
|
||||
void gap_start_advertising();
|
||||
|
||||
void gap_stop_advertising();
|
||||
|
||||
GapState gap_get_state();
|
||||
|
||||
void gap_thread_stop();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
81
targets/f7/ble_glue/hsem_map.h
Normal file
81
targets/f7/ble_glue/hsem_map.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
/******************************************************************************
|
||||
* Semaphores
|
||||
* THIS SHALL NO BE CHANGED AS THESE SEMAPHORES ARE USED AS WELL ON THE CM0+
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* Index of the semaphore used the prevent conflicts after standby sleep.
|
||||
* Each CPUs takes this semaphore at standby wakeup until conflicting elements are restored.
|
||||
*/
|
||||
#define CFG_HW_PWR_STANDBY_SEMID 10
|
||||
/**
|
||||
* The CPU2 may be configured to store the Thread persistent data either in internal NVM storage on CPU2 or in
|
||||
* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config()
|
||||
* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed.
|
||||
* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be:
|
||||
* + CPU1 takes CFG_HW_THREAD_NVM_SRAM_SEMID semaphore
|
||||
* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1)
|
||||
* + CPU1 releases CFG_HW_THREAD_NVM_SRAM_SEMID semaphore
|
||||
* CFG_HW_THREAD_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them.
|
||||
* There is no timing constraint on how long this semaphore can be kept.
|
||||
*/
|
||||
#define CFG_HW_THREAD_NVM_SRAM_SEMID 9
|
||||
|
||||
/**
|
||||
* The CPU2 may be configured to store the BLE persistent data either in internal NVM storage on CPU2 or in
|
||||
* SRAM2 buffer provided by the user application. This can be configured with the system command SHCI_C2_Config()
|
||||
* When the CPU2 is requested to store persistent data in SRAM2, it can write data in this buffer at any time when needed.
|
||||
* In order to read consistent data with the CPU1 from the SRAM2 buffer, the flow should be:
|
||||
* + CPU1 takes CFG_HW_BLE_NVM_SRAM_SEMID semaphore
|
||||
* + CPU1 reads all persistent data from SRAM2 (most of the time, the goal is to write these data into an NVM managed by CPU1)
|
||||
* + CPU1 releases CFG_HW_BLE_NVM_SRAM_SEMID semaphore
|
||||
* CFG_HW_BLE_NVM_SRAM_SEMID semaphore makes sure CPU2 does not update the persistent data in SRAM2 at the same time CPU1 is reading them.
|
||||
* There is no timing constraint on how long this semaphore can be kept.
|
||||
*/
|
||||
#define CFG_HW_BLE_NVM_SRAM_SEMID 8
|
||||
|
||||
/**
|
||||
* Index of the semaphore used by CPU2 to prevent the CPU1 to either write or erase data in flash
|
||||
* The CPU1 shall not either write or erase in flash when this semaphore is taken by the CPU2
|
||||
* When the CPU1 needs to either write or erase in flash, it shall first get the semaphore and release it just
|
||||
* after writing a raw (64bits data) or erasing one sector.
|
||||
* Once the Semaphore has been released, there shall be at least 1us before it can be taken again. This is required
|
||||
* to give the opportunity to CPU2 to take it.
|
||||
* On v1.4.0 and older CPU2 wireless firmware, this semaphore is unused and CPU2 is using PES bit.
|
||||
* By default, CPU2 is using the PES bit to protect its timing. The CPU1 may request the CPU2 to use the semaphore
|
||||
* instead of the PES bit by sending the system command SHCI_C2_SetFlashActivityControl()
|
||||
*/
|
||||
#define CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID 7
|
||||
|
||||
/**
|
||||
* Index of the semaphore used by CPU1 to prevent the CPU2 to either write or erase data in flash
|
||||
* In order to protect its timing, the CPU1 may get this semaphore to prevent the CPU2 to either
|
||||
* write or erase in flash (as this will stall both CPUs)
|
||||
* The PES bit shall not be used as this may stall the CPU2 in some cases.
|
||||
*/
|
||||
#define CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID 6
|
||||
|
||||
/**
|
||||
* Index of the semaphore used to manage the CLK48 clock configuration
|
||||
* When the USB is required, this semaphore shall be taken before configuring te CLK48 for USB
|
||||
* and should be released after the application switch OFF the clock when the USB is not used anymore
|
||||
* When using the RNG, it is good enough to use CFG_HW_RNG_SEMID to control CLK48.
|
||||
* More details in AN5289
|
||||
*/
|
||||
#define CFG_HW_CLK48_CONFIG_SEMID 5
|
||||
|
||||
/* Index of the semaphore used to manage the entry Stop Mode procedure */
|
||||
#define CFG_HW_ENTRY_STOP_MODE_SEMID 4
|
||||
|
||||
/* Index of the semaphore used to access the RCC */
|
||||
#define CFG_HW_RCC_SEMID 3
|
||||
|
||||
/* Index of the semaphore used to access the FLASH */
|
||||
#define CFG_HW_FLASH_SEMID 2
|
||||
|
||||
/* Index of the semaphore used to access the PKA */
|
||||
#define CFG_HW_PKA_SEMID 1
|
||||
|
||||
/* Index of the semaphore used to access the RNG */
|
||||
#define CFG_HW_RNG_SEMID 0
|
||||
164
targets/f7/ble_glue/hw_ipcc.c
Normal file
164
targets/f7/ble_glue/hw_ipcc.c
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "app_common.h"
|
||||
#include <interface/patterns/ble_thread/tl/mbox_def.h>
|
||||
#include <interface/patterns/ble_thread/hw.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <stm32wbxx_ll_ipcc.h>
|
||||
#include <stm32wbxx_ll_pwr.h>
|
||||
|
||||
#include <hsem_map.h>
|
||||
|
||||
#define HW_IPCC_TX_PENDING(channel) \
|
||||
((!(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, channel))) && \
|
||||
LL_C1_IPCC_IsEnabledTransmitChannel(IPCC, channel))
|
||||
#define HW_IPCC_RX_PENDING(channel) \
|
||||
(LL_C2_IPCC_IsActiveFlag_CHx(IPCC, channel) && \
|
||||
LL_C1_IPCC_IsEnabledReceiveChannel(IPCC, channel))
|
||||
|
||||
static void (*FreeBufCb)();
|
||||
|
||||
static void HW_IPCC_BLE_EvtHandler();
|
||||
static void HW_IPCC_BLE_AclDataEvtHandler();
|
||||
static void HW_IPCC_MM_FreeBufHandler();
|
||||
static void HW_IPCC_SYS_CmdEvtHandler();
|
||||
static void HW_IPCC_SYS_EvtHandler();
|
||||
static void HW_IPCC_TRACES_EvtHandler();
|
||||
|
||||
void HW_IPCC_Rx_Handler() {
|
||||
if(HW_IPCC_RX_PENDING(HW_IPCC_SYSTEM_EVENT_CHANNEL)) {
|
||||
HW_IPCC_SYS_EvtHandler();
|
||||
} else if(HW_IPCC_RX_PENDING(HW_IPCC_BLE_EVENT_CHANNEL)) {
|
||||
HW_IPCC_BLE_EvtHandler();
|
||||
} else if(HW_IPCC_RX_PENDING(HW_IPCC_TRACES_CHANNEL)) {
|
||||
HW_IPCC_TRACES_EvtHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void HW_IPCC_Tx_Handler() {
|
||||
if(HW_IPCC_TX_PENDING(HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) {
|
||||
HW_IPCC_SYS_CmdEvtHandler();
|
||||
} else if(HW_IPCC_TX_PENDING(HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) {
|
||||
HW_IPCC_SYS_CmdEvtHandler();
|
||||
} else if(HW_IPCC_TX_PENDING(HW_IPCC_MM_RELEASE_BUFFER_CHANNEL)) {
|
||||
HW_IPCC_MM_FreeBufHandler();
|
||||
} else if(HW_IPCC_TX_PENDING(HW_IPCC_HCI_ACL_DATA_CHANNEL)) {
|
||||
HW_IPCC_BLE_AclDataEvtHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void HW_IPCC_Enable() {
|
||||
/**
|
||||
* Such as IPCC IP available to the CPU2, it is required to keep the IPCC clock running
|
||||
when FUS is running on CPU2 and CPU1 enters deep sleep mode
|
||||
*/
|
||||
/**
|
||||
* When the device is out of standby, it is required to use the EXTI mechanism to wakeup CPU2
|
||||
*/
|
||||
LL_C2_EXTI_EnableEvent_32_63(LL_EXTI_LINE_41);
|
||||
LL_EXTI_EnableRisingTrig_32_63(LL_EXTI_LINE_41);
|
||||
|
||||
/**
|
||||
* In case the SBSFU is implemented, it may have already set the C2BOOT bit to startup the CPU2.
|
||||
* In that case, to keep the mechanism transparent to the user application, it shall call the system command
|
||||
* SHCI_C2_Reinit( ) before jumping to the application.
|
||||
* When the CPU2 receives that command, it waits for its event input to be set to restart the CPU2 firmware.
|
||||
* This is required because once C2BOOT has been set once, a clear/set on C2BOOT has no effect.
|
||||
* When SHCI_C2_Reinit( ) is not called, generating an event to the CPU2 does not have any effect
|
||||
* So, by default, the application shall both set the event flag and set the C2BOOT bit.
|
||||
*/
|
||||
__SEV(); /* Set the internal event flag and send an event to the CPU2 */
|
||||
__WFE(); /* Clear the internal event flag */
|
||||
LL_PWR_EnableBootC2();
|
||||
}
|
||||
|
||||
void HW_IPCC_Init() {
|
||||
LL_C1_IPCC_EnableIT_RXO(IPCC);
|
||||
LL_C1_IPCC_EnableIT_TXF(IPCC);
|
||||
|
||||
NVIC_SetPriority(IPCC_C1_RX_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 6, 0));
|
||||
NVIC_EnableIRQ(IPCC_C1_RX_IRQn);
|
||||
NVIC_SetPriority(IPCC_C1_TX_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 6, 0));
|
||||
NVIC_EnableIRQ(IPCC_C1_TX_IRQn);
|
||||
}
|
||||
|
||||
void HW_IPCC_BLE_Init() {
|
||||
LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_BLE_EVENT_CHANNEL);
|
||||
}
|
||||
|
||||
void HW_IPCC_BLE_SendCmd() {
|
||||
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_BLE_CMD_CHANNEL);
|
||||
}
|
||||
|
||||
static void HW_IPCC_BLE_EvtHandler() {
|
||||
HW_IPCC_BLE_RxEvtNot();
|
||||
|
||||
LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_BLE_EVENT_CHANNEL);
|
||||
}
|
||||
|
||||
void HW_IPCC_BLE_SendAclData() {
|
||||
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_HCI_ACL_DATA_CHANNEL);
|
||||
LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_HCI_ACL_DATA_CHANNEL);
|
||||
}
|
||||
|
||||
static void HW_IPCC_BLE_AclDataEvtHandler() {
|
||||
LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_HCI_ACL_DATA_CHANNEL);
|
||||
|
||||
HW_IPCC_BLE_AclDataAckNot();
|
||||
}
|
||||
|
||||
void HW_IPCC_SYS_Init() {
|
||||
LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_SYSTEM_EVENT_CHANNEL);
|
||||
}
|
||||
|
||||
void HW_IPCC_SYS_SendCmd() {
|
||||
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL);
|
||||
|
||||
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(33000000);
|
||||
|
||||
while(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL)) {
|
||||
furi_check(!furi_hal_cortex_timer_is_expired(timer), "HW_IPCC_SYS_SendCmd timeout");
|
||||
}
|
||||
|
||||
HW_IPCC_SYS_CmdEvtHandler();
|
||||
}
|
||||
|
||||
static void HW_IPCC_SYS_CmdEvtHandler() {
|
||||
LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_SYSTEM_CMD_RSP_CHANNEL);
|
||||
|
||||
HW_IPCC_SYS_CmdEvtNot();
|
||||
}
|
||||
|
||||
static void HW_IPCC_SYS_EvtHandler() {
|
||||
HW_IPCC_SYS_EvtNot();
|
||||
|
||||
LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_SYSTEM_EVENT_CHANNEL);
|
||||
}
|
||||
|
||||
void HW_IPCC_MM_SendFreeBuf(void (*cb)()) {
|
||||
if(LL_C1_IPCC_IsActiveFlag_CHx(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL)) {
|
||||
FreeBufCb = cb;
|
||||
LL_C1_IPCC_EnableTransmitChannel(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
||||
} else {
|
||||
cb();
|
||||
|
||||
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
||||
}
|
||||
}
|
||||
|
||||
static void HW_IPCC_MM_FreeBufHandler() {
|
||||
LL_C1_IPCC_DisableTransmitChannel(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
||||
|
||||
FreeBufCb();
|
||||
|
||||
LL_C1_IPCC_SetFlag_CHx(IPCC, HW_IPCC_MM_RELEASE_BUFFER_CHANNEL);
|
||||
}
|
||||
|
||||
void HW_IPCC_TRACES_Init() {
|
||||
LL_C1_IPCC_EnableReceiveChannel(IPCC, HW_IPCC_TRACES_CHANNEL);
|
||||
}
|
||||
|
||||
static void HW_IPCC_TRACES_EvtHandler() {
|
||||
HW_IPCC_TRACES_EvtNot();
|
||||
|
||||
LL_C1_IPCC_ClearFlag_CHx(IPCC, HW_IPCC_TRACES_CHANNEL);
|
||||
}
|
||||
40
targets/f7/ble_glue/osal.h
Normal file
40
targets/f7/ble_glue/osal.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* This function copies size number of bytes from a
|
||||
* memory location pointed by src to a destination
|
||||
* memory location pointed by dest
|
||||
*
|
||||
* @param[in] dest Destination address
|
||||
* @param[in] src Source address
|
||||
* @param[in] size size in the bytes
|
||||
*
|
||||
* @return Address of the destination
|
||||
*/
|
||||
|
||||
extern void* Osal_MemCpy(void* dest, const void* src, unsigned int size);
|
||||
|
||||
/**
|
||||
* This function sets first number of bytes, specified
|
||||
* by size, to the destination memory pointed by ptr
|
||||
* to the specified value
|
||||
*
|
||||
* @param[in] ptr Destination address
|
||||
* @param[in] value Value to be set
|
||||
* @param[in] size Size in the bytes
|
||||
*
|
||||
* @return Address of the destination
|
||||
*/
|
||||
|
||||
extern void* Osal_MemSet(void* ptr, int value, unsigned int size);
|
||||
|
||||
/**
|
||||
* This function compares n bytes of two regions of memory
|
||||
*
|
||||
* @param[in] s1 First buffer to compare.
|
||||
* @param[in] s2 Second buffer to compare.
|
||||
* @param[in] size Number of bytes to compare.
|
||||
*
|
||||
* @return 0 if the two buffers are equal, 1 otherwise
|
||||
*/
|
||||
extern int Osal_MemCmp(const void* s1, const void* s2, unsigned int size);
|
||||
150
targets/f7/ble_glue/services/battery_service.c
Normal file
150
targets/f7/ble_glue/services/battery_service.c
Normal file
@@ -0,0 +1,150 @@
|
||||
#include "battery_service.h"
|
||||
#include "app_common.h"
|
||||
#include "gatt_char.h"
|
||||
|
||||
#include <ble/ble.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_power.h>
|
||||
|
||||
#define TAG "BtBatterySvc"
|
||||
|
||||
enum {
|
||||
// Common states
|
||||
BatterySvcPowerStateUnknown = 0b00,
|
||||
BatterySvcPowerStateUnsupported = 0b01,
|
||||
// Level states
|
||||
BatterySvcPowerStateGoodLevel = 0b10,
|
||||
BatterySvcPowerStateCriticallyLowLevel = 0b11,
|
||||
// Charging states
|
||||
BatterySvcPowerStateNotCharging = 0b10,
|
||||
BatterySvcPowerStateCharging = 0b11,
|
||||
// Discharging states
|
||||
BatterySvcPowerStateNotDischarging = 0b10,
|
||||
BatterySvcPowerStateDischarging = 0b11,
|
||||
// Battery states
|
||||
BatterySvcPowerStateBatteryNotPresent = 0b10,
|
||||
BatterySvcPowerStateBatteryPresent = 0b11,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t present : 2;
|
||||
uint8_t discharging : 2;
|
||||
uint8_t charging : 2;
|
||||
uint8_t level : 2;
|
||||
} BattrySvcPowerState;
|
||||
|
||||
_Static_assert(sizeof(BattrySvcPowerState) == 1, "Incorrect structure size");
|
||||
|
||||
#define BATTERY_POWER_STATE (0x2A1A)
|
||||
|
||||
static const uint16_t service_uuid = BATTERY_SERVICE_UUID;
|
||||
|
||||
typedef enum {
|
||||
BatterySvcGattCharacteristicBatteryLevel = 0,
|
||||
BatterySvcGattCharacteristicPowerState,
|
||||
BatterySvcGattCharacteristicCount,
|
||||
} BatterySvcGattCharacteristicId;
|
||||
|
||||
static const FlipperGattCharacteristicParams battery_svc_chars[BatterySvcGattCharacteristicCount] =
|
||||
{[BatterySvcGattCharacteristicBatteryLevel] =
|
||||
{.name = "Battery Level",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = 1,
|
||||
.uuid.Char_UUID_16 = BATTERY_LEVEL_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[BatterySvcGattCharacteristicPowerState] = {
|
||||
.name = "Power State",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = 1,
|
||||
.uuid.Char_UUID_16 = BATTERY_POWER_STATE,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT}};
|
||||
|
||||
typedef struct {
|
||||
uint16_t svc_handle;
|
||||
FlipperGattCharacteristicInstance chars[BatterySvcGattCharacteristicCount];
|
||||
} BatterySvc;
|
||||
|
||||
static BatterySvc* battery_svc = NULL;
|
||||
|
||||
void battery_svc_start() {
|
||||
battery_svc = malloc(sizeof(BatterySvc));
|
||||
tBleStatus status;
|
||||
|
||||
// Add Battery service
|
||||
status = aci_gatt_add_service(
|
||||
UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 8, &battery_svc->svc_handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to add Battery service: %d", status);
|
||||
}
|
||||
for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) {
|
||||
flipper_gatt_characteristic_init(
|
||||
battery_svc->svc_handle, &battery_svc_chars[i], &battery_svc->chars[i]);
|
||||
}
|
||||
|
||||
battery_svc_update_power_state();
|
||||
}
|
||||
|
||||
void battery_svc_stop() {
|
||||
tBleStatus status;
|
||||
if(battery_svc) {
|
||||
for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) {
|
||||
flipper_gatt_characteristic_delete(battery_svc->svc_handle, &battery_svc->chars[i]);
|
||||
}
|
||||
// Delete Battery service
|
||||
status = aci_gatt_del_service(battery_svc->svc_handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to delete Battery service: %d", status);
|
||||
}
|
||||
free(battery_svc);
|
||||
battery_svc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool battery_svc_is_started() {
|
||||
return battery_svc != NULL;
|
||||
}
|
||||
|
||||
bool battery_svc_update_level(uint8_t battery_charge) {
|
||||
// Check if service was started
|
||||
if(battery_svc == NULL) {
|
||||
return false;
|
||||
}
|
||||
// Update battery level characteristic
|
||||
return flipper_gatt_characteristic_update(
|
||||
battery_svc->svc_handle,
|
||||
&battery_svc->chars[BatterySvcGattCharacteristicBatteryLevel],
|
||||
&battery_charge);
|
||||
}
|
||||
|
||||
bool battery_svc_update_power_state() {
|
||||
// Check if service was started
|
||||
if(battery_svc == NULL) {
|
||||
return false;
|
||||
}
|
||||
// Update power state characteristic
|
||||
BattrySvcPowerState power_state = {
|
||||
.level = BatterySvcPowerStateUnsupported,
|
||||
.present = BatterySvcPowerStateBatteryPresent,
|
||||
};
|
||||
if(furi_hal_power_is_charging()) {
|
||||
power_state.charging = BatterySvcPowerStateCharging;
|
||||
power_state.discharging = BatterySvcPowerStateNotDischarging;
|
||||
} else {
|
||||
power_state.charging = BatterySvcPowerStateNotCharging;
|
||||
power_state.discharging = BatterySvcPowerStateDischarging;
|
||||
}
|
||||
|
||||
return flipper_gatt_characteristic_update(
|
||||
battery_svc->svc_handle,
|
||||
&battery_svc->chars[BatterySvcGattCharacteristicPowerState],
|
||||
&power_state);
|
||||
}
|
||||
22
targets/f7/ble_glue/services/battery_service.h
Normal file
22
targets/f7/ble_glue/services/battery_service.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void battery_svc_start();
|
||||
|
||||
void battery_svc_stop();
|
||||
|
||||
bool battery_svc_is_started();
|
||||
|
||||
bool battery_svc_update_level(uint8_t battery_level);
|
||||
|
||||
bool battery_svc_update_power_state();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
178
targets/f7/ble_glue/services/dev_info_service.c
Normal file
178
targets/f7/ble_glue/services/dev_info_service.c
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "dev_info_service.h"
|
||||
#include "app_common.h"
|
||||
#include "gatt_char.h"
|
||||
#include <ble/ble.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <protobuf_version.h>
|
||||
#include <lib/toolbox/version.h>
|
||||
|
||||
#include "dev_info_service_uuid.inc"
|
||||
|
||||
#define TAG "BtDevInfoSvc"
|
||||
|
||||
typedef enum {
|
||||
DevInfoSvcGattCharacteristicMfgName = 0,
|
||||
DevInfoSvcGattCharacteristicSerial,
|
||||
DevInfoSvcGattCharacteristicFirmwareRev,
|
||||
DevInfoSvcGattCharacteristicSoftwareRev,
|
||||
DevInfoSvcGattCharacteristicRpcVersion,
|
||||
DevInfoSvcGattCharacteristicCount,
|
||||
} DevInfoSvcGattCharacteristicId;
|
||||
|
||||
#define DEVICE_INFO_HARDWARE_REV_SIZE 4
|
||||
typedef struct {
|
||||
uint16_t service_handle;
|
||||
FlipperGattCharacteristicInstance characteristics[DevInfoSvcGattCharacteristicCount];
|
||||
FuriString* version_string;
|
||||
char hardware_revision[DEVICE_INFO_HARDWARE_REV_SIZE];
|
||||
} DevInfoSvc;
|
||||
|
||||
static DevInfoSvc* dev_info_svc = NULL;
|
||||
|
||||
static const char dev_info_man_name[] = "Flipper Devices Inc.";
|
||||
static const char dev_info_serial_num[] = "1.0";
|
||||
static const char dev_info_rpc_version[] = TOSTRING(PROTOBUF_MAJOR_VERSION.PROTOBUF_MINOR_VERSION);
|
||||
|
||||
static bool dev_info_char_firmware_rev_callback(
|
||||
const void* context,
|
||||
const uint8_t** data,
|
||||
uint16_t* data_len) {
|
||||
const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context;
|
||||
*data_len = strlen(dev_info_svc->hardware_revision);
|
||||
if(data) {
|
||||
*data = (const uint8_t*)&dev_info_svc->hardware_revision;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool dev_info_char_software_rev_callback(
|
||||
const void* context,
|
||||
const uint8_t** data,
|
||||
uint16_t* data_len) {
|
||||
const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context;
|
||||
*data_len = furi_string_size(dev_info_svc->version_string);
|
||||
if(data) {
|
||||
*data = (const uint8_t*)furi_string_get_cstr(dev_info_svc->version_string);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCharacteristicCount] =
|
||||
{[DevInfoSvcGattCharacteristicMfgName] =
|
||||
{.name = "Manufacturer Name",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = sizeof(dev_info_man_name) - 1,
|
||||
.data.fixed.ptr = (const uint8_t*)&dev_info_man_name,
|
||||
.uuid.Char_UUID_16 = MANUFACTURER_NAME_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[DevInfoSvcGattCharacteristicSerial] =
|
||||
{.name = "Serial Number",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = sizeof(dev_info_serial_num) - 1,
|
||||
.data.fixed.ptr = (const uint8_t*)&dev_info_serial_num,
|
||||
.uuid.Char_UUID_16 = SERIAL_NUMBER_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[DevInfoSvcGattCharacteristicFirmwareRev] =
|
||||
{.name = "Firmware Revision",
|
||||
.data_prop_type = FlipperGattCharacteristicDataCallback,
|
||||
.data.callback.context = &dev_info_svc,
|
||||
.data.callback.fn = dev_info_char_firmware_rev_callback,
|
||||
.uuid.Char_UUID_16 = FIRMWARE_REVISION_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[DevInfoSvcGattCharacteristicSoftwareRev] =
|
||||
{.name = "Software Revision",
|
||||
.data_prop_type = FlipperGattCharacteristicDataCallback,
|
||||
.data.callback.context = &dev_info_svc,
|
||||
.data.callback.fn = dev_info_char_software_rev_callback,
|
||||
.uuid.Char_UUID_16 = SOFTWARE_REVISION_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[DevInfoSvcGattCharacteristicRpcVersion] = {
|
||||
.name = "RPC Version",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = sizeof(dev_info_rpc_version) - 1,
|
||||
.data.fixed.ptr = (const uint8_t*)&dev_info_rpc_version,
|
||||
.uuid.Char_UUID_128 = DEV_INVO_RPC_VERSION_UID,
|
||||
.uuid_type = UUID_TYPE_128,
|
||||
.char_properties = CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT}};
|
||||
|
||||
void dev_info_svc_start() {
|
||||
dev_info_svc = malloc(sizeof(DevInfoSvc));
|
||||
dev_info_svc->version_string = furi_string_alloc_printf(
|
||||
"%s %s %s %s",
|
||||
version_get_githash(NULL),
|
||||
version_get_gitbranch(NULL),
|
||||
version_get_gitbranchnum(NULL),
|
||||
version_get_builddate(NULL));
|
||||
snprintf(
|
||||
dev_info_svc->hardware_revision,
|
||||
sizeof(dev_info_svc->hardware_revision),
|
||||
"%d",
|
||||
version_get_target(NULL));
|
||||
tBleStatus status;
|
||||
|
||||
// Add Device Information Service
|
||||
uint16_t uuid = DEVICE_INFORMATION_SERVICE_UUID;
|
||||
status = aci_gatt_add_service(
|
||||
UUID_TYPE_16,
|
||||
(Service_UUID_t*)&uuid,
|
||||
PRIMARY_SERVICE,
|
||||
1 + 2 * DevInfoSvcGattCharacteristicCount,
|
||||
&dev_info_svc->service_handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to add Device Information Service: %d", status);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) {
|
||||
flipper_gatt_characteristic_init(
|
||||
dev_info_svc->service_handle,
|
||||
&dev_info_svc_chars[i],
|
||||
&dev_info_svc->characteristics[i]);
|
||||
flipper_gatt_characteristic_update(
|
||||
dev_info_svc->service_handle, &dev_info_svc->characteristics[i], NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void dev_info_svc_stop() {
|
||||
tBleStatus status;
|
||||
if(dev_info_svc) {
|
||||
// Delete service characteristics
|
||||
for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) {
|
||||
flipper_gatt_characteristic_delete(
|
||||
dev_info_svc->service_handle, &dev_info_svc->characteristics[i]);
|
||||
}
|
||||
|
||||
// Delete service
|
||||
status = aci_gatt_del_service(dev_info_svc->service_handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to delete device info service: %d", status);
|
||||
}
|
||||
|
||||
furi_string_free(dev_info_svc->version_string);
|
||||
free(dev_info_svc);
|
||||
dev_info_svc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool dev_info_svc_is_started() {
|
||||
return dev_info_svc != NULL;
|
||||
}
|
||||
18
targets/f7/ble_glue/services/dev_info_service.h
Normal file
18
targets/f7/ble_glue/services/dev_info_service.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void dev_info_svc_start();
|
||||
|
||||
void dev_info_svc_stop();
|
||||
|
||||
bool dev_info_svc_is_started();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
3
targets/f7/ble_glue/services/dev_info_service_uuid.inc
Normal file
3
targets/f7/ble_glue/services/dev_info_service_uuid.inc
Normal file
@@ -0,0 +1,3 @@
|
||||
#define DEV_INVO_RPC_VERSION_UID \
|
||||
{ 0x33, 0xa9, 0xb5, 0x3e, 0x87, 0x5d, 0x1a, 0x8e, 0xc8, 0x47, 0x5e, 0xae, 0x6d, 0x66, 0xf6, 0x03 }
|
||||
|
||||
122
targets/f7/ble_glue/services/gatt_char.c
Normal file
122
targets/f7/ble_glue/services/gatt_char.c
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "gatt_char.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "GattChar"
|
||||
|
||||
#define GATT_MIN_READ_KEY_SIZE (10)
|
||||
|
||||
void flipper_gatt_characteristic_init(
|
||||
uint16_t svc_handle,
|
||||
const FlipperGattCharacteristicParams* char_descriptor,
|
||||
FlipperGattCharacteristicInstance* char_instance) {
|
||||
furi_assert(char_descriptor);
|
||||
furi_assert(char_instance);
|
||||
|
||||
// Copy the descriptor to the instance, since it may point to stack memory
|
||||
char_instance->characteristic = malloc(sizeof(FlipperGattCharacteristicParams));
|
||||
memcpy(
|
||||
(void*)char_instance->characteristic,
|
||||
char_descriptor,
|
||||
sizeof(FlipperGattCharacteristicParams));
|
||||
|
||||
uint16_t char_data_size = 0;
|
||||
if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataFixed) {
|
||||
char_data_size = char_descriptor->data.fixed.length;
|
||||
} else if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataCallback) {
|
||||
char_descriptor->data.callback.fn(
|
||||
char_descriptor->data.callback.context, NULL, &char_data_size);
|
||||
}
|
||||
|
||||
tBleStatus status = aci_gatt_add_char(
|
||||
svc_handle,
|
||||
char_descriptor->uuid_type,
|
||||
&char_descriptor->uuid,
|
||||
char_data_size,
|
||||
char_descriptor->char_properties,
|
||||
char_descriptor->security_permissions,
|
||||
char_descriptor->gatt_evt_mask,
|
||||
GATT_MIN_READ_KEY_SIZE,
|
||||
char_descriptor->is_variable,
|
||||
&char_instance->handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to add %s char: %d", char_descriptor->name, status);
|
||||
}
|
||||
|
||||
char_instance->descriptor_handle = 0;
|
||||
if((status == 0) && char_descriptor->descriptor_params) {
|
||||
uint8_t const* char_data = NULL;
|
||||
const FlipperGattCharacteristicDescriptorParams* char_data_descriptor =
|
||||
char_descriptor->descriptor_params;
|
||||
bool release_data = char_data_descriptor->data_callback.fn(
|
||||
char_data_descriptor->data_callback.context, &char_data, &char_data_size);
|
||||
|
||||
status = aci_gatt_add_char_desc(
|
||||
svc_handle,
|
||||
char_instance->handle,
|
||||
char_data_descriptor->uuid_type,
|
||||
&char_data_descriptor->uuid,
|
||||
char_data_descriptor->max_length,
|
||||
char_data_size,
|
||||
char_data,
|
||||
char_data_descriptor->security_permissions,
|
||||
char_data_descriptor->access_permissions,
|
||||
char_data_descriptor->gatt_evt_mask,
|
||||
GATT_MIN_READ_KEY_SIZE,
|
||||
char_data_descriptor->is_variable,
|
||||
&char_instance->descriptor_handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to add %s char descriptor: %d", char_descriptor->name, status);
|
||||
}
|
||||
if(release_data) {
|
||||
free((void*)char_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flipper_gatt_characteristic_delete(
|
||||
uint16_t svc_handle,
|
||||
FlipperGattCharacteristicInstance* char_instance) {
|
||||
tBleStatus status = aci_gatt_del_char(svc_handle, char_instance->handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Failed to delete %s char: %d", char_instance->characteristic->name, status);
|
||||
}
|
||||
free((void*)char_instance->characteristic);
|
||||
}
|
||||
|
||||
bool flipper_gatt_characteristic_update(
|
||||
uint16_t svc_handle,
|
||||
FlipperGattCharacteristicInstance* char_instance,
|
||||
const void* source) {
|
||||
furi_assert(char_instance);
|
||||
const FlipperGattCharacteristicParams* char_descriptor = char_instance->characteristic;
|
||||
FURI_LOG_D(TAG, "Updating %s char", char_descriptor->name);
|
||||
|
||||
const uint8_t* char_data = NULL;
|
||||
uint16_t char_data_size = 0;
|
||||
bool release_data = false;
|
||||
if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataFixed) {
|
||||
char_data = char_descriptor->data.fixed.ptr;
|
||||
if(source) {
|
||||
char_data = (uint8_t*)source;
|
||||
}
|
||||
char_data_size = char_descriptor->data.fixed.length;
|
||||
} else if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataCallback) {
|
||||
const void* context = char_descriptor->data.callback.context;
|
||||
if(source) {
|
||||
context = source;
|
||||
}
|
||||
release_data = char_descriptor->data.callback.fn(context, &char_data, &char_data_size);
|
||||
}
|
||||
|
||||
tBleStatus result = aci_gatt_update_char_value(
|
||||
svc_handle, char_instance->handle, 0, char_data_size, char_data);
|
||||
if(result) {
|
||||
FURI_LOG_E(TAG, "Failed updating %s characteristic: %d", char_descriptor->name, result);
|
||||
}
|
||||
if(release_data) {
|
||||
free((void*)char_data);
|
||||
}
|
||||
return result != BLE_STATUS_SUCCESS;
|
||||
}
|
||||
96
targets/f7/ble_glue/services/gatt_char.h
Normal file
96
targets/f7/ble_glue/services/gatt_char.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <ble/ble.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Callback signature for getting characteristic data
|
||||
// Is called when characteristic is created to get max data length. Data ptr is NULL in this case
|
||||
// The result is passed to aci_gatt_add_char as "Char_Value_Length"
|
||||
// For updates, called with a context - see flipper_gatt_characteristic_update
|
||||
// Returns true if *data ownership is transferred to the caller and will be freed
|
||||
typedef bool (*cbFlipperGattCharacteristicData)(
|
||||
const void* context,
|
||||
const uint8_t** data,
|
||||
uint16_t* data_len);
|
||||
|
||||
typedef enum {
|
||||
FlipperGattCharacteristicDataFixed,
|
||||
FlipperGattCharacteristicDataCallback,
|
||||
} FlipperGattCharacteristicDataType;
|
||||
|
||||
typedef struct {
|
||||
Char_Desc_Uuid_t uuid;
|
||||
struct {
|
||||
cbFlipperGattCharacteristicData fn;
|
||||
const void* context;
|
||||
} data_callback;
|
||||
uint8_t uuid_type;
|
||||
uint8_t max_length;
|
||||
uint8_t security_permissions;
|
||||
uint8_t access_permissions;
|
||||
uint8_t gatt_evt_mask;
|
||||
uint8_t is_variable;
|
||||
} FlipperGattCharacteristicDescriptorParams;
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
FlipperGattCharacteristicDescriptorParams* descriptor_params;
|
||||
union {
|
||||
struct {
|
||||
const uint8_t* ptr;
|
||||
uint16_t length;
|
||||
} fixed;
|
||||
struct {
|
||||
cbFlipperGattCharacteristicData fn;
|
||||
const void* context;
|
||||
} callback;
|
||||
} data;
|
||||
Char_UUID_t uuid;
|
||||
// Some packed bitfields to save space
|
||||
FlipperGattCharacteristicDataType data_prop_type : 2;
|
||||
uint8_t is_variable : 2;
|
||||
uint8_t uuid_type : 2;
|
||||
uint8_t char_properties;
|
||||
uint8_t security_permissions;
|
||||
uint8_t gatt_evt_mask;
|
||||
} FlipperGattCharacteristicParams;
|
||||
|
||||
_Static_assert(
|
||||
sizeof(FlipperGattCharacteristicParams) == 36,
|
||||
"FlipperGattCharacteristicParams size must be 36 bytes");
|
||||
|
||||
typedef struct {
|
||||
const FlipperGattCharacteristicParams* characteristic;
|
||||
uint16_t handle;
|
||||
uint16_t descriptor_handle;
|
||||
} FlipperGattCharacteristicInstance;
|
||||
|
||||
// Initialize a characteristic instance; copies the characteristic descriptor into the instance
|
||||
void flipper_gatt_characteristic_init(
|
||||
uint16_t svc_handle,
|
||||
const FlipperGattCharacteristicParams* char_descriptor,
|
||||
FlipperGattCharacteristicInstance* char_instance);
|
||||
|
||||
// Delete a characteristic instance; frees the copied characteristic descriptor from the instance
|
||||
void flipper_gatt_characteristic_delete(
|
||||
uint16_t svc_handle,
|
||||
FlipperGattCharacteristicInstance* char_instance);
|
||||
|
||||
// Update a characteristic instance; if source==NULL, uses the data from the characteristic
|
||||
// - For fixed data, fixed.ptr is used as the source if source==NULL
|
||||
// - For callback-based data, collback.context is passed as the context if source==NULL
|
||||
bool flipper_gatt_characteristic_update(
|
||||
uint16_t svc_handle,
|
||||
FlipperGattCharacteristicInstance* char_instance,
|
||||
const void* source);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
302
targets/f7/ble_glue/services/hid_service.c
Normal file
302
targets/f7/ble_glue/services/hid_service.c
Normal file
@@ -0,0 +1,302 @@
|
||||
#include "hid_service.h"
|
||||
#include "app_common.h"
|
||||
#include <ble/ble.h>
|
||||
#include "gatt_char.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "BtHid"
|
||||
|
||||
typedef enum {
|
||||
HidSvcGattCharacteristicProtocolMode = 0,
|
||||
HidSvcGattCharacteristicReportMap,
|
||||
HidSvcGattCharacteristicInfo,
|
||||
HidSvcGattCharacteristicCtrlPoint,
|
||||
HidSvcGattCharacteristicCount,
|
||||
} HidSvcGattCharacteristicId;
|
||||
|
||||
typedef struct {
|
||||
uint8_t report_idx;
|
||||
uint8_t report_type;
|
||||
} HidSvcReportId;
|
||||
|
||||
static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes");
|
||||
|
||||
static const Service_UUID_t hid_svc_uuid = {
|
||||
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
|
||||
};
|
||||
|
||||
static bool
|
||||
hid_svc_char_desc_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) {
|
||||
const HidSvcReportId* report_id = context;
|
||||
*data_len = sizeof(HidSvcReportId);
|
||||
if(data) {
|
||||
*data = (const uint8_t*)report_id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const void* data_ptr;
|
||||
uint16_t data_len;
|
||||
} HidSvcDataWrapper;
|
||||
|
||||
static bool
|
||||
hid_svc_report_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) {
|
||||
const HidSvcDataWrapper* report_data = context;
|
||||
if(data) {
|
||||
*data = report_data->data_ptr;
|
||||
*data_len = report_data->data_len;
|
||||
} else {
|
||||
*data_len = HID_SVC_REPORT_MAP_MAX_LEN;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteristicCount] = {
|
||||
[HidSvcGattCharacteristicProtocolMode] =
|
||||
{.name = "Protocol Mode",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = 1,
|
||||
.uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[HidSvcGattCharacteristicReportMap] =
|
||||
{.name = "Report Map",
|
||||
.data_prop_type = FlipperGattCharacteristicDataCallback,
|
||||
.data.callback.fn = hid_svc_report_data_callback,
|
||||
.data.callback.context = NULL,
|
||||
.uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_VARIABLE},
|
||||
[HidSvcGattCharacteristicInfo] =
|
||||
{.name = "HID Information",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = HID_SVC_INFO_LEN,
|
||||
.data.fixed.ptr = NULL,
|
||||
.uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[HidSvcGattCharacteristicCtrlPoint] =
|
||||
{.name = "HID Control Point",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = HID_SVC_CONTROL_POINT_LEN,
|
||||
.uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_WRITE_WITHOUT_RESP,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
};
|
||||
|
||||
static const FlipperGattCharacteristicDescriptorParams hid_svc_char_descr_template = {
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID,
|
||||
.max_length = HID_SVC_REPORT_REF_LEN,
|
||||
.data_callback.fn = hid_svc_char_desc_data_callback,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.access_permissions = ATTR_ACCESS_READ_WRITE,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT,
|
||||
};
|
||||
|
||||
static const FlipperGattCharacteristicParams hid_svc_report_template = {
|
||||
.name = "Report",
|
||||
.data_prop_type = FlipperGattCharacteristicDataCallback,
|
||||
.data.callback.fn = hid_svc_report_data_callback,
|
||||
.data.callback.context = NULL,
|
||||
.uuid.Char_UUID_16 = REPORT_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_VARIABLE,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint16_t svc_handle;
|
||||
FlipperGattCharacteristicInstance chars[HidSvcGattCharacteristicCount];
|
||||
FlipperGattCharacteristicInstance input_report_chars[HID_SVC_INPUT_REPORT_COUNT];
|
||||
FlipperGattCharacteristicInstance output_report_chars[HID_SVC_OUTPUT_REPORT_COUNT];
|
||||
FlipperGattCharacteristicInstance feature_report_chars[HID_SVC_FEATURE_REPORT_COUNT];
|
||||
} HIDSvc;
|
||||
|
||||
static HIDSvc* hid_svc = NULL;
|
||||
|
||||
static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) {
|
||||
SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck;
|
||||
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
|
||||
evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data;
|
||||
// aci_gatt_attribute_modified_event_rp0* attribute_modified;
|
||||
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
|
||||
if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) {
|
||||
// Process modification events
|
||||
ret = SVCCTL_EvtAckFlowEnable;
|
||||
} else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) {
|
||||
// Process notification confirmation
|
||||
ret = SVCCTL_EvtAckFlowEnable;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void hid_svc_start() {
|
||||
tBleStatus status;
|
||||
hid_svc = malloc(sizeof(HIDSvc));
|
||||
|
||||
// Register event handler
|
||||
SVCCTL_RegisterSvcHandler(hid_svc_event_handler);
|
||||
/**
|
||||
* Add Human Interface Device Service
|
||||
*/
|
||||
status = aci_gatt_add_service(
|
||||
UUID_TYPE_16,
|
||||
&hid_svc_uuid,
|
||||
PRIMARY_SERVICE,
|
||||
2 + /* protocol mode */
|
||||
(4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) +
|
||||
(3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 +
|
||||
2, /* Service + Report Map + HID Information + HID Control Point */
|
||||
&hid_svc->svc_handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to add HID service: %d", status);
|
||||
}
|
||||
|
||||
// Maintain previously defined characteristic order
|
||||
flipper_gatt_characteristic_init(
|
||||
hid_svc->svc_handle,
|
||||
&hid_svc_chars[HidSvcGattCharacteristicProtocolMode],
|
||||
&hid_svc->chars[HidSvcGattCharacteristicProtocolMode]);
|
||||
|
||||
uint8_t protocol_mode = 1;
|
||||
flipper_gatt_characteristic_update(
|
||||
hid_svc->svc_handle,
|
||||
&hid_svc->chars[HidSvcGattCharacteristicProtocolMode],
|
||||
&protocol_mode);
|
||||
|
||||
// reports
|
||||
FlipperGattCharacteristicDescriptorParams hid_svc_char_descr;
|
||||
FlipperGattCharacteristicParams report_char;
|
||||
HidSvcReportId report_id;
|
||||
|
||||
memcpy(&hid_svc_char_descr, &hid_svc_char_descr_template, sizeof(hid_svc_char_descr));
|
||||
memcpy(&report_char, &hid_svc_report_template, sizeof(report_char));
|
||||
|
||||
hid_svc_char_descr.data_callback.context = &report_id;
|
||||
report_char.descriptor_params = &hid_svc_char_descr;
|
||||
|
||||
typedef struct {
|
||||
uint8_t report_type;
|
||||
uint8_t report_count;
|
||||
FlipperGattCharacteristicInstance* chars;
|
||||
} HidSvcReportCharProps;
|
||||
|
||||
HidSvcReportCharProps hid_report_chars[] = {
|
||||
{0x01, HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars},
|
||||
{0x02, HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars},
|
||||
{0x03, HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars},
|
||||
};
|
||||
|
||||
for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars);
|
||||
report_type_idx++) {
|
||||
report_id.report_type = hid_report_chars[report_type_idx].report_type;
|
||||
for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count;
|
||||
report_idx++) {
|
||||
report_id.report_idx = report_idx + 1;
|
||||
flipper_gatt_characteristic_init(
|
||||
hid_svc->svc_handle,
|
||||
&report_char,
|
||||
&hid_report_chars[report_type_idx].chars[report_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup remaining characteristics
|
||||
for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) {
|
||||
flipper_gatt_characteristic_init(
|
||||
hid_svc->svc_handle, &hid_svc_chars[i], &hid_svc->chars[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) {
|
||||
furi_assert(data);
|
||||
furi_assert(hid_svc);
|
||||
|
||||
HidSvcDataWrapper report_data = {
|
||||
.data_ptr = data,
|
||||
.data_len = len,
|
||||
};
|
||||
return flipper_gatt_characteristic_update(
|
||||
hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data);
|
||||
}
|
||||
|
||||
bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len) {
|
||||
furi_assert(data);
|
||||
furi_assert(hid_svc);
|
||||
furi_assert(input_report_num < HID_SVC_INPUT_REPORT_COUNT);
|
||||
|
||||
HidSvcDataWrapper report_data = {
|
||||
.data_ptr = data,
|
||||
.data_len = len,
|
||||
};
|
||||
return flipper_gatt_characteristic_update(
|
||||
hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data);
|
||||
}
|
||||
|
||||
bool hid_svc_update_info(uint8_t* data) {
|
||||
furi_assert(data);
|
||||
furi_assert(hid_svc);
|
||||
|
||||
return flipper_gatt_characteristic_update(
|
||||
hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data);
|
||||
}
|
||||
|
||||
bool hid_svc_is_started() {
|
||||
return hid_svc != NULL;
|
||||
}
|
||||
|
||||
void hid_svc_stop() {
|
||||
tBleStatus status;
|
||||
if(hid_svc) {
|
||||
// Delete characteristics
|
||||
for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) {
|
||||
flipper_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t report_count;
|
||||
FlipperGattCharacteristicInstance* chars;
|
||||
} HidSvcReportCharProps;
|
||||
|
||||
HidSvcReportCharProps hid_report_chars[] = {
|
||||
{HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars},
|
||||
{HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars},
|
||||
{HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars},
|
||||
};
|
||||
|
||||
for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars);
|
||||
report_type_idx++) {
|
||||
for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count;
|
||||
report_idx++) {
|
||||
flipper_gatt_characteristic_delete(
|
||||
hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete service
|
||||
status = aci_gatt_del_service(hid_svc->svc_handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to delete HID service: %d", status);
|
||||
}
|
||||
free(hid_svc);
|
||||
hid_svc = NULL;
|
||||
}
|
||||
}
|
||||
29
targets/f7/ble_glue/services/hid_service.h
Normal file
29
targets/f7/ble_glue/services/hid_service.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define HID_SVC_REPORT_MAP_MAX_LEN (255)
|
||||
#define HID_SVC_REPORT_MAX_LEN (255)
|
||||
#define HID_SVC_REPORT_REF_LEN (2)
|
||||
#define HID_SVC_INFO_LEN (4)
|
||||
#define HID_SVC_CONTROL_POINT_LEN (1)
|
||||
|
||||
#define HID_SVC_INPUT_REPORT_COUNT (3)
|
||||
#define HID_SVC_OUTPUT_REPORT_COUNT (0)
|
||||
#define HID_SVC_FEATURE_REPORT_COUNT (0)
|
||||
#define HID_SVC_REPORT_COUNT \
|
||||
(HID_SVC_INPUT_REPORT_COUNT + HID_SVC_OUTPUT_REPORT_COUNT + HID_SVC_FEATURE_REPORT_COUNT)
|
||||
|
||||
void hid_svc_start();
|
||||
|
||||
void hid_svc_stop();
|
||||
|
||||
bool hid_svc_is_started();
|
||||
|
||||
bool hid_svc_update_report_map(const uint8_t* data, uint16_t len);
|
||||
|
||||
bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len);
|
||||
|
||||
// Expects data to be of length HID_SVC_INFO_LEN (4 bytes)
|
||||
bool hid_svc_update_info(uint8_t* data);
|
||||
262
targets/f7/ble_glue/services/serial_service.c
Normal file
262
targets/f7/ble_glue/services/serial_service.c
Normal file
@@ -0,0 +1,262 @@
|
||||
#include "serial_service.h"
|
||||
#include "app_common.h"
|
||||
#include <ble/ble.h>
|
||||
#include "gatt_char.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "serial_service_uuid.inc"
|
||||
|
||||
#define TAG "BtSerialSvc"
|
||||
|
||||
typedef enum {
|
||||
SerialSvcGattCharacteristicRx = 0,
|
||||
SerialSvcGattCharacteristicTx,
|
||||
SerialSvcGattCharacteristicFlowCtrl,
|
||||
SerialSvcGattCharacteristicStatus,
|
||||
SerialSvcGattCharacteristicCount,
|
||||
} SerialSvcGattCharacteristicId;
|
||||
|
||||
static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattCharacteristicCount] = {
|
||||
[SerialSvcGattCharacteristicRx] =
|
||||
{.name = "RX",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = SERIAL_SVC_DATA_LEN_MAX,
|
||||
.uuid.Char_UUID_128 = SERIAL_SVC_RX_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_128,
|
||||
.char_properties = CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE | CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE,
|
||||
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
|
||||
.is_variable = CHAR_VALUE_LEN_VARIABLE},
|
||||
[SerialSvcGattCharacteristicTx] =
|
||||
{.name = "TX",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = SERIAL_SVC_DATA_LEN_MAX,
|
||||
.uuid.Char_UUID_128 = SERIAL_SVC_TX_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_128,
|
||||
.char_properties = CHAR_PROP_READ | CHAR_PROP_INDICATE,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_VARIABLE},
|
||||
[SerialSvcGattCharacteristicFlowCtrl] =
|
||||
{.name = "Flow control",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = sizeof(uint32_t),
|
||||
.uuid.Char_UUID_128 = SERIAL_SVC_FLOW_CONTROL_UUID,
|
||||
.uuid_type = UUID_TYPE_128,
|
||||
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[SerialSvcGattCharacteristicStatus] = {
|
||||
.name = "RPC status",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = sizeof(SerialServiceRpcStatus),
|
||||
.uuid.Char_UUID_128 = SERIAL_SVC_RPC_STATUS_UUID,
|
||||
.uuid_type = UUID_TYPE_128,
|
||||
.char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE | CHAR_PROP_NOTIFY,
|
||||
.security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE,
|
||||
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT}};
|
||||
|
||||
typedef struct {
|
||||
uint16_t svc_handle;
|
||||
FlipperGattCharacteristicInstance chars[SerialSvcGattCharacteristicCount];
|
||||
FuriMutex* buff_size_mtx;
|
||||
uint32_t buff_size;
|
||||
uint16_t bytes_ready_to_receive;
|
||||
SerialServiceEventCallback callback;
|
||||
void* context;
|
||||
} SerialSvc;
|
||||
|
||||
static SerialSvc* serial_svc = NULL;
|
||||
|
||||
static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) {
|
||||
SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck;
|
||||
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
|
||||
evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data;
|
||||
aci_gatt_attribute_modified_event_rp0* attribute_modified;
|
||||
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
|
||||
if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) {
|
||||
attribute_modified = (aci_gatt_attribute_modified_event_rp0*)blecore_evt->data;
|
||||
if(attribute_modified->Attr_Handle ==
|
||||
serial_svc->chars[SerialSvcGattCharacteristicRx].handle + 2) {
|
||||
// Descriptor handle
|
||||
ret = SVCCTL_EvtAckFlowEnable;
|
||||
FURI_LOG_D(TAG, "RX descriptor event");
|
||||
} else if(
|
||||
attribute_modified->Attr_Handle ==
|
||||
serial_svc->chars[SerialSvcGattCharacteristicRx].handle + 1) {
|
||||
FURI_LOG_D(TAG, "Received %d bytes", attribute_modified->Attr_Data_Length);
|
||||
if(serial_svc->callback) {
|
||||
furi_check(
|
||||
furi_mutex_acquire(serial_svc->buff_size_mtx, FuriWaitForever) ==
|
||||
FuriStatusOk);
|
||||
if(attribute_modified->Attr_Data_Length > serial_svc->bytes_ready_to_receive) {
|
||||
FURI_LOG_W(
|
||||
TAG,
|
||||
"Received %d, while was ready to receive %d bytes. Can lead to buffer overflow!",
|
||||
attribute_modified->Attr_Data_Length,
|
||||
serial_svc->bytes_ready_to_receive);
|
||||
}
|
||||
serial_svc->bytes_ready_to_receive -= MIN(
|
||||
serial_svc->bytes_ready_to_receive, attribute_modified->Attr_Data_Length);
|
||||
SerialServiceEvent event = {
|
||||
.event = SerialServiceEventTypeDataReceived,
|
||||
.data = {
|
||||
.buffer = attribute_modified->Attr_Data,
|
||||
.size = attribute_modified->Attr_Data_Length,
|
||||
}};
|
||||
uint32_t buff_free_size = serial_svc->callback(event, serial_svc->context);
|
||||
FURI_LOG_D(TAG, "Available buff size: %ld", buff_free_size);
|
||||
furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk);
|
||||
}
|
||||
ret = SVCCTL_EvtAckFlowEnable;
|
||||
} else if(
|
||||
attribute_modified->Attr_Handle ==
|
||||
serial_svc->chars[SerialSvcGattCharacteristicStatus].handle + 1) {
|
||||
SerialServiceRpcStatus* rpc_status =
|
||||
(SerialServiceRpcStatus*)attribute_modified->Attr_Data;
|
||||
if(*rpc_status == SerialServiceRpcStatusNotActive) {
|
||||
if(serial_svc->callback) {
|
||||
SerialServiceEvent event = {
|
||||
.event = SerialServiceEventTypesBleResetRequest,
|
||||
};
|
||||
serial_svc->callback(event, serial_svc->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) {
|
||||
FURI_LOG_T(TAG, "Ack received");
|
||||
if(serial_svc->callback) {
|
||||
SerialServiceEvent event = {
|
||||
.event = SerialServiceEventTypeDataSent,
|
||||
};
|
||||
serial_svc->callback(event, serial_svc->context);
|
||||
}
|
||||
ret = SVCCTL_EvtAckFlowEnable;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void serial_svc_update_rpc_char(SerialServiceRpcStatus status) {
|
||||
flipper_gatt_characteristic_update(
|
||||
serial_svc->svc_handle, &serial_svc->chars[SerialSvcGattCharacteristicStatus], &status);
|
||||
}
|
||||
|
||||
void serial_svc_start() {
|
||||
UNUSED(serial_svc_chars);
|
||||
tBleStatus status;
|
||||
serial_svc = malloc(sizeof(SerialSvc));
|
||||
// Register event handler
|
||||
SVCCTL_RegisterSvcHandler(serial_svc_event_handler);
|
||||
|
||||
// Add service
|
||||
status = aci_gatt_add_service(
|
||||
UUID_TYPE_128, &service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to add Serial service: %d", status);
|
||||
}
|
||||
|
||||
// Add characteristics
|
||||
for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) {
|
||||
flipper_gatt_characteristic_init(
|
||||
serial_svc->svc_handle, &serial_svc_chars[i], &serial_svc->chars[i]);
|
||||
}
|
||||
|
||||
serial_svc_update_rpc_char(SerialServiceRpcStatusNotActive);
|
||||
// Allocate buffer size mutex
|
||||
serial_svc->buff_size_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
}
|
||||
|
||||
void serial_svc_set_callbacks(
|
||||
uint16_t buff_size,
|
||||
SerialServiceEventCallback callback,
|
||||
void* context) {
|
||||
furi_assert(serial_svc);
|
||||
serial_svc->callback = callback;
|
||||
serial_svc->context = context;
|
||||
serial_svc->buff_size = buff_size;
|
||||
serial_svc->bytes_ready_to_receive = buff_size;
|
||||
|
||||
uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size);
|
||||
flipper_gatt_characteristic_update(
|
||||
serial_svc->svc_handle,
|
||||
&serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl],
|
||||
&buff_size_reversed);
|
||||
}
|
||||
|
||||
void serial_svc_notify_buffer_is_empty() {
|
||||
furi_assert(serial_svc);
|
||||
furi_assert(serial_svc->buff_size_mtx);
|
||||
|
||||
furi_check(furi_mutex_acquire(serial_svc->buff_size_mtx, FuriWaitForever) == FuriStatusOk);
|
||||
if(serial_svc->bytes_ready_to_receive == 0) {
|
||||
FURI_LOG_D(TAG, "Buffer is empty. Notifying client");
|
||||
serial_svc->bytes_ready_to_receive = serial_svc->buff_size;
|
||||
|
||||
uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size);
|
||||
flipper_gatt_characteristic_update(
|
||||
serial_svc->svc_handle,
|
||||
&serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl],
|
||||
&buff_size_reversed);
|
||||
}
|
||||
furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void serial_svc_stop() {
|
||||
tBleStatus status;
|
||||
if(serial_svc) {
|
||||
for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) {
|
||||
flipper_gatt_characteristic_delete(serial_svc->svc_handle, &serial_svc->chars[i]);
|
||||
}
|
||||
// Delete service
|
||||
status = aci_gatt_del_service(serial_svc->svc_handle);
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Failed to delete Serial service: %d", status);
|
||||
}
|
||||
// Delete buffer size mutex
|
||||
furi_mutex_free(serial_svc->buff_size_mtx);
|
||||
free(serial_svc);
|
||||
serial_svc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool serial_svc_is_started() {
|
||||
return serial_svc != NULL;
|
||||
}
|
||||
|
||||
bool serial_svc_update_tx(uint8_t* data, uint16_t data_len) {
|
||||
if(data_len > SERIAL_SVC_DATA_LEN_MAX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint16_t remained = data_len; remained > 0;) {
|
||||
uint8_t value_len = MIN(SERIAL_SVC_CHAR_VALUE_LEN_MAX, remained);
|
||||
uint16_t value_offset = data_len - remained;
|
||||
remained -= value_len;
|
||||
|
||||
tBleStatus result = aci_gatt_update_char_value_ext(
|
||||
0,
|
||||
serial_svc->svc_handle,
|
||||
serial_svc->chars[SerialSvcGattCharacteristicTx].handle,
|
||||
remained ? 0x00 : 0x02,
|
||||
data_len,
|
||||
value_offset,
|
||||
value_len,
|
||||
data + value_offset);
|
||||
|
||||
if(result) {
|
||||
FURI_LOG_E(TAG, "Failed updating TX characteristic: %d", result);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void serial_svc_set_rpc_status(SerialServiceRpcStatus status) {
|
||||
furi_assert(serial_svc);
|
||||
serial_svc_update_rpc_char(status);
|
||||
}
|
||||
55
targets/f7/ble_glue/services/serial_service.h
Normal file
55
targets/f7/ble_glue/services/serial_service.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define SERIAL_SVC_DATA_LEN_MAX (486)
|
||||
#define SERIAL_SVC_CHAR_VALUE_LEN_MAX (243)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
SerialServiceRpcStatusNotActive = 0UL,
|
||||
SerialServiceRpcStatusActive = 1UL,
|
||||
} SerialServiceRpcStatus;
|
||||
|
||||
typedef enum {
|
||||
SerialServiceEventTypeDataReceived,
|
||||
SerialServiceEventTypeDataSent,
|
||||
SerialServiceEventTypesBleResetRequest,
|
||||
} SerialServiceEventType;
|
||||
|
||||
typedef struct {
|
||||
uint8_t* buffer;
|
||||
uint16_t size;
|
||||
} SerialServiceData;
|
||||
|
||||
typedef struct {
|
||||
SerialServiceEventType event;
|
||||
SerialServiceData data;
|
||||
} SerialServiceEvent;
|
||||
|
||||
typedef uint16_t (*SerialServiceEventCallback)(SerialServiceEvent event, void* context);
|
||||
|
||||
void serial_svc_start();
|
||||
|
||||
void serial_svc_set_callbacks(
|
||||
uint16_t buff_size,
|
||||
SerialServiceEventCallback callback,
|
||||
void* context);
|
||||
|
||||
void serial_svc_set_rpc_status(SerialServiceRpcStatus status);
|
||||
|
||||
void serial_svc_notify_buffer_is_empty();
|
||||
|
||||
void serial_svc_stop();
|
||||
|
||||
bool serial_svc_is_started();
|
||||
|
||||
bool serial_svc_update_tx(uint8_t* data, uint16_t data_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
12
targets/f7/ble_glue/services/serial_service_uuid.inc
Normal file
12
targets/f7/ble_glue/services/serial_service_uuid.inc
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
static const Service_UUID_t service_uuid = { .Service_UUID_128 = \
|
||||
{ 0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f }};
|
||||
|
||||
#define SERIAL_SVC_TX_CHAR_UUID \
|
||||
{ 0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 }
|
||||
#define SERIAL_SVC_RX_CHAR_UUID \
|
||||
{ 0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 }
|
||||
#define SERIAL_SVC_FLOW_CONTROL_UUID \
|
||||
{ 0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 }
|
||||
#define SERIAL_SVC_RPC_STATUS_UUID \
|
||||
{ 0x00, 0x00, 0xfe, 0x64, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 }
|
||||
102
targets/f7/ble_glue/tl_dbg_conf.h
Normal file
102
targets/f7/ble_glue/tl_dbg_conf.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_conf.h" /* required as some configuration used in dbg_trace.h are set there */
|
||||
#include "dbg_trace.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Enable or Disable traces
|
||||
* The raw data output is the hci binary packet format as specified by the BT specification *
|
||||
*/
|
||||
#define TL_SHCI_CMD_DBG_EN 1 /* Reports System commands sent to CPU2 and the command response */
|
||||
#define TL_SHCI_CMD_DBG_RAW_EN \
|
||||
0 /* Reports raw data System commands sent to CPU2 and the command response */
|
||||
#define TL_SHCI_EVT_DBG_EN 1 /* Reports System Asynchronous Events received from CPU2 */
|
||||
#define TL_SHCI_EVT_DBG_RAW_EN \
|
||||
0 /* Reports raw data System Asynchronous Events received from CPU2 */
|
||||
|
||||
#define TL_HCI_CMD_DBG_EN 1 /* Reports BLE command sent to CPU2 and the command response */
|
||||
#define TL_HCI_CMD_DBG_RAW_EN \
|
||||
0 /* Reports raw data BLE command sent to CPU2 and the command response */
|
||||
#define TL_HCI_EVT_DBG_EN 1 /* Reports BLE Asynchronous Events received from CPU2 */
|
||||
#define TL_HCI_EVT_DBG_RAW_EN 0 /* Reports raw data BLE Asynchronous Events received from CPU2 */
|
||||
|
||||
#define TL_MM_DBG_EN 1 /* Reports the informations of the buffer released to CPU2 */
|
||||
|
||||
/**
|
||||
* System Transport Layer
|
||||
*/
|
||||
#if(TL_SHCI_CMD_DBG_EN != 0)
|
||||
#define TL_SHCI_CMD_DBG_MSG PRINT_MESG_DBG
|
||||
#define TL_SHCI_CMD_DBG_BUF PRINT_LOG_BUFF_DBG
|
||||
#else
|
||||
#define TL_SHCI_CMD_DBG_MSG(...)
|
||||
#define TL_SHCI_CMD_DBG_BUF(...)
|
||||
#endif
|
||||
|
||||
#if(TL_SHCI_CMD_DBG_RAW_EN != 0)
|
||||
#define TL_SHCI_CMD_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_)
|
||||
#else
|
||||
#define TL_SHCI_CMD_DBG_RAW(...)
|
||||
#endif
|
||||
|
||||
#if(TL_SHCI_EVT_DBG_EN != 0)
|
||||
#define TL_SHCI_EVT_DBG_MSG PRINT_MESG_DBG
|
||||
#define TL_SHCI_EVT_DBG_BUF PRINT_LOG_BUFF_DBG
|
||||
#else
|
||||
#define TL_SHCI_EVT_DBG_MSG(...)
|
||||
#define TL_SHCI_EVT_DBG_BUF(...)
|
||||
#endif
|
||||
|
||||
#if(TL_SHCI_EVT_DBG_RAW_EN != 0)
|
||||
#define TL_SHCI_EVT_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_)
|
||||
#else
|
||||
#define TL_SHCI_EVT_DBG_RAW(...)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* BLE Transport Layer
|
||||
*/
|
||||
#if(TL_HCI_CMD_DBG_EN != 0)
|
||||
#define TL_HCI_CMD_DBG_MSG PRINT_MESG_DBG
|
||||
#define TL_HCI_CMD_DBG_BUF PRINT_LOG_BUFF_DBG
|
||||
#else
|
||||
#define TL_HCI_CMD_DBG_MSG(...)
|
||||
#define TL_HCI_CMD_DBG_BUF(...)
|
||||
#endif
|
||||
|
||||
#if(TL_HCI_CMD_DBG_RAW_EN != 0)
|
||||
#define TL_HCI_CMD_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_)
|
||||
#else
|
||||
#define TL_HCI_CMD_DBG_RAW(...)
|
||||
#endif
|
||||
|
||||
#if(TL_HCI_EVT_DBG_EN != 0)
|
||||
#define TL_HCI_EVT_DBG_MSG PRINT_MESG_DBG
|
||||
#define TL_HCI_EVT_DBG_BUF PRINT_LOG_BUFF_DBG
|
||||
#else
|
||||
#define TL_HCI_EVT_DBG_MSG(...)
|
||||
#define TL_HCI_EVT_DBG_BUF(...)
|
||||
#endif
|
||||
|
||||
#if(TL_HCI_EVT_DBG_RAW_EN != 0)
|
||||
#define TL_HCI_EVT_DBG_RAW(_PDATA_, _SIZE_) furi_hal_console_tx_with_new_line(_PDATA_, _SIZE_)
|
||||
#else
|
||||
#define TL_HCI_EVT_DBG_RAW(...)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Memory Manager - Released buffer tracing
|
||||
*/
|
||||
#if(TL_MM_DBG_EN != 0)
|
||||
#define TL_MM_DBG_MSG PRINT_MESG_DBG
|
||||
#else
|
||||
#define TL_MM_DBG_MSG(...)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
23
targets/f7/fatfs/fatfs.c
Normal file
23
targets/f7/fatfs/fatfs.c
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "fatfs.h"
|
||||
#include "furi_hal_rtc.h"
|
||||
|
||||
/** logical drive path */
|
||||
char fatfs_path[4];
|
||||
/** File system object */
|
||||
FATFS fatfs_object;
|
||||
|
||||
void fatfs_init(void) {
|
||||
FATFS_LinkDriver(&sd_fatfs_driver, fatfs_path);
|
||||
}
|
||||
|
||||
/** Gets Time from RTC
|
||||
*
|
||||
* @return Time in DWORD (toasters per square washing machine)
|
||||
*/
|
||||
DWORD get_fattime() {
|
||||
FuriHalRtcDateTime furi_time;
|
||||
furi_hal_rtc_get_datetime(&furi_time);
|
||||
|
||||
return ((uint32_t)(furi_time.year - 1980) << 25) | furi_time.month << 21 |
|
||||
furi_time.day << 16 | furi_time.hour << 11 | furi_time.minute << 5 | furi_time.second;
|
||||
}
|
||||
19
targets/f7/fatfs/fatfs.h
Normal file
19
targets/f7/fatfs/fatfs.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "fatfs/ff.h"
|
||||
#include "fatfs/ff_gen_drv.h"
|
||||
#include "user_diskio.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** File system object */
|
||||
extern FATFS fatfs_object;
|
||||
|
||||
/** Init file system driver */
|
||||
void fatfs_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
270
targets/f7/fatfs/ffconf.h
Normal file
270
targets/f7/fatfs/ffconf.h
Normal file
@@ -0,0 +1,270 @@
|
||||
/* USER CODE BEGIN Header */
|
||||
/**
|
||||
******************************************************************************
|
||||
* FatFs - Generic FAT file system module R0.12c (C)ChaN, 2017
|
||||
******************************************************************************
|
||||
* @attention
|
||||
*
|
||||
* <h2><center>© Copyright (c) 2020 STMicroelectronics.
|
||||
* All rights reserved.</center></h2>
|
||||
*
|
||||
* This software component is licensed by ST under Ultimate Liberty license
|
||||
* SLA0044, the "License"; You may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at:
|
||||
* www.st.com/SLA0044
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
/* USER CODE END Header */
|
||||
|
||||
#ifndef _FFCONF
|
||||
#define _FFCONF 68300 /* Revision ID */
|
||||
|
||||
/*-----------------------------------------------------------------------------/
|
||||
/ Additional user header to be used
|
||||
/-----------------------------------------------------------------------------*/
|
||||
|
||||
/*-----------------------------------------------------------------------------/
|
||||
/ Function Configurations
|
||||
/-----------------------------------------------------------------------------*/
|
||||
#ifdef FURI_RAM_EXEC
|
||||
#define _FS_READONLY 1 /* 0:Read/Write or 1:Read only */
|
||||
#else
|
||||
#define _FS_READONLY 0 /* 0:Read/Write or 1:Read only */
|
||||
#endif
|
||||
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
|
||||
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
|
||||
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
|
||||
/ and optional writing functions as well. */
|
||||
|
||||
#define _FS_MINIMIZE 0 /* 0 to 3 */
|
||||
/* This option defines minimization level to remove some basic API functions.
|
||||
/
|
||||
/ 0: All basic functions are enabled.
|
||||
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
|
||||
/ are removed.
|
||||
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
|
||||
/ 3: f_lseek() function is removed in addition to 2. */
|
||||
|
||||
#define _USE_STRFUNC 0 /* 0:Disable or 1-2:Enable */
|
||||
/* This option switches string functions, f_gets(), f_putc(), f_puts() and
|
||||
/ f_printf().
|
||||
/
|
||||
/ 0: Disable string functions.
|
||||
/ 1: Enable without LF-CRLF conversion.
|
||||
/ 2: Enable with LF-CRLF conversion. */
|
||||
|
||||
#define _USE_FIND 0
|
||||
/* This option switches filtered directory read functions, f_findfirst() and
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
#ifdef FURI_RAM_EXEC
|
||||
#define _USE_MKFS 0
|
||||
#else
|
||||
#define _USE_MKFS 1
|
||||
#endif
|
||||
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
|
||||
|
||||
#define _USE_FASTSEEK 1
|
||||
/* This option switches fast seek feature. (0:Disable or 1:Enable) */
|
||||
|
||||
#define _USE_EXPAND 0
|
||||
/* This option switches f_expand function. (0:Disable or 1:Enable) */
|
||||
|
||||
#define _USE_CHMOD 0
|
||||
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
|
||||
/ (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */
|
||||
|
||||
#define _USE_LABEL 1
|
||||
/* This option switches volume label functions, f_getlabel() and f_setlabel().
|
||||
/ (0:Disable or 1:Enable) */
|
||||
|
||||
#define _USE_FORWARD 0
|
||||
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
|
||||
|
||||
/*-----------------------------------------------------------------------------/
|
||||
/ Locale and Namespace Configurations
|
||||
/-----------------------------------------------------------------------------*/
|
||||
|
||||
#define _CODE_PAGE 850
|
||||
/* This option specifies the OEM code page to be used on the target system.
|
||||
/ Incorrect setting of the code page can cause a file open failure.
|
||||
/
|
||||
/ 1 - ASCII (No extended character. Non-LFN cfg. only)
|
||||
/ 437 - U.S.
|
||||
/ 720 - Arabic
|
||||
/ 737 - Greek
|
||||
/ 771 - KBL
|
||||
/ 775 - Baltic
|
||||
/ 850 - Latin 1
|
||||
/ 852 - Latin 2
|
||||
/ 855 - Cyrillic
|
||||
/ 857 - Turkish
|
||||
/ 860 - Portuguese
|
||||
/ 861 - Icelandic
|
||||
/ 862 - Hebrew
|
||||
/ 863 - Canadian French
|
||||
/ 864 - Arabic
|
||||
/ 865 - Nordic
|
||||
/ 866 - Russian
|
||||
/ 869 - Greek 2
|
||||
/ 932 - Japanese (DBCS)
|
||||
/ 936 - Simplified Chinese (DBCS)
|
||||
/ 949 - Korean (DBCS)
|
||||
/ 950 - Traditional Chinese (DBCS)
|
||||
*/
|
||||
|
||||
#define _USE_LFN 2 /* 0 to 3 */
|
||||
#define _MAX_LFN 255 /* Maximum LFN length to handle (12 to 255) */
|
||||
/* The _USE_LFN switches the support of long file name (LFN).
|
||||
/
|
||||
/ 0: Disable support of LFN. _MAX_LFN has no effect.
|
||||
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
||||
/
|
||||
/ To enable the LFN, Unicode handling functions (option/unicode.c) must be added
|
||||
/ to the project. The working buffer occupies (_MAX_LFN + 1) * 2 bytes and
|
||||
/ additional 608 bytes at exFAT enabled. _MAX_LFN can be in range from 12 to 255.
|
||||
/ It should be set 255 to support full featured LFN operations.
|
||||
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
||||
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||
/ ff_memfree(), must be added to the project. */
|
||||
|
||||
#define _LFN_UNICODE 0 /* 0:ANSI/OEM or 1:Unicode */
|
||||
/* This option switches character encoding on the API. (0:ANSI/OEM or 1:UTF-16)
|
||||
/ To use Unicode string for the path name, enable LFN and set _LFN_UNICODE = 1.
|
||||
/ This option also affects behavior of string I/O functions. */
|
||||
|
||||
#define _STRF_ENCODE 0
|
||||
/* When _LFN_UNICODE == 1, this option selects the character encoding ON THE FILE to
|
||||
/ be read/written via string I/O functions, f_gets(), f_putc(), f_puts and f_printf().
|
||||
/
|
||||
/ 0: ANSI/OEM
|
||||
/ 1: UTF-16LE
|
||||
/ 2: UTF-16BE
|
||||
/ 3: UTF-8
|
||||
/
|
||||
/ This option has no effect when _LFN_UNICODE == 0. */
|
||||
|
||||
#define _FS_RPATH 0 /* 0 to 2 */
|
||||
/* This option configures support of relative path.
|
||||
/
|
||||
/ 0: Disable relative path and remove related functions.
|
||||
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
|
||||
/ 2: f_getcwd() function is available in addition to 1.
|
||||
*/
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Drive/Volume Configurations
|
||||
/----------------------------------------------------------------------------*/
|
||||
|
||||
#define _VOLUMES 1
|
||||
/* Number of volumes (logical drives) to be used. */
|
||||
|
||||
/* USER CODE BEGIN Volumes */
|
||||
#define _STR_VOLUME_ID 0 /* 0:Use only 0-9 for drive ID, 1:Use strings for drive ID */
|
||||
#define _VOLUME_STRS "SD"
|
||||
/* _STR_VOLUME_ID switches string support of volume ID.
|
||||
/ When _STR_VOLUME_ID is set to 1, also pre-defined strings can be used as drive
|
||||
/ number in the path name. _VOLUME_STRS defines the drive ID strings for each
|
||||
/ logical drives. Number of items must be equal to _VOLUMES. Valid characters for
|
||||
/ the drive ID strings are: A-Z and 0-9. */
|
||||
/* USER CODE END Volumes */
|
||||
|
||||
#define _MULTI_PARTITION 0 /* 0:Single partition, 1:Multiple partition */
|
||||
/* This option switches support of multi-partition on a physical drive.
|
||||
/ By default (0), each logical drive number is bound to the same physical drive
|
||||
/ number and only an FAT volume found on the physical drive will be mounted.
|
||||
/ When multi-partition is enabled (1), each logical drive number can be bound to
|
||||
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
|
||||
/ funciton will be available. */
|
||||
#define _MIN_SS 512 /* 512, 1024, 2048 or 4096 */
|
||||
#define _MAX_SS 512 /* 512, 1024, 2048 or 4096 */
|
||||
/* These options configure the range of sector size to be supported. (512, 1024,
|
||||
/ 2048 or 4096) Always set both 512 for most systems, all type of memory cards and
|
||||
/ harddisk. But a larger value may be required for on-board flash memory and some
|
||||
/ type of optical media. When _MAX_SS is larger than _MIN_SS, FatFs is configured
|
||||
/ to variable sector size and GET_SECTOR_SIZE command must be implemented to the
|
||||
/ disk_ioctl() function. */
|
||||
|
||||
#define _USE_TRIM 0
|
||||
/* This option switches support of ATA-TRIM. (0:Disable or 1:Enable)
|
||||
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
|
||||
/ disk_ioctl() function. */
|
||||
|
||||
#define _FS_NOFSINFO 0 /* 0,1,2 or 3 */
|
||||
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
|
||||
/ option, and f_getfree() function at first time after volume mount will force
|
||||
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
|
||||
/
|
||||
/ bit0=0: Use free cluster count in the FSINFO if available.
|
||||
/ bit0=1: Do not trust free cluster count in the FSINFO.
|
||||
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
|
||||
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
|
||||
*/
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ System Configurations
|
||||
/----------------------------------------------------------------------------*/
|
||||
|
||||
#define _FS_TINY 0 /* 0:Normal or 1:Tiny */
|
||||
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
|
||||
/ At the tiny configuration, size of file object (FIL) is reduced _MAX_SS bytes.
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
/ buffer in the file system object (FATFS) is used for the file data transfer. */
|
||||
|
||||
#define _FS_EXFAT 1
|
||||
/* This option switches support of exFAT file system. (0:Disable or 1:Enable)
|
||||
/ When enable exFAT, also LFN needs to be enabled. (_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards C89 compatibility. */
|
||||
|
||||
#define _FS_NORTC 0
|
||||
/* The option _FS_NORTC switches timestamp functiton. If the system does not have
|
||||
/ any RTC function or valid timestamp is not needed, set _FS_NORTC = 1 to disable
|
||||
/ the timestamp function. All objects modified by FatFs will have a fixed timestamp
|
||||
/ defined by _NORTC_MON, _NORTC_MDAY and _NORTC_YEAR in local time.
|
||||
/ To enable timestamp function (_FS_NORTC = 0), get_fattime() function need to be
|
||||
/ added to the project to get current time form real-time clock. _NORTC_MON,
|
||||
/ _NORTC_MDAY and _NORTC_YEAR have no effect.
|
||||
/ These options have no effect at read-only configuration (_FS_READONLY = 1). */
|
||||
|
||||
#define _FS_LOCK 0 /* 0:Disable or >=1:Enable */
|
||||
/* The option _FS_LOCK switches file lock function to control duplicated file open
|
||||
/ and illegal operation to open objects. This option must be 0 when _FS_READONLY
|
||||
/ is 1.
|
||||
/
|
||||
/ 0: Disable file lock function. To avoid volume corruption, application program
|
||||
/ should avoid illegal open, remove and rename to the open objects.
|
||||
/ >0: Enable file lock function. The value defines how many files/sub-directories
|
||||
/ can be opened simultaneously under file lock control. Note that the file
|
||||
/ lock control is independent of re-entrancy. */
|
||||
|
||||
#define _FS_REENTRANT 0 /* 0:Disable or 1:Enable */
|
||||
#define _FS_TIMEOUT 1000 /* Timeout period in unit of time ticks */
|
||||
#define _SYNC_t FuriMutex*
|
||||
/* The option _FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
|
||||
/ module itself. Note that regardless of this option, file access to different
|
||||
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
|
||||
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
|
||||
/ to the same volume is under control of this function.
|
||||
/
|
||||
/ 0: Disable re-entrancy. _FS_TIMEOUT and _SYNC_t have no effect.
|
||||
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
|
||||
/ function, must be added to the project. Samples are available in
|
||||
/ option/syscall.c.
|
||||
/
|
||||
/ The _FS_TIMEOUT defines timeout period in unit of time tick.
|
||||
/ The _SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
|
||||
/ SemaphoreHandle_t and etc.. A header file for O/S definitions needs to be
|
||||
/ included somewhere in the scope of ff.h. */
|
||||
|
||||
/* define the ff_malloc ff_free macros as standard malloc free */
|
||||
#if !defined(ff_malloc) && !defined(ff_free)
|
||||
#include <stdlib.h>
|
||||
#define ff_malloc malloc
|
||||
#define ff_free free
|
||||
#endif
|
||||
|
||||
#endif /* _FFCONF */
|
||||
56
targets/f7/fatfs/sector_cache.c
Normal file
56
targets/f7/fatfs/sector_cache.c
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "sector_cache.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal_memory.h>
|
||||
|
||||
#define SECTOR_SIZE 512
|
||||
#define N_SECTORS 8
|
||||
|
||||
typedef struct {
|
||||
uint32_t itr;
|
||||
uint32_t sectors[N_SECTORS];
|
||||
uint8_t sector_data[N_SECTORS][SECTOR_SIZE];
|
||||
} SectorCache;
|
||||
|
||||
static SectorCache* cache = NULL;
|
||||
|
||||
void sector_cache_init() {
|
||||
if(cache == NULL) {
|
||||
cache = memmgr_alloc_from_pool(sizeof(SectorCache));
|
||||
}
|
||||
|
||||
if(cache != NULL) {
|
||||
memset(cache, 0, sizeof(SectorCache));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* sector_cache_get(uint32_t n_sector) {
|
||||
if(cache != NULL && n_sector != 0) {
|
||||
for(int sector_i = 0; sector_i < N_SECTORS; ++sector_i) {
|
||||
if(cache->sectors[sector_i] == n_sector) {
|
||||
return cache->sector_data[sector_i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sector_cache_put(uint32_t n_sector, uint8_t* data) {
|
||||
if(cache == NULL) return;
|
||||
cache->sectors[cache->itr % N_SECTORS] = n_sector;
|
||||
memcpy(cache->sector_data[cache->itr % N_SECTORS], data, SECTOR_SIZE);
|
||||
cache->itr++;
|
||||
}
|
||||
|
||||
void sector_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector) {
|
||||
if(cache == NULL) return;
|
||||
for(int sector_i = 0; sector_i < N_SECTORS; ++sector_i) {
|
||||
if((cache->sectors[sector_i] >= start_sector) &&
|
||||
(cache->sectors[sector_i] <= end_sector)) {
|
||||
cache->sectors[sector_i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
targets/f7/fatfs/sector_cache.h
Normal file
36
targets/f7/fatfs/sector_cache.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Init sector cache system
|
||||
*/
|
||||
void sector_cache_init();
|
||||
|
||||
/**
|
||||
* @brief Get sector data from cache
|
||||
* @param n_sector Sector number
|
||||
* @return Pointer to sector data or NULL if not found
|
||||
*/
|
||||
uint8_t* sector_cache_get(uint32_t n_sector);
|
||||
|
||||
/**
|
||||
* @brief Put sector data to cache
|
||||
* @param n_sector Sector number
|
||||
* @param data Pointer to sector data
|
||||
*/
|
||||
void sector_cache_put(uint32_t n_sector, uint8_t* data);
|
||||
|
||||
/**
|
||||
* @brief Invalidate sector cache for given range
|
||||
* @param start_sector Start sector number
|
||||
* @param end_sector End sector number
|
||||
*/
|
||||
void sector_cache_invalidate_range(uint32_t start_sector, uint32_t end_sector);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
119
targets/f7/fatfs/user_diskio.c
Normal file
119
targets/f7/fatfs/user_diskio.c
Normal file
@@ -0,0 +1,119 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "user_diskio.h"
|
||||
#include "sector_cache.h"
|
||||
|
||||
static DSTATUS driver_initialize(BYTE pdrv);
|
||||
static DSTATUS driver_status(BYTE pdrv);
|
||||
static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
|
||||
static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
|
||||
static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff);
|
||||
|
||||
Diskio_drvTypeDef sd_fatfs_driver = {
|
||||
driver_initialize,
|
||||
driver_status,
|
||||
driver_read,
|
||||
driver_write,
|
||||
driver_ioctl,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initializes a Drive
|
||||
* @param pdrv: Physical drive number (0..)
|
||||
* @retval DSTATUS: Operation status
|
||||
*/
|
||||
static DSTATUS driver_initialize(BYTE pdrv) {
|
||||
UNUSED(pdrv);
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets Disk Status
|
||||
* @param pdrv: Physical drive number (0..)
|
||||
* @retval DSTATUS: Operation status
|
||||
*/
|
||||
static DSTATUS driver_status(BYTE pdrv) {
|
||||
UNUSED(pdrv);
|
||||
DSTATUS status = 0;
|
||||
if(furi_hal_sd_get_card_state() != FuriStatusOk) {
|
||||
status = STA_NOINIT;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads Sector(s)
|
||||
* @param pdrv: Physical drive number (0..)
|
||||
* @param *buff: Data buffer to store read data
|
||||
* @param sector: Sector address (LBA)
|
||||
* @param count: Number of sectors to read (1..128)
|
||||
* @retval DRESULT: Operation result
|
||||
*/
|
||||
static DRESULT driver_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) {
|
||||
UNUSED(pdrv);
|
||||
FuriStatus status = furi_hal_sd_read_blocks((uint32_t*)buff, (uint32_t)(sector), count);
|
||||
return status == FuriStatusOk ? RES_OK : RES_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes Sector(s)
|
||||
* @param pdrv: Physical drive number (0..)
|
||||
* @param *buff: Data to be written
|
||||
* @param sector: Sector address (LBA)
|
||||
* @param count: Number of sectors to write (1..128)
|
||||
* @retval DRESULT: Operation result
|
||||
*/
|
||||
static DRESULT driver_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) {
|
||||
UNUSED(pdrv);
|
||||
FuriStatus status = furi_hal_sd_write_blocks((uint32_t*)buff, (uint32_t)(sector), count);
|
||||
return status == FuriStatusOk ? RES_OK : RES_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief I/O control operation
|
||||
* @param pdrv: Physical drive number (0..)
|
||||
* @param cmd: Control code
|
||||
* @param *buff: Buffer to send/receive control data
|
||||
* @retval DRESULT: Operation result
|
||||
*/
|
||||
static DRESULT driver_ioctl(BYTE pdrv, BYTE cmd, void* buff) {
|
||||
DRESULT res = RES_ERROR;
|
||||
FuriHalSdInfo sd_info;
|
||||
|
||||
DSTATUS status = driver_status(pdrv);
|
||||
if(status & STA_NOINIT) return RES_NOTRDY;
|
||||
|
||||
switch(cmd) {
|
||||
/* Make sure that no pending write process */
|
||||
case CTRL_SYNC:
|
||||
res = RES_OK;
|
||||
break;
|
||||
|
||||
/* Get number of sectors on the disk (DWORD) */
|
||||
case GET_SECTOR_COUNT:
|
||||
furi_hal_sd_info(&sd_info);
|
||||
*(DWORD*)buff = sd_info.logical_block_count;
|
||||
res = RES_OK;
|
||||
break;
|
||||
|
||||
/* Get R/W sector size (WORD) */
|
||||
case GET_SECTOR_SIZE:
|
||||
furi_hal_sd_info(&sd_info);
|
||||
*(WORD*)buff = sd_info.logical_block_size;
|
||||
res = RES_OK;
|
||||
break;
|
||||
|
||||
/* Get erase block size in unit of sector (DWORD) */
|
||||
case GET_BLOCK_SIZE:
|
||||
furi_hal_sd_info(&sd_info);
|
||||
*(DWORD*)buff = sd_info.logical_block_size;
|
||||
res = RES_OK;
|
||||
break;
|
||||
|
||||
default:
|
||||
res = RES_PARERR;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
13
targets/f7/fatfs/user_diskio.h
Normal file
13
targets/f7/fatfs/user_diskio.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "fatfs/ff_gen_drv.h"
|
||||
|
||||
extern Diskio_drvTypeDef sd_fatfs_driver;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
72
targets/f7/furi_hal/furi_hal.c
Normal file
72
targets/f7/furi_hal/furi_hal.c
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_mpu.h>
|
||||
#include <furi_hal_memory.h>
|
||||
|
||||
#include <stm32wbxx_ll_cortex.h>
|
||||
|
||||
#define TAG "FuriHal"
|
||||
|
||||
void furi_hal_init_early() {
|
||||
furi_hal_cortex_init_early();
|
||||
furi_hal_clock_init_early();
|
||||
furi_hal_bus_init_early();
|
||||
furi_hal_dma_init_early();
|
||||
furi_hal_resources_init_early();
|
||||
furi_hal_os_init();
|
||||
furi_hal_spi_config_init_early();
|
||||
furi_hal_i2c_init_early();
|
||||
furi_hal_light_init();
|
||||
furi_hal_rtc_init_early();
|
||||
}
|
||||
|
||||
void furi_hal_deinit_early() {
|
||||
furi_hal_rtc_deinit_early();
|
||||
furi_hal_i2c_deinit_early();
|
||||
furi_hal_spi_config_deinit_early();
|
||||
furi_hal_resources_deinit_early();
|
||||
furi_hal_dma_deinit_early();
|
||||
furi_hal_bus_deinit_early();
|
||||
furi_hal_clock_deinit_early();
|
||||
}
|
||||
|
||||
void furi_hal_init() {
|
||||
furi_hal_mpu_init();
|
||||
furi_hal_clock_init();
|
||||
furi_hal_random_init();
|
||||
furi_hal_console_init();
|
||||
furi_hal_rtc_init();
|
||||
furi_hal_interrupt_init();
|
||||
furi_hal_flash_init();
|
||||
furi_hal_resources_init();
|
||||
furi_hal_version_init();
|
||||
furi_hal_region_init();
|
||||
furi_hal_spi_config_init();
|
||||
furi_hal_spi_dma_init();
|
||||
furi_hal_ibutton_init();
|
||||
furi_hal_speaker_init();
|
||||
furi_hal_crypto_init();
|
||||
furi_hal_i2c_init();
|
||||
furi_hal_power_init();
|
||||
furi_hal_light_init();
|
||||
furi_hal_bt_init();
|
||||
furi_hal_memory_init();
|
||||
|
||||
#ifndef FURI_RAM_EXEC
|
||||
furi_hal_usb_init();
|
||||
furi_hal_vibro_init();
|
||||
furi_hal_subghz_init();
|
||||
furi_hal_nfc_init();
|
||||
furi_hal_rfid_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
void furi_hal_switch(void* address) {
|
||||
__set_BASEPRI(0);
|
||||
asm volatile("ldr r3, [%0] \n"
|
||||
"msr msp, r3 \n"
|
||||
"ldr r3, [%1] \n"
|
||||
"mov pc, r3 \n"
|
||||
:
|
||||
: "r"(address), "r"(address + 0x4)
|
||||
: "r3");
|
||||
}
|
||||
494
targets/f7/furi_hal/furi_hal_bt.c
Normal file
494
targets/f7/furi_hal/furi_hal_bt.c
Normal file
@@ -0,0 +1,494 @@
|
||||
#include <furi_hal_bt.h>
|
||||
|
||||
#include <ble/ble.h>
|
||||
#include <interface/patterns/ble_thread/shci/shci.h>
|
||||
|
||||
#include <stm32wbxx.h>
|
||||
#include <stm32wbxx_ll_hsem.h>
|
||||
|
||||
#include <hsem_map.h>
|
||||
|
||||
#include <furi_hal_version.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_bt_hid.h>
|
||||
#include <furi_hal_bt_serial.h>
|
||||
#include <furi_hal_bus.c>
|
||||
#include <services/battery_service.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "FuriHalBt"
|
||||
|
||||
#define FURI_HAL_BT_DEFAULT_MAC_ADDR \
|
||||
{ 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 }
|
||||
|
||||
/* Time, in ms, to wait for mode transition before crashing */
|
||||
#define C2_MODE_SWITCH_TIMEOUT 10000
|
||||
|
||||
#define FURI_HAL_BT_HARDFAULT_INFO_MAGIC 0x1170FD0F
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* core2_mtx;
|
||||
FuriTimer* hardfault_check_timer;
|
||||
FuriHalBtStack stack;
|
||||
} FuriHalBt;
|
||||
|
||||
static FuriHalBt furi_hal_bt = {
|
||||
.core2_mtx = NULL,
|
||||
.hardfault_check_timer = NULL,
|
||||
.stack = FuriHalBtStackUnknown,
|
||||
};
|
||||
|
||||
typedef void (*FuriHalBtProfileStart)(void);
|
||||
typedef void (*FuriHalBtProfileStop)(void);
|
||||
|
||||
typedef struct {
|
||||
FuriHalBtProfileStart start;
|
||||
FuriHalBtProfileStart stop;
|
||||
GapConfig config;
|
||||
uint16_t appearance_char;
|
||||
uint16_t advertise_service_uuid;
|
||||
} FuriHalBtProfileConfig;
|
||||
|
||||
FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = {
|
||||
[FuriHalBtProfileSerial] =
|
||||
{
|
||||
.start = furi_hal_bt_serial_start,
|
||||
.stop = furi_hal_bt_serial_stop,
|
||||
.config =
|
||||
{
|
||||
.adv_service_uuid = 0x3080,
|
||||
.appearance_char = 0x8600,
|
||||
.bonding_mode = true,
|
||||
.pairing_method = GapPairingPinCodeShow,
|
||||
.mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR,
|
||||
.conn_param =
|
||||
{
|
||||
.conn_int_min = 0x18, // 30 ms
|
||||
.conn_int_max = 0x24, // 45 ms
|
||||
.slave_latency = 0,
|
||||
.supervisor_timeout = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
[FuriHalBtProfileHidKeyboard] =
|
||||
{
|
||||
.start = furi_hal_bt_hid_start,
|
||||
.stop = furi_hal_bt_hid_stop,
|
||||
.config =
|
||||
{
|
||||
.adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
|
||||
.appearance_char = GAP_APPEARANCE_KEYBOARD,
|
||||
.bonding_mode = true,
|
||||
.pairing_method = GapPairingPinCodeVerifyYesNo,
|
||||
.mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR,
|
||||
.conn_param =
|
||||
{
|
||||
.conn_int_min = 0x18, // 30 ms
|
||||
.conn_int_max = 0x24, // 45 ms
|
||||
.slave_latency = 0,
|
||||
.supervisor_timeout = 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
FuriHalBtProfileConfig* current_profile = NULL;
|
||||
|
||||
static void furi_hal_bt_hardfault_check(void* context) {
|
||||
UNUSED(context);
|
||||
if(furi_hal_bt_get_hardfault_info()) {
|
||||
furi_crash("ST(R) Copro(R) HardFault");
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_bt_init() {
|
||||
furi_hal_bus_enable(FuriHalBusHSEM);
|
||||
furi_hal_bus_enable(FuriHalBusIPCC);
|
||||
furi_hal_bus_enable(FuriHalBusAES2);
|
||||
furi_hal_bus_enable(FuriHalBusPKA);
|
||||
furi_hal_bus_enable(FuriHalBusCRC);
|
||||
|
||||
if(!furi_hal_bt.core2_mtx) {
|
||||
furi_hal_bt.core2_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
furi_assert(furi_hal_bt.core2_mtx);
|
||||
}
|
||||
|
||||
if(!furi_hal_bt.hardfault_check_timer) {
|
||||
furi_hal_bt.hardfault_check_timer =
|
||||
furi_timer_alloc(furi_hal_bt_hardfault_check, FuriTimerTypePeriodic, NULL);
|
||||
furi_timer_start(furi_hal_bt.hardfault_check_timer, 5000);
|
||||
}
|
||||
|
||||
// Explicitly tell that we are in charge of CLK48 domain
|
||||
furi_check(LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID) == 0);
|
||||
|
||||
// Start Core2
|
||||
ble_glue_init();
|
||||
}
|
||||
|
||||
void furi_hal_bt_lock_core2() {
|
||||
furi_assert(furi_hal_bt.core2_mtx);
|
||||
furi_check(furi_mutex_acquire(furi_hal_bt.core2_mtx, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void furi_hal_bt_unlock_core2() {
|
||||
furi_assert(furi_hal_bt.core2_mtx);
|
||||
furi_check(furi_mutex_release(furi_hal_bt.core2_mtx) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static bool furi_hal_bt_radio_stack_is_supported(const BleGlueC2Info* info) {
|
||||
bool supported = false;
|
||||
if(info->StackType == INFO_STACK_TYPE_BLE_LIGHT) {
|
||||
if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR &&
|
||||
info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) {
|
||||
furi_hal_bt.stack = FuriHalBtStackLight;
|
||||
supported = true;
|
||||
}
|
||||
} else if(info->StackType == INFO_STACK_TYPE_BLE_FULL) {
|
||||
if(info->VersionMajor >= FURI_HAL_BT_STACK_VERSION_MAJOR &&
|
||||
info->VersionMinor >= FURI_HAL_BT_STACK_VERSION_MINOR) {
|
||||
furi_hal_bt.stack = FuriHalBtStackFull;
|
||||
supported = true;
|
||||
}
|
||||
} else {
|
||||
furi_hal_bt.stack = FuriHalBtStackUnknown;
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
bool furi_hal_bt_start_radio_stack() {
|
||||
bool res = false;
|
||||
furi_assert(furi_hal_bt.core2_mtx);
|
||||
|
||||
furi_mutex_acquire(furi_hal_bt.core2_mtx, FuriWaitForever);
|
||||
|
||||
// Explicitly tell that we are in charge of CLK48 domain
|
||||
furi_check(LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID) == 0);
|
||||
|
||||
do {
|
||||
// Wait until C2 is started or timeout
|
||||
if(!ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)) {
|
||||
FURI_LOG_E(TAG, "Core2 start failed");
|
||||
ble_glue_thread_stop();
|
||||
break;
|
||||
}
|
||||
|
||||
// If C2 is running, start radio stack fw
|
||||
if(!furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check whether we support radio stack
|
||||
const BleGlueC2Info* c2_info = ble_glue_get_c2_info();
|
||||
if(!furi_hal_bt_radio_stack_is_supported(c2_info)) {
|
||||
FURI_LOG_E(TAG, "Unsupported radio stack");
|
||||
// Don't stop SHCI for crypto enclave support
|
||||
break;
|
||||
}
|
||||
// Starting radio stack
|
||||
if(!ble_glue_start()) {
|
||||
FURI_LOG_E(TAG, "Failed to start radio stack");
|
||||
ble_glue_thread_stop();
|
||||
ble_app_thread_stop();
|
||||
break;
|
||||
}
|
||||
res = true;
|
||||
} while(false);
|
||||
furi_mutex_release(furi_hal_bt.core2_mtx);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
FuriHalBtStack furi_hal_bt_get_radio_stack() {
|
||||
return furi_hal_bt.stack;
|
||||
}
|
||||
|
||||
bool furi_hal_bt_is_ble_gatt_gap_supported() {
|
||||
if(furi_hal_bt.stack == FuriHalBtStackLight || furi_hal_bt.stack == FuriHalBtStackFull) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool furi_hal_bt_is_testing_supported() {
|
||||
if(furi_hal_bt.stack == FuriHalBtStackFull) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) {
|
||||
furi_assert(event_cb);
|
||||
furi_assert(profile < FuriHalBtProfileNumber);
|
||||
bool ret = false;
|
||||
|
||||
do {
|
||||
if(!ble_glue_is_radio_stack_ready()) {
|
||||
FURI_LOG_E(TAG, "Can't start BLE App - radio stack did not start");
|
||||
break;
|
||||
}
|
||||
if(!furi_hal_bt_is_ble_gatt_gap_supported()) {
|
||||
FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack");
|
||||
break;
|
||||
}
|
||||
// Set mac address
|
||||
memcpy(
|
||||
profile_config[profile].config.mac_address,
|
||||
furi_hal_version_get_ble_mac(),
|
||||
sizeof(profile_config[profile].config.mac_address));
|
||||
// Set advertise name
|
||||
strlcpy(
|
||||
profile_config[profile].config.adv_name,
|
||||
furi_hal_version_get_ble_local_device_name_ptr(),
|
||||
FURI_HAL_VERSION_DEVICE_NAME_LENGTH);
|
||||
// Configure GAP
|
||||
GapConfig* config = &profile_config[profile].config;
|
||||
if(profile == FuriHalBtProfileSerial) {
|
||||
config->adv_service_uuid |= furi_hal_version_get_hw_color();
|
||||
} else if(profile == FuriHalBtProfileHidKeyboard) {
|
||||
// Change MAC address for HID profile
|
||||
config->mac_address[2]++;
|
||||
// Change name Flipper -> Control
|
||||
const char* clicker_str = "Control";
|
||||
memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str));
|
||||
}
|
||||
if(!gap_init(config, event_cb, context)) {
|
||||
gap_thread_stop();
|
||||
FURI_LOG_E(TAG, "Failed to init GAP");
|
||||
break;
|
||||
}
|
||||
// Start selected profile services
|
||||
if(furi_hal_bt_is_ble_gatt_gap_supported()) {
|
||||
profile_config[profile].start();
|
||||
}
|
||||
ret = true;
|
||||
} while(false);
|
||||
current_profile = &profile_config[profile];
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_hal_bt_reinit() {
|
||||
furi_hal_power_insomnia_enter();
|
||||
FURI_LOG_I(TAG, "Disconnect and stop advertising");
|
||||
furi_hal_bt_stop_advertising();
|
||||
|
||||
FURI_LOG_I(TAG, "Stop current profile services");
|
||||
current_profile->stop();
|
||||
|
||||
// Magic happens here
|
||||
hci_reset();
|
||||
|
||||
FURI_LOG_I(TAG, "Stop BLE related RTOS threads");
|
||||
ble_app_thread_stop();
|
||||
gap_thread_stop();
|
||||
|
||||
FURI_LOG_I(TAG, "Reset SHCI");
|
||||
furi_check(ble_glue_reinit_c2());
|
||||
|
||||
furi_delay_ms(100);
|
||||
ble_glue_thread_stop();
|
||||
|
||||
furi_hal_bus_disable(FuriHalBusHSEM);
|
||||
furi_hal_bus_disable(FuriHalBusIPCC);
|
||||
furi_hal_bus_disable(FuriHalBusAES2);
|
||||
furi_hal_bus_disable(FuriHalBusPKA);
|
||||
furi_hal_bus_disable(FuriHalBusCRC);
|
||||
|
||||
FURI_LOG_I(TAG, "Start BT initialization");
|
||||
furi_hal_bt_init();
|
||||
|
||||
furi_hal_bt_start_radio_stack();
|
||||
furi_hal_power_insomnia_exit();
|
||||
}
|
||||
|
||||
bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) {
|
||||
furi_assert(event_cb);
|
||||
furi_assert(profile < FuriHalBtProfileNumber);
|
||||
bool ret = true;
|
||||
|
||||
furi_hal_bt_reinit();
|
||||
|
||||
ret = furi_hal_bt_start_app(profile, event_cb, context);
|
||||
if(ret) {
|
||||
current_profile = &profile_config[profile];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool furi_hal_bt_is_active() {
|
||||
return gap_get_state() > GapStateIdle;
|
||||
}
|
||||
|
||||
void furi_hal_bt_start_advertising() {
|
||||
if(gap_get_state() == GapStateIdle) {
|
||||
gap_start_advertising();
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_bt_stop_advertising() {
|
||||
if(furi_hal_bt_is_active()) {
|
||||
gap_stop_advertising();
|
||||
while(furi_hal_bt_is_active()) {
|
||||
furi_delay_tick(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_bt_update_battery_level(uint8_t battery_level) {
|
||||
if(battery_svc_is_started()) {
|
||||
battery_svc_update_level(battery_level);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_bt_update_power_state() {
|
||||
if(battery_svc_is_started()) {
|
||||
battery_svc_update_power_state();
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) {
|
||||
ble_app_get_key_storage_buff(key_buff_addr, key_buff_size);
|
||||
}
|
||||
|
||||
void furi_hal_bt_set_key_storage_change_callback(
|
||||
BleGlueKeyStorageChangedCallback callback,
|
||||
void* context) {
|
||||
furi_assert(callback);
|
||||
ble_glue_set_key_storage_changed_callback(callback, context);
|
||||
}
|
||||
|
||||
void furi_hal_bt_nvm_sram_sem_acquire() {
|
||||
while(LL_HSEM_1StepLock(HSEM, CFG_HW_BLE_NVM_SRAM_SEMID)) {
|
||||
furi_thread_yield();
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_bt_nvm_sram_sem_release() {
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_BLE_NVM_SRAM_SEMID, 0);
|
||||
}
|
||||
|
||||
bool furi_hal_bt_clear_white_list() {
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
tBleStatus status = aci_gap_clear_security_db();
|
||||
if(status) {
|
||||
FURI_LOG_E(TAG, "Clear while list failed with status %d", status);
|
||||
}
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
return status != BLE_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
void furi_hal_bt_dump_state(FuriString* buffer) {
|
||||
if(furi_hal_bt_is_alive()) {
|
||||
uint8_t HCI_Version;
|
||||
uint16_t HCI_Revision;
|
||||
uint8_t LMP_PAL_Version;
|
||||
uint16_t Manufacturer_Name;
|
||||
uint16_t LMP_PAL_Subversion;
|
||||
|
||||
tBleStatus ret = hci_read_local_version_information(
|
||||
&HCI_Version, &HCI_Revision, &LMP_PAL_Version, &Manufacturer_Name, &LMP_PAL_Subversion);
|
||||
|
||||
furi_string_cat_printf(
|
||||
buffer,
|
||||
"Ret: %d, HCI_Version: %d, HCI_Revision: %d, LMP_PAL_Version: %d, Manufacturer_Name: %d, LMP_PAL_Subversion: %d",
|
||||
ret,
|
||||
HCI_Version,
|
||||
HCI_Revision,
|
||||
LMP_PAL_Version,
|
||||
Manufacturer_Name,
|
||||
LMP_PAL_Subversion);
|
||||
} else {
|
||||
furi_string_cat_printf(buffer, "BLE not ready");
|
||||
}
|
||||
}
|
||||
|
||||
bool furi_hal_bt_is_alive() {
|
||||
return ble_glue_is_alive();
|
||||
}
|
||||
|
||||
void furi_hal_bt_start_tone_tx(uint8_t channel, uint8_t power) {
|
||||
aci_hal_set_tx_power_level(0, power);
|
||||
aci_hal_tone_start(channel, 0);
|
||||
}
|
||||
|
||||
void furi_hal_bt_stop_tone_tx() {
|
||||
aci_hal_tone_stop();
|
||||
}
|
||||
|
||||
void furi_hal_bt_start_packet_tx(uint8_t channel, uint8_t pattern, uint8_t datarate) {
|
||||
hci_le_enhanced_transmitter_test(channel, 0x25, pattern, datarate);
|
||||
}
|
||||
|
||||
void furi_hal_bt_start_packet_rx(uint8_t channel, uint8_t datarate) {
|
||||
hci_le_enhanced_receiver_test(channel, datarate, 0);
|
||||
}
|
||||
|
||||
uint16_t furi_hal_bt_stop_packet_test() {
|
||||
uint16_t num_of_packets = 0;
|
||||
hci_le_test_end(&num_of_packets);
|
||||
return num_of_packets;
|
||||
}
|
||||
|
||||
void furi_hal_bt_start_rx(uint8_t channel) {
|
||||
aci_hal_rx_start(channel);
|
||||
}
|
||||
|
||||
float furi_hal_bt_get_rssi() {
|
||||
float val;
|
||||
uint8_t rssi_raw[3];
|
||||
|
||||
if(aci_hal_read_raw_rssi(rssi_raw) != BLE_STATUS_SUCCESS) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Some ST magic with rssi
|
||||
uint8_t agc = rssi_raw[2] & 0xFF;
|
||||
int rssi = (((int)rssi_raw[1] << 8) & 0xFF00) + (rssi_raw[0] & 0xFF);
|
||||
if(rssi == 0 || agc > 11) {
|
||||
val = -127.0;
|
||||
} else {
|
||||
val = agc * 6.0f - 127.0f;
|
||||
while(rssi > 30) {
|
||||
val += 6.0;
|
||||
rssi >>= 1;
|
||||
}
|
||||
val += (float)((417 * rssi + 18080) >> 10);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
uint32_t furi_hal_bt_get_transmitted_packets() {
|
||||
uint32_t packets = 0;
|
||||
aci_hal_le_tx_test_packet_number(&packets);
|
||||
return packets;
|
||||
}
|
||||
|
||||
void furi_hal_bt_stop_rx() {
|
||||
aci_hal_rx_stop();
|
||||
}
|
||||
|
||||
bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) {
|
||||
BleGlueCommandResult fw_start_res = ble_glue_force_c2_mode(mode);
|
||||
if(fw_start_res == BleGlueCommandResultOK) {
|
||||
return true;
|
||||
} else if(fw_start_res == BleGlueCommandResultRestartPending) {
|
||||
// Do nothing and wait for system reset
|
||||
furi_delay_ms(C2_MODE_SWITCH_TIMEOUT);
|
||||
furi_crash("Waiting for FUS->radio stack transition");
|
||||
return true;
|
||||
}
|
||||
|
||||
FURI_LOG_E(TAG, "Failed to switch C2 mode: %d", fw_start_res);
|
||||
return false;
|
||||
}
|
||||
|
||||
const FuriHalBtHardfaultInfo* furi_hal_bt_get_hardfault_info() {
|
||||
/* AN5289, 4.8.2 */
|
||||
const FuriHalBtHardfaultInfo* info = (FuriHalBtHardfaultInfo*)(SRAM2A_BASE);
|
||||
if(info->magic != FURI_HAL_BT_HARDFAULT_INFO_MAGIC) {
|
||||
return NULL;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
289
targets/f7/furi_hal/furi_hal_bt_hid.c
Normal file
289
targets/f7/furi_hal/furi_hal_bt_hid.c
Normal file
@@ -0,0 +1,289 @@
|
||||
#include <furi_hal_bt_hid.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <services/dev_info_service.h>
|
||||
#include <services/battery_service.h>
|
||||
#include <services/hid_service.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <usb_hid.h>
|
||||
|
||||
#define FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION (0x0101)
|
||||
#define FURI_HAL_BT_INFO_COUNTRY_CODE (0x00)
|
||||
#define FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01)
|
||||
#define FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02)
|
||||
|
||||
#define FURI_HAL_BT_HID_KB_MAX_KEYS 6
|
||||
#define FURI_HAL_BT_HID_CONSUMER_MAX_KEYS 1
|
||||
|
||||
// Report ids cant be 0
|
||||
enum HidReportId {
|
||||
ReportIdKeyboard = 1,
|
||||
ReportIdMouse = 2,
|
||||
ReportIdConsumer = 3,
|
||||
};
|
||||
// Report numbers corresponded to the report id with an offset of 1
|
||||
enum HidInputNumber {
|
||||
ReportNumberKeyboard = 0,
|
||||
ReportNumberMouse = 1,
|
||||
ReportNumberConsumer = 2,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t mods;
|
||||
uint8_t reserved;
|
||||
uint8_t key[FURI_HAL_BT_HID_KB_MAX_KEYS];
|
||||
} __attribute__((__packed__)) FuriHalBtHidKbReport;
|
||||
|
||||
typedef struct {
|
||||
uint8_t btn;
|
||||
int8_t x;
|
||||
int8_t y;
|
||||
int8_t wheel;
|
||||
} __attribute__((__packed__)) FuriHalBtHidMouseReport;
|
||||
|
||||
typedef struct {
|
||||
uint16_t key[FURI_HAL_BT_HID_CONSUMER_MAX_KEYS];
|
||||
} __attribute__((__packed__)) FuriHalBtHidConsumerReport;
|
||||
|
||||
// keyboard+mouse+consumer hid report
|
||||
static const uint8_t furi_hal_bt_hid_report_map_data[] = {
|
||||
// Keyboard Report
|
||||
HID_USAGE_PAGE(HID_PAGE_DESKTOP),
|
||||
HID_USAGE(HID_DESKTOP_KEYBOARD),
|
||||
HID_COLLECTION(HID_APPLICATION_COLLECTION),
|
||||
HID_REPORT_ID(ReportIdKeyboard),
|
||||
HID_USAGE_PAGE(HID_DESKTOP_KEYPAD),
|
||||
HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL),
|
||||
HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI),
|
||||
HID_LOGICAL_MINIMUM(0),
|
||||
HID_LOGICAL_MAXIMUM(1),
|
||||
HID_REPORT_SIZE(1),
|
||||
HID_REPORT_COUNT(8),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_REPORT_COUNT(1),
|
||||
HID_REPORT_SIZE(8),
|
||||
HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_USAGE_PAGE(HID_PAGE_LED),
|
||||
HID_REPORT_COUNT(8),
|
||||
HID_REPORT_SIZE(1),
|
||||
HID_USAGE_MINIMUM(1),
|
||||
HID_USAGE_MAXIMUM(8),
|
||||
HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_REPORT_COUNT(FURI_HAL_BT_HID_KB_MAX_KEYS),
|
||||
HID_REPORT_SIZE(8),
|
||||
HID_LOGICAL_MINIMUM(0),
|
||||
HID_LOGICAL_MAXIMUM(101),
|
||||
HID_USAGE_PAGE(HID_DESKTOP_KEYPAD),
|
||||
HID_USAGE_MINIMUM(0),
|
||||
HID_USAGE_MAXIMUM(101),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE),
|
||||
HID_END_COLLECTION,
|
||||
// Mouse Report
|
||||
HID_USAGE_PAGE(HID_PAGE_DESKTOP),
|
||||
HID_USAGE(HID_DESKTOP_MOUSE),
|
||||
HID_COLLECTION(HID_APPLICATION_COLLECTION),
|
||||
HID_USAGE(HID_DESKTOP_POINTER),
|
||||
HID_COLLECTION(HID_PHYSICAL_COLLECTION),
|
||||
HID_REPORT_ID(ReportIdMouse),
|
||||
HID_USAGE_PAGE(HID_PAGE_BUTTON),
|
||||
HID_USAGE_MINIMUM(1),
|
||||
HID_USAGE_MAXIMUM(3),
|
||||
HID_LOGICAL_MINIMUM(0),
|
||||
HID_LOGICAL_MAXIMUM(1),
|
||||
HID_REPORT_COUNT(3),
|
||||
HID_REPORT_SIZE(1),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_REPORT_SIZE(1),
|
||||
HID_REPORT_COUNT(5),
|
||||
HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_USAGE_PAGE(HID_PAGE_DESKTOP),
|
||||
HID_USAGE(HID_DESKTOP_X),
|
||||
HID_USAGE(HID_DESKTOP_Y),
|
||||
HID_USAGE(HID_DESKTOP_WHEEL),
|
||||
HID_LOGICAL_MINIMUM(-127),
|
||||
HID_LOGICAL_MAXIMUM(127),
|
||||
HID_REPORT_SIZE(8),
|
||||
HID_REPORT_COUNT(3),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE),
|
||||
HID_END_COLLECTION,
|
||||
HID_END_COLLECTION,
|
||||
// Consumer Report
|
||||
HID_USAGE_PAGE(HID_PAGE_CONSUMER),
|
||||
HID_USAGE(HID_CONSUMER_CONTROL),
|
||||
HID_COLLECTION(HID_APPLICATION_COLLECTION),
|
||||
HID_REPORT_ID(ReportIdConsumer),
|
||||
HID_LOGICAL_MINIMUM(0),
|
||||
HID_RI_LOGICAL_MAXIMUM(16, 0x3FF),
|
||||
HID_USAGE_MINIMUM(0),
|
||||
HID_RI_USAGE_MAXIMUM(16, 0x3FF),
|
||||
HID_REPORT_COUNT(FURI_HAL_BT_HID_CONSUMER_MAX_KEYS),
|
||||
HID_REPORT_SIZE(16),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE),
|
||||
HID_END_COLLECTION,
|
||||
};
|
||||
FuriHalBtHidKbReport* kb_report = NULL;
|
||||
FuriHalBtHidMouseReport* mouse_report = NULL;
|
||||
FuriHalBtHidConsumerReport* consumer_report = NULL;
|
||||
|
||||
void furi_hal_bt_hid_start() {
|
||||
// Start device info
|
||||
if(!dev_info_svc_is_started()) {
|
||||
dev_info_svc_start();
|
||||
}
|
||||
// Start battery service
|
||||
if(!battery_svc_is_started()) {
|
||||
battery_svc_start();
|
||||
}
|
||||
// Start HID service
|
||||
if(!hid_svc_is_started()) {
|
||||
hid_svc_start();
|
||||
}
|
||||
// Configure HID Keyboard
|
||||
kb_report = malloc(sizeof(FuriHalBtHidKbReport));
|
||||
mouse_report = malloc(sizeof(FuriHalBtHidMouseReport));
|
||||
consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport));
|
||||
// Configure Report Map characteristic
|
||||
hid_svc_update_report_map(
|
||||
furi_hal_bt_hid_report_map_data, sizeof(furi_hal_bt_hid_report_map_data));
|
||||
// Configure HID Information characteristic
|
||||
uint8_t hid_info_val[4] = {
|
||||
FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0x00ff,
|
||||
(FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8,
|
||||
FURI_HAL_BT_INFO_COUNTRY_CODE,
|
||||
FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK |
|
||||
FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK,
|
||||
};
|
||||
hid_svc_update_info(hid_info_val);
|
||||
}
|
||||
|
||||
void furi_hal_bt_hid_stop() {
|
||||
furi_assert(kb_report);
|
||||
furi_assert(mouse_report);
|
||||
furi_assert(consumer_report);
|
||||
// Stop all services
|
||||
if(dev_info_svc_is_started()) {
|
||||
dev_info_svc_stop();
|
||||
}
|
||||
if(battery_svc_is_started()) {
|
||||
battery_svc_stop();
|
||||
}
|
||||
if(hid_svc_is_started()) {
|
||||
hid_svc_stop();
|
||||
}
|
||||
free(kb_report);
|
||||
free(mouse_report);
|
||||
free(consumer_report);
|
||||
kb_report = NULL;
|
||||
mouse_report = NULL;
|
||||
consumer_report = NULL;
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_kb_press(uint16_t button) {
|
||||
furi_assert(kb_report);
|
||||
for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) {
|
||||
if(kb_report->key[i] == 0) {
|
||||
kb_report->key[i] = button & 0xFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
kb_report->mods |= (button >> 8);
|
||||
return hid_svc_update_input_report(
|
||||
ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport));
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_kb_release(uint16_t button) {
|
||||
furi_assert(kb_report);
|
||||
for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) {
|
||||
if(kb_report->key[i] == (button & 0xFF)) {
|
||||
kb_report->key[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
kb_report->mods &= ~(button >> 8);
|
||||
return hid_svc_update_input_report(
|
||||
ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport));
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_kb_release_all() {
|
||||
furi_assert(kb_report);
|
||||
for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) {
|
||||
kb_report->key[i] = 0;
|
||||
}
|
||||
kb_report->mods = 0;
|
||||
return hid_svc_update_input_report(
|
||||
ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport));
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_consumer_key_press(uint16_t button) {
|
||||
furi_assert(consumer_report);
|
||||
for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008
|
||||
if(consumer_report->key[i] == 0) {
|
||||
consumer_report->key[i] = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hid_svc_update_input_report(
|
||||
ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport));
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_consumer_key_release(uint16_t button) {
|
||||
furi_assert(consumer_report);
|
||||
for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008
|
||||
if(consumer_report->key[i] == button) {
|
||||
consumer_report->key[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hid_svc_update_input_report(
|
||||
ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport));
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_consumer_key_release_all() {
|
||||
furi_assert(consumer_report);
|
||||
for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008
|
||||
consumer_report->key[i] = 0;
|
||||
}
|
||||
return hid_svc_update_input_report(
|
||||
ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport));
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy) {
|
||||
furi_assert(mouse_report);
|
||||
mouse_report->x = dx;
|
||||
mouse_report->y = dy;
|
||||
bool state = hid_svc_update_input_report(
|
||||
ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport));
|
||||
mouse_report->x = 0;
|
||||
mouse_report->y = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_mouse_press(uint8_t button) {
|
||||
furi_assert(mouse_report);
|
||||
mouse_report->btn |= button;
|
||||
return hid_svc_update_input_report(
|
||||
ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport));
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_mouse_release(uint8_t button) {
|
||||
furi_assert(mouse_report);
|
||||
mouse_report->btn &= ~button;
|
||||
return hid_svc_update_input_report(
|
||||
ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport));
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_mouse_release_all() {
|
||||
furi_assert(mouse_report);
|
||||
mouse_report->btn = 0;
|
||||
return hid_svc_update_input_report(
|
||||
ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport));
|
||||
}
|
||||
|
||||
bool furi_hal_bt_hid_mouse_scroll(int8_t delta) {
|
||||
furi_assert(mouse_report);
|
||||
mouse_report->wheel = delta;
|
||||
bool state = hid_svc_update_input_report(
|
||||
ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport));
|
||||
mouse_report->wheel = 0;
|
||||
return state;
|
||||
}
|
||||
64
targets/f7/furi_hal/furi_hal_bt_serial.c
Normal file
64
targets/f7/furi_hal/furi_hal_bt_serial.c
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <furi_hal_bt_serial.h>
|
||||
#include <services/dev_info_service.h>
|
||||
#include <services/battery_service.h>
|
||||
#include <services/serial_service.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
void furi_hal_bt_serial_start() {
|
||||
// Start device info
|
||||
if(!dev_info_svc_is_started()) {
|
||||
dev_info_svc_start();
|
||||
}
|
||||
// Start battery service
|
||||
if(!battery_svc_is_started()) {
|
||||
battery_svc_start();
|
||||
}
|
||||
// Start Serial service
|
||||
if(!serial_svc_is_started()) {
|
||||
serial_svc_start();
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_bt_serial_set_event_callback(
|
||||
uint16_t buff_size,
|
||||
FuriHalBtSerialCallback callback,
|
||||
void* context) {
|
||||
serial_svc_set_callbacks(buff_size, callback, context);
|
||||
}
|
||||
|
||||
void furi_hal_bt_serial_notify_buffer_is_empty() {
|
||||
serial_svc_notify_buffer_is_empty();
|
||||
}
|
||||
|
||||
void furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatus status) {
|
||||
SerialServiceRpcStatus st;
|
||||
if(status == FuriHalBtSerialRpcStatusActive) {
|
||||
st = SerialServiceRpcStatusActive;
|
||||
} else {
|
||||
st = SerialServiceRpcStatusNotActive;
|
||||
}
|
||||
serial_svc_set_rpc_status(st);
|
||||
}
|
||||
|
||||
bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size) {
|
||||
if(size > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) {
|
||||
return false;
|
||||
}
|
||||
return serial_svc_update_tx(data, size);
|
||||
}
|
||||
|
||||
void furi_hal_bt_serial_stop() {
|
||||
// Stop all services
|
||||
if(dev_info_svc_is_started()) {
|
||||
dev_info_svc_stop();
|
||||
}
|
||||
// Start battery service
|
||||
if(battery_svc_is_started()) {
|
||||
battery_svc_stop();
|
||||
}
|
||||
// Start Serial service
|
||||
if(serial_svc_is_started()) {
|
||||
serial_svc_stop();
|
||||
}
|
||||
}
|
||||
302
targets/f7/furi_hal/furi_hal_bus.c
Normal file
302
targets/f7/furi_hal/furi_hal_bus.c
Normal file
@@ -0,0 +1,302 @@
|
||||
#include <furi_hal_bus.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <stm32wbxx_ll_bus.h>
|
||||
|
||||
/* Bus bitmask definitions */
|
||||
#define FURI_HAL_BUS_IGNORE (0x0U)
|
||||
|
||||
#define FURI_HAL_BUS_AHB1_GRP1 \
|
||||
(LL_AHB1_GRP1_PERIPH_DMA1 | LL_AHB1_GRP1_PERIPH_DMA2 | LL_AHB1_GRP1_PERIPH_DMAMUX1 | \
|
||||
LL_AHB1_GRP1_PERIPH_CRC | LL_AHB1_GRP1_PERIPH_TSC)
|
||||
|
||||
#if defined(ADC_SUPPORT_5_MSPS)
|
||||
#define FURI_HAL_BUS_AHB2_GRP1 \
|
||||
(LL_AHB2_GRP1_PERIPH_GPIOA | LL_AHB2_GRP1_PERIPH_GPIOB | LL_AHB2_GRP1_PERIPH_GPIOC | \
|
||||
LL_AHB2_GRP1_PERIPH_GPIOD | LL_AHB2_GRP1_PERIPH_GPIOE | LL_AHB2_GRP1_PERIPH_GPIOH | \
|
||||
LL_AHB2_GRP1_PERIPH_ADC | LL_AHB2_GRP1_PERIPH_AES1)
|
||||
|
||||
#define FURI_HAL_BUS_APB2_GRP1 \
|
||||
(LL_APB2_GRP1_PERIPH_TIM1 | LL_APB2_GRP1_PERIPH_SPI1 | LL_APB2_GRP1_PERIPH_USART1 | \
|
||||
LL_APB2_GRP1_PERIPH_TIM16 | LL_APB2_GRP1_PERIPH_TIM17 | LL_APB2_GRP1_PERIPH_SAI1)
|
||||
#else
|
||||
#define FURI_HAL_BUS_AHB2_GRP1 \
|
||||
(LL_AHB2_GRP1_PERIPH_GPIOA | LL_AHB2_GRP1_PERIPH_GPIOB | LL_AHB2_GRP1_PERIPH_GPIOC | \
|
||||
LL_AHB2_GRP1_PERIPH_GPIOD | LL_AHB2_GRP1_PERIPH_GPIOE | LL_AHB2_GRP1_PERIPH_GPIOH | \
|
||||
LL_AHB2_GRP1_PERIPH_AES1)
|
||||
|
||||
#define FURI_HAL_BUS_APB2_GRP1 \
|
||||
(LL_APB2_GRP1_PERIPH_ADC | LL_APB2_GRP1_PERIPH_TIM1 | LL_APB2_GRP1_PERIPH_SPI1 | \
|
||||
LL_APB2_GRP1_PERIPH_USART1 | LL_APB2_GRP1_PERIPH_TIM16 | LL_APB2_GRP1_PERIPH_TIM17 | \
|
||||
LL_APB2_GRP1_PERIPH_SAI1)
|
||||
#endif
|
||||
|
||||
#define FURI_HAL_BUS_AHB3_GRP1 \
|
||||
(LL_AHB3_GRP1_PERIPH_QUADSPI | LL_AHB3_GRP1_PERIPH_PKA | LL_AHB3_GRP1_PERIPH_AES2 | \
|
||||
LL_AHB3_GRP1_PERIPH_RNG | LL_AHB3_GRP1_PERIPH_HSEM | LL_AHB3_GRP1_PERIPH_IPCC)
|
||||
// LL_AHB3_GRP1_PERIPH_FLASH enabled by default
|
||||
|
||||
#define FURI_HAL_BUS_APB1_GRP1 \
|
||||
(LL_APB1_GRP1_PERIPH_TIM2 | LL_APB1_GRP1_PERIPH_LCD | LL_APB1_GRP1_PERIPH_SPI2 | \
|
||||
LL_APB1_GRP1_PERIPH_I2C1 | LL_APB1_GRP1_PERIPH_I2C3 | LL_APB1_GRP1_PERIPH_CRS | \
|
||||
LL_APB1_GRP1_PERIPH_USB | LL_APB1_GRP1_PERIPH_LPTIM1)
|
||||
|
||||
#define FURI_HAL_BUS_APB1_GRP2 (LL_APB1_GRP2_PERIPH_LPUART1 | LL_APB1_GRP2_PERIPH_LPTIM2)
|
||||
#define FURI_HAL_BUS_APB3_GRP1 (LL_APB3_GRP1_PERIPH_RF)
|
||||
|
||||
/* Test macro definitions */
|
||||
#define FURI_HAL_BUS_IS_ALL_CLEAR(reg, value) (READ_BIT((reg), (value)) == 0UL)
|
||||
#define FURI_HAL_BUS_IS_ALL_SET(reg, value) (READ_BIT((reg), (value)) == (value))
|
||||
|
||||
#define FURI_HAL_BUS_IS_CLOCK_ENABLED(bus, value, ...) \
|
||||
(FURI_HAL_BUS_IS_ALL_SET(RCC->bus##ENR##__VA_ARGS__, (value)))
|
||||
#define FURI_HAL_BUS_IS_CLOCK_DISABLED(bus, value, ...) \
|
||||
(FURI_HAL_BUS_IS_ALL_CLEAR(RCC->bus##ENR##__VA_ARGS__, (value)))
|
||||
|
||||
#define FURI_HAL_BUS_IS_RESET_ASSERTED(bus, value, ...) \
|
||||
(FURI_HAL_BUS_IS_ALL_SET(RCC->bus##RSTR##__VA_ARGS__, (value)))
|
||||
#define FURI_HAL_BUS_IS_RESET_DEASSERTED(bus, value, ...) \
|
||||
(FURI_HAL_BUS_IS_ALL_CLEAR(RCC->bus##RSTR##__VA_ARGS__, (value)))
|
||||
|
||||
#define FURI_HAL_BUS_IS_PERIPH_ENABLED(bus, value, ...) \
|
||||
(FURI_HAL_BUS_IS_RESET_DEASSERTED(bus, (value), __VA_ARGS__) && \
|
||||
FURI_HAL_BUS_IS_CLOCK_ENABLED(bus, (value), __VA_ARGS__))
|
||||
|
||||
#define FURI_HAL_BUS_IS_PERIPH_DISABLED(bus, value, ...) \
|
||||
(FURI_HAL_BUS_IS_CLOCK_DISABLED(bus, (value), __VA_ARGS__) && \
|
||||
FURI_HAL_BUS_IS_RESET_ASSERTED(bus, (value), __VA_ARGS__))
|
||||
|
||||
/* Control macro definitions */
|
||||
#define FURI_HAL_BUS_RESET_ASSERT(bus, value, grp) LL_##bus##_GRP##grp##_ForceReset(value)
|
||||
#define FURI_HAL_BUS_RESET_DEASSERT(bus, value, grp) LL_##bus##_GRP##grp##_ReleaseReset(value)
|
||||
|
||||
#define FURI_HAL_BUS_CLOCK_ENABLE(bus, value, grp) LL_##bus##_GRP##grp##_EnableClock(value)
|
||||
#define FURI_HAL_BUS_CLOCK_DISABLE(bus, value, grp) LL_##bus##_GRP##grp##_DisableClock(value)
|
||||
|
||||
#define FURI_HAL_BUS_PERIPH_ENABLE(bus, value, grp) \
|
||||
FURI_HAL_BUS_CLOCK_ENABLE(bus, value, grp); \
|
||||
FURI_HAL_BUS_RESET_DEASSERT(bus, value, grp)
|
||||
|
||||
#define FURI_HAL_BUS_PERIPH_DISABLE(bus, value, grp) \
|
||||
FURI_HAL_BUS_RESET_ASSERT(bus, value, grp); \
|
||||
FURI_HAL_BUS_CLOCK_DISABLE(bus, value, grp)
|
||||
|
||||
#define FURI_HAL_BUS_PERIPH_RESET(bus, value, grp) \
|
||||
FURI_HAL_BUS_RESET_ASSERT(bus, value, grp); \
|
||||
FURI_HAL_BUS_RESET_DEASSERT(bus, value, grp)
|
||||
|
||||
static const uint32_t furi_hal_bus[] = {
|
||||
[FuriHalBusAHB1_GRP1] = FURI_HAL_BUS_IGNORE,
|
||||
[FuriHalBusDMA1] = LL_AHB1_GRP1_PERIPH_DMA1,
|
||||
[FuriHalBusDMA2] = LL_AHB1_GRP1_PERIPH_DMA2,
|
||||
[FuriHalBusDMAMUX1] = LL_AHB1_GRP1_PERIPH_DMAMUX1,
|
||||
[FuriHalBusCRC] = LL_AHB1_GRP1_PERIPH_CRC,
|
||||
[FuriHalBusTSC] = LL_AHB1_GRP1_PERIPH_TSC,
|
||||
|
||||
[FuriHalBusAHB2_GRP1] = FURI_HAL_BUS_IGNORE,
|
||||
[FuriHalBusGPIOA] = LL_AHB2_GRP1_PERIPH_GPIOA,
|
||||
[FuriHalBusGPIOB] = LL_AHB2_GRP1_PERIPH_GPIOB,
|
||||
[FuriHalBusGPIOC] = LL_AHB2_GRP1_PERIPH_GPIOC,
|
||||
[FuriHalBusGPIOD] = LL_AHB2_GRP1_PERIPH_GPIOD,
|
||||
[FuriHalBusGPIOE] = LL_AHB2_GRP1_PERIPH_GPIOE,
|
||||
[FuriHalBusGPIOH] = LL_AHB2_GRP1_PERIPH_GPIOH,
|
||||
#if defined(ADC_SUPPORT_5_MSPS)
|
||||
[FuriHalBusADC] = LL_AHB2_GRP1_PERIPH_ADC,
|
||||
#endif
|
||||
[FuriHalBusAES1] = LL_AHB2_GRP1_PERIPH_AES1,
|
||||
|
||||
[FuriHalBusAHB3_GRP1] = FURI_HAL_BUS_IGNORE,
|
||||
[FuriHalBusQUADSPI] = LL_AHB3_GRP1_PERIPH_QUADSPI,
|
||||
[FuriHalBusPKA] = LL_AHB3_GRP1_PERIPH_PKA,
|
||||
[FuriHalBusAES2] = LL_AHB3_GRP1_PERIPH_AES2,
|
||||
[FuriHalBusRNG] = LL_AHB3_GRP1_PERIPH_RNG,
|
||||
[FuriHalBusHSEM] = LL_AHB3_GRP1_PERIPH_HSEM,
|
||||
[FuriHalBusIPCC] = LL_AHB3_GRP1_PERIPH_IPCC,
|
||||
[FuriHalBusFLASH] = LL_AHB3_GRP1_PERIPH_FLASH,
|
||||
|
||||
[FuriHalBusAPB1_GRP1] = FURI_HAL_BUS_APB1_GRP1,
|
||||
[FuriHalBusTIM2] = LL_APB1_GRP1_PERIPH_TIM2,
|
||||
[FuriHalBusLCD] = LL_APB1_GRP1_PERIPH_LCD,
|
||||
[FuriHalBusSPI2] = LL_APB1_GRP1_PERIPH_SPI2,
|
||||
[FuriHalBusI2C1] = LL_APB1_GRP1_PERIPH_I2C1,
|
||||
[FuriHalBusI2C3] = LL_APB1_GRP1_PERIPH_I2C3,
|
||||
[FuriHalBusCRS] = LL_APB1_GRP1_PERIPH_CRS,
|
||||
[FuriHalBusUSB] = LL_APB1_GRP1_PERIPH_USB,
|
||||
[FuriHalBusLPTIM1] = LL_APB1_GRP1_PERIPH_LPTIM1,
|
||||
|
||||
[FuriHalBusAPB1_GRP2] = FURI_HAL_BUS_APB1_GRP2,
|
||||
[FuriHalBusLPUART1] = LL_APB1_GRP2_PERIPH_LPUART1,
|
||||
[FuriHalBusLPTIM2] = LL_APB1_GRP2_PERIPH_LPTIM2,
|
||||
|
||||
[FuriHalBusAPB2_GRP1] = FURI_HAL_BUS_APB2_GRP1,
|
||||
#if defined(ADC_SUPPORT_2_5_MSPS)
|
||||
[FuriHalBusADC] = LL_APB2_GRP1_PERIPH_ADC,
|
||||
#endif
|
||||
[FuriHalBusTIM1] = LL_APB2_GRP1_PERIPH_TIM1,
|
||||
[FuriHalBusSPI1] = LL_APB2_GRP1_PERIPH_SPI1,
|
||||
[FuriHalBusUSART1] = LL_APB2_GRP1_PERIPH_USART1,
|
||||
[FuriHalBusTIM16] = LL_APB2_GRP1_PERIPH_TIM16,
|
||||
[FuriHalBusTIM17] = LL_APB2_GRP1_PERIPH_TIM17,
|
||||
[FuriHalBusSAI1] = LL_APB2_GRP1_PERIPH_SAI1,
|
||||
|
||||
[FuriHalBusAPB3_GRP1] = FURI_HAL_BUS_IGNORE, // APB3_GRP1 clocking cannot be changed
|
||||
[FuriHalBusRF] = LL_APB3_GRP1_PERIPH_RF,
|
||||
};
|
||||
|
||||
void furi_hal_bus_init_early() {
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
// FURI_HAL_BUS_PERIPH_DISABLE(AHB1, FURI_HAL_BUS_AHB1_GRP1, 1);
|
||||
// FURI_HAL_BUS_PERIPH_DISABLE(AHB2, FURI_HAL_BUS_AHB2_GRP1, 1);
|
||||
// FURI_HAL_BUS_PERIPH_DISABLE(AHB3, FURI_HAL_BUS_AHB3_GRP1, 1);
|
||||
FURI_HAL_BUS_PERIPH_DISABLE(APB1, FURI_HAL_BUS_APB1_GRP1, 1);
|
||||
FURI_HAL_BUS_PERIPH_DISABLE(APB1, FURI_HAL_BUS_APB1_GRP2, 2);
|
||||
FURI_HAL_BUS_PERIPH_DISABLE(APB2, FURI_HAL_BUS_APB2_GRP1, 1);
|
||||
|
||||
FURI_HAL_BUS_RESET_ASSERT(APB3, FURI_HAL_BUS_APB3_GRP1, 1);
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_bus_deinit_early() {
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
// FURI_HAL_BUS_PERIPH_ENABLE(AHB1, FURI_HAL_BUS_AHB1_GRP1, 1);
|
||||
// FURI_HAL_BUS_PERIPH_ENABLE(AHB2, FURI_HAL_BUS_AHB2_GRP1, 1);
|
||||
// FURI_HAL_BUS_PERIPH_ENABLE(AHB3, FURI_HAL_BUS_AHB3_GRP1, 1);
|
||||
FURI_HAL_BUS_PERIPH_ENABLE(APB1, FURI_HAL_BUS_APB1_GRP1, 1);
|
||||
FURI_HAL_BUS_PERIPH_ENABLE(APB1, FURI_HAL_BUS_APB1_GRP2, 2);
|
||||
FURI_HAL_BUS_PERIPH_ENABLE(APB2, FURI_HAL_BUS_APB2_GRP1, 1);
|
||||
|
||||
FURI_HAL_BUS_RESET_DEASSERT(APB3, FURI_HAL_BUS_APB3_GRP1, 1);
|
||||
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_bus_enable(FuriHalBus bus) {
|
||||
furi_check(bus < FuriHalBusMAX);
|
||||
const uint32_t value = furi_hal_bus[bus];
|
||||
if(!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
if(bus < FuriHalBusAHB2_GRP1) {
|
||||
// furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB1, value));
|
||||
FURI_HAL_BUS_PERIPH_ENABLE(AHB1, value, 1);
|
||||
} else if(bus < FuriHalBusAHB3_GRP1) {
|
||||
// furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB2, value));
|
||||
FURI_HAL_BUS_PERIPH_ENABLE(AHB2, value, 1);
|
||||
} else if(bus < FuriHalBusAPB1_GRP1) {
|
||||
// furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(AHB3, value));
|
||||
FURI_HAL_BUS_PERIPH_ENABLE(AHB3, value, 1);
|
||||
} else if(bus < FuriHalBusAPB1_GRP2) {
|
||||
furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(APB1, value, 1));
|
||||
FURI_HAL_BUS_PERIPH_ENABLE(APB1, value, 1);
|
||||
} else if(bus < FuriHalBusAPB2_GRP1) {
|
||||
furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(APB1, value, 2));
|
||||
FURI_HAL_BUS_PERIPH_ENABLE(APB1, value, 2);
|
||||
} else if(bus < FuriHalBusAPB3_GRP1) {
|
||||
furi_check(FURI_HAL_BUS_IS_PERIPH_DISABLED(APB2, value));
|
||||
FURI_HAL_BUS_PERIPH_ENABLE(APB2, value, 1);
|
||||
} else {
|
||||
furi_check(FURI_HAL_BUS_IS_RESET_ASSERTED(APB3, value));
|
||||
FURI_HAL_BUS_RESET_DEASSERT(APB3, FURI_HAL_BUS_APB3_GRP1, 1);
|
||||
}
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_bus_reset(FuriHalBus bus) {
|
||||
furi_check(bus < FuriHalBusMAX);
|
||||
const uint32_t value = furi_hal_bus[bus];
|
||||
if(!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
if(bus < FuriHalBusAHB2_GRP1) {
|
||||
// furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value));
|
||||
FURI_HAL_BUS_PERIPH_RESET(AHB1, value, 1);
|
||||
} else if(bus < FuriHalBusAHB3_GRP1) {
|
||||
// furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value));
|
||||
FURI_HAL_BUS_PERIPH_RESET(AHB2, value, 1);
|
||||
} else if(bus < FuriHalBusAPB1_GRP1) {
|
||||
// furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value));
|
||||
FURI_HAL_BUS_PERIPH_RESET(AHB3, value, 1);
|
||||
} else if(bus < FuriHalBusAPB1_GRP2) {
|
||||
furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 1));
|
||||
FURI_HAL_BUS_PERIPH_RESET(APB1, value, 1);
|
||||
} else if(bus < FuriHalBusAPB2_GRP1) {
|
||||
furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 2));
|
||||
FURI_HAL_BUS_PERIPH_RESET(APB1, value, 2);
|
||||
} else if(bus < FuriHalBusAPB3_GRP1) {
|
||||
furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB2, value));
|
||||
FURI_HAL_BUS_PERIPH_RESET(APB2, value, 1);
|
||||
} else {
|
||||
furi_check(FURI_HAL_BUS_IS_RESET_DEASSERTED(APB3, value));
|
||||
FURI_HAL_BUS_PERIPH_RESET(APB3, value, 1);
|
||||
}
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_bus_disable(FuriHalBus bus) {
|
||||
furi_check(bus < FuriHalBusMAX);
|
||||
const uint32_t value = furi_hal_bus[bus];
|
||||
if(!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
if(bus < FuriHalBusAHB2_GRP1) {
|
||||
// furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value));
|
||||
FURI_HAL_BUS_PERIPH_DISABLE(AHB1, value, 1);
|
||||
} else if(bus < FuriHalBusAHB3_GRP1) {
|
||||
// furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value));
|
||||
FURI_HAL_BUS_PERIPH_DISABLE(AHB2, value, 1);
|
||||
} else if(bus < FuriHalBusAPB1_GRP1) {
|
||||
// furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value));
|
||||
FURI_HAL_BUS_PERIPH_DISABLE(AHB3, value, 1);
|
||||
} else if(bus < FuriHalBusAPB1_GRP2) {
|
||||
furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 1));
|
||||
FURI_HAL_BUS_PERIPH_DISABLE(APB1, value, 1);
|
||||
} else if(bus < FuriHalBusAPB2_GRP1) {
|
||||
furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 2));
|
||||
FURI_HAL_BUS_PERIPH_DISABLE(APB1, value, 2);
|
||||
} else if(bus < FuriHalBusAPB3_GRP1) {
|
||||
furi_check(FURI_HAL_BUS_IS_PERIPH_ENABLED(APB2, value));
|
||||
FURI_HAL_BUS_PERIPH_DISABLE(APB2, value, 1);
|
||||
} else {
|
||||
furi_check(FURI_HAL_BUS_IS_RESET_DEASSERTED(APB3, value));
|
||||
FURI_HAL_BUS_RESET_ASSERT(APB3, FURI_HAL_BUS_APB3_GRP1, 1);
|
||||
}
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
bool furi_hal_bus_is_enabled(FuriHalBus bus) {
|
||||
furi_check(bus < FuriHalBusMAX);
|
||||
const uint32_t value = furi_hal_bus[bus];
|
||||
if(value == FURI_HAL_BUS_IGNORE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
FURI_CRITICAL_ENTER();
|
||||
if(bus < FuriHalBusAHB2_GRP1) {
|
||||
ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB1, value);
|
||||
} else if(bus < FuriHalBusAHB3_GRP1) {
|
||||
ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB2, value);
|
||||
} else if(bus < FuriHalBusAPB1_GRP1) {
|
||||
ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(AHB3, value);
|
||||
} else if(bus < FuriHalBusAPB1_GRP2) {
|
||||
ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 1);
|
||||
} else if(bus < FuriHalBusAPB2_GRP1) {
|
||||
ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(APB1, value, 2);
|
||||
} else if(bus < FuriHalBusAPB3_GRP1) {
|
||||
ret = FURI_HAL_BUS_IS_PERIPH_ENABLED(APB2, value);
|
||||
} else {
|
||||
ret = FURI_HAL_BUS_IS_RESET_DEASSERTED(APB3, value);
|
||||
}
|
||||
FURI_CRITICAL_EXIT();
|
||||
|
||||
return ret;
|
||||
}
|
||||
112
targets/f7/furi_hal/furi_hal_bus.h
Normal file
112
targets/f7/furi_hal/furi_hal_bus.h
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "stm32wbxx.h"
|
||||
#include "stdbool.h"
|
||||
|
||||
typedef enum {
|
||||
FuriHalBusAHB1_GRP1,
|
||||
FuriHalBusDMA1,
|
||||
FuriHalBusDMA2,
|
||||
FuriHalBusDMAMUX1,
|
||||
FuriHalBusCRC,
|
||||
FuriHalBusTSC,
|
||||
|
||||
FuriHalBusAHB2_GRP1,
|
||||
FuriHalBusGPIOA,
|
||||
FuriHalBusGPIOB,
|
||||
FuriHalBusGPIOC,
|
||||
FuriHalBusGPIOD,
|
||||
FuriHalBusGPIOE,
|
||||
FuriHalBusGPIOH,
|
||||
#if defined(ADC_SUPPORT_5_MSPS)
|
||||
FuriHalBusADC,
|
||||
#endif
|
||||
FuriHalBusAES1,
|
||||
|
||||
FuriHalBusAHB3_GRP1,
|
||||
FuriHalBusQUADSPI,
|
||||
FuriHalBusPKA,
|
||||
FuriHalBusAES2,
|
||||
FuriHalBusRNG,
|
||||
FuriHalBusHSEM,
|
||||
FuriHalBusIPCC,
|
||||
FuriHalBusFLASH,
|
||||
|
||||
FuriHalBusAPB1_GRP1,
|
||||
FuriHalBusTIM2,
|
||||
FuriHalBusLCD,
|
||||
FuriHalBusSPI2,
|
||||
FuriHalBusI2C1,
|
||||
FuriHalBusI2C3,
|
||||
FuriHalBusCRS,
|
||||
FuriHalBusUSB,
|
||||
FuriHalBusLPTIM1,
|
||||
|
||||
FuriHalBusAPB1_GRP2,
|
||||
FuriHalBusLPUART1,
|
||||
FuriHalBusLPTIM2,
|
||||
|
||||
FuriHalBusAPB2_GRP1,
|
||||
#if defined(ADC_SUPPORT_2_5_MSPS)
|
||||
FuriHalBusADC,
|
||||
#endif
|
||||
FuriHalBusTIM1,
|
||||
FuriHalBusSPI1,
|
||||
FuriHalBusUSART1,
|
||||
FuriHalBusTIM16,
|
||||
FuriHalBusTIM17,
|
||||
FuriHalBusSAI1,
|
||||
|
||||
FuriHalBusAPB3_GRP1,
|
||||
FuriHalBusRF,
|
||||
|
||||
FuriHalBusMAX,
|
||||
} FuriHalBus;
|
||||
|
||||
/** Early initialization */
|
||||
void furi_hal_bus_init_early();
|
||||
|
||||
/** Early de-initialization */
|
||||
void furi_hal_bus_deinit_early();
|
||||
|
||||
/**
|
||||
* Enable a peripheral by turning the clocking on and deasserting the reset.
|
||||
* @param [in] bus Peripheral to be enabled.
|
||||
* @warning Peripheral must be in disabled state in order to be enabled.
|
||||
*/
|
||||
void furi_hal_bus_enable(FuriHalBus bus);
|
||||
|
||||
/**
|
||||
* Reset a peripheral by sequentially asserting and deasserting the reset.
|
||||
* @param [in] bus Peripheral to be reset.
|
||||
* @warning Peripheral must be in enabled state in order to be reset.
|
||||
*/
|
||||
void furi_hal_bus_reset(FuriHalBus bus);
|
||||
|
||||
/**
|
||||
* Disable a peripheral by turning the clocking off and asserting the reset.
|
||||
* @param [in] bus Peripheral to be disabled.
|
||||
* @warning Peripheral must be in enabled state in order to be disabled.
|
||||
*/
|
||||
void furi_hal_bus_disable(FuriHalBus bus);
|
||||
|
||||
/** Check if peripheral is enabled
|
||||
*
|
||||
* @warning FuriHalBusAPB3_GRP1 is a special group of shared peripherals, for
|
||||
* core1 its clock is always on and the only status we can report is
|
||||
* peripheral reset status. Check code and Reference Manual for
|
||||
* details.
|
||||
*
|
||||
* @param[in] bus The peripheral to check
|
||||
*
|
||||
* @return true if enabled or always enabled, false otherwise
|
||||
*/
|
||||
bool furi_hal_bus_is_enabled(FuriHalBus bus);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
279
targets/f7/furi_hal/furi_hal_clock.c
Normal file
279
targets/f7/furi_hal/furi_hal_clock.c
Normal file
@@ -0,0 +1,279 @@
|
||||
#include <furi_hal_clock.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <stm32wbxx_ll_pwr.h>
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
#include <stm32wbxx_ll_hsem.h>
|
||||
#include <stm32wbxx_ll_utils.h>
|
||||
#include <stm32wbxx_ll_cortex.h>
|
||||
|
||||
#include <hsem_map.h>
|
||||
#include <interface/patterns/ble_thread/shci/shci.h>
|
||||
|
||||
#define TAG "FuriHalClock"
|
||||
|
||||
#define CPU_CLOCK_EARLY_HZ 4000000
|
||||
#define CPU_CLOCK_HSI16_HZ 16000000
|
||||
#define CPU_CLOCK_HSE_HZ 32000000
|
||||
#define CPU_CLOCK_PLL_HZ 64000000
|
||||
|
||||
#define TICK_INT_PRIORITY 15U
|
||||
#define HS_CLOCK_IS_READY() (LL_RCC_HSE_IsReady() && LL_RCC_HSI_IsReady())
|
||||
#define LS_CLOCK_IS_READY() (LL_RCC_LSE_IsReady() && LL_RCC_LSI1_IsReady())
|
||||
|
||||
void furi_hal_clock_init_early() {
|
||||
LL_SetSystemCoreClock(CPU_CLOCK_EARLY_HZ);
|
||||
LL_Init1msTick(SystemCoreClock);
|
||||
}
|
||||
|
||||
void furi_hal_clock_deinit_early() {
|
||||
}
|
||||
|
||||
void furi_hal_clock_init() {
|
||||
/* HSE and HSI configuration and activation */
|
||||
LL_RCC_HSE_SetCapacitorTuning(0x26);
|
||||
LL_RCC_HSE_Enable();
|
||||
LL_RCC_HSI_Enable();
|
||||
while(!HS_CLOCK_IS_READY())
|
||||
;
|
||||
/* Select HSI as system clock source after Wake Up from Stop mode
|
||||
* Must be set before enabling CSS */
|
||||
LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI);
|
||||
|
||||
LL_RCC_HSE_EnableCSS();
|
||||
|
||||
/* LSE and LSI1 configuration and activation */
|
||||
LL_PWR_EnableBkUpAccess();
|
||||
LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_HIGH);
|
||||
LL_RCC_LSE_Enable();
|
||||
LL_RCC_LSI1_Enable();
|
||||
while(!LS_CLOCK_IS_READY())
|
||||
;
|
||||
|
||||
LL_EXTI_EnableIT_0_31(
|
||||
LL_EXTI_LINE_18); /* Why? Because that's why. See RM0434, Table 61. CPU1 vector table. */
|
||||
LL_EXTI_EnableRisingTrig_0_31(LL_EXTI_LINE_18);
|
||||
LL_RCC_EnableIT_LSECSS();
|
||||
/* ES0394, extended case of 2.2.2 */
|
||||
if(!LL_RCC_IsActiveFlag_BORRST()) {
|
||||
LL_RCC_LSE_EnableCSS();
|
||||
}
|
||||
|
||||
/* Main PLL configuration and activation */
|
||||
LL_RCC_PLL_ConfigDomain_SYS(LL_RCC_PLLSOURCE_HSE, LL_RCC_PLLM_DIV_2, 8, LL_RCC_PLLR_DIV_2);
|
||||
LL_RCC_PLL_Enable();
|
||||
LL_RCC_PLL_EnableDomain_SYS();
|
||||
while(LL_RCC_PLL_IsReady() != 1)
|
||||
;
|
||||
|
||||
LL_RCC_PLLSAI1_ConfigDomain_48M(
|
||||
LL_RCC_PLLSOURCE_HSE, LL_RCC_PLLM_DIV_2, 6, LL_RCC_PLLSAI1Q_DIV_2);
|
||||
LL_RCC_PLLSAI1_ConfigDomain_ADC(
|
||||
LL_RCC_PLLSOURCE_HSE, LL_RCC_PLLM_DIV_2, 6, LL_RCC_PLLSAI1R_DIV_2);
|
||||
LL_RCC_PLLSAI1_Enable();
|
||||
LL_RCC_PLLSAI1_EnableDomain_48M();
|
||||
LL_RCC_PLLSAI1_EnableDomain_ADC();
|
||||
while(LL_RCC_PLLSAI1_IsReady() != 1)
|
||||
;
|
||||
|
||||
/* Sysclk activation on the main PLL */
|
||||
/* Set CPU1 prescaler */
|
||||
LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
|
||||
|
||||
/* Set CPU2 prescaler, from this point we are not allowed to touch it. */
|
||||
LL_C2_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_2);
|
||||
|
||||
/* Prepare Flash memory for work on 64MHz system clock */
|
||||
LL_FLASH_SetLatency(LL_FLASH_LATENCY_3);
|
||||
while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_3)
|
||||
;
|
||||
|
||||
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_PLL);
|
||||
while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_PLL)
|
||||
;
|
||||
|
||||
/* Set AHB SHARED prescaler*/
|
||||
LL_RCC_SetAHB4Prescaler(LL_RCC_SYSCLK_DIV_1);
|
||||
|
||||
/* Set APB1 prescaler*/
|
||||
LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
|
||||
|
||||
/* Set APB2 prescaler*/
|
||||
LL_RCC_SetAPB2Prescaler(LL_RCC_APB2_DIV_1);
|
||||
|
||||
/* Disable MSI */
|
||||
LL_RCC_MSI_Disable();
|
||||
while(LL_RCC_MSI_IsReady() != 0)
|
||||
;
|
||||
|
||||
/* Update CMSIS variable (which can be updated also through SystemCoreClockUpdate function) */
|
||||
LL_SetSystemCoreClock(CPU_CLOCK_PLL_HZ);
|
||||
|
||||
/* Update the time base */
|
||||
LL_Init1msTick(SystemCoreClock);
|
||||
LL_SYSTICK_EnableIT();
|
||||
NVIC_SetPriority(
|
||||
SysTick_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), TICK_INT_PRIORITY, 0));
|
||||
NVIC_EnableIRQ(SysTick_IRQn);
|
||||
|
||||
LL_RCC_SetCLK48ClockSource(LL_RCC_CLK48_CLKSOURCE_PLLSAI1);
|
||||
LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI);
|
||||
LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1);
|
||||
LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE);
|
||||
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
void furi_hal_clock_switch_hse2hsi() {
|
||||
LL_RCC_HSI_Enable();
|
||||
|
||||
while(!LL_RCC_HSI_IsReady())
|
||||
;
|
||||
|
||||
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSI);
|
||||
furi_assert(LL_RCC_GetSMPSClockSelection() == LL_RCC_SMPS_CLKSOURCE_HSI);
|
||||
|
||||
while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSI)
|
||||
;
|
||||
|
||||
LL_FLASH_SetLatency(LL_FLASH_LATENCY_0);
|
||||
while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_0)
|
||||
;
|
||||
}
|
||||
|
||||
void furi_hal_clock_switch_hsi2hse() {
|
||||
#ifdef FURI_HAL_CLOCK_TRACK_STARTUP
|
||||
uint32_t clock_start_time = DWT->CYCCNT;
|
||||
#endif
|
||||
|
||||
LL_RCC_HSE_Enable();
|
||||
while(!LL_RCC_HSE_IsReady())
|
||||
;
|
||||
|
||||
LL_FLASH_SetLatency(LL_FLASH_LATENCY_1);
|
||||
while(LL_FLASH_GetLatency() != LL_FLASH_LATENCY_1)
|
||||
;
|
||||
|
||||
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_HSE);
|
||||
|
||||
while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_HSE)
|
||||
;
|
||||
|
||||
#ifdef FURI_HAL_CLOCK_TRACK_STARTUP
|
||||
uint32_t total = DWT->CYCCNT - clock_start_time;
|
||||
if(total > (20 * 0x148)) {
|
||||
furi_crash("Slow HSE/PLL startup");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool furi_hal_clock_switch_hse2pll() {
|
||||
furi_assert(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE);
|
||||
|
||||
LL_RCC_PLL_Enable();
|
||||
LL_RCC_PLLSAI1_Enable();
|
||||
|
||||
while(!LL_RCC_PLL_IsReady())
|
||||
;
|
||||
while(!LL_RCC_PLLSAI1_IsReady())
|
||||
;
|
||||
|
||||
if(SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_HSE_TO_PLL) != SHCI_Success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL);
|
||||
|
||||
LL_SetSystemCoreClock(CPU_CLOCK_PLL_HZ);
|
||||
SysTick->LOAD = (uint32_t)((SystemCoreClock / 1000) - 1UL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_clock_switch_pll2hse() {
|
||||
furi_assert(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_PLL);
|
||||
|
||||
LL_RCC_HSE_Enable();
|
||||
while(!LL_RCC_HSE_IsReady())
|
||||
;
|
||||
|
||||
if(SHCI_C2_SetSystemClock(SET_SYSTEM_CLOCK_PLL_ON_TO_HSE) != SHCI_Success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE);
|
||||
|
||||
LL_SetSystemCoreClock(CPU_CLOCK_HSE_HZ);
|
||||
SysTick->LOAD = (uint32_t)((SystemCoreClock / 1000) - 1UL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void furi_hal_clock_suspend_tick() {
|
||||
CLEAR_BIT(SysTick->CTRL, SysTick_CTRL_ENABLE_Msk);
|
||||
}
|
||||
|
||||
void furi_hal_clock_resume_tick() {
|
||||
SET_BIT(SysTick->CTRL, SysTick_CTRL_ENABLE_Msk);
|
||||
}
|
||||
|
||||
void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div) {
|
||||
if(source == FuriHalClockMcoLse) {
|
||||
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_LSE, div);
|
||||
} else if(source == FuriHalClockMcoSysclk) {
|
||||
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_SYSCLK, div);
|
||||
} else {
|
||||
LL_RCC_MSI_Enable();
|
||||
while(LL_RCC_MSI_IsReady() != 1)
|
||||
;
|
||||
switch(source) {
|
||||
case FuriHalClockMcoMsi100k:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_0);
|
||||
break;
|
||||
case FuriHalClockMcoMsi200k:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_1);
|
||||
break;
|
||||
case FuriHalClockMcoMsi400k:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_2);
|
||||
break;
|
||||
case FuriHalClockMcoMsi800k:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_3);
|
||||
break;
|
||||
case FuriHalClockMcoMsi1m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_4);
|
||||
break;
|
||||
case FuriHalClockMcoMsi2m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_5);
|
||||
break;
|
||||
case FuriHalClockMcoMsi4m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_6);
|
||||
break;
|
||||
case FuriHalClockMcoMsi8m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_7);
|
||||
break;
|
||||
case FuriHalClockMcoMsi16m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_8);
|
||||
break;
|
||||
case FuriHalClockMcoMsi24m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_9);
|
||||
break;
|
||||
case FuriHalClockMcoMsi32m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_10);
|
||||
break;
|
||||
case FuriHalClockMcoMsi48m:
|
||||
LL_RCC_MSI_SetRange(LL_RCC_MSIRANGE_11);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_MSI, div);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_clock_mco_disable() {
|
||||
LL_RCC_ConfigMCO(LL_RCC_MCO1SOURCE_NOCLOCK, FuriHalClockMcoDiv1);
|
||||
LL_RCC_MSI_Disable();
|
||||
while(LL_RCC_MSI_IsReady() != 0)
|
||||
;
|
||||
}
|
||||
80
targets/f7/furi_hal/furi_hal_clock.h
Normal file
80
targets/f7/furi_hal/furi_hal_clock.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
FuriHalClockMcoLse,
|
||||
FuriHalClockMcoSysclk,
|
||||
FuriHalClockMcoMsi100k,
|
||||
FuriHalClockMcoMsi200k,
|
||||
FuriHalClockMcoMsi400k,
|
||||
FuriHalClockMcoMsi800k,
|
||||
FuriHalClockMcoMsi1m,
|
||||
FuriHalClockMcoMsi2m,
|
||||
FuriHalClockMcoMsi4m,
|
||||
FuriHalClockMcoMsi8m,
|
||||
FuriHalClockMcoMsi16m,
|
||||
FuriHalClockMcoMsi24m,
|
||||
FuriHalClockMcoMsi32m,
|
||||
FuriHalClockMcoMsi48m,
|
||||
} FuriHalClockMcoSourceId;
|
||||
|
||||
typedef enum {
|
||||
FuriHalClockMcoDiv1 = LL_RCC_MCO1_DIV_1,
|
||||
FuriHalClockMcoDiv2 = LL_RCC_MCO1_DIV_2,
|
||||
FuriHalClockMcoDiv4 = LL_RCC_MCO1_DIV_4,
|
||||
FuriHalClockMcoDiv8 = LL_RCC_MCO1_DIV_8,
|
||||
FuriHalClockMcoDiv16 = LL_RCC_MCO1_DIV_16,
|
||||
} FuriHalClockMcoDivisorId;
|
||||
|
||||
/** Early initialization */
|
||||
void furi_hal_clock_init_early();
|
||||
|
||||
/** Early deinitialization */
|
||||
void furi_hal_clock_deinit_early();
|
||||
|
||||
/** Initialize clocks */
|
||||
void furi_hal_clock_init();
|
||||
|
||||
/** Switch clock from HSE to HSI */
|
||||
void furi_hal_clock_switch_hse2hsi();
|
||||
|
||||
/** Switch clock from HSI to HSE */
|
||||
void furi_hal_clock_switch_hsi2hse();
|
||||
|
||||
/** Switch clock from HSE to PLL
|
||||
*
|
||||
* @return true if changed, false if failed or not possible at this moment
|
||||
*/
|
||||
bool furi_hal_clock_switch_hse2pll();
|
||||
|
||||
/** Switch clock from PLL to HSE
|
||||
*
|
||||
* @return true if changed, false if failed or not possible at this moment
|
||||
*/
|
||||
bool furi_hal_clock_switch_pll2hse();
|
||||
|
||||
/** Stop SysTick counter without resetting */
|
||||
void furi_hal_clock_suspend_tick();
|
||||
|
||||
/** Continue SysTick counter operation */
|
||||
void furi_hal_clock_resume_tick();
|
||||
|
||||
/** Enable clock output on MCO pin
|
||||
*
|
||||
* @param source MCO clock source
|
||||
* @param div MCO clock division
|
||||
*/
|
||||
void furi_hal_clock_mco_enable(FuriHalClockMcoSourceId source, FuriHalClockMcoDivisorId div);
|
||||
|
||||
/** Disable clock output on MCO pin */
|
||||
void furi_hal_clock_mco_disable();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
99
targets/f7/furi_hal/furi_hal_console.c
Normal file
99
targets/f7/furi_hal/furi_hal_console.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include <furi_hal_console.h>
|
||||
#include <furi_hal_uart.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stm32wbxx_ll_gpio.h>
|
||||
#include <stm32wbxx_ll_usart.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "FuriHalConsole"
|
||||
|
||||
#ifdef HEAP_PRINT_DEBUG
|
||||
#define CONSOLE_BAUDRATE 1843200
|
||||
#else
|
||||
#define CONSOLE_BAUDRATE 230400
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
bool alive;
|
||||
FuriHalConsoleTxCallback tx_callback;
|
||||
void* tx_callback_context;
|
||||
} FuriHalConsole;
|
||||
|
||||
FuriHalConsole furi_hal_console = {
|
||||
.alive = false,
|
||||
.tx_callback = NULL,
|
||||
.tx_callback_context = NULL,
|
||||
};
|
||||
|
||||
void furi_hal_console_init() {
|
||||
furi_hal_uart_init(FuriHalUartIdUSART1, CONSOLE_BAUDRATE);
|
||||
furi_hal_console.alive = true;
|
||||
}
|
||||
|
||||
void furi_hal_console_enable() {
|
||||
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, NULL, NULL);
|
||||
while(!LL_USART_IsActiveFlag_TC(USART1))
|
||||
;
|
||||
furi_hal_uart_set_br(FuriHalUartIdUSART1, CONSOLE_BAUDRATE);
|
||||
furi_hal_console.alive = true;
|
||||
}
|
||||
|
||||
void furi_hal_console_disable() {
|
||||
while(!LL_USART_IsActiveFlag_TC(USART1))
|
||||
;
|
||||
furi_hal_console.alive = false;
|
||||
}
|
||||
|
||||
void furi_hal_console_set_tx_callback(FuriHalConsoleTxCallback callback, void* context) {
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_hal_console.tx_callback = callback;
|
||||
furi_hal_console.tx_callback_context = context;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size) {
|
||||
if(!furi_hal_console.alive) return;
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
// Transmit data
|
||||
|
||||
if(furi_hal_console.tx_callback) {
|
||||
furi_hal_console.tx_callback(buffer, buffer_size, furi_hal_console.tx_callback_context);
|
||||
}
|
||||
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size);
|
||||
// Wait for TC flag to be raised for last char
|
||||
while(!LL_USART_IsActiveFlag_TC(USART1))
|
||||
;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size) {
|
||||
if(!furi_hal_console.alive) return;
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
// Transmit data
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size);
|
||||
// Transmit new line symbols
|
||||
furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)"\r\n", 2);
|
||||
// Wait for TC flag to be raised for last char
|
||||
while(!LL_USART_IsActiveFlag_TC(USART1))
|
||||
;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_console_printf(const char format[], ...) {
|
||||
FuriString* string;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
string = furi_string_alloc_vprintf(format, args);
|
||||
va_end(args);
|
||||
furi_hal_console_tx((const uint8_t*)furi_string_get_cstr(string), furi_string_size(string));
|
||||
furi_string_free(string);
|
||||
}
|
||||
|
||||
void furi_hal_console_puts(const char* data) {
|
||||
furi_hal_console_tx((const uint8_t*)data, strlen(data));
|
||||
}
|
||||
37
targets/f7/furi_hal/furi_hal_console.h
Normal file
37
targets/f7/furi_hal/furi_hal_console.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*FuriHalConsoleTxCallback)(const uint8_t* buffer, size_t size, void* context);
|
||||
|
||||
void furi_hal_console_init();
|
||||
|
||||
void furi_hal_console_enable();
|
||||
|
||||
void furi_hal_console_disable();
|
||||
|
||||
void furi_hal_console_set_tx_callback(FuriHalConsoleTxCallback callback, void* context);
|
||||
|
||||
void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
/**
|
||||
* Printf-like plain uart interface
|
||||
* @warning Will not work in ISR context
|
||||
* @param format
|
||||
* @param ...
|
||||
*/
|
||||
void furi_hal_console_printf(const char format[], ...) _ATTRIBUTE((__format__(__printf__, 1, 2)));
|
||||
|
||||
void furi_hal_console_puts(const char* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
114
targets/f7/furi_hal/furi_hal_cortex.c
Normal file
114
targets/f7/furi_hal/furi_hal_cortex.c
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <furi_hal_cortex.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <stm32wbxx.h>
|
||||
|
||||
#define FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND (SystemCoreClock / 1000000)
|
||||
|
||||
void furi_hal_cortex_init_early() {
|
||||
CoreDebug->DEMCR |= (CoreDebug_DEMCR_TRCENA_Msk | CoreDebug_DEMCR_MON_EN_Msk);
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
DWT->CYCCNT = 0U;
|
||||
|
||||
/* Enable instruction prefetch */
|
||||
SET_BIT(FLASH->ACR, FLASH_ACR_PRFTEN);
|
||||
}
|
||||
|
||||
void furi_hal_cortex_delay_us(uint32_t microseconds) {
|
||||
furi_check(microseconds < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND));
|
||||
|
||||
uint32_t start = DWT->CYCCNT;
|
||||
uint32_t time_ticks = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * microseconds;
|
||||
|
||||
while((DWT->CYCCNT - start) < time_ticks) {
|
||||
};
|
||||
}
|
||||
|
||||
uint32_t furi_hal_cortex_instructions_per_microsecond() {
|
||||
return FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND;
|
||||
}
|
||||
|
||||
FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us) {
|
||||
furi_check(timeout_us < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND));
|
||||
|
||||
FuriHalCortexTimer cortex_timer = {0};
|
||||
cortex_timer.start = DWT->CYCCNT;
|
||||
cortex_timer.value = FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND * timeout_us;
|
||||
return cortex_timer;
|
||||
}
|
||||
|
||||
bool furi_hal_cortex_timer_is_expired(FuriHalCortexTimer cortex_timer) {
|
||||
return !((DWT->CYCCNT - cortex_timer.start) < cortex_timer.value);
|
||||
}
|
||||
|
||||
void furi_hal_cortex_timer_wait(FuriHalCortexTimer cortex_timer) {
|
||||
while(!furi_hal_cortex_timer_is_expired(cortex_timer))
|
||||
;
|
||||
}
|
||||
|
||||
// Duck ST
|
||||
#undef COMP0
|
||||
#undef COMP1
|
||||
#undef COMP2
|
||||
#undef COMP3
|
||||
|
||||
void furi_hal_cortex_comp_enable(
|
||||
FuriHalCortexComp comp,
|
||||
FuriHalCortexCompFunction function,
|
||||
uint32_t value,
|
||||
uint32_t mask,
|
||||
FuriHalCortexCompSize size) {
|
||||
uint32_t function_reg = (uint32_t)function | ((uint32_t)size << 10);
|
||||
|
||||
switch(comp) {
|
||||
case FuriHalCortexComp0:
|
||||
(DWT->COMP0) = value;
|
||||
(DWT->MASK0) = mask;
|
||||
(DWT->FUNCTION0) = function_reg;
|
||||
break;
|
||||
case FuriHalCortexComp1:
|
||||
(DWT->COMP1) = value;
|
||||
(DWT->MASK1) = mask;
|
||||
(DWT->FUNCTION1) = function_reg;
|
||||
break;
|
||||
case FuriHalCortexComp2:
|
||||
(DWT->COMP2) = value;
|
||||
(DWT->MASK2) = mask;
|
||||
(DWT->FUNCTION2) = function_reg;
|
||||
break;
|
||||
case FuriHalCortexComp3:
|
||||
(DWT->COMP3) = value;
|
||||
(DWT->MASK3) = mask;
|
||||
(DWT->FUNCTION3) = function_reg;
|
||||
break;
|
||||
default:
|
||||
furi_crash("Invalid parameter");
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_cortex_comp_reset(FuriHalCortexComp comp) {
|
||||
switch(comp) {
|
||||
case FuriHalCortexComp0:
|
||||
(DWT->COMP0) = 0;
|
||||
(DWT->MASK0) = 0;
|
||||
(DWT->FUNCTION0) = 0;
|
||||
break;
|
||||
case FuriHalCortexComp1:
|
||||
(DWT->COMP1) = 0;
|
||||
(DWT->MASK1) = 0;
|
||||
(DWT->FUNCTION1) = 0;
|
||||
break;
|
||||
case FuriHalCortexComp2:
|
||||
(DWT->COMP2) = 0;
|
||||
(DWT->MASK2) = 0;
|
||||
(DWT->FUNCTION2) = 0;
|
||||
break;
|
||||
case FuriHalCortexComp3:
|
||||
(DWT->COMP3) = 0;
|
||||
(DWT->MASK3) = 0;
|
||||
(DWT->FUNCTION3) = 0;
|
||||
break;
|
||||
default:
|
||||
furi_crash("Invalid parameter");
|
||||
}
|
||||
}
|
||||
732
targets/f7/furi_hal/furi_hal_crypto.c
Normal file
732
targets/f7/furi_hal/furi_hal_crypto.c
Normal file
@@ -0,0 +1,732 @@
|
||||
#include <furi_hal_crypto.h>
|
||||
#include <furi_hal_cortex.h>
|
||||
#include <furi_hal_bt.h>
|
||||
#include <furi_hal_random.h>
|
||||
#include <furi_hal_bus.h>
|
||||
|
||||
#include <stm32wbxx_ll_cortex.h>
|
||||
#include <furi.h>
|
||||
#include <interface/patterns/ble_thread/shci/shci.h>
|
||||
|
||||
#define TAG "FuriHalCrypto"
|
||||
|
||||
#define ENCLAVE_FACTORY_KEY_SLOTS 10
|
||||
#define ENCLAVE_SIGNATURE_SIZE 16
|
||||
|
||||
#define CRYPTO_BLK_LEN (4 * sizeof(uint32_t))
|
||||
#define CRYPTO_TIMEOUT_US (1000000)
|
||||
|
||||
#define CRYPTO_MODE_ENCRYPT 0U
|
||||
#define CRYPTO_MODE_INIT (AES_CR_MODE_0)
|
||||
#define CRYPTO_MODE_DECRYPT (AES_CR_MODE_1)
|
||||
#define CRYPTO_MODE_DECRYPT_INIT (AES_CR_MODE_0 | AES_CR_MODE_1)
|
||||
|
||||
#define CRYPTO_DATATYPE_32B 0U
|
||||
#define CRYPTO_KEYSIZE_256B (AES_CR_KEYSIZE)
|
||||
#define CRYPTO_AES_CBC (AES_CR_CHMOD_0)
|
||||
|
||||
#define CRYPTO_AES_CTR (AES_CR_CHMOD_1)
|
||||
#define CRYPTO_CTR_IV_LEN (12U)
|
||||
#define CRYPTO_CTR_CTR_LEN (4U)
|
||||
|
||||
#define CRYPTO_AES_GCM (AES_CR_CHMOD_1 | AES_CR_CHMOD_0)
|
||||
#define CRYPTO_GCM_IV_LEN (12U)
|
||||
#define CRYPTO_GCM_CTR_LEN (4U)
|
||||
#define CRYPTO_GCM_TAG_LEN (16U)
|
||||
#define CRYPTO_GCM_PH_INIT (0x0U << AES_CR_GCMPH_Pos)
|
||||
#define CRYPTO_GCM_PH_HEADER (AES_CR_GCMPH_0)
|
||||
#define CRYPTO_GCM_PH_PAYLOAD (AES_CR_GCMPH_1)
|
||||
#define CRYPTO_GCM_PH_FINAL (AES_CR_GCMPH_1 | AES_CR_GCMPH_0)
|
||||
|
||||
static FuriMutex* furi_hal_crypto_mutex = NULL;
|
||||
static bool furi_hal_crypto_mode_init_done = false;
|
||||
|
||||
static const uint8_t enclave_signature_iv[ENCLAVE_FACTORY_KEY_SLOTS][16] = {
|
||||
{0xac, 0x5d, 0x68, 0xb8, 0x79, 0x74, 0xfc, 0x7f, 0x45, 0x02, 0x82, 0xf1, 0x48, 0x7e, 0x75, 0x8a},
|
||||
{0x38, 0xe6, 0x6a, 0x90, 0x5e, 0x5b, 0x8a, 0xa6, 0x70, 0x30, 0x04, 0x72, 0xc2, 0x42, 0xea, 0xaf},
|
||||
{0x73, 0xd5, 0x8e, 0xfb, 0x0f, 0x4b, 0xa9, 0x79, 0x0f, 0xde, 0x0e, 0x53, 0x44, 0x7d, 0xaa, 0xfd},
|
||||
{0x3c, 0x9a, 0xf4, 0x43, 0x2b, 0xfe, 0xea, 0xae, 0x8c, 0xc6, 0xd1, 0x60, 0xd2, 0x96, 0x64, 0xa9},
|
||||
{0x10, 0xac, 0x7b, 0x63, 0x03, 0x7f, 0x43, 0x18, 0xec, 0x9d, 0x9c, 0xc4, 0x01, 0xdc, 0x35, 0xa7},
|
||||
{0x26, 0x21, 0x64, 0xe6, 0xd0, 0xf2, 0x47, 0x49, 0xdc, 0x36, 0xcd, 0x68, 0x0c, 0x91, 0x03, 0x44},
|
||||
{0x7a, 0xbd, 0xce, 0x9c, 0x24, 0x7a, 0x2a, 0xb1, 0x3c, 0x4f, 0x5a, 0x7d, 0x80, 0x3e, 0xfc, 0x0d},
|
||||
{0xcd, 0xdd, 0xd3, 0x02, 0x85, 0x65, 0x43, 0x83, 0xf9, 0xac, 0x75, 0x2f, 0x21, 0xef, 0x28, 0x6b},
|
||||
{0xab, 0x73, 0x70, 0xe8, 0xe2, 0x56, 0x0f, 0x58, 0xab, 0x29, 0xa5, 0xb1, 0x13, 0x47, 0x5e, 0xe8},
|
||||
{0x4f, 0x3c, 0x43, 0x77, 0xde, 0xed, 0x79, 0xa1, 0x8d, 0x4c, 0x1f, 0xfd, 0xdb, 0x96, 0x87, 0x2e},
|
||||
};
|
||||
|
||||
static const uint8_t enclave_signature_input[ENCLAVE_FACTORY_KEY_SLOTS][ENCLAVE_SIGNATURE_SIZE] = {
|
||||
{0x9f, 0x5c, 0xb1, 0x43, 0x17, 0x53, 0x18, 0x8c, 0x66, 0x3d, 0x39, 0x45, 0x90, 0x13, 0xa9, 0xde},
|
||||
{0xc5, 0x98, 0xe9, 0x17, 0xb8, 0x97, 0x9e, 0x03, 0x33, 0x14, 0x13, 0x8f, 0xce, 0x74, 0x0d, 0x54},
|
||||
{0x34, 0xba, 0x99, 0x59, 0x9f, 0x70, 0x67, 0xe9, 0x09, 0xee, 0x64, 0x0e, 0xb3, 0xba, 0xfb, 0x75},
|
||||
{0xdc, 0xfa, 0x6c, 0x9a, 0x6f, 0x0a, 0x3e, 0xdc, 0x42, 0xf6, 0xae, 0x0d, 0x3c, 0xf7, 0x83, 0xaf},
|
||||
{0xea, 0x2d, 0xe3, 0x1f, 0x02, 0x99, 0x1a, 0x7e, 0x6d, 0x93, 0x4c, 0xb5, 0x42, 0xf0, 0x7a, 0x9b},
|
||||
{0x53, 0x5e, 0x04, 0xa2, 0x49, 0xa0, 0x73, 0x49, 0x56, 0xb0, 0x88, 0x8c, 0x12, 0xa0, 0xe4, 0x18},
|
||||
{0x7d, 0xa7, 0xc5, 0x21, 0x7f, 0x12, 0x95, 0xdd, 0x4d, 0x77, 0x01, 0xfa, 0x71, 0x88, 0x2b, 0x7f},
|
||||
{0xdc, 0x9b, 0xc5, 0xa7, 0x6b, 0x84, 0x5c, 0x37, 0x7c, 0xec, 0x05, 0xa1, 0x9f, 0x91, 0x17, 0x3b},
|
||||
{0xea, 0xcf, 0xd9, 0x9b, 0x86, 0xcd, 0x2b, 0x43, 0x54, 0x45, 0x82, 0xc6, 0xfe, 0x73, 0x1a, 0x1a},
|
||||
{0x77, 0xb8, 0x1b, 0x90, 0xb4, 0xb7, 0x32, 0x76, 0x8f, 0x8a, 0x57, 0x06, 0xc7, 0xdd, 0x08, 0x90},
|
||||
};
|
||||
|
||||
static const uint8_t enclave_signature_expected[ENCLAVE_FACTORY_KEY_SLOTS][ENCLAVE_SIGNATURE_SIZE] = {
|
||||
{0xe9, 0x9a, 0xce, 0xe9, 0x4d, 0xe1, 0x7f, 0x55, 0xcb, 0x8a, 0xbf, 0xf2, 0x4d, 0x98, 0x27, 0x67},
|
||||
{0x34, 0x27, 0xa7, 0xea, 0xa8, 0x98, 0x66, 0x9b, 0xed, 0x43, 0xd3, 0x93, 0xb5, 0xa2, 0x87, 0x8e},
|
||||
{0x6c, 0xf3, 0x01, 0x78, 0x53, 0x1b, 0x11, 0x32, 0xf0, 0x27, 0x2f, 0xe3, 0x7d, 0xa6, 0xe2, 0xfd},
|
||||
{0xdf, 0x7f, 0x37, 0x65, 0x2f, 0xdb, 0x7c, 0xcf, 0x5b, 0xb6, 0xe4, 0x9c, 0x63, 0xc5, 0x0f, 0xe0},
|
||||
{0x9b, 0x5c, 0xee, 0x44, 0x0e, 0xd1, 0xcb, 0x5f, 0x28, 0x9f, 0x12, 0x17, 0x59, 0x64, 0x40, 0xbb},
|
||||
{0x94, 0xc2, 0x09, 0x98, 0x62, 0xa7, 0x2b, 0x93, 0xed, 0x36, 0x1f, 0x10, 0xbc, 0x26, 0xbd, 0x41},
|
||||
{0x4d, 0xb2, 0x2b, 0xc5, 0x96, 0x47, 0x61, 0xf4, 0x16, 0xe0, 0x81, 0xc3, 0x8e, 0xb9, 0x9c, 0x9b},
|
||||
{0xc3, 0x6b, 0x83, 0x55, 0x90, 0x38, 0x0f, 0xea, 0xd1, 0x65, 0xbf, 0x32, 0x4f, 0x8e, 0x62, 0x5b},
|
||||
{0x8d, 0x5e, 0x27, 0xbc, 0x14, 0x4f, 0x08, 0xa8, 0x2b, 0x14, 0x89, 0x5e, 0xdf, 0x77, 0x04, 0x31},
|
||||
{0xc9, 0xf7, 0x03, 0xf1, 0x6c, 0x65, 0xad, 0x49, 0x74, 0xbe, 0x00, 0x54, 0xfd, 0xa6, 0x9c, 0x32},
|
||||
};
|
||||
|
||||
void furi_hal_crypto_init() {
|
||||
furi_hal_crypto_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_generate_unique_keys(uint8_t start_slot, uint8_t end_slot) {
|
||||
FuriHalCryptoKey key;
|
||||
uint8_t key_data[32];
|
||||
FURI_LOG_I(TAG, "Generating keys %u..%u", start_slot, end_slot);
|
||||
for(uint8_t slot = start_slot; slot <= end_slot; slot++) {
|
||||
key.type = FuriHalCryptoKeyTypeSimple;
|
||||
key.size = FuriHalCryptoKeySize256;
|
||||
key.data = key_data;
|
||||
furi_hal_random_fill_buf(key_data, 32);
|
||||
if(!furi_hal_crypto_enclave_store_key(&key, &slot)) {
|
||||
FURI_LOG_E(TAG, "Error writing key to slot %u", slot);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_enclave_ensure_key(uint8_t key_slot) {
|
||||
uint8_t keys_nb = 0;
|
||||
uint8_t valid_keys_nb = 0;
|
||||
uint8_t last_valid_slot = ENCLAVE_FACTORY_KEY_SLOTS;
|
||||
uint8_t empty_iv[16] = {0};
|
||||
furi_hal_crypto_enclave_verify(&keys_nb, &valid_keys_nb);
|
||||
if(key_slot <= ENCLAVE_FACTORY_KEY_SLOTS) { // It's a factory key
|
||||
if(key_slot > keys_nb) return false;
|
||||
} else { // Unique key
|
||||
if(keys_nb < ENCLAVE_FACTORY_KEY_SLOTS) // Some factory keys are missing
|
||||
return false;
|
||||
for(uint8_t i = key_slot; i > ENCLAVE_FACTORY_KEY_SLOTS; i--) {
|
||||
if(furi_hal_crypto_enclave_load_key(i, empty_iv)) {
|
||||
last_valid_slot = i;
|
||||
furi_hal_crypto_enclave_unload_key(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(last_valid_slot == key_slot)
|
||||
return true;
|
||||
else // Generate missing unique keys
|
||||
return furi_hal_crypto_generate_unique_keys(last_valid_slot + 1, key_slot);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_enclave_verify(uint8_t* keys_nb, uint8_t* valid_keys_nb) {
|
||||
furi_assert(keys_nb);
|
||||
furi_assert(valid_keys_nb);
|
||||
uint8_t keys = 0;
|
||||
uint8_t keys_valid = 0;
|
||||
uint8_t buffer[ENCLAVE_SIGNATURE_SIZE];
|
||||
for(size_t key_slot = 0; key_slot < ENCLAVE_FACTORY_KEY_SLOTS; key_slot++) {
|
||||
if(furi_hal_crypto_enclave_load_key(key_slot + 1, enclave_signature_iv[key_slot])) {
|
||||
keys++;
|
||||
if(furi_hal_crypto_encrypt(
|
||||
enclave_signature_input[key_slot], buffer, ENCLAVE_SIGNATURE_SIZE)) {
|
||||
keys_valid +=
|
||||
memcmp(buffer, enclave_signature_expected[key_slot], ENCLAVE_SIGNATURE_SIZE) ==
|
||||
0;
|
||||
}
|
||||
furi_hal_crypto_enclave_unload_key(key_slot + 1);
|
||||
}
|
||||
}
|
||||
*keys_nb = keys;
|
||||
*valid_keys_nb = keys_valid;
|
||||
if(*valid_keys_nb == ENCLAVE_FACTORY_KEY_SLOTS)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_enclave_store_key(FuriHalCryptoKey* key, uint8_t* slot) {
|
||||
furi_assert(key);
|
||||
furi_assert(slot);
|
||||
|
||||
furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
if(!furi_hal_bt_is_alive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SHCI_C2_FUS_StoreUsrKey_Cmd_Param_t pParam;
|
||||
size_t key_data_size = 0;
|
||||
|
||||
if(key->type == FuriHalCryptoKeyTypeMaster) {
|
||||
pParam.KeyType = KEYTYPE_MASTER;
|
||||
} else if(key->type == FuriHalCryptoKeyTypeSimple) {
|
||||
pParam.KeyType = KEYTYPE_SIMPLE;
|
||||
} else if(key->type == FuriHalCryptoKeyTypeEncrypted) {
|
||||
pParam.KeyType = KEYTYPE_ENCRYPTED;
|
||||
key_data_size += 12;
|
||||
} else {
|
||||
furi_crash("Incorrect key type");
|
||||
}
|
||||
|
||||
if(key->size == FuriHalCryptoKeySize128) {
|
||||
pParam.KeySize = KEYSIZE_16;
|
||||
key_data_size += 16;
|
||||
} else if(key->size == FuriHalCryptoKeySize256) {
|
||||
pParam.KeySize = KEYSIZE_32;
|
||||
key_data_size += 32;
|
||||
} else {
|
||||
furi_crash("Incorrect key size");
|
||||
}
|
||||
|
||||
memcpy(pParam.KeyData, key->data, key_data_size);
|
||||
|
||||
SHCI_CmdStatus_t shci_state = SHCI_C2_FUS_StoreUsrKey(&pParam, slot);
|
||||
furi_check(furi_mutex_release(furi_hal_crypto_mutex) == FuriStatusOk);
|
||||
return (shci_state == SHCI_Success);
|
||||
}
|
||||
|
||||
static void crypto_key_init(uint32_t* key, uint32_t* iv) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
MODIFY_REG(
|
||||
AES1->CR,
|
||||
AES_CR_DATATYPE | AES_CR_KEYSIZE | AES_CR_CHMOD,
|
||||
CRYPTO_DATATYPE_32B | CRYPTO_KEYSIZE_256B | CRYPTO_AES_CBC);
|
||||
|
||||
if(key != NULL) {
|
||||
AES1->KEYR7 = key[0];
|
||||
AES1->KEYR6 = key[1];
|
||||
AES1->KEYR5 = key[2];
|
||||
AES1->KEYR4 = key[3];
|
||||
AES1->KEYR3 = key[4];
|
||||
AES1->KEYR2 = key[5];
|
||||
AES1->KEYR1 = key[6];
|
||||
AES1->KEYR0 = key[7];
|
||||
}
|
||||
|
||||
AES1->IVR3 = iv[0];
|
||||
AES1->IVR2 = iv[1];
|
||||
AES1->IVR1 = iv[2];
|
||||
AES1->IVR0 = iv[3];
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_wait_flag(uint32_t flag) {
|
||||
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(CRYPTO_TIMEOUT_US);
|
||||
while(!READ_BIT(AES1->SR, flag)) {
|
||||
if(furi_hal_cortex_timer_is_expired(timer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool crypto_process_block(uint32_t* in, uint32_t* out, uint8_t blk_len) {
|
||||
furi_check((blk_len <= 4) && (blk_len > 0));
|
||||
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
if(i < blk_len) {
|
||||
AES1->DINR = in[i];
|
||||
} else {
|
||||
AES1->DINR = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(!furi_hal_crypto_wait_flag(AES_SR_CCF)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SET_BIT(AES1->CR, AES_CR_CCFC);
|
||||
|
||||
uint32_t out_temp[4];
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
out_temp[i] = AES1->DOUTR;
|
||||
}
|
||||
|
||||
memcpy(out, out_temp, blk_len * sizeof(uint32_t));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_enclave_load_key(uint8_t slot, const uint8_t* iv) {
|
||||
furi_assert(slot > 0 && slot <= 100);
|
||||
furi_assert(furi_hal_crypto_mutex);
|
||||
furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
furi_hal_bus_enable(FuriHalBusAES1);
|
||||
|
||||
if(!furi_hal_bt_is_alive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
furi_hal_crypto_mode_init_done = false;
|
||||
crypto_key_init(NULL, (uint32_t*)iv);
|
||||
|
||||
if(SHCI_C2_FUS_LoadUsrKey(slot) == SHCI_Success) {
|
||||
return true;
|
||||
} else {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
furi_check(furi_mutex_release(furi_hal_crypto_mutex) == FuriStatusOk);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_enclave_unload_key(uint8_t slot) {
|
||||
if(!furi_hal_bt_is_alive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
SHCI_CmdStatus_t shci_state = SHCI_C2_FUS_UnloadUsrKey(slot);
|
||||
furi_assert(shci_state == SHCI_Success);
|
||||
|
||||
furi_hal_bus_disable(FuriHalBusAES1);
|
||||
|
||||
furi_check(furi_mutex_release(furi_hal_crypto_mutex) == FuriStatusOk);
|
||||
return (shci_state == SHCI_Success);
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_load_key(const uint8_t* key, const uint8_t* iv) {
|
||||
furi_assert(furi_hal_crypto_mutex);
|
||||
furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
furi_hal_bus_enable(FuriHalBusAES1);
|
||||
|
||||
furi_hal_crypto_mode_init_done = false;
|
||||
crypto_key_init((uint32_t*)key, (uint32_t*)iv);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_unload_key(void) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
furi_hal_bus_disable(FuriHalBusAES1);
|
||||
|
||||
furi_check(furi_mutex_release(furi_hal_crypto_mutex) == FuriStatusOk);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_encrypt(const uint8_t* input, uint8_t* output, size_t size) {
|
||||
bool state = false;
|
||||
|
||||
SET_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_ENCRYPT);
|
||||
|
||||
for(size_t i = 0; i < size; i += CRYPTO_BLK_LEN) {
|
||||
size_t blk_len = size - i;
|
||||
if(blk_len > CRYPTO_BLK_LEN) {
|
||||
blk_len = CRYPTO_BLK_LEN;
|
||||
}
|
||||
state = crypto_process_block((uint32_t*)&input[i], (uint32_t*)&output[i], blk_len / 4);
|
||||
if(state == false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_decrypt(const uint8_t* input, uint8_t* output, size_t size) {
|
||||
bool state = false;
|
||||
|
||||
if(!furi_hal_crypto_mode_init_done) {
|
||||
MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_INIT);
|
||||
|
||||
SET_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
if(!furi_hal_crypto_wait_flag(AES_SR_CCF)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SET_BIT(AES1->CR, AES_CR_CCFC);
|
||||
|
||||
furi_hal_crypto_mode_init_done = true;
|
||||
}
|
||||
|
||||
MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_DECRYPT);
|
||||
SET_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
for(size_t i = 0; i < size; i += CRYPTO_BLK_LEN) {
|
||||
size_t blk_len = size - i;
|
||||
if(blk_len > CRYPTO_BLK_LEN) {
|
||||
blk_len = CRYPTO_BLK_LEN;
|
||||
}
|
||||
state = crypto_process_block((uint32_t*)&input[i], (uint32_t*)&output[i], blk_len / 4);
|
||||
if(state == false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static void crypto_key_init_bswap(uint32_t* key, uint32_t* iv, uint32_t chaining_mode) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
MODIFY_REG(
|
||||
AES1->CR,
|
||||
AES_CR_DATATYPE | AES_CR_KEYSIZE | AES_CR_CHMOD,
|
||||
CRYPTO_DATATYPE_32B | CRYPTO_KEYSIZE_256B | chaining_mode);
|
||||
|
||||
if(key != NULL) {
|
||||
AES1->KEYR7 = __builtin_bswap32(key[0]);
|
||||
AES1->KEYR6 = __builtin_bswap32(key[1]);
|
||||
AES1->KEYR5 = __builtin_bswap32(key[2]);
|
||||
AES1->KEYR4 = __builtin_bswap32(key[3]);
|
||||
AES1->KEYR3 = __builtin_bswap32(key[4]);
|
||||
AES1->KEYR2 = __builtin_bswap32(key[5]);
|
||||
AES1->KEYR1 = __builtin_bswap32(key[6]);
|
||||
AES1->KEYR0 = __builtin_bswap32(key[7]);
|
||||
}
|
||||
|
||||
AES1->IVR3 = __builtin_bswap32(iv[0]);
|
||||
AES1->IVR2 = __builtin_bswap32(iv[1]);
|
||||
AES1->IVR1 = __builtin_bswap32(iv[2]);
|
||||
AES1->IVR0 = __builtin_bswap32(iv[3]);
|
||||
}
|
||||
|
||||
static bool
|
||||
furi_hal_crypto_load_key_bswap(const uint8_t* key, const uint8_t* iv, uint32_t chaining_mode) {
|
||||
furi_assert(furi_hal_crypto_mutex);
|
||||
furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
furi_hal_bus_enable(FuriHalBusAES1);
|
||||
|
||||
crypto_key_init_bswap((uint32_t*)key, (uint32_t*)iv, chaining_mode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool wait_for_crypto(void) {
|
||||
if(!furi_hal_crypto_wait_flag(AES_SR_CCF)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SET_BIT(AES1->CR, AES_CR_CCFC);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_process_block_bswap(const uint8_t* in, uint8_t* out, size_t bytes) {
|
||||
uint32_t block[CRYPTO_BLK_LEN / 4];
|
||||
memset(block, 0, sizeof(block));
|
||||
|
||||
memcpy(block, in, bytes);
|
||||
|
||||
block[0] = __builtin_bswap32(block[0]);
|
||||
block[1] = __builtin_bswap32(block[1]);
|
||||
block[2] = __builtin_bswap32(block[2]);
|
||||
block[3] = __builtin_bswap32(block[3]);
|
||||
|
||||
if(!crypto_process_block(block, block, CRYPTO_BLK_LEN / 4)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
block[0] = __builtin_bswap32(block[0]);
|
||||
block[1] = __builtin_bswap32(block[1]);
|
||||
block[2] = __builtin_bswap32(block[2]);
|
||||
block[3] = __builtin_bswap32(block[3]);
|
||||
|
||||
memcpy(out, block, bytes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_process_block_no_read_bswap(const uint8_t* in, size_t bytes) {
|
||||
uint32_t block[CRYPTO_BLK_LEN / 4];
|
||||
memset(block, 0, sizeof(block));
|
||||
|
||||
memcpy(block, in, bytes);
|
||||
|
||||
AES1->DINR = __builtin_bswap32(block[0]);
|
||||
AES1->DINR = __builtin_bswap32(block[1]);
|
||||
AES1->DINR = __builtin_bswap32(block[2]);
|
||||
AES1->DINR = __builtin_bswap32(block[3]);
|
||||
|
||||
return wait_for_crypto();
|
||||
}
|
||||
|
||||
static void furi_hal_crypto_ctr_prep_iv(uint8_t* iv) {
|
||||
/* append counter to IV */
|
||||
iv[CRYPTO_CTR_IV_LEN] = 0;
|
||||
iv[CRYPTO_CTR_IV_LEN + 1] = 0;
|
||||
iv[CRYPTO_CTR_IV_LEN + 2] = 0;
|
||||
iv[CRYPTO_CTR_IV_LEN + 3] = 1;
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_ctr_payload(const uint8_t* input, uint8_t* output, size_t length) {
|
||||
SET_BIT(AES1->CR, AES_CR_EN);
|
||||
MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_ENCRYPT);
|
||||
|
||||
size_t last_block_bytes = length % CRYPTO_BLK_LEN;
|
||||
|
||||
size_t i;
|
||||
for(i = 0; i < length - last_block_bytes; i += CRYPTO_BLK_LEN) {
|
||||
if(!furi_hal_crypto_process_block_bswap(&input[i], &output[i], CRYPTO_BLK_LEN)) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(last_block_bytes > 0) {
|
||||
if(!furi_hal_crypto_process_block_bswap(&input[i], &output[i], last_block_bytes)) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_ctr(
|
||||
const uint8_t* key,
|
||||
const uint8_t* iv,
|
||||
const uint8_t* input,
|
||||
uint8_t* output,
|
||||
size_t length) {
|
||||
/* prepare IV and counter */
|
||||
uint8_t iv_and_counter[CRYPTO_CTR_IV_LEN + CRYPTO_CTR_CTR_LEN];
|
||||
memcpy(iv_and_counter, iv, CRYPTO_CTR_IV_LEN); //-V1086
|
||||
furi_hal_crypto_ctr_prep_iv(iv_and_counter);
|
||||
|
||||
/* load key and IV and set the mode to CTR */
|
||||
if(!furi_hal_crypto_load_key_bswap(key, iv_and_counter, CRYPTO_AES_CTR)) {
|
||||
furi_hal_crypto_unload_key();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* process the input and write to output */
|
||||
bool state = furi_hal_crypto_ctr_payload(input, output, length);
|
||||
|
||||
furi_hal_crypto_unload_key();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static void furi_hal_crypto_gcm_prep_iv(uint8_t* iv) {
|
||||
/* append counter to IV */
|
||||
iv[CRYPTO_GCM_IV_LEN] = 0;
|
||||
iv[CRYPTO_GCM_IV_LEN + 1] = 0;
|
||||
iv[CRYPTO_GCM_IV_LEN + 2] = 0;
|
||||
iv[CRYPTO_GCM_IV_LEN + 3] = 2;
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_gcm_init(bool decrypt) {
|
||||
/* GCM init phase */
|
||||
|
||||
MODIFY_REG(AES1->CR, AES_CR_GCMPH, CRYPTO_GCM_PH_INIT);
|
||||
if(decrypt) {
|
||||
MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_DECRYPT);
|
||||
} else {
|
||||
MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_ENCRYPT);
|
||||
}
|
||||
|
||||
SET_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
if(!wait_for_crypto()) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_gcm_header(const uint8_t* aad, size_t aad_length) {
|
||||
/* GCM header phase */
|
||||
|
||||
MODIFY_REG(AES1->CR, AES_CR_GCMPH, CRYPTO_GCM_PH_HEADER);
|
||||
SET_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
size_t last_block_bytes = aad_length % CRYPTO_BLK_LEN;
|
||||
|
||||
size_t i;
|
||||
for(i = 0; i < aad_length - last_block_bytes; i += CRYPTO_BLK_LEN) {
|
||||
if(!furi_hal_crypto_process_block_no_read_bswap(&aad[i], CRYPTO_BLK_LEN)) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(last_block_bytes > 0) {
|
||||
if(!furi_hal_crypto_process_block_no_read_bswap(&aad[i], last_block_bytes)) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_gcm_payload(
|
||||
const uint8_t* input,
|
||||
uint8_t* output,
|
||||
size_t length,
|
||||
bool decrypt) {
|
||||
/* GCM payload phase */
|
||||
|
||||
MODIFY_REG(AES1->CR, AES_CR_GCMPH, CRYPTO_GCM_PH_PAYLOAD);
|
||||
SET_BIT(AES1->CR, AES_CR_EN);
|
||||
|
||||
size_t last_block_bytes = length % CRYPTO_BLK_LEN;
|
||||
|
||||
size_t i;
|
||||
for(i = 0; i < length - last_block_bytes; i += CRYPTO_BLK_LEN) {
|
||||
if(!furi_hal_crypto_process_block_bswap(&input[i], &output[i], CRYPTO_BLK_LEN)) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(last_block_bytes > 0) {
|
||||
if(!decrypt) {
|
||||
MODIFY_REG(
|
||||
AES1->CR, AES_CR_NPBLB, (CRYPTO_BLK_LEN - last_block_bytes) << AES_CR_NPBLB_Pos);
|
||||
}
|
||||
if(!furi_hal_crypto_process_block_bswap(&input[i], &output[i], last_block_bytes)) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_gcm_finish(size_t aad_length, size_t payload_length, uint8_t* tag) {
|
||||
/* GCM final phase */
|
||||
|
||||
MODIFY_REG(AES1->CR, AES_CR_GCMPH, CRYPTO_GCM_PH_FINAL);
|
||||
|
||||
uint32_t last_block[CRYPTO_BLK_LEN / 4];
|
||||
memset(last_block, 0, sizeof(last_block));
|
||||
last_block[1] = __builtin_bswap32((uint32_t)(aad_length * 8));
|
||||
last_block[3] = __builtin_bswap32((uint32_t)(payload_length * 8));
|
||||
|
||||
if(!furi_hal_crypto_process_block_bswap((uint8_t*)&last_block[0], tag, CRYPTO_BLK_LEN)) {
|
||||
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool furi_hal_crypto_gcm_compare_tag(const uint8_t* tag1, const uint8_t* tag2) {
|
||||
uint8_t diff = 0;
|
||||
|
||||
size_t i;
|
||||
for(i = 0; i < CRYPTO_GCM_TAG_LEN; i++) {
|
||||
diff |= tag1[i] ^ tag2[i];
|
||||
}
|
||||
|
||||
return (diff == 0);
|
||||
}
|
||||
|
||||
bool furi_hal_crypto_gcm(
|
||||
const uint8_t* key,
|
||||
const uint8_t* iv,
|
||||
const uint8_t* aad,
|
||||
size_t aad_length,
|
||||
const uint8_t* input,
|
||||
uint8_t* output,
|
||||
size_t length,
|
||||
uint8_t* tag,
|
||||
bool decrypt) {
|
||||
/* GCM init phase */
|
||||
|
||||
/* prepare IV and counter */
|
||||
uint8_t iv_and_counter[CRYPTO_GCM_IV_LEN + CRYPTO_GCM_CTR_LEN];
|
||||
memcpy(iv_and_counter, iv, CRYPTO_GCM_IV_LEN); //-V1086
|
||||
furi_hal_crypto_gcm_prep_iv(iv_and_counter);
|
||||
|
||||
/* load key and IV and set the mode to CTR */
|
||||
if(!furi_hal_crypto_load_key_bswap(key, iv_and_counter, CRYPTO_AES_GCM)) {
|
||||
furi_hal_crypto_unload_key();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!furi_hal_crypto_gcm_init(decrypt)) {
|
||||
furi_hal_crypto_unload_key();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* GCM header phase */
|
||||
|
||||
if(aad_length > 0) {
|
||||
if(!furi_hal_crypto_gcm_header(aad, aad_length)) {
|
||||
furi_hal_crypto_unload_key();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* GCM payload phase */
|
||||
|
||||
if(!furi_hal_crypto_gcm_payload(input, output, length, decrypt)) {
|
||||
furi_hal_crypto_unload_key();
|
||||
return false;
|
||||
}
|
||||
|
||||
/* GCM final phase */
|
||||
|
||||
if(!furi_hal_crypto_gcm_finish(aad_length, length, tag)) {
|
||||
furi_hal_crypto_unload_key();
|
||||
return false;
|
||||
}
|
||||
|
||||
furi_hal_crypto_unload_key();
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriHalCryptoGCMState furi_hal_crypto_gcm_encrypt_and_tag(
|
||||
const uint8_t* key,
|
||||
const uint8_t* iv,
|
||||
const uint8_t* aad,
|
||||
size_t aad_length,
|
||||
const uint8_t* input,
|
||||
uint8_t* output,
|
||||
size_t length,
|
||||
uint8_t* tag) {
|
||||
if(!furi_hal_crypto_gcm(key, iv, aad, aad_length, input, output, length, tag, false)) {
|
||||
memset(output, 0, length);
|
||||
memset(tag, 0, CRYPTO_GCM_TAG_LEN);
|
||||
return FuriHalCryptoGCMStateError;
|
||||
}
|
||||
|
||||
return FuriHalCryptoGCMStateOk;
|
||||
}
|
||||
|
||||
FuriHalCryptoGCMState furi_hal_crypto_gcm_decrypt_and_verify(
|
||||
const uint8_t* key,
|
||||
const uint8_t* iv,
|
||||
const uint8_t* aad,
|
||||
size_t aad_length,
|
||||
const uint8_t* input,
|
||||
uint8_t* output,
|
||||
size_t length,
|
||||
const uint8_t* tag) {
|
||||
uint8_t dtag[CRYPTO_GCM_TAG_LEN];
|
||||
|
||||
if(!furi_hal_crypto_gcm(key, iv, aad, aad_length, input, output, length, dtag, true)) {
|
||||
memset(output, 0, length);
|
||||
return FuriHalCryptoGCMStateError;
|
||||
}
|
||||
|
||||
if(!furi_hal_crypto_gcm_compare_tag(dtag, tag)) {
|
||||
memset(output, 0, length);
|
||||
return FuriHalCryptoGCMStateAuthFailure;
|
||||
}
|
||||
|
||||
return FuriHalCryptoGCMStateOk;
|
||||
}
|
||||
41
targets/f7/furi_hal/furi_hal_debug.c
Normal file
41
targets/f7/furi_hal/furi_hal_debug.c
Normal file
@@ -0,0 +1,41 @@
|
||||
#include <furi_hal_debug.h>
|
||||
|
||||
#include <stm32wbxx_ll_exti.h>
|
||||
#include <stm32wbxx_ll_system.h>
|
||||
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_resources.h>
|
||||
|
||||
volatile bool furi_hal_debug_gdb_session_active = false;
|
||||
|
||||
void furi_hal_debug_enable() {
|
||||
// Low power mode debug
|
||||
LL_DBGMCU_EnableDBGSleepMode();
|
||||
LL_DBGMCU_EnableDBGStopMode();
|
||||
LL_DBGMCU_EnableDBGStandbyMode();
|
||||
LL_EXTI_EnableIT_32_63(LL_EXTI_LINE_48);
|
||||
// SWD GPIO
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_swdio,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullUp,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn0JTMS_SWDIO);
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_swclk, GpioModeAltFunctionPushPull, GpioPullDown, GpioSpeedLow, GpioAltFn0JTCK_SWCLK);
|
||||
}
|
||||
|
||||
void furi_hal_debug_disable() {
|
||||
// Low power mode debug
|
||||
LL_DBGMCU_DisableDBGSleepMode();
|
||||
LL_DBGMCU_DisableDBGStopMode();
|
||||
LL_DBGMCU_DisableDBGStandbyMode();
|
||||
LL_EXTI_DisableIT_32_63(LL_EXTI_LINE_48);
|
||||
// SWD GPIO
|
||||
furi_hal_gpio_init_simple(&gpio_swdio, GpioModeAnalog);
|
||||
furi_hal_gpio_init_simple(&gpio_swclk, GpioModeAnalog);
|
||||
}
|
||||
|
||||
bool furi_hal_debug_is_gdb_session_active() {
|
||||
return furi_hal_debug_gdb_session_active;
|
||||
}
|
||||
14
targets/f7/furi_hal/furi_hal_dma.c
Normal file
14
targets/f7/furi_hal/furi_hal_dma.c
Normal file
@@ -0,0 +1,14 @@
|
||||
#include <furi_hal_dma.h>
|
||||
#include <furi_hal_bus.h>
|
||||
|
||||
void furi_hal_dma_init_early() {
|
||||
furi_hal_bus_enable(FuriHalBusDMA1);
|
||||
furi_hal_bus_enable(FuriHalBusDMA2);
|
||||
furi_hal_bus_enable(FuriHalBusDMAMUX1);
|
||||
}
|
||||
|
||||
void furi_hal_dma_deinit_early() {
|
||||
furi_hal_bus_disable(FuriHalBusDMA1);
|
||||
furi_hal_bus_disable(FuriHalBusDMA2);
|
||||
furi_hal_bus_disable(FuriHalBusDMAMUX1);
|
||||
}
|
||||
15
targets/f7/furi_hal/furi_hal_dma.h
Normal file
15
targets/f7/furi_hal/furi_hal_dma.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Early initialization */
|
||||
void furi_hal_dma_init_early();
|
||||
|
||||
/** Early de-initialization */
|
||||
void furi_hal_dma_deinit_early();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
560
targets/f7/furi_hal/furi_hal_flash.c
Normal file
560
targets/f7/furi_hal/furi_hal_flash.c
Normal file
@@ -0,0 +1,560 @@
|
||||
#include <furi_hal_flash.h>
|
||||
#include <furi_hal_bt.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_cortex.h>
|
||||
#include <furi.h>
|
||||
#include <ble/ble.h>
|
||||
#include <interface/patterns/ble_thread/shci/shci.h>
|
||||
|
||||
#include <stm32wbxx.h>
|
||||
#include <stm32wbxx_ll_hsem.h>
|
||||
|
||||
#include <hsem_map.h>
|
||||
|
||||
#define TAG "FuriHalFlash"
|
||||
|
||||
#define FURI_HAL_CRITICAL_MSG "Critical flash operation fail"
|
||||
#define FURI_HAL_FLASH_READ_BLOCK (8U)
|
||||
#define FURI_HAL_FLASH_WRITE_BLOCK (8U)
|
||||
#define FURI_HAL_FLASH_PAGE_SIZE (4096U)
|
||||
#define FURI_HAL_FLASH_CYCLES_COUNT (10000U)
|
||||
#define FURI_HAL_FLASH_TIMEOUT (1000U)
|
||||
#define FURI_HAL_FLASH_KEY1 (0x45670123U)
|
||||
#define FURI_HAL_FLASH_KEY2 (0xCDEF89ABU)
|
||||
#define FURI_HAL_FLASH_TOTAL_PAGES (256U)
|
||||
#define FURI_HAL_FLASH_SR_ERRORS \
|
||||
(FLASH_SR_OPERR | FLASH_SR_PROGERR | FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_SIZERR | \
|
||||
FLASH_SR_PGSERR | FLASH_SR_MISERR | FLASH_SR_FASTERR | FLASH_SR_RDERR | FLASH_SR_OPTVERR)
|
||||
|
||||
#define FURI_HAL_FLASH_OPT_KEY1 (0x08192A3BU)
|
||||
#define FURI_HAL_FLASH_OPT_KEY2 (0x4C5D6E7FU)
|
||||
#define FURI_HAL_FLASH_OB_TOTAL_WORDS (0x80 / (sizeof(uint32_t) * 2))
|
||||
|
||||
/* STM32CubeWB/Projects/P-NUCLEO-WB55.Nucleo/Applications/BLE/BLE_RfWithFlash/Core/Src/flash_driver.c
|
||||
* ProcessSingleFlashOperation, quote:
|
||||
> In most BLE application, the flash should not be blocked by the CPU2 longer than FLASH_TIMEOUT_VALUE (1000ms)
|
||||
> However, it could be that for some marginal application, this time is longer.
|
||||
> ... there is no other way than waiting the operation to be completed.
|
||||
> If for any reason this test is never passed, this means there is a failure in the system and there is no other
|
||||
> way to recover than applying a device reset.
|
||||
*/
|
||||
#define FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS (3000U) /* 3 seconds */
|
||||
|
||||
#define IS_ADDR_ALIGNED_64BITS(__VALUE__) (((__VALUE__)&0x7U) == (0x00UL))
|
||||
#define IS_FLASH_PROGRAM_ADDRESS(__VALUE__) \
|
||||
(((__VALUE__) >= FLASH_BASE) && ((__VALUE__) <= (FLASH_BASE + FLASH_SIZE - 8UL)) && \
|
||||
(((__VALUE__) % 8UL) == 0UL))
|
||||
|
||||
/* Free flash space borders, exported by linker */
|
||||
extern const void __free_flash_start__;
|
||||
|
||||
size_t furi_hal_flash_get_base() {
|
||||
return FLASH_BASE;
|
||||
}
|
||||
|
||||
size_t furi_hal_flash_get_read_block_size() {
|
||||
return FURI_HAL_FLASH_READ_BLOCK;
|
||||
}
|
||||
|
||||
size_t furi_hal_flash_get_write_block_size() {
|
||||
return FURI_HAL_FLASH_WRITE_BLOCK;
|
||||
}
|
||||
|
||||
size_t furi_hal_flash_get_page_size() {
|
||||
return FURI_HAL_FLASH_PAGE_SIZE;
|
||||
}
|
||||
|
||||
size_t furi_hal_flash_get_cycles_count() {
|
||||
return FURI_HAL_FLASH_CYCLES_COUNT;
|
||||
}
|
||||
|
||||
const void* furi_hal_flash_get_free_start_address() {
|
||||
return &__free_flash_start__;
|
||||
}
|
||||
|
||||
const void* furi_hal_flash_get_free_end_address() {
|
||||
uint32_t sfr_reg_val = READ_REG(FLASH->SFR);
|
||||
uint32_t sfsa = (READ_BIT(sfr_reg_val, FLASH_SFR_SFSA) >> FLASH_SFR_SFSA_Pos);
|
||||
return (const void*)((sfsa * FURI_HAL_FLASH_PAGE_SIZE) + FLASH_BASE);
|
||||
}
|
||||
|
||||
size_t furi_hal_flash_get_free_page_start_address() {
|
||||
size_t start = (size_t)furi_hal_flash_get_free_start_address();
|
||||
size_t page_start = start - start % FURI_HAL_FLASH_PAGE_SIZE;
|
||||
if(page_start != start) {
|
||||
page_start += FURI_HAL_FLASH_PAGE_SIZE;
|
||||
}
|
||||
return page_start;
|
||||
}
|
||||
|
||||
size_t furi_hal_flash_get_free_page_count() {
|
||||
size_t end = (size_t)furi_hal_flash_get_free_end_address();
|
||||
size_t page_start = (size_t)furi_hal_flash_get_free_page_start_address();
|
||||
return (end - page_start) / FURI_HAL_FLASH_PAGE_SIZE;
|
||||
}
|
||||
|
||||
void furi_hal_flash_init() {
|
||||
/* Errata 2.2.9, Flash OPTVERR flag is always set after system reset */
|
||||
// WRITE_REG(FLASH->SR, FLASH_SR_OPTVERR);
|
||||
/* Actually, reset all error flags on start */
|
||||
if(READ_BIT(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS)) {
|
||||
FURI_LOG_E(TAG, "FLASH->SR 0x%08lX", FLASH->SR);
|
||||
WRITE_REG(FLASH->SR, FURI_HAL_FLASH_SR_ERRORS);
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_hal_flash_unlock() {
|
||||
/* verify Flash is locked */
|
||||
furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != 0U);
|
||||
|
||||
/* Authorize the FLASH Registers access */
|
||||
WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY1);
|
||||
__ISB();
|
||||
WRITE_REG(FLASH->KEYR, FURI_HAL_FLASH_KEY2);
|
||||
|
||||
/* verify Flash is unlocked */
|
||||
furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U);
|
||||
}
|
||||
|
||||
static void furi_hal_flash_lock(void) {
|
||||
/* verify Flash is unlocked */
|
||||
furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U);
|
||||
|
||||
/* Set the LOCK Bit to lock the FLASH Registers access */
|
||||
/* @Note The lock and unlock procedure is done only using CR registers even from CPU2 */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_LOCK);
|
||||
|
||||
/* verify Flash is locked */
|
||||
furi_check(READ_BIT(FLASH->CR, FLASH_CR_LOCK) != 0U);
|
||||
}
|
||||
|
||||
static void furi_hal_flash_begin_with_core2(bool erase_flag) {
|
||||
furi_hal_power_insomnia_enter();
|
||||
/* Take flash controller ownership */
|
||||
while(LL_HSEM_1StepLock(HSEM, CFG_HW_FLASH_SEMID) != 0) {
|
||||
furi_thread_yield();
|
||||
}
|
||||
|
||||
/* Unlock flash operation */
|
||||
furi_hal_flash_unlock();
|
||||
|
||||
/* Erase activity notification */
|
||||
if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_ON);
|
||||
|
||||
/* 64mHz 5us core2 flag protection */
|
||||
for(volatile uint32_t i = 0; i < 35; i++)
|
||||
;
|
||||
|
||||
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS * 1000);
|
||||
while(true) {
|
||||
/* Wait till flash controller become usable */
|
||||
while(LL_FLASH_IsActiveFlag_OperationSuspended()) {
|
||||
furi_check(!furi_hal_cortex_timer_is_expired(timer));
|
||||
furi_thread_yield();
|
||||
};
|
||||
|
||||
/* Just a little more love */
|
||||
taskENTER_CRITICAL();
|
||||
|
||||
/* Actually we already have mutex for it, but specification is specification */
|
||||
if(LL_HSEM_IsSemaphoreLocked(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU1_SEMID)) {
|
||||
taskEXIT_CRITICAL();
|
||||
furi_check(!furi_hal_cortex_timer_is_expired(timer));
|
||||
furi_thread_yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Take sempahopre and prevent core2 from anything funky */
|
||||
if(LL_HSEM_1StepLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID) != 0) {
|
||||
taskEXIT_CRITICAL();
|
||||
furi_check(!furi_hal_cortex_timer_is_expired(timer));
|
||||
furi_thread_yield();
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_hal_flash_begin(bool erase_flag) {
|
||||
/* Acquire dangerous ops mutex */
|
||||
furi_hal_bt_lock_core2();
|
||||
|
||||
/* If Core2 is running use IPC locking */
|
||||
if(furi_hal_bt_is_alive()) {
|
||||
furi_hal_flash_begin_with_core2(erase_flag);
|
||||
} else {
|
||||
furi_hal_flash_unlock();
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_hal_flash_end_with_core2(bool erase_flag) {
|
||||
/* Funky ops are ok at this point */
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_BLOCK_FLASH_REQ_BY_CPU2_SEMID, 0);
|
||||
|
||||
/* Task switching is ok */
|
||||
taskEXIT_CRITICAL();
|
||||
|
||||
/* Doesn't make much sense, does it? */
|
||||
while(READ_BIT(FLASH->SR, FLASH_SR_BSY)) {
|
||||
furi_thread_yield();
|
||||
}
|
||||
|
||||
/* Erase activity over, core2 can continue */
|
||||
if(erase_flag) SHCI_C2_FLASH_EraseActivity(ERASE_ACTIVITY_OFF);
|
||||
|
||||
/* Lock flash controller */
|
||||
furi_hal_flash_lock();
|
||||
|
||||
/* Release flash controller ownership */
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_FLASH_SEMID, 0);
|
||||
furi_hal_power_insomnia_exit();
|
||||
}
|
||||
|
||||
static void furi_hal_flash_end(bool erase_flag) {
|
||||
/* If Core2 is running - use IPC locking */
|
||||
if(furi_hal_bt_is_alive()) {
|
||||
furi_hal_flash_end_with_core2(erase_flag);
|
||||
} else {
|
||||
furi_hal_flash_lock();
|
||||
}
|
||||
|
||||
/* Release dangerous ops mutex */
|
||||
furi_hal_bt_unlock_core2();
|
||||
}
|
||||
|
||||
static void furi_hal_flush_cache(void) {
|
||||
/* Flush instruction cache */
|
||||
if(READ_BIT(FLASH->ACR, FLASH_ACR_ICEN) == FLASH_ACR_ICEN) {
|
||||
/* Disable instruction cache */
|
||||
LL_FLASH_DisableInstCache();
|
||||
/* Reset instruction cache */
|
||||
LL_FLASH_EnableInstCacheReset();
|
||||
LL_FLASH_DisableInstCacheReset();
|
||||
/* Enable instruction cache */
|
||||
LL_FLASH_EnableInstCache();
|
||||
}
|
||||
|
||||
/* Flush data cache */
|
||||
if(READ_BIT(FLASH->ACR, FLASH_ACR_DCEN) == FLASH_ACR_DCEN) {
|
||||
/* Disable data cache */
|
||||
LL_FLASH_DisableDataCache();
|
||||
/* Reset data cache */
|
||||
LL_FLASH_EnableDataCacheReset();
|
||||
LL_FLASH_DisableDataCacheReset();
|
||||
/* Enable data cache */
|
||||
LL_FLASH_EnableDataCache();
|
||||
}
|
||||
}
|
||||
|
||||
bool furi_hal_flash_wait_last_operation(uint32_t timeout) {
|
||||
uint32_t error = 0;
|
||||
|
||||
/* Wait for the FLASH operation to complete by polling on BUSY flag to be reset.
|
||||
Even if the FLASH operation fails, the BUSY flag will be reset and an error
|
||||
flag will be set */
|
||||
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000);
|
||||
while(READ_BIT(FLASH->SR, FLASH_SR_BSY)) {
|
||||
if(furi_hal_cortex_timer_is_expired(timer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check FLASH operation error flags */
|
||||
error = FLASH->SR;
|
||||
|
||||
/* Check FLASH End of Operation flag */
|
||||
if((error & FLASH_SR_EOP) != 0U) {
|
||||
/* Clear FLASH End of Operation pending bit */
|
||||
CLEAR_BIT(FLASH->SR, FLASH_SR_EOP);
|
||||
}
|
||||
|
||||
/* Now update error variable to only error value */
|
||||
error &= FURI_HAL_FLASH_SR_ERRORS;
|
||||
|
||||
furi_check(error == 0);
|
||||
|
||||
/* clear error flags */
|
||||
CLEAR_BIT(FLASH->SR, error);
|
||||
|
||||
/* Wait for control register to be written */
|
||||
timer = furi_hal_cortex_timer_get(timeout * 1000);
|
||||
while(READ_BIT(FLASH->SR, FLASH_SR_CFGBSY)) {
|
||||
if(furi_hal_cortex_timer_is_expired(timer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void furi_hal_flash_erase(uint8_t page) {
|
||||
furi_hal_flash_begin(true);
|
||||
|
||||
/* Ensure that controller state is valid */
|
||||
furi_check(FLASH->SR == 0);
|
||||
|
||||
/* Verify that next operation can be proceed */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
|
||||
/* Select page and start operation */
|
||||
MODIFY_REG(
|
||||
FLASH->CR, FLASH_CR_PNB, ((page << FLASH_CR_PNB_Pos) | FLASH_CR_PER | FLASH_CR_STRT));
|
||||
|
||||
/* Wait for last operation to be completed */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
|
||||
/* If operation is completed or interrupted, disable the Page Erase Bit */
|
||||
CLEAR_BIT(FLASH->CR, (FLASH_CR_PER | FLASH_CR_PNB));
|
||||
|
||||
/* Flush the caches to be sure of the data consistency */
|
||||
furi_hal_flush_cache();
|
||||
|
||||
furi_hal_flash_end(true);
|
||||
}
|
||||
|
||||
static inline void furi_hal_flash_write_dword_internal_nowait(size_t address, uint64_t* data) {
|
||||
/* Program first word */
|
||||
*(uint32_t*)address = (uint32_t)*data;
|
||||
|
||||
/* Barrier to ensure programming is performed in 2 steps, in right order
|
||||
(independently of compiler optimization behavior) */
|
||||
__ISB();
|
||||
|
||||
/* Program second word */
|
||||
*(uint32_t*)(address + 4U) = (uint32_t)(*data >> 32U);
|
||||
}
|
||||
|
||||
static inline void furi_hal_flash_write_dword_internal(size_t address, uint64_t* data) {
|
||||
furi_hal_flash_write_dword_internal_nowait(address, data);
|
||||
|
||||
/* Wait for last operation to be completed */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
}
|
||||
|
||||
void furi_hal_flash_write_dword(size_t address, uint64_t data) {
|
||||
furi_hal_flash_begin(false);
|
||||
|
||||
/* Ensure that controller state is valid */
|
||||
furi_check(FLASH->SR == 0);
|
||||
|
||||
/* Check the parameters */
|
||||
furi_check(IS_ADDR_ALIGNED_64BITS(address));
|
||||
furi_check(IS_FLASH_PROGRAM_ADDRESS(address));
|
||||
|
||||
/* Set PG bit */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
|
||||
/* Do the thing */
|
||||
furi_hal_flash_write_dword_internal(address, &data);
|
||||
|
||||
/* If the program operation is completed, disable the PG or FSTPG Bit */
|
||||
CLEAR_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
|
||||
furi_hal_flash_end(false);
|
||||
|
||||
/* Wait for last operation to be completed */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
}
|
||||
|
||||
static size_t furi_hal_flash_get_page_address(uint8_t page) {
|
||||
return furi_hal_flash_get_base() + page * FURI_HAL_FLASH_PAGE_SIZE;
|
||||
}
|
||||
|
||||
void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t _length) {
|
||||
uint16_t length = _length;
|
||||
furi_check(length <= FURI_HAL_FLASH_PAGE_SIZE);
|
||||
|
||||
furi_hal_flash_erase(page);
|
||||
|
||||
furi_hal_flash_begin(false);
|
||||
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
|
||||
/* Ensure that controller state is valid */
|
||||
furi_check(FLASH->SR == 0);
|
||||
|
||||
size_t page_start_address = furi_hal_flash_get_page_address(page);
|
||||
|
||||
size_t length_written = 0;
|
||||
|
||||
const uint16_t FAST_PROG_BLOCK_SIZE = 512;
|
||||
const uint8_t DWORD_PROG_BLOCK_SIZE = 8;
|
||||
|
||||
/* Write as much data as we can in fast mode */
|
||||
if(length >= FAST_PROG_BLOCK_SIZE) {
|
||||
taskENTER_CRITICAL();
|
||||
/* Enable fast flash programming mode */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_FSTPG);
|
||||
|
||||
while(length_written < (length / FAST_PROG_BLOCK_SIZE * FAST_PROG_BLOCK_SIZE)) {
|
||||
/* No context switch in the middle of the operation */
|
||||
furi_hal_flash_write_dword_internal_nowait(
|
||||
page_start_address + length_written, (uint64_t*)(data + length_written));
|
||||
length_written += DWORD_PROG_BLOCK_SIZE;
|
||||
|
||||
if((length_written % FAST_PROG_BLOCK_SIZE) == 0) {
|
||||
/* Wait for block operation to be completed */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
}
|
||||
}
|
||||
CLEAR_BIT(FLASH->CR, FLASH_CR_FSTPG);
|
||||
taskEXIT_CRITICAL();
|
||||
}
|
||||
|
||||
/* Enable regular (dword) programming mode */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
if((length % FAST_PROG_BLOCK_SIZE) != 0) {
|
||||
/* Write tail in regular, dword mode */
|
||||
while(length_written < (length / DWORD_PROG_BLOCK_SIZE * DWORD_PROG_BLOCK_SIZE)) {
|
||||
furi_hal_flash_write_dword_internal(
|
||||
page_start_address + length_written, (uint64_t*)&data[length_written]);
|
||||
length_written += DWORD_PROG_BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
if((length % DWORD_PROG_BLOCK_SIZE) != 0) {
|
||||
/* there are more bytes, not fitting into dwords */
|
||||
uint64_t tail_data = 0;
|
||||
for(int32_t tail_i = 0; tail_i < (length % DWORD_PROG_BLOCK_SIZE); ++tail_i) {
|
||||
tail_data |= (((uint64_t)data[length_written + tail_i]) << (tail_i * 8));
|
||||
}
|
||||
|
||||
furi_hal_flash_write_dword_internal(page_start_address + length_written, &tail_data);
|
||||
}
|
||||
/* Disable the PG Bit */
|
||||
CLEAR_BIT(FLASH->CR, FLASH_CR_PG);
|
||||
|
||||
furi_hal_flash_end(false);
|
||||
}
|
||||
|
||||
int16_t furi_hal_flash_get_page_number(size_t address) {
|
||||
const size_t flash_base = furi_hal_flash_get_base();
|
||||
if((address < flash_base) ||
|
||||
(address > flash_base + FURI_HAL_FLASH_TOTAL_PAGES * FURI_HAL_FLASH_PAGE_SIZE)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (address - flash_base) / FURI_HAL_FLASH_PAGE_SIZE;
|
||||
}
|
||||
|
||||
uint32_t furi_hal_flash_ob_get_word(size_t word_idx, bool complementary) {
|
||||
furi_check(word_idx <= FURI_HAL_FLASH_OB_TOTAL_WORDS);
|
||||
const uint32_t* ob_data = (const uint32_t*)(OPTION_BYTE_BASE);
|
||||
size_t raw_word_idx = word_idx * 2;
|
||||
if(complementary) {
|
||||
raw_word_idx += 1;
|
||||
}
|
||||
return ob_data[raw_word_idx];
|
||||
}
|
||||
|
||||
void furi_hal_flash_ob_unlock() {
|
||||
furi_check(READ_BIT(FLASH->CR, FLASH_CR_OPTLOCK) != 0U);
|
||||
furi_hal_flash_begin(true);
|
||||
WRITE_REG(FLASH->OPTKEYR, FURI_HAL_FLASH_OPT_KEY1);
|
||||
__ISB();
|
||||
WRITE_REG(FLASH->OPTKEYR, FURI_HAL_FLASH_OPT_KEY2);
|
||||
/* verify OB area is unlocked */
|
||||
furi_check(READ_BIT(FLASH->CR, FLASH_CR_OPTLOCK) == 0U);
|
||||
}
|
||||
|
||||
void furi_hal_flash_ob_lock() {
|
||||
furi_check(READ_BIT(FLASH->CR, FLASH_CR_OPTLOCK) == 0U);
|
||||
SET_BIT(FLASH->CR, FLASH_CR_OPTLOCK);
|
||||
furi_hal_flash_end(true);
|
||||
furi_check(READ_BIT(FLASH->CR, FLASH_CR_OPTLOCK) != 0U);
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
FuriHalFlashObInvalid,
|
||||
FuriHalFlashObRegisterUserRead,
|
||||
FuriHalFlashObRegisterPCROP1AStart,
|
||||
FuriHalFlashObRegisterPCROP1AEnd,
|
||||
FuriHalFlashObRegisterWRPA,
|
||||
FuriHalFlashObRegisterWRPB,
|
||||
FuriHalFlashObRegisterPCROP1BStart,
|
||||
FuriHalFlashObRegisterPCROP1BEnd,
|
||||
FuriHalFlashObRegisterIPCCMail,
|
||||
FuriHalFlashObRegisterSecureFlash,
|
||||
FuriHalFlashObRegisterC2Opts,
|
||||
} FuriHalFlashObRegister;
|
||||
|
||||
typedef struct {
|
||||
FuriHalFlashObRegister ob_reg;
|
||||
uint32_t* ob_register_address;
|
||||
} FuriHalFlashObMapping;
|
||||
|
||||
#define OB_REG_DEF(INDEX, REG) \
|
||||
{ .ob_reg = INDEX, .ob_register_address = (uint32_t*)(REG) }
|
||||
|
||||
static const FuriHalFlashObMapping furi_hal_flash_ob_reg_map[FURI_HAL_FLASH_OB_TOTAL_WORDS] = {
|
||||
OB_REG_DEF(FuriHalFlashObRegisterUserRead, (&FLASH->OPTR)),
|
||||
OB_REG_DEF(FuriHalFlashObRegisterPCROP1AStart, (&FLASH->PCROP1ASR)),
|
||||
OB_REG_DEF(FuriHalFlashObRegisterPCROP1AEnd, (&FLASH->PCROP1AER)),
|
||||
OB_REG_DEF(FuriHalFlashObRegisterWRPA, (&FLASH->WRP1AR)),
|
||||
OB_REG_DEF(FuriHalFlashObRegisterWRPB, (&FLASH->WRP1BR)),
|
||||
OB_REG_DEF(FuriHalFlashObRegisterPCROP1BStart, (&FLASH->PCROP1BSR)),
|
||||
OB_REG_DEF(FuriHalFlashObRegisterPCROP1BEnd, (&FLASH->PCROP1BER)),
|
||||
|
||||
OB_REG_DEF(FuriHalFlashObInvalid, (NULL)),
|
||||
OB_REG_DEF(FuriHalFlashObInvalid, (NULL)),
|
||||
OB_REG_DEF(FuriHalFlashObInvalid, (NULL)),
|
||||
OB_REG_DEF(FuriHalFlashObInvalid, (NULL)),
|
||||
OB_REG_DEF(FuriHalFlashObInvalid, (NULL)),
|
||||
OB_REG_DEF(FuriHalFlashObInvalid, (NULL)),
|
||||
|
||||
OB_REG_DEF(FuriHalFlashObRegisterIPCCMail, (&FLASH->IPCCBR)),
|
||||
OB_REG_DEF(FuriHalFlashObRegisterSecureFlash, (NULL)),
|
||||
OB_REG_DEF(FuriHalFlashObRegisterC2Opts, (NULL)),
|
||||
};
|
||||
#undef OB_REG_DEF
|
||||
|
||||
void furi_hal_flash_ob_apply() {
|
||||
furi_hal_flash_ob_unlock();
|
||||
/* OBL_LAUNCH: When set to 1, this bit forces the option byte reloading.
|
||||
* It cannot be written if OPTLOCK is set */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_OBL_LAUNCH);
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
furi_hal_flash_ob_lock();
|
||||
}
|
||||
|
||||
bool furi_hal_flash_ob_set_word(size_t word_idx, const uint32_t value) {
|
||||
furi_check(word_idx < FURI_HAL_FLASH_OB_TOTAL_WORDS);
|
||||
|
||||
const FuriHalFlashObMapping* reg_def = &furi_hal_flash_ob_reg_map[word_idx];
|
||||
if(reg_def->ob_register_address == NULL) {
|
||||
FURI_LOG_E(TAG, "Attempt to set RO OB word %d", word_idx);
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_W(
|
||||
TAG,
|
||||
"Setting OB reg %d for word %d (addr 0x%08lX) to 0x%08lX",
|
||||
reg_def->ob_reg,
|
||||
word_idx,
|
||||
(uint32_t)reg_def->ob_register_address,
|
||||
value);
|
||||
|
||||
/* 1. Clear OPTLOCK option lock bit with the clearing sequence */
|
||||
furi_hal_flash_ob_unlock();
|
||||
|
||||
/* 2. Write the desired options value in the options registers */
|
||||
*reg_def->ob_register_address = value;
|
||||
|
||||
/* 3. Check that no Flash memory operation is on going by checking the BSY && PESD */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
while(LL_FLASH_IsActiveFlag_OperationSuspended()) {
|
||||
furi_thread_yield();
|
||||
};
|
||||
|
||||
/* 4. Set the Options start bit OPTSTRT */
|
||||
SET_BIT(FLASH->CR, FLASH_CR_OPTSTRT);
|
||||
|
||||
/* 5. Wait for the BSY bit to be cleared. */
|
||||
furi_check(furi_hal_flash_wait_last_operation(FURI_HAL_FLASH_TIMEOUT));
|
||||
furi_hal_flash_ob_lock();
|
||||
return true;
|
||||
}
|
||||
|
||||
const FuriHalFlashRawOptionByteData* furi_hal_flash_ob_get_raw_ptr() {
|
||||
return (const FuriHalFlashRawOptionByteData*)OPTION_BYTE_BASE;
|
||||
}
|
||||
146
targets/f7/furi_hal/furi_hal_flash.h
Normal file
146
targets/f7/furi_hal/furi_hal_flash.h
Normal file
@@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FURI_HAL_FLASH_OB_RAW_SIZE_BYTES 0x80
|
||||
#define FURI_HAL_FLASH_OB_SIZE_WORDS (FURI_HAL_FLASH_OB_RAW_SIZE_BYTES / sizeof(uint32_t))
|
||||
#define FURI_HAL_FLASH_OB_TOTAL_VALUES (FURI_HAL_FLASH_OB_SIZE_WORDS / 2)
|
||||
|
||||
typedef union {
|
||||
uint8_t bytes[FURI_HAL_FLASH_OB_RAW_SIZE_BYTES];
|
||||
union {
|
||||
struct {
|
||||
uint32_t base;
|
||||
uint32_t complementary_value;
|
||||
} values;
|
||||
uint64_t dword;
|
||||
} obs[FURI_HAL_FLASH_OB_TOTAL_VALUES];
|
||||
} FuriHalFlashRawOptionByteData;
|
||||
|
||||
_Static_assert(
|
||||
sizeof(FuriHalFlashRawOptionByteData) == FURI_HAL_FLASH_OB_RAW_SIZE_BYTES,
|
||||
"UpdateManifestOptionByteData size error");
|
||||
|
||||
/** Init flash, applying necessary workarounds
|
||||
*/
|
||||
void furi_hal_flash_init();
|
||||
|
||||
/** Get flash base address
|
||||
*
|
||||
* @return pointer to flash base
|
||||
*/
|
||||
size_t furi_hal_flash_get_base();
|
||||
|
||||
/** Get flash read block size
|
||||
*
|
||||
* @return size in bytes
|
||||
*/
|
||||
size_t furi_hal_flash_get_read_block_size();
|
||||
|
||||
/** Get flash write block size
|
||||
*
|
||||
* @return size in bytes
|
||||
*/
|
||||
size_t furi_hal_flash_get_write_block_size();
|
||||
|
||||
/** Get flash page size
|
||||
*
|
||||
* @return size in bytes
|
||||
*/
|
||||
size_t furi_hal_flash_get_page_size();
|
||||
|
||||
/** Get expected flash cycles count
|
||||
*
|
||||
* @return count of erase-write operations
|
||||
*/
|
||||
size_t furi_hal_flash_get_cycles_count();
|
||||
|
||||
/** Get free flash start address
|
||||
*
|
||||
* @return pointer to free region start
|
||||
*/
|
||||
const void* furi_hal_flash_get_free_start_address();
|
||||
|
||||
/** Get free flash end address
|
||||
*
|
||||
* @return pointer to free region end
|
||||
*/
|
||||
const void* furi_hal_flash_get_free_end_address();
|
||||
|
||||
/** Get first free page start address
|
||||
*
|
||||
* @return first free page memory address
|
||||
*/
|
||||
size_t furi_hal_flash_get_free_page_start_address();
|
||||
|
||||
/** Get free page count
|
||||
*
|
||||
* @return free page count
|
||||
*/
|
||||
size_t furi_hal_flash_get_free_page_count();
|
||||
|
||||
/** Erase Flash
|
||||
*
|
||||
* @warning locking operation with critical section, stalls execution
|
||||
*
|
||||
* @param page The page to erase
|
||||
*/
|
||||
void furi_hal_flash_erase(uint8_t page);
|
||||
|
||||
/** Write double word (64 bits)
|
||||
*
|
||||
* @warning locking operation with critical section, stalls execution
|
||||
*
|
||||
* @param address destination address, must be double word aligned.
|
||||
* @param data data to write
|
||||
*/
|
||||
void furi_hal_flash_write_dword(size_t address, uint64_t data);
|
||||
|
||||
/** Write aligned page data (up to page size)
|
||||
*
|
||||
* @warning locking operation with critical section, stalls execution
|
||||
*
|
||||
* @param address destination address, must be page aligned.
|
||||
* @param data data to write
|
||||
* @param length data length
|
||||
*/
|
||||
void furi_hal_flash_program_page(const uint8_t page, const uint8_t* data, uint16_t length);
|
||||
|
||||
/** Get flash page number for address
|
||||
*
|
||||
* @return page number, -1 for invalid address
|
||||
*/
|
||||
int16_t furi_hal_flash_get_page_number(size_t address);
|
||||
|
||||
/** Writes OB word, using non-compl. index of register in Flash, OPTION_BYTE_BASE
|
||||
*
|
||||
* @warning locking operation with critical section, stalls execution
|
||||
*
|
||||
* @param word_idx OB word number
|
||||
* @param value data to write
|
||||
* @return true if value was written, false for read-only word
|
||||
*/
|
||||
bool furi_hal_flash_ob_set_word(size_t word_idx, const uint32_t value);
|
||||
|
||||
/** Forces a reload of OB data from flash to registers
|
||||
*
|
||||
* @warning Initializes system restart
|
||||
*
|
||||
*/
|
||||
void furi_hal_flash_ob_apply();
|
||||
|
||||
/** Get raw OB storage data
|
||||
*
|
||||
* @return pointer to read-only data of OB (raw + complementary values)
|
||||
*/
|
||||
const FuriHalFlashRawOptionByteData* furi_hal_flash_ob_get_raw_ptr();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
309
targets/f7/furi_hal/furi_hal_gpio.c
Normal file
309
targets/f7/furi_hal/furi_hal_gpio.c
Normal file
@@ -0,0 +1,309 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_version.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <stm32wbxx_ll_comp.h>
|
||||
|
||||
#define GET_SYSCFG_EXTI_PORT(gpio) \
|
||||
(((gpio) == (GPIOA)) ? LL_SYSCFG_EXTI_PORTA : \
|
||||
((gpio) == (GPIOB)) ? LL_SYSCFG_EXTI_PORTB : \
|
||||
((gpio) == (GPIOC)) ? LL_SYSCFG_EXTI_PORTC : \
|
||||
((gpio) == (GPIOD)) ? LL_SYSCFG_EXTI_PORTD : \
|
||||
((gpio) == (GPIOE)) ? LL_SYSCFG_EXTI_PORTE : \
|
||||
LL_SYSCFG_EXTI_PORTH)
|
||||
|
||||
#define GPIO_PIN_MAP(pin, prefix) \
|
||||
(((pin) == (LL_GPIO_PIN_0)) ? prefix##0 : \
|
||||
((pin) == (LL_GPIO_PIN_1)) ? prefix##1 : \
|
||||
((pin) == (LL_GPIO_PIN_2)) ? prefix##2 : \
|
||||
((pin) == (LL_GPIO_PIN_3)) ? prefix##3 : \
|
||||
((pin) == (LL_GPIO_PIN_4)) ? prefix##4 : \
|
||||
((pin) == (LL_GPIO_PIN_5)) ? prefix##5 : \
|
||||
((pin) == (LL_GPIO_PIN_6)) ? prefix##6 : \
|
||||
((pin) == (LL_GPIO_PIN_7)) ? prefix##7 : \
|
||||
((pin) == (LL_GPIO_PIN_8)) ? prefix##8 : \
|
||||
((pin) == (LL_GPIO_PIN_9)) ? prefix##9 : \
|
||||
((pin) == (LL_GPIO_PIN_10)) ? prefix##10 : \
|
||||
((pin) == (LL_GPIO_PIN_11)) ? prefix##11 : \
|
||||
((pin) == (LL_GPIO_PIN_12)) ? prefix##12 : \
|
||||
((pin) == (LL_GPIO_PIN_13)) ? prefix##13 : \
|
||||
((pin) == (LL_GPIO_PIN_14)) ? prefix##14 : \
|
||||
prefix##15)
|
||||
|
||||
#define GET_SYSCFG_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_SYSCFG_EXTI_LINE)
|
||||
#define GET_EXTI_LINE(pin) GPIO_PIN_MAP(pin, LL_EXTI_LINE_)
|
||||
|
||||
static volatile GpioInterrupt gpio_interrupt[GPIO_NUMBER];
|
||||
|
||||
static uint8_t furi_hal_gpio_get_pin_num(const GpioPin* gpio) {
|
||||
uint8_t pin_num = 0;
|
||||
for(pin_num = 0; pin_num < GPIO_NUMBER; pin_num++) {
|
||||
if(gpio->pin & (1 << pin_num)) break;
|
||||
}
|
||||
return pin_num;
|
||||
}
|
||||
|
||||
void furi_hal_gpio_init_simple(const GpioPin* gpio, const GpioMode mode) {
|
||||
furi_hal_gpio_init(gpio, mode, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
|
||||
void furi_hal_gpio_init(
|
||||
const GpioPin* gpio,
|
||||
const GpioMode mode,
|
||||
const GpioPull pull,
|
||||
const GpioSpeed speed) {
|
||||
// we cannot set alternate mode in this function
|
||||
furi_check(mode != GpioModeAltFunctionPushPull);
|
||||
furi_check(mode != GpioModeAltFunctionOpenDrain);
|
||||
|
||||
furi_hal_gpio_init_ex(gpio, mode, pull, speed, GpioAltFnUnused);
|
||||
}
|
||||
|
||||
void furi_hal_gpio_init_ex(
|
||||
const GpioPin* gpio,
|
||||
const GpioMode mode,
|
||||
const GpioPull pull,
|
||||
const GpioSpeed speed,
|
||||
const GpioAltFn alt_fn) {
|
||||
uint32_t sys_exti_port = GET_SYSCFG_EXTI_PORT(gpio->port);
|
||||
uint32_t sys_exti_line = GET_SYSCFG_EXTI_LINE(gpio->pin);
|
||||
uint32_t exti_line = GET_EXTI_LINE(gpio->pin);
|
||||
|
||||
// Configure gpio with interrupts disabled
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
// Set gpio speed
|
||||
switch(speed) {
|
||||
case GpioSpeedLow:
|
||||
LL_GPIO_SetPinSpeed(gpio->port, gpio->pin, LL_GPIO_SPEED_FREQ_LOW);
|
||||
break;
|
||||
case GpioSpeedMedium:
|
||||
LL_GPIO_SetPinSpeed(gpio->port, gpio->pin, LL_GPIO_SPEED_FREQ_MEDIUM);
|
||||
break;
|
||||
case GpioSpeedHigh:
|
||||
LL_GPIO_SetPinSpeed(gpio->port, gpio->pin, LL_GPIO_SPEED_FREQ_HIGH);
|
||||
break;
|
||||
case GpioSpeedVeryHigh:
|
||||
LL_GPIO_SetPinSpeed(gpio->port, gpio->pin, LL_GPIO_SPEED_FREQ_VERY_HIGH);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set gpio pull mode
|
||||
switch(pull) {
|
||||
case GpioPullNo:
|
||||
LL_GPIO_SetPinPull(gpio->port, gpio->pin, LL_GPIO_PULL_NO);
|
||||
break;
|
||||
case GpioPullUp:
|
||||
LL_GPIO_SetPinPull(gpio->port, gpio->pin, LL_GPIO_PULL_UP);
|
||||
break;
|
||||
case GpioPullDown:
|
||||
LL_GPIO_SetPinPull(gpio->port, gpio->pin, LL_GPIO_PULL_DOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set gpio mode
|
||||
if(mode >= GpioModeInterruptRise) {
|
||||
// Set pin in interrupt mode
|
||||
LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_INPUT);
|
||||
LL_SYSCFG_SetEXTISource(sys_exti_port, sys_exti_line);
|
||||
if(mode == GpioModeInterruptRise || mode == GpioModeInterruptRiseFall) {
|
||||
LL_EXTI_EnableIT_0_31(exti_line);
|
||||
LL_EXTI_EnableRisingTrig_0_31(exti_line);
|
||||
}
|
||||
if(mode == GpioModeInterruptFall || mode == GpioModeInterruptRiseFall) {
|
||||
LL_EXTI_EnableIT_0_31(exti_line);
|
||||
LL_EXTI_EnableFallingTrig_0_31(exti_line);
|
||||
}
|
||||
if(mode == GpioModeEventRise || mode == GpioModeEventRiseFall) {
|
||||
LL_EXTI_EnableEvent_0_31(exti_line);
|
||||
LL_EXTI_EnableRisingTrig_0_31(exti_line);
|
||||
}
|
||||
if(mode == GpioModeEventFall || mode == GpioModeEventRiseFall) {
|
||||
LL_EXTI_EnableEvent_0_31(exti_line);
|
||||
LL_EXTI_EnableFallingTrig_0_31(exti_line);
|
||||
}
|
||||
} else {
|
||||
// Disable interrupts if set
|
||||
if(LL_SYSCFG_GetEXTISource(sys_exti_line) == sys_exti_port &&
|
||||
LL_EXTI_IsEnabledIT_0_31(exti_line)) {
|
||||
LL_EXTI_DisableIT_0_31(exti_line);
|
||||
LL_EXTI_DisableRisingTrig_0_31(exti_line);
|
||||
LL_EXTI_DisableFallingTrig_0_31(exti_line);
|
||||
}
|
||||
|
||||
// Prepare alternative part if any
|
||||
if(mode == GpioModeAltFunctionPushPull || mode == GpioModeAltFunctionOpenDrain) {
|
||||
// set alternate function
|
||||
if(furi_hal_gpio_get_pin_num(gpio) < 8) {
|
||||
LL_GPIO_SetAFPin_0_7(gpio->port, gpio->pin, alt_fn);
|
||||
} else {
|
||||
LL_GPIO_SetAFPin_8_15(gpio->port, gpio->pin, alt_fn);
|
||||
}
|
||||
}
|
||||
|
||||
// Set not interrupt pin modes
|
||||
switch(mode) {
|
||||
case GpioModeInput:
|
||||
LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_INPUT);
|
||||
break;
|
||||
case GpioModeOutputPushPull:
|
||||
LL_GPIO_SetPinOutputType(gpio->port, gpio->pin, LL_GPIO_OUTPUT_PUSHPULL);
|
||||
LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_OUTPUT);
|
||||
break;
|
||||
case GpioModeAltFunctionPushPull:
|
||||
LL_GPIO_SetPinOutputType(gpio->port, gpio->pin, LL_GPIO_OUTPUT_PUSHPULL);
|
||||
LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_ALTERNATE);
|
||||
break;
|
||||
case GpioModeOutputOpenDrain:
|
||||
LL_GPIO_SetPinOutputType(gpio->port, gpio->pin, LL_GPIO_OUTPUT_OPENDRAIN);
|
||||
LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_OUTPUT);
|
||||
break;
|
||||
case GpioModeAltFunctionOpenDrain:
|
||||
LL_GPIO_SetPinOutputType(gpio->port, gpio->pin, LL_GPIO_OUTPUT_OPENDRAIN);
|
||||
LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_ALTERNATE);
|
||||
break;
|
||||
case GpioModeAnalog:
|
||||
LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_ANALOG);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, void* ctx) {
|
||||
furi_assert(gpio);
|
||||
furi_assert(cb);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio);
|
||||
furi_check(gpio_interrupt[pin_num].callback == NULL);
|
||||
gpio_interrupt[pin_num].callback = cb;
|
||||
gpio_interrupt[pin_num].context = ctx;
|
||||
gpio_interrupt[pin_num].ready = true;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_gpio_enable_int_callback(const GpioPin* gpio) {
|
||||
furi_assert(gpio);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio);
|
||||
if(gpio_interrupt[pin_num].callback) {
|
||||
gpio_interrupt[pin_num].ready = true;
|
||||
}
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_gpio_disable_int_callback(const GpioPin* gpio) {
|
||||
furi_assert(gpio);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio);
|
||||
gpio_interrupt[pin_num].ready = false;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_gpio_remove_int_callback(const GpioPin* gpio) {
|
||||
furi_assert(gpio);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio);
|
||||
gpio_interrupt[pin_num].callback = NULL;
|
||||
gpio_interrupt[pin_num].context = NULL;
|
||||
gpio_interrupt[pin_num].ready = false;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
static void furi_hal_gpio_int_call(uint16_t pin_num) {
|
||||
if(gpio_interrupt[pin_num].callback && gpio_interrupt[pin_num].ready) {
|
||||
gpio_interrupt[pin_num].callback(gpio_interrupt[pin_num].context);
|
||||
}
|
||||
}
|
||||
|
||||
/* Interrupt handlers */
|
||||
void EXTI0_IRQHandler(void) {
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_0)) {
|
||||
furi_hal_gpio_int_call(0);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_0);
|
||||
}
|
||||
}
|
||||
|
||||
void EXTI1_IRQHandler(void) {
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_1)) {
|
||||
furi_hal_gpio_int_call(1);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_1);
|
||||
}
|
||||
}
|
||||
|
||||
void EXTI2_IRQHandler(void) {
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_2)) {
|
||||
furi_hal_gpio_int_call(2);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_2);
|
||||
}
|
||||
}
|
||||
|
||||
void EXTI3_IRQHandler(void) {
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_3)) {
|
||||
furi_hal_gpio_int_call(3);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_3);
|
||||
}
|
||||
}
|
||||
|
||||
void EXTI4_IRQHandler(void) {
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_4)) {
|
||||
furi_hal_gpio_int_call(4);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_4);
|
||||
}
|
||||
}
|
||||
|
||||
void EXTI9_5_IRQHandler(void) {
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_5)) {
|
||||
furi_hal_gpio_int_call(5);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_5);
|
||||
}
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_6)) {
|
||||
furi_hal_gpio_int_call(6);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_6);
|
||||
}
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_7)) {
|
||||
furi_hal_gpio_int_call(7);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_7);
|
||||
}
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_8)) {
|
||||
furi_hal_gpio_int_call(8);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_8);
|
||||
}
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_9)) {
|
||||
furi_hal_gpio_int_call(9);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_9);
|
||||
}
|
||||
}
|
||||
|
||||
void EXTI15_10_IRQHandler(void) {
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_10)) {
|
||||
furi_hal_gpio_int_call(10);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_10);
|
||||
}
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_11)) {
|
||||
furi_hal_gpio_int_call(11);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_11);
|
||||
}
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_12)) {
|
||||
furi_hal_gpio_int_call(12);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_12);
|
||||
}
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_13)) {
|
||||
furi_hal_gpio_int_call(13);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_13);
|
||||
}
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_14)) {
|
||||
furi_hal_gpio_int_call(14);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_14);
|
||||
}
|
||||
if(LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_15)) {
|
||||
furi_hal_gpio_int_call(15);
|
||||
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_15);
|
||||
}
|
||||
}
|
||||
287
targets/f7/furi_hal/furi_hal_gpio.h
Normal file
287
targets/f7/furi_hal/furi_hal_gpio.h
Normal file
@@ -0,0 +1,287 @@
|
||||
#pragma once
|
||||
#include "stdbool.h"
|
||||
#include <stm32wbxx_ll_gpio.h>
|
||||
#include <stm32wbxx_ll_system.h>
|
||||
#include <stm32wbxx_ll_exti.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Number of gpio on one port
|
||||
*/
|
||||
#define GPIO_NUMBER (16U)
|
||||
|
||||
/**
|
||||
* Interrupt callback prototype
|
||||
*/
|
||||
typedef void (*GpioExtiCallback)(void* ctx);
|
||||
|
||||
/**
|
||||
* Gpio interrupt type
|
||||
*/
|
||||
typedef struct {
|
||||
GpioExtiCallback callback;
|
||||
void* context;
|
||||
volatile bool ready;
|
||||
} GpioInterrupt;
|
||||
|
||||
/**
|
||||
* Gpio modes
|
||||
*/
|
||||
typedef enum {
|
||||
GpioModeInput,
|
||||
GpioModeOutputPushPull,
|
||||
GpioModeOutputOpenDrain,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioModeAltFunctionOpenDrain,
|
||||
GpioModeAnalog,
|
||||
GpioModeInterruptRise,
|
||||
GpioModeInterruptFall,
|
||||
GpioModeInterruptRiseFall,
|
||||
GpioModeEventRise,
|
||||
GpioModeEventFall,
|
||||
GpioModeEventRiseFall,
|
||||
} GpioMode;
|
||||
|
||||
/**
|
||||
* Gpio pull modes
|
||||
*/
|
||||
typedef enum {
|
||||
GpioPullNo,
|
||||
GpioPullUp,
|
||||
GpioPullDown,
|
||||
} GpioPull;
|
||||
|
||||
/**
|
||||
* Gpio speed modes
|
||||
*/
|
||||
typedef enum {
|
||||
GpioSpeedLow,
|
||||
GpioSpeedMedium,
|
||||
GpioSpeedHigh,
|
||||
GpioSpeedVeryHigh,
|
||||
} GpioSpeed;
|
||||
|
||||
/**
|
||||
* Gpio alternate functions
|
||||
*/
|
||||
typedef enum {
|
||||
GpioAltFn0MCO = 0, /*!< MCO Alternate Function mapping */
|
||||
GpioAltFn0LSCO = 0, /*!< LSCO Alternate Function mapping */
|
||||
GpioAltFn0JTMS_SWDIO = 0, /*!< JTMS-SWDIO Alternate Function mapping */
|
||||
GpioAltFn0JTCK_SWCLK = 0, /*!< JTCK-SWCLK Alternate Function mapping */
|
||||
GpioAltFn0JTDI = 0, /*!< JTDI Alternate Function mapping */
|
||||
GpioAltFn0RTC_OUT = 0, /*!< RCT_OUT Alternate Function mapping */
|
||||
GpioAltFn0JTD_TRACE = 0, /*!< JTDO-TRACESWO Alternate Function mapping */
|
||||
GpioAltFn0NJTRST = 0, /*!< NJTRST Alternate Function mapping */
|
||||
GpioAltFn0RTC_REFIN = 0, /*!< RTC_REFIN Alternate Function mapping */
|
||||
GpioAltFn0TRACED0 = 0, /*!< TRACED0 Alternate Function mapping */
|
||||
GpioAltFn0TRACED1 = 0, /*!< TRACED1 Alternate Function mapping */
|
||||
GpioAltFn0TRACED2 = 0, /*!< TRACED2 Alternate Function mapping */
|
||||
GpioAltFn0TRACED3 = 0, /*!< TRACED3 Alternate Function mapping */
|
||||
GpioAltFn0TRIG_INOUT = 0, /*!< TRIG_INOUT Alternate Function mapping */
|
||||
GpioAltFn0TRACECK = 0, /*!< TRACECK Alternate Function mapping */
|
||||
GpioAltFn0SYS = 0, /*!< System Function mapping */
|
||||
|
||||
GpioAltFn1TIM1 = 1, /*!< TIM1 Alternate Function mapping */
|
||||
GpioAltFn1TIM2 = 1, /*!< TIM2 Alternate Function mapping */
|
||||
GpioAltFn1LPTIM1 = 1, /*!< LPTIM1 Alternate Function mapping */
|
||||
|
||||
GpioAltFn2TIM2 = 2, /*!< TIM2 Alternate Function mapping */
|
||||
GpioAltFn2TIM1 = 2, /*!< TIM1 Alternate Function mapping */
|
||||
|
||||
GpioAltFn3SAI1 = 3, /*!< SAI1_CK1 Alternate Function mapping */
|
||||
GpioAltFn3SPI2 = 3, /*!< SPI2 Alternate Function mapping */
|
||||
GpioAltFn3TIM1 = 3, /*!< TIM1 Alternate Function mapping */
|
||||
|
||||
GpioAltFn4I2C1 = 4, /*!< I2C1 Alternate Function mapping */
|
||||
GpioAltFn4I2C3 = 4, /*!< I2C3 Alternate Function mapping */
|
||||
|
||||
GpioAltFn5SPI1 = 5, /*!< SPI1 Alternate Function mapping */
|
||||
GpioAltFn5SPI2 = 5, /*!< SPI2 Alternate Function mapping */
|
||||
|
||||
GpioAltFn6MCO = 6, /*!< MCO Alternate Function mapping */
|
||||
GpioAltFn6LSCO = 6, /*!< LSCO Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB0 = 6, /*!< RF_DTB0 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB1 = 6, /*!< RF_DTB1 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB2 = 6, /*!< RF_DTB2 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB3 = 6, /*!< RF_DTB3 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB4 = 6, /*!< RF_DTB4 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB5 = 6, /*!< RF_DTB5 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB6 = 6, /*!< RF_DTB6 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB7 = 6, /*!< RF_DTB7 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB8 = 6, /*!< RF_DTB8 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB9 = 6, /*!< RF_DTB9 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB10 = 6, /*!< RF_DTB10 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB11 = 6, /*!< RF_DTB11 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB12 = 6, /*!< RF_DTB12 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB13 = 6, /*!< RF_DTB13 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB14 = 6, /*!< RF_DTB14 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB15 = 6, /*!< RF_DTB15 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB16 = 6, /*!< RF_DTB16 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB17 = 6, /*!< RF_DTB17 Alternate Function mapping */
|
||||
GpioAltFn6RF_DTB18 = 6, /*!< RF_DTB18 Alternate Function mapping */
|
||||
GpioAltFn6RF_MISO = 6, /*!< RF_MISO Alternate Function mapping */
|
||||
GpioAltFn6RF_MOSI = 6, /*!< RF_MOSI Alternate Function mapping */
|
||||
GpioAltFn6RF_SCK = 6, /*!< RF_SCK Alternate Function mapping */
|
||||
GpioAltFn6RF_NSS = 6, /*!< RF_NSS Alternate Function mapping */
|
||||
|
||||
GpioAltFn7USART1 = 7, /*!< USART1 Alternate Function mapping */
|
||||
|
||||
GpioAltFn8LPUART1 = 8, /*!< LPUART1 Alternate Function mapping */
|
||||
GpioAltFn8IR = 8, /*!< IR Alternate Function mapping */
|
||||
|
||||
GpioAltFn9TSC = 9, /*!< TSC Alternate Function mapping */
|
||||
|
||||
GpioAltFn10QUADSPI = 10, /*!< QUADSPI Alternate Function mapping */
|
||||
GpioAltFn10USB = 10, /*!< USB Alternate Function mapping */
|
||||
|
||||
GpioAltFn11LCD = 11, /*!< LCD Alternate Function mapping */
|
||||
|
||||
GpioAltFn12COMP1 = 12, /*!< COMP1 Alternate Function mapping */
|
||||
GpioAltFn12COMP2 = 12, /*!< COMP2 Alternate Function mapping */
|
||||
GpioAltFn12TIM1 = 12, /*!< TIM1 Alternate Function mapping */
|
||||
|
||||
GpioAltFn13SAI1 = 13, /*!< SAI1 Alternate Function mapping */
|
||||
|
||||
GpioAltFn14TIM2 = 14, /*!< TIM2 Alternate Function mapping */
|
||||
GpioAltFn14TIM16 = 14, /*!< TIM16 Alternate Function mapping */
|
||||
GpioAltFn14TIM17 = 14, /*!< TIM17 Alternate Function mapping */
|
||||
GpioAltFn14LPTIM2 = 14, /*!< LPTIM2 Alternate Function mapping */
|
||||
|
||||
GpioAltFn15EVENTOUT = 15, /*!< EVENTOUT Alternate Function mapping */
|
||||
|
||||
GpioAltFnUnused = 16, /*!< just dummy value */
|
||||
} GpioAltFn;
|
||||
|
||||
/**
|
||||
* Gpio structure
|
||||
*/
|
||||
typedef struct {
|
||||
GPIO_TypeDef* port;
|
||||
uint16_t pin;
|
||||
} GpioPin;
|
||||
|
||||
/**
|
||||
* GPIO initialization function, simple version
|
||||
* @param gpio GpioPin
|
||||
* @param mode GpioMode
|
||||
*/
|
||||
void furi_hal_gpio_init_simple(const GpioPin* gpio, const GpioMode mode);
|
||||
|
||||
/**
|
||||
* GPIO initialization function, normal version
|
||||
* @param gpio GpioPin
|
||||
* @param mode GpioMode
|
||||
* @param pull GpioPull
|
||||
* @param speed GpioSpeed
|
||||
*/
|
||||
void furi_hal_gpio_init(
|
||||
const GpioPin* gpio,
|
||||
const GpioMode mode,
|
||||
const GpioPull pull,
|
||||
const GpioSpeed speed);
|
||||
|
||||
/**
|
||||
* GPIO initialization function, extended version
|
||||
* @param gpio GpioPin
|
||||
* @param mode GpioMode
|
||||
* @param pull GpioPull
|
||||
* @param speed GpioSpeed
|
||||
* @param alt_fn GpioAltFn
|
||||
*/
|
||||
void furi_hal_gpio_init_ex(
|
||||
const GpioPin* gpio,
|
||||
const GpioMode mode,
|
||||
const GpioPull pull,
|
||||
const GpioSpeed speed,
|
||||
const GpioAltFn alt_fn);
|
||||
|
||||
/**
|
||||
* Add and enable interrupt
|
||||
* @param gpio GpioPin
|
||||
* @param cb GpioExtiCallback
|
||||
* @param ctx context for callback
|
||||
*/
|
||||
void furi_hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, void* ctx);
|
||||
|
||||
/**
|
||||
* Enable interrupt
|
||||
* @param gpio GpioPin
|
||||
*/
|
||||
void furi_hal_gpio_enable_int_callback(const GpioPin* gpio);
|
||||
|
||||
/**
|
||||
* Disable interrupt
|
||||
* @param gpio GpioPin
|
||||
*/
|
||||
void furi_hal_gpio_disable_int_callback(const GpioPin* gpio);
|
||||
|
||||
/**
|
||||
* Remove interrupt
|
||||
* @param gpio GpioPin
|
||||
*/
|
||||
void furi_hal_gpio_remove_int_callback(const GpioPin* gpio);
|
||||
|
||||
/**
|
||||
* GPIO write pin
|
||||
* @param gpio GpioPin
|
||||
* @param state true / false
|
||||
*/
|
||||
static inline void furi_hal_gpio_write(const GpioPin* gpio, const bool state) {
|
||||
// writing to BSSR is an atomic operation
|
||||
if(state == true) {
|
||||
gpio->port->BSRR = gpio->pin;
|
||||
} else {
|
||||
gpio->port->BSRR = (uint32_t)gpio->pin << GPIO_NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GPIO read pin
|
||||
* @param port GPIO port
|
||||
* @param pin pin mask
|
||||
* @return true / false
|
||||
*/
|
||||
static inline void
|
||||
furi_hal_gpio_write_port_pin(GPIO_TypeDef* port, uint16_t pin, const bool state) {
|
||||
// writing to BSSR is an atomic operation
|
||||
if(state == true) {
|
||||
port->BSRR = pin;
|
||||
} else {
|
||||
port->BSRR = pin << GPIO_NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GPIO read pin
|
||||
* @param gpio GpioPin
|
||||
* @return true / false
|
||||
*/
|
||||
static inline bool furi_hal_gpio_read(const GpioPin* gpio) {
|
||||
if((gpio->port->IDR & gpio->pin) != 0x00U) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GPIO read pin
|
||||
* @param port GPIO port
|
||||
* @param pin pin mask
|
||||
* @return true / false
|
||||
*/
|
||||
static inline bool furi_hal_gpio_read_port_pin(GPIO_TypeDef* port, uint16_t pin) {
|
||||
if((port->IDR & pin) != 0x00U) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
416
targets/f7/furi_hal/furi_hal_i2c.c
Normal file
416
targets/f7/furi_hal/furi_hal_i2c.c
Normal file
@@ -0,0 +1,416 @@
|
||||
#include <furi_hal_i2c.h>
|
||||
#include <furi_hal_version.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_cortex.h>
|
||||
|
||||
#include <stm32wbxx_ll_i2c.h>
|
||||
#include <stm32wbxx_ll_gpio.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "FuriHalI2c"
|
||||
|
||||
void furi_hal_i2c_init_early() {
|
||||
furi_hal_i2c_bus_power.callback(&furi_hal_i2c_bus_power, FuriHalI2cBusEventInit);
|
||||
}
|
||||
|
||||
void furi_hal_i2c_deinit_early() {
|
||||
furi_hal_i2c_bus_power.callback(&furi_hal_i2c_bus_power, FuriHalI2cBusEventDeinit);
|
||||
}
|
||||
|
||||
void furi_hal_i2c_init() {
|
||||
furi_hal_i2c_bus_external.callback(&furi_hal_i2c_bus_external, FuriHalI2cBusEventInit);
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
void furi_hal_i2c_acquire(FuriHalI2cBusHandle* handle) {
|
||||
furi_hal_power_insomnia_enter();
|
||||
// Lock bus access
|
||||
handle->bus->callback(handle->bus, FuriHalI2cBusEventLock);
|
||||
// Ensure that no active handle set
|
||||
furi_check(handle->bus->current_handle == NULL);
|
||||
// Set current handle
|
||||
handle->bus->current_handle = handle;
|
||||
// Activate bus
|
||||
handle->bus->callback(handle->bus, FuriHalI2cBusEventActivate);
|
||||
// Activate handle
|
||||
handle->callback(handle, FuriHalI2cBusHandleEventActivate);
|
||||
}
|
||||
|
||||
void furi_hal_i2c_release(FuriHalI2cBusHandle* handle) {
|
||||
// Ensure that current handle is our handle
|
||||
furi_check(handle->bus->current_handle == handle);
|
||||
// Deactivate handle
|
||||
handle->callback(handle, FuriHalI2cBusHandleEventDeactivate);
|
||||
// Deactivate bus
|
||||
handle->bus->callback(handle->bus, FuriHalI2cBusEventDeactivate);
|
||||
// Reset current handle
|
||||
handle->bus->current_handle = NULL;
|
||||
// Unlock bus
|
||||
handle->bus->callback(handle->bus, FuriHalI2cBusEventUnlock);
|
||||
furi_hal_power_insomnia_exit();
|
||||
}
|
||||
|
||||
static bool
|
||||
furi_hal_i2c_wait_for_idle(I2C_TypeDef* i2c, FuriHalI2cBegin begin, FuriHalCortexTimer timer) {
|
||||
do {
|
||||
if(furi_hal_cortex_timer_is_expired(timer)) {
|
||||
return false;
|
||||
}
|
||||
} while(begin == FuriHalI2cBeginStart && LL_I2C_IsActiveFlag_BUSY(i2c));
|
||||
// Only check if the bus is busy if starting a new transaction, if not we already control the bus
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
furi_hal_i2c_wait_for_end(I2C_TypeDef* i2c, FuriHalI2cEnd end, FuriHalCortexTimer timer) {
|
||||
// If ending the transaction with a stop condition, wait for it to be detected, otherwise wait for a transfer complete flag
|
||||
bool wait_for_stop = end == FuriHalI2cEndStop;
|
||||
uint32_t end_mask = (wait_for_stop) ? I2C_ISR_STOPF : (I2C_ISR_TC | I2C_ISR_TCR);
|
||||
|
||||
while((i2c->ISR & end_mask) == 0) {
|
||||
if(furi_hal_cortex_timer_is_expired(timer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
furi_hal_i2c_get_start_signal(FuriHalI2cBegin begin, bool ten_bit_address, bool read) {
|
||||
switch(begin) {
|
||||
case FuriHalI2cBeginRestart:
|
||||
if(read) {
|
||||
return ten_bit_address ? LL_I2C_GENERATE_RESTART_10BIT_READ :
|
||||
LL_I2C_GENERATE_RESTART_7BIT_READ;
|
||||
} else {
|
||||
return ten_bit_address ? LL_I2C_GENERATE_RESTART_10BIT_WRITE :
|
||||
LL_I2C_GENERATE_RESTART_7BIT_WRITE;
|
||||
}
|
||||
case FuriHalI2cBeginResume:
|
||||
return LL_I2C_GENERATE_NOSTARTSTOP;
|
||||
case FuriHalI2cBeginStart:
|
||||
default:
|
||||
return read ? LL_I2C_GENERATE_START_READ : LL_I2C_GENERATE_START_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t furi_hal_i2c_get_end_signal(FuriHalI2cEnd end) {
|
||||
switch(end) {
|
||||
case FuriHalI2cEndAwaitRestart:
|
||||
return LL_I2C_MODE_SOFTEND;
|
||||
case FuriHalI2cEndPause:
|
||||
return LL_I2C_MODE_RELOAD;
|
||||
case FuriHalI2cEndStop:
|
||||
default:
|
||||
return LL_I2C_MODE_AUTOEND;
|
||||
}
|
||||
}
|
||||
|
||||
static bool furi_hal_i2c_transfer_is_aborted(I2C_TypeDef* i2c) {
|
||||
return LL_I2C_IsActiveFlag_STOP(i2c) &&
|
||||
!(LL_I2C_IsActiveFlag_TC(i2c) || LL_I2C_IsActiveFlag_TCR(i2c));
|
||||
}
|
||||
|
||||
static bool furi_hal_i2c_transfer(
|
||||
I2C_TypeDef* i2c,
|
||||
uint8_t* data,
|
||||
uint32_t size,
|
||||
FuriHalI2cEnd end,
|
||||
bool read,
|
||||
FuriHalCortexTimer timer) {
|
||||
bool ret = true;
|
||||
|
||||
while(size > 0) {
|
||||
bool should_stop = furi_hal_cortex_timer_is_expired(timer) ||
|
||||
furi_hal_i2c_transfer_is_aborted(i2c);
|
||||
|
||||
// Modifying the data pointer's data is UB if read is true
|
||||
if(read && LL_I2C_IsActiveFlag_RXNE(i2c)) {
|
||||
*data = LL_I2C_ReceiveData8(i2c);
|
||||
data++;
|
||||
size--;
|
||||
} else if(!read && LL_I2C_IsActiveFlag_TXIS(i2c)) {
|
||||
LL_I2C_TransmitData8(i2c, *data);
|
||||
data++;
|
||||
size--;
|
||||
}
|
||||
|
||||
// Exit on timeout or premature stop, probably caused by a nacked address or byte
|
||||
if(should_stop) {
|
||||
ret = size == 0; // If the transfer was over, still a success
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret) {
|
||||
ret = furi_hal_i2c_wait_for_end(i2c, end, timer);
|
||||
}
|
||||
|
||||
LL_I2C_ClearFlag_STOP(i2c);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool furi_hal_i2c_transaction(
|
||||
I2C_TypeDef* i2c,
|
||||
uint16_t address,
|
||||
bool ten_bit,
|
||||
uint8_t* data,
|
||||
size_t size,
|
||||
FuriHalI2cBegin begin,
|
||||
FuriHalI2cEnd end,
|
||||
bool read,
|
||||
FuriHalCortexTimer timer) {
|
||||
uint32_t addr_size = ten_bit ? LL_I2C_ADDRSLAVE_10BIT : LL_I2C_ADDRSLAVE_7BIT;
|
||||
uint32_t start_signal = furi_hal_i2c_get_start_signal(begin, ten_bit, read);
|
||||
|
||||
if(!furi_hal_i2c_wait_for_idle(i2c, begin, timer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
do {
|
||||
uint8_t transfer_size = size;
|
||||
FuriHalI2cEnd transfer_end = end;
|
||||
|
||||
if(size > 255) {
|
||||
transfer_size = 255;
|
||||
transfer_end = FuriHalI2cEndPause;
|
||||
}
|
||||
|
||||
uint32_t end_signal = furi_hal_i2c_get_end_signal(transfer_end);
|
||||
|
||||
LL_I2C_HandleTransfer(i2c, address, addr_size, transfer_size, end_signal, start_signal);
|
||||
|
||||
if(!furi_hal_i2c_transfer(i2c, data, transfer_size, transfer_end, read, timer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size -= transfer_size;
|
||||
data += transfer_size;
|
||||
start_signal = LL_I2C_GENERATE_NOSTARTSTOP;
|
||||
} while(size > 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_rx_ext(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint16_t address,
|
||||
bool ten_bit,
|
||||
uint8_t* data,
|
||||
size_t size,
|
||||
FuriHalI2cBegin begin,
|
||||
FuriHalI2cEnd end,
|
||||
uint32_t timeout) {
|
||||
furi_check(handle->bus->current_handle == handle);
|
||||
|
||||
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000);
|
||||
|
||||
return furi_hal_i2c_transaction(
|
||||
handle->bus->i2c, address, ten_bit, data, size, begin, end, true, timer);
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_tx_ext(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint16_t address,
|
||||
bool ten_bit,
|
||||
const uint8_t* data,
|
||||
size_t size,
|
||||
FuriHalI2cBegin begin,
|
||||
FuriHalI2cEnd end,
|
||||
uint32_t timeout) {
|
||||
furi_check(handle->bus->current_handle == handle);
|
||||
|
||||
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000);
|
||||
|
||||
return furi_hal_i2c_transaction(
|
||||
handle->bus->i2c, address, ten_bit, (uint8_t*)data, size, begin, end, false, timer);
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_tx(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint8_t address,
|
||||
const uint8_t* data,
|
||||
size_t size,
|
||||
uint32_t timeout) {
|
||||
furi_assert(timeout > 0);
|
||||
|
||||
return furi_hal_i2c_tx_ext(
|
||||
handle, address, false, data, size, FuriHalI2cBeginStart, FuriHalI2cEndStop, timeout);
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_rx(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint8_t address,
|
||||
uint8_t* data,
|
||||
size_t size,
|
||||
uint32_t timeout) {
|
||||
furi_assert(timeout > 0);
|
||||
|
||||
return furi_hal_i2c_rx_ext(
|
||||
handle, address, false, data, size, FuriHalI2cBeginStart, FuriHalI2cEndStop, timeout);
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_trx(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint8_t address,
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_size,
|
||||
uint8_t* rx_data,
|
||||
size_t rx_size,
|
||||
uint32_t timeout) {
|
||||
return furi_hal_i2c_tx_ext(
|
||||
handle,
|
||||
address,
|
||||
false,
|
||||
tx_data,
|
||||
tx_size,
|
||||
FuriHalI2cBeginStart,
|
||||
FuriHalI2cEndStop,
|
||||
timeout) &&
|
||||
furi_hal_i2c_rx_ext(
|
||||
handle,
|
||||
address,
|
||||
false,
|
||||
rx_data,
|
||||
rx_size,
|
||||
FuriHalI2cBeginStart,
|
||||
FuriHalI2cEndStop,
|
||||
timeout);
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_is_device_ready(FuriHalI2cBusHandle* handle, uint8_t i2c_addr, uint32_t timeout) {
|
||||
furi_check(handle);
|
||||
furi_check(handle->bus->current_handle == handle);
|
||||
furi_assert(timeout > 0);
|
||||
|
||||
bool ret = true;
|
||||
FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout * 1000);
|
||||
|
||||
if(!furi_hal_i2c_wait_for_idle(handle->bus->i2c, FuriHalI2cBeginStart, timer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LL_I2C_HandleTransfer(
|
||||
handle->bus->i2c,
|
||||
i2c_addr,
|
||||
LL_I2C_ADDRSLAVE_7BIT,
|
||||
0,
|
||||
LL_I2C_MODE_AUTOEND,
|
||||
LL_I2C_GENERATE_START_WRITE);
|
||||
|
||||
if(!furi_hal_i2c_wait_for_end(handle->bus->i2c, FuriHalI2cEndStop, timer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = !LL_I2C_IsActiveFlag_NACK(handle->bus->i2c);
|
||||
|
||||
LL_I2C_ClearFlag_NACK(handle->bus->i2c);
|
||||
LL_I2C_ClearFlag_STOP(handle->bus->i2c);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_read_reg_8(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint8_t i2c_addr,
|
||||
uint8_t reg_addr,
|
||||
uint8_t* data,
|
||||
uint32_t timeout) {
|
||||
furi_check(handle);
|
||||
|
||||
return furi_hal_i2c_trx(handle, i2c_addr, ®_addr, 1, data, 1, timeout);
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_read_reg_16(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint8_t i2c_addr,
|
||||
uint8_t reg_addr,
|
||||
uint16_t* data,
|
||||
uint32_t timeout) {
|
||||
furi_check(handle);
|
||||
|
||||
uint8_t reg_data[2];
|
||||
bool ret = furi_hal_i2c_trx(handle, i2c_addr, ®_addr, 1, reg_data, 2, timeout);
|
||||
*data = (reg_data[0] << 8) | (reg_data[1]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_read_mem(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint8_t i2c_addr,
|
||||
uint8_t mem_addr,
|
||||
uint8_t* data,
|
||||
size_t len,
|
||||
uint32_t timeout) {
|
||||
furi_check(handle);
|
||||
|
||||
return furi_hal_i2c_trx(handle, i2c_addr, &mem_addr, 1, data, len, timeout);
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_write_reg_8(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint8_t i2c_addr,
|
||||
uint8_t reg_addr,
|
||||
uint8_t data,
|
||||
uint32_t timeout) {
|
||||
furi_check(handle);
|
||||
|
||||
const uint8_t tx_data[2] = {
|
||||
reg_addr,
|
||||
data,
|
||||
};
|
||||
|
||||
return furi_hal_i2c_tx(handle, i2c_addr, tx_data, 2, timeout);
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_write_reg_16(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint8_t i2c_addr,
|
||||
uint8_t reg_addr,
|
||||
uint16_t data,
|
||||
uint32_t timeout) {
|
||||
furi_check(handle);
|
||||
|
||||
const uint8_t tx_data[3] = {
|
||||
reg_addr,
|
||||
(data >> 8) & 0xFF,
|
||||
data & 0xFF,
|
||||
};
|
||||
|
||||
return furi_hal_i2c_tx(handle, i2c_addr, tx_data, 3, timeout);
|
||||
}
|
||||
|
||||
bool furi_hal_i2c_write_mem(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
uint8_t i2c_addr,
|
||||
uint8_t mem_addr,
|
||||
const uint8_t* data,
|
||||
size_t len,
|
||||
uint32_t timeout) {
|
||||
furi_check(handle);
|
||||
furi_check(handle->bus->current_handle == handle);
|
||||
furi_assert(timeout > 0);
|
||||
|
||||
return furi_hal_i2c_tx_ext(
|
||||
handle,
|
||||
i2c_addr,
|
||||
false,
|
||||
&mem_addr,
|
||||
1,
|
||||
FuriHalI2cBeginStart,
|
||||
FuriHalI2cEndPause,
|
||||
timeout) &&
|
||||
furi_hal_i2c_tx_ext(
|
||||
handle,
|
||||
i2c_addr,
|
||||
false,
|
||||
data,
|
||||
len,
|
||||
FuriHalI2cBeginResume,
|
||||
FuriHalI2cEndStop,
|
||||
timeout);
|
||||
}
|
||||
166
targets/f7/furi_hal/furi_hal_i2c_config.c
Normal file
166
targets/f7/furi_hal/furi_hal_i2c_config.c
Normal file
@@ -0,0 +1,166 @@
|
||||
#include <furi_hal_i2c_config.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_version.h>
|
||||
#include <furi_hal_bus.h>
|
||||
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
|
||||
/** Timing register value is computed with the STM32CubeMX Tool,
|
||||
* Standard Mode @100kHz with I2CCLK = 64 MHz,
|
||||
* rise time = 0ns, fall time = 0ns
|
||||
*/
|
||||
#define FURI_HAL_I2C_CONFIG_POWER_I2C_TIMINGS_100 0x10707DBC
|
||||
|
||||
/** Timing register value is computed with the STM32CubeMX Tool,
|
||||
* Fast Mode @400kHz with I2CCLK = 64 MHz,
|
||||
* rise time = 0ns, fall time = 0ns
|
||||
*/
|
||||
#define FURI_HAL_I2C_CONFIG_POWER_I2C_TIMINGS_400 0x00602173
|
||||
|
||||
FuriMutex* furi_hal_i2c_bus_power_mutex = NULL;
|
||||
|
||||
static void furi_hal_i2c_bus_power_event(FuriHalI2cBus* bus, FuriHalI2cBusEvent event) {
|
||||
if(event == FuriHalI2cBusEventInit) {
|
||||
furi_hal_i2c_bus_power_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
bus->current_handle = NULL;
|
||||
} else if(event == FuriHalI2cBusEventDeinit) {
|
||||
furi_mutex_free(furi_hal_i2c_bus_power_mutex);
|
||||
} else if(event == FuriHalI2cBusEventLock) {
|
||||
furi_check(
|
||||
furi_mutex_acquire(furi_hal_i2c_bus_power_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
} else if(event == FuriHalI2cBusEventUnlock) {
|
||||
furi_check(furi_mutex_release(furi_hal_i2c_bus_power_mutex) == FuriStatusOk);
|
||||
} else if(event == FuriHalI2cBusEventActivate) {
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_hal_bus_enable(FuriHalBusI2C1);
|
||||
LL_RCC_SetI2CClockSource(LL_RCC_I2C1_CLKSOURCE_PCLK1);
|
||||
FURI_CRITICAL_EXIT();
|
||||
} else if(event == FuriHalI2cBusEventDeactivate) {
|
||||
furi_hal_bus_disable(FuriHalBusI2C1);
|
||||
}
|
||||
}
|
||||
|
||||
FuriHalI2cBus furi_hal_i2c_bus_power = {
|
||||
.i2c = I2C1,
|
||||
.callback = furi_hal_i2c_bus_power_event,
|
||||
};
|
||||
|
||||
FuriMutex* furi_hal_i2c_bus_external_mutex = NULL;
|
||||
|
||||
static void furi_hal_i2c_bus_external_event(FuriHalI2cBus* bus, FuriHalI2cBusEvent event) {
|
||||
if(event == FuriHalI2cBusEventInit) {
|
||||
furi_hal_i2c_bus_external_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
bus->current_handle = NULL;
|
||||
} else if(event == FuriHalI2cBusEventDeinit) {
|
||||
furi_mutex_free(furi_hal_i2c_bus_external_mutex);
|
||||
} else if(event == FuriHalI2cBusEventLock) {
|
||||
furi_check(
|
||||
furi_mutex_acquire(furi_hal_i2c_bus_external_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
} else if(event == FuriHalI2cBusEventUnlock) {
|
||||
furi_check(furi_mutex_release(furi_hal_i2c_bus_external_mutex) == FuriStatusOk);
|
||||
} else if(event == FuriHalI2cBusEventActivate) {
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_hal_bus_enable(FuriHalBusI2C3);
|
||||
LL_RCC_SetI2CClockSource(LL_RCC_I2C3_CLKSOURCE_PCLK1);
|
||||
FURI_CRITICAL_EXIT();
|
||||
} else if(event == FuriHalI2cBusEventDeactivate) {
|
||||
furi_hal_bus_disable(FuriHalBusI2C3);
|
||||
}
|
||||
}
|
||||
|
||||
FuriHalI2cBus furi_hal_i2c_bus_external = {
|
||||
.i2c = I2C3,
|
||||
.callback = furi_hal_i2c_bus_external_event,
|
||||
};
|
||||
|
||||
void furi_hal_i2c_bus_handle_power_event(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
FuriHalI2cBusHandleEvent event) {
|
||||
if(event == FuriHalI2cBusHandleEventActivate) {
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_i2c_power_sda,
|
||||
GpioModeAltFunctionOpenDrain,
|
||||
GpioPullNo,
|
||||
GpioSpeedLow,
|
||||
GpioAltFn4I2C1);
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_i2c_power_scl,
|
||||
GpioModeAltFunctionOpenDrain,
|
||||
GpioPullNo,
|
||||
GpioSpeedLow,
|
||||
GpioAltFn4I2C1);
|
||||
|
||||
LL_I2C_InitTypeDef I2C_InitStruct;
|
||||
I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C;
|
||||
I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE;
|
||||
I2C_InitStruct.DigitalFilter = 0;
|
||||
I2C_InitStruct.OwnAddress1 = 0;
|
||||
I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK;
|
||||
I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;
|
||||
if(furi_hal_version_get_hw_version() > 10) {
|
||||
I2C_InitStruct.Timing = FURI_HAL_I2C_CONFIG_POWER_I2C_TIMINGS_400;
|
||||
} else {
|
||||
I2C_InitStruct.Timing = FURI_HAL_I2C_CONFIG_POWER_I2C_TIMINGS_100;
|
||||
}
|
||||
LL_I2C_Init(handle->bus->i2c, &I2C_InitStruct);
|
||||
// I2C is enabled at this point
|
||||
LL_I2C_EnableAutoEndMode(handle->bus->i2c);
|
||||
LL_I2C_SetOwnAddress2(handle->bus->i2c, 0, LL_I2C_OWNADDRESS2_NOMASK);
|
||||
LL_I2C_DisableOwnAddress2(handle->bus->i2c);
|
||||
LL_I2C_DisableGeneralCall(handle->bus->i2c);
|
||||
LL_I2C_EnableClockStretching(handle->bus->i2c);
|
||||
} else if(event == FuriHalI2cBusHandleEventDeactivate) {
|
||||
LL_I2C_Disable(handle->bus->i2c);
|
||||
furi_hal_gpio_write(&gpio_i2c_power_sda, 1);
|
||||
furi_hal_gpio_write(&gpio_i2c_power_scl, 1);
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_i2c_power_sda, GpioModeAnalog, GpioPullNo, GpioSpeedLow, GpioAltFnUnused);
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_i2c_power_scl, GpioModeAnalog, GpioPullNo, GpioSpeedLow, GpioAltFnUnused);
|
||||
}
|
||||
}
|
||||
|
||||
FuriHalI2cBusHandle furi_hal_i2c_handle_power = {
|
||||
.bus = &furi_hal_i2c_bus_power,
|
||||
.callback = furi_hal_i2c_bus_handle_power_event,
|
||||
};
|
||||
|
||||
void furi_hal_i2c_bus_handle_external_event(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
FuriHalI2cBusHandleEvent event) {
|
||||
if(event == FuriHalI2cBusHandleEventActivate) {
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_ext_pc0, GpioModeAltFunctionOpenDrain, GpioPullNo, GpioSpeedLow, GpioAltFn4I2C3);
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_ext_pc1, GpioModeAltFunctionOpenDrain, GpioPullNo, GpioSpeedLow, GpioAltFn4I2C3);
|
||||
|
||||
LL_I2C_InitTypeDef I2C_InitStruct;
|
||||
I2C_InitStruct.PeripheralMode = LL_I2C_MODE_I2C;
|
||||
I2C_InitStruct.AnalogFilter = LL_I2C_ANALOGFILTER_ENABLE;
|
||||
I2C_InitStruct.DigitalFilter = 0;
|
||||
I2C_InitStruct.OwnAddress1 = 0;
|
||||
I2C_InitStruct.TypeAcknowledge = LL_I2C_ACK;
|
||||
I2C_InitStruct.OwnAddrSize = LL_I2C_OWNADDRESS1_7BIT;
|
||||
I2C_InitStruct.Timing = FURI_HAL_I2C_CONFIG_POWER_I2C_TIMINGS_100;
|
||||
LL_I2C_Init(handle->bus->i2c, &I2C_InitStruct);
|
||||
// I2C is enabled at this point
|
||||
LL_I2C_EnableAutoEndMode(handle->bus->i2c);
|
||||
LL_I2C_SetOwnAddress2(handle->bus->i2c, 0, LL_I2C_OWNADDRESS2_NOMASK);
|
||||
LL_I2C_DisableOwnAddress2(handle->bus->i2c);
|
||||
LL_I2C_DisableGeneralCall(handle->bus->i2c);
|
||||
LL_I2C_EnableClockStretching(handle->bus->i2c);
|
||||
} else if(event == FuriHalI2cBusHandleEventDeactivate) {
|
||||
LL_I2C_Disable(handle->bus->i2c);
|
||||
furi_hal_gpio_write(&gpio_ext_pc0, 1);
|
||||
furi_hal_gpio_write(&gpio_ext_pc1, 1);
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow, GpioAltFnUnused);
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_ext_pc1, GpioModeAnalog, GpioPullNo, GpioSpeedLow, GpioAltFnUnused);
|
||||
}
|
||||
}
|
||||
|
||||
FuriHalI2cBusHandle furi_hal_i2c_handle_external = {
|
||||
.bus = &furi_hal_i2c_bus_external,
|
||||
.callback = furi_hal_i2c_bus_handle_external_event,
|
||||
};
|
||||
31
targets/f7/furi_hal/furi_hal_i2c_config.h
Normal file
31
targets/f7/furi_hal/furi_hal_i2c_config.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal_i2c_types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Internal(power) i2c bus, I2C1, under reset when not used */
|
||||
extern FuriHalI2cBus furi_hal_i2c_bus_power;
|
||||
|
||||
/** External i2c bus, I2C3, under reset when not used */
|
||||
extern FuriHalI2cBus furi_hal_i2c_bus_external;
|
||||
|
||||
/** Handle for internal(power) i2c bus
|
||||
* Bus: furi_hal_i2c_bus_external
|
||||
* Pins: PA9(SCL) / PA10(SDA), float on release
|
||||
* Params: 400khz
|
||||
*/
|
||||
extern FuriHalI2cBusHandle furi_hal_i2c_handle_power;
|
||||
|
||||
/** Handle for external i2c bus
|
||||
* Bus: furi_hal_i2c_bus_external
|
||||
* Pins: PC0(SCL) / PC1(SDA), float on release
|
||||
* Params: 100khz
|
||||
*/
|
||||
extern FuriHalI2cBusHandle furi_hal_i2c_handle_external;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
51
targets/f7/furi_hal/furi_hal_i2c_types.h
Normal file
51
targets/f7/furi_hal/furi_hal_i2c_types.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <stm32wbxx_ll_i2c.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FuriHalI2cBus FuriHalI2cBus;
|
||||
typedef struct FuriHalI2cBusHandle FuriHalI2cBusHandle;
|
||||
|
||||
/** FuriHal i2c bus states */
|
||||
typedef enum {
|
||||
FuriHalI2cBusEventInit, /**< Bus initialization event, called on system start */
|
||||
FuriHalI2cBusEventDeinit, /**< Bus deinitialization event, called on system stop */
|
||||
FuriHalI2cBusEventLock, /**< Bus lock event, called before activation */
|
||||
FuriHalI2cBusEventUnlock, /**< Bus unlock event, called after deactivation */
|
||||
FuriHalI2cBusEventActivate, /**< Bus activation event, called before handle activation */
|
||||
FuriHalI2cBusEventDeactivate, /**< Bus deactivation event, called after handle deactivation */
|
||||
} FuriHalI2cBusEvent;
|
||||
|
||||
/** FuriHal i2c bus event callback */
|
||||
typedef void (*FuriHalI2cBusEventCallback)(FuriHalI2cBus* bus, FuriHalI2cBusEvent event);
|
||||
|
||||
/** FuriHal i2c bus */
|
||||
struct FuriHalI2cBus {
|
||||
I2C_TypeDef* i2c;
|
||||
FuriHalI2cBusHandle* current_handle;
|
||||
FuriHalI2cBusEventCallback callback;
|
||||
};
|
||||
|
||||
/** FuriHal i2c handle states */
|
||||
typedef enum {
|
||||
FuriHalI2cBusHandleEventActivate, /**< Handle activate: connect gpio and apply bus config */
|
||||
FuriHalI2cBusHandleEventDeactivate, /**< Handle deactivate: disconnect gpio and reset bus config */
|
||||
} FuriHalI2cBusHandleEvent;
|
||||
|
||||
/** FuriHal i2c handle event callback */
|
||||
typedef void (*FuriHalI2cBusHandleEventCallback)(
|
||||
FuriHalI2cBusHandle* handle,
|
||||
FuriHalI2cBusHandleEvent event);
|
||||
|
||||
/** FuriHal i2c handle */
|
||||
struct FuriHalI2cBusHandle {
|
||||
FuriHalI2cBus* bus;
|
||||
FuriHalI2cBusHandleEventCallback callback;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
103
targets/f7/furi_hal/furi_hal_ibutton.c
Normal file
103
targets/f7/furi_hal/furi_hal_ibutton.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <furi_hal_ibutton.h>
|
||||
#include <furi_hal_interrupt.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_bus.h>
|
||||
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "FuriHalIbutton"
|
||||
#define FURI_HAL_IBUTTON_TIMER TIM1
|
||||
#define FURI_HAL_IBUTTON_TIMER_BUS FuriHalBusTIM1
|
||||
#define FURI_HAL_IBUTTON_TIMER_IRQ FuriHalInterruptIdTim1UpTim16
|
||||
|
||||
typedef enum {
|
||||
FuriHalIbuttonStateIdle,
|
||||
FuriHalIbuttonStateRunning,
|
||||
} FuriHalIbuttonState;
|
||||
|
||||
typedef struct {
|
||||
FuriHalIbuttonState state;
|
||||
FuriHalIbuttonEmulateCallback callback;
|
||||
void* context;
|
||||
} FuriHalIbutton;
|
||||
|
||||
FuriHalIbutton* furi_hal_ibutton = NULL;
|
||||
|
||||
static void furi_hal_ibutton_emulate_isr() {
|
||||
if(LL_TIM_IsActiveFlag_UPDATE(FURI_HAL_IBUTTON_TIMER)) {
|
||||
LL_TIM_ClearFlag_UPDATE(FURI_HAL_IBUTTON_TIMER);
|
||||
furi_hal_ibutton->callback(furi_hal_ibutton->context);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_ibutton_init() {
|
||||
furi_hal_ibutton = malloc(sizeof(FuriHalIbutton));
|
||||
furi_hal_ibutton->state = FuriHalIbuttonStateIdle;
|
||||
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
void furi_hal_ibutton_emulate_start(
|
||||
uint32_t period,
|
||||
FuriHalIbuttonEmulateCallback callback,
|
||||
void* context) {
|
||||
furi_assert(furi_hal_ibutton);
|
||||
furi_assert(furi_hal_ibutton->state == FuriHalIbuttonStateIdle);
|
||||
|
||||
furi_hal_ibutton->state = FuriHalIbuttonStateRunning;
|
||||
furi_hal_ibutton->callback = callback;
|
||||
furi_hal_ibutton->context = context;
|
||||
|
||||
furi_hal_bus_enable(FURI_HAL_IBUTTON_TIMER_BUS);
|
||||
|
||||
furi_hal_interrupt_set_isr(FURI_HAL_IBUTTON_TIMER_IRQ, furi_hal_ibutton_emulate_isr, NULL);
|
||||
|
||||
LL_TIM_SetPrescaler(FURI_HAL_IBUTTON_TIMER, 0);
|
||||
LL_TIM_SetCounterMode(FURI_HAL_IBUTTON_TIMER, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_SetAutoReload(FURI_HAL_IBUTTON_TIMER, period);
|
||||
LL_TIM_DisableARRPreload(FURI_HAL_IBUTTON_TIMER);
|
||||
LL_TIM_SetRepetitionCounter(FURI_HAL_IBUTTON_TIMER, 0);
|
||||
|
||||
LL_TIM_SetClockDivision(FURI_HAL_IBUTTON_TIMER, LL_TIM_CLOCKDIVISION_DIV1);
|
||||
LL_TIM_SetClockSource(FURI_HAL_IBUTTON_TIMER, LL_TIM_CLOCKSOURCE_INTERNAL);
|
||||
LL_TIM_GenerateEvent_UPDATE(FURI_HAL_IBUTTON_TIMER);
|
||||
|
||||
LL_TIM_EnableIT_UPDATE(FURI_HAL_IBUTTON_TIMER);
|
||||
|
||||
LL_TIM_EnableCounter(FURI_HAL_IBUTTON_TIMER);
|
||||
}
|
||||
|
||||
void furi_hal_ibutton_emulate_set_next(uint32_t period) {
|
||||
LL_TIM_SetAutoReload(FURI_HAL_IBUTTON_TIMER, period);
|
||||
}
|
||||
|
||||
void furi_hal_ibutton_emulate_stop() {
|
||||
furi_assert(furi_hal_ibutton);
|
||||
|
||||
if(furi_hal_ibutton->state == FuriHalIbuttonStateRunning) {
|
||||
furi_hal_ibutton->state = FuriHalIbuttonStateIdle;
|
||||
LL_TIM_DisableCounter(FURI_HAL_IBUTTON_TIMER);
|
||||
|
||||
furi_hal_bus_disable(FURI_HAL_IBUTTON_TIMER_BUS);
|
||||
furi_hal_interrupt_set_isr(FURI_HAL_IBUTTON_TIMER_IRQ, NULL, NULL);
|
||||
|
||||
furi_hal_ibutton->callback = NULL;
|
||||
furi_hal_ibutton->context = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_ibutton_pin_configure() {
|
||||
furi_hal_gpio_write(&gpio_ibutton, true);
|
||||
furi_hal_gpio_init(&gpio_ibutton, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
|
||||
void furi_hal_ibutton_pin_reset() {
|
||||
furi_hal_gpio_write(&gpio_ibutton, true);
|
||||
furi_hal_gpio_init(&gpio_ibutton, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
|
||||
void furi_hal_ibutton_pin_write(const bool state) {
|
||||
furi_hal_gpio_write(&gpio_ibutton, state);
|
||||
}
|
||||
60
targets/f7/furi_hal/furi_hal_ibutton.h
Normal file
60
targets/f7/furi_hal/furi_hal_ibutton.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @file furi_hal_ibutton.h
|
||||
* iButton HAL API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*FuriHalIbuttonEmulateCallback)(void* context);
|
||||
|
||||
/** Initialize */
|
||||
void furi_hal_ibutton_init();
|
||||
|
||||
/**
|
||||
* Start emulation timer
|
||||
* @param period timer period
|
||||
* @param callback timer callback
|
||||
* @param context callback context
|
||||
*/
|
||||
void furi_hal_ibutton_emulate_start(
|
||||
uint32_t period,
|
||||
FuriHalIbuttonEmulateCallback callback,
|
||||
void* context);
|
||||
|
||||
/**
|
||||
* Update emulation timer period
|
||||
* @param period new timer period
|
||||
*/
|
||||
void furi_hal_ibutton_emulate_set_next(uint32_t period);
|
||||
|
||||
/**
|
||||
* Stop emulation timer
|
||||
*/
|
||||
void furi_hal_ibutton_emulate_stop();
|
||||
|
||||
/**
|
||||
* Set the pin to normal mode (open collector), and sets it to float
|
||||
*/
|
||||
void furi_hal_ibutton_pin_configure();
|
||||
|
||||
/**
|
||||
* Sets the pin to analog mode, and sets it to float
|
||||
*/
|
||||
void furi_hal_ibutton_pin_reset();
|
||||
|
||||
/**
|
||||
* iButton write pin
|
||||
* @param state true / false
|
||||
*/
|
||||
void furi_hal_ibutton_pin_write(const bool state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
59
targets/f7/furi_hal/furi_hal_idle_timer.h
Normal file
59
targets/f7/furi_hal/furi_hal_idle_timer.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <stm32wbxx_ll_lptim.h>
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
#include <stm32wbxx_ll_bus.h>
|
||||
|
||||
#include <furi_hal_bus.h>
|
||||
|
||||
// Timer used for tickless idle
|
||||
#define FURI_HAL_IDLE_TIMER_MAX 0xFFFF
|
||||
#define FURI_HAL_IDLE_TIMER LPTIM1
|
||||
#define FURI_HAL_IDLE_TIMER_IRQ LPTIM1_IRQn
|
||||
|
||||
static inline void furi_hal_idle_timer_init() {
|
||||
furi_hal_bus_enable(FuriHalBusLPTIM1);
|
||||
// Configure clock source
|
||||
LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM1_CLKSOURCE_LSE);
|
||||
// There is a theoretical possibility that we need it
|
||||
LL_APB1_GRP1_EnableClockSleep(LL_APB1_GRP1_PERIPH_LPTIM1);
|
||||
// Set interrupt priority and enable them
|
||||
NVIC_SetPriority(
|
||||
FURI_HAL_IDLE_TIMER_IRQ, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 15, 0));
|
||||
NVIC_EnableIRQ(FURI_HAL_IDLE_TIMER_IRQ);
|
||||
}
|
||||
|
||||
static inline void furi_hal_idle_timer_start(uint32_t count) {
|
||||
count--;
|
||||
// Enable timer
|
||||
LL_LPTIM_Enable(FURI_HAL_IDLE_TIMER);
|
||||
while(!LL_LPTIM_IsEnabled(FURI_HAL_IDLE_TIMER))
|
||||
;
|
||||
|
||||
// Enable compare match interrupt
|
||||
LL_LPTIM_EnableIT_CMPM(FURI_HAL_IDLE_TIMER);
|
||||
|
||||
// Set compare, autoreload and start counter
|
||||
// Include some marging to workaround ARRM behaviour
|
||||
LL_LPTIM_SetCompare(FURI_HAL_IDLE_TIMER, count - 3);
|
||||
LL_LPTIM_SetAutoReload(FURI_HAL_IDLE_TIMER, count);
|
||||
LL_LPTIM_StartCounter(FURI_HAL_IDLE_TIMER, LL_LPTIM_OPERATING_MODE_ONESHOT);
|
||||
}
|
||||
|
||||
static inline void furi_hal_idle_timer_reset() {
|
||||
// Hard reset timer
|
||||
// THE ONLY RELIABLE WAY to stop it according to errata
|
||||
furi_hal_bus_reset(FuriHalBusLPTIM1);
|
||||
// Prevent IRQ handler call
|
||||
NVIC_ClearPendingIRQ(FURI_HAL_IDLE_TIMER_IRQ);
|
||||
}
|
||||
|
||||
static inline uint32_t furi_hal_idle_timer_get_cnt() {
|
||||
uint32_t counter = LL_LPTIM_GetCounter(FURI_HAL_IDLE_TIMER);
|
||||
uint32_t counter_shadow = LL_LPTIM_GetCounter(FURI_HAL_IDLE_TIMER);
|
||||
while(counter != counter_shadow) {
|
||||
counter = counter_shadow;
|
||||
counter_shadow = LL_LPTIM_GetCounter(FURI_HAL_IDLE_TIMER);
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
366
targets/f7/furi_hal/furi_hal_info.c
Normal file
366
targets/f7/furi_hal/furi_hal_info.c
Normal file
@@ -0,0 +1,366 @@
|
||||
#include <furi_hal_info.h>
|
||||
#include <furi_hal_region.h>
|
||||
#include <furi_hal_version.h>
|
||||
#include <furi_hal_bt.h>
|
||||
#include <furi_hal_crypto.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
#include <interface/patterns/ble_thread/shci/shci.h>
|
||||
#include <furi.h>
|
||||
#include <protobuf_version.h>
|
||||
|
||||
FURI_WEAK void furi_hal_info_get_api_version(uint16_t* major, uint16_t* minor) {
|
||||
*major = 0;
|
||||
*minor = 0;
|
||||
}
|
||||
|
||||
void furi_hal_info_get(PropertyValueCallback out, char sep, void* context) {
|
||||
FuriString* key = furi_string_alloc();
|
||||
FuriString* value = furi_string_alloc();
|
||||
|
||||
PropertyValueContext property_context = {
|
||||
.key = key, .value = value, .out = out, .sep = sep, .last = false, .context = context};
|
||||
|
||||
// Device Info version
|
||||
if(sep == '.') {
|
||||
property_value_out(&property_context, NULL, 2, "format", "major", "3");
|
||||
property_value_out(&property_context, NULL, 2, "format", "minor", "3");
|
||||
} else {
|
||||
property_value_out(&property_context, NULL, 3, "device", "info", "major", "2");
|
||||
property_value_out(&property_context, NULL, 3, "device", "info", "minor", "4");
|
||||
}
|
||||
|
||||
// Model name
|
||||
property_value_out(
|
||||
&property_context, NULL, 2, "hardware", "model", furi_hal_version_get_model_name());
|
||||
|
||||
// Unique ID
|
||||
furi_string_reset(value);
|
||||
const uint8_t* uid = furi_hal_version_uid();
|
||||
for(size_t i = 0; i < furi_hal_version_uid_size(); i++) {
|
||||
furi_string_cat_printf(value, "%02X", uid[i]);
|
||||
}
|
||||
property_value_out(&property_context, NULL, 2, "hardware", "uid", furi_string_get_cstr(value));
|
||||
|
||||
// OTP Revision
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "hardware", "otp", "ver", furi_hal_version_get_otp_version());
|
||||
property_value_out(
|
||||
&property_context, "%lu", 2, "hardware", "timestamp", furi_hal_version_get_hw_timestamp());
|
||||
|
||||
// Board Revision
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "hardware", "ver", furi_hal_version_get_hw_version());
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "hardware", "target", furi_hal_version_get_hw_target());
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "hardware", "body", furi_hal_version_get_hw_body());
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "hardware", "connect", furi_hal_version_get_hw_connect());
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "hardware", "display", furi_hal_version_get_hw_display());
|
||||
|
||||
// Board Personification
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "hardware", "color", furi_hal_version_get_hw_color());
|
||||
|
||||
if(sep == '.') {
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
3,
|
||||
"hardware",
|
||||
"region",
|
||||
"builtin",
|
||||
furi_hal_version_get_hw_region());
|
||||
} else {
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "hardware", "region", furi_hal_version_get_hw_region());
|
||||
}
|
||||
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
3,
|
||||
"hardware",
|
||||
"region",
|
||||
"provisioned",
|
||||
furi_hal_region_get_name());
|
||||
|
||||
const char* name = furi_hal_version_get_name_ptr();
|
||||
if(name) {
|
||||
property_value_out(&property_context, NULL, 2, "hardware", "name", name);
|
||||
}
|
||||
|
||||
// Firmware version
|
||||
const Version* firmware_version = furi_hal_version_get_firmware_version();
|
||||
if(firmware_version) {
|
||||
if(sep == '.') {
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
3,
|
||||
"firmware",
|
||||
"commit",
|
||||
"hash",
|
||||
version_get_githash(firmware_version));
|
||||
} else {
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
2,
|
||||
"firmware",
|
||||
"commit",
|
||||
version_get_githash(firmware_version));
|
||||
}
|
||||
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
3,
|
||||
"firmware",
|
||||
"commit",
|
||||
"dirty",
|
||||
version_get_dirty_flag(firmware_version) ? "true" : "false");
|
||||
|
||||
if(sep == '.') {
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
3,
|
||||
"firmware",
|
||||
"branch",
|
||||
"name",
|
||||
version_get_gitbranch(firmware_version));
|
||||
} else {
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
2,
|
||||
"firmware",
|
||||
"branch",
|
||||
version_get_gitbranch(firmware_version));
|
||||
}
|
||||
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
3,
|
||||
"firmware",
|
||||
"branch",
|
||||
"num",
|
||||
version_get_gitbranchnum(firmware_version));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
2,
|
||||
"firmware",
|
||||
"version",
|
||||
version_get_version(firmware_version));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
3,
|
||||
"firmware",
|
||||
"build",
|
||||
"date",
|
||||
version_get_builddate(firmware_version));
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "firmware", "target", version_get_target(firmware_version));
|
||||
|
||||
uint16_t api_version_major, api_version_minor;
|
||||
furi_hal_info_get_api_version(&api_version_major, &api_version_minor);
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "firmware", "api", "major", api_version_major);
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "firmware", "api", "minor", api_version_minor);
|
||||
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
3,
|
||||
"firmware",
|
||||
"origin",
|
||||
"fork",
|
||||
version_get_firmware_origin(firmware_version));
|
||||
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
3,
|
||||
"firmware",
|
||||
"origin",
|
||||
"git",
|
||||
version_get_git_origin(firmware_version));
|
||||
}
|
||||
|
||||
if(furi_hal_bt_is_alive()) {
|
||||
const BleGlueC2Info* ble_c2_info = ble_glue_get_c2_info();
|
||||
property_value_out(&property_context, NULL, 2, "radio", "alive", "true");
|
||||
property_value_out(
|
||||
&property_context,
|
||||
NULL,
|
||||
2,
|
||||
"radio",
|
||||
"mode",
|
||||
ble_c2_info->mode == BleGlueC2ModeFUS ? "FUS" : "Stack");
|
||||
|
||||
// FUS Info
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "radio", "fus", "major", ble_c2_info->FusVersionMajor);
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "radio", "fus", "minor", ble_c2_info->FusVersionMinor);
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "radio", "fus", "sub", ble_c2_info->FusVersionSub);
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%dK",
|
||||
3,
|
||||
"radio",
|
||||
"fus",
|
||||
"sram2b",
|
||||
ble_c2_info->FusMemorySizeSram2B);
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%dK",
|
||||
3,
|
||||
"radio",
|
||||
"fus",
|
||||
"sram2a",
|
||||
ble_c2_info->FusMemorySizeSram2A);
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%dK",
|
||||
3,
|
||||
"radio",
|
||||
"fus",
|
||||
"flash",
|
||||
ble_c2_info->FusMemorySizeFlash * 4);
|
||||
|
||||
// Stack Info
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "radio", "stack", "type", ble_c2_info->StackType);
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "radio", "stack", "major", ble_c2_info->VersionMajor);
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "radio", "stack", "minor", ble_c2_info->VersionMinor);
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "radio", "stack", "sub", ble_c2_info->VersionSub);
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "radio", "stack", "branch", ble_c2_info->VersionBranch);
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
3,
|
||||
"radio",
|
||||
"stack",
|
||||
"release",
|
||||
ble_c2_info->VersionReleaseType);
|
||||
property_value_out(
|
||||
&property_context, "%dK", 3, "radio", "stack", "sram2b", ble_c2_info->MemorySizeSram2B);
|
||||
property_value_out(
|
||||
&property_context, "%dK", 3, "radio", "stack", "sram2a", ble_c2_info->MemorySizeSram2A);
|
||||
property_value_out(
|
||||
&property_context, "%dK", 3, "radio", "stack", "sram1", ble_c2_info->MemorySizeSram1);
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%dK",
|
||||
3,
|
||||
"radio",
|
||||
"stack",
|
||||
"flash",
|
||||
ble_c2_info->MemorySizeFlash * 4);
|
||||
|
||||
// Mac address
|
||||
furi_string_reset(value);
|
||||
const uint8_t* ble_mac = furi_hal_version_get_ble_mac();
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
furi_string_cat_printf(value, "%02X", ble_mac[i]);
|
||||
}
|
||||
property_value_out(
|
||||
&property_context, NULL, 3, "radio", "ble", "mac", furi_string_get_cstr(value));
|
||||
|
||||
// Signature verification
|
||||
uint8_t enclave_keys = 0;
|
||||
uint8_t enclave_valid_keys = 0;
|
||||
bool enclave_valid = furi_hal_crypto_enclave_verify(&enclave_keys, &enclave_valid_keys);
|
||||
if(sep == '.') {
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "enclave", "keys", "valid", enclave_valid_keys);
|
||||
} else {
|
||||
property_value_out(
|
||||
&property_context, "%d", 3, "enclave", "valid", "keys", enclave_valid_keys);
|
||||
}
|
||||
|
||||
property_value_out(
|
||||
&property_context, NULL, 2, "enclave", "valid", enclave_valid ? "true" : "false");
|
||||
} else {
|
||||
property_value_out(&property_context, NULL, 2, "radio", "alive", "false");
|
||||
}
|
||||
|
||||
// RTC flags
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%u",
|
||||
2,
|
||||
"system",
|
||||
"debug",
|
||||
furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug));
|
||||
property_value_out(
|
||||
&property_context, "%u", 2, "system", "lock", furi_hal_rtc_is_flag_set(FuriHalRtcFlagLock));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%u",
|
||||
2,
|
||||
"system",
|
||||
"orient",
|
||||
furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%u",
|
||||
3,
|
||||
"system",
|
||||
"sleep",
|
||||
"legacy",
|
||||
furi_hal_rtc_is_flag_set(FuriHalRtcFlagLegacySleep));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%u",
|
||||
2,
|
||||
"system",
|
||||
"stealth",
|
||||
furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode));
|
||||
|
||||
property_value_out(
|
||||
&property_context, "%u", 3, "system", "heap", "track", furi_hal_rtc_get_heap_track_mode());
|
||||
property_value_out(&property_context, "%u", 2, "system", "boot", furi_hal_rtc_get_boot_mode());
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%u",
|
||||
3,
|
||||
"system",
|
||||
"locale",
|
||||
"time",
|
||||
furi_hal_rtc_get_locale_timeformat());
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%u",
|
||||
3,
|
||||
"system",
|
||||
"locale",
|
||||
"date",
|
||||
furi_hal_rtc_get_locale_dateformat());
|
||||
property_value_out(
|
||||
&property_context, "%u", 3, "system", "locale", "unit", furi_hal_rtc_get_locale_units());
|
||||
property_value_out(
|
||||
&property_context, "%u", 3, "system", "log", "level", furi_hal_rtc_get_log_level());
|
||||
|
||||
property_value_out(
|
||||
&property_context, "%u", 3, "protobuf", "version", "major", PROTOBUF_MAJOR_VERSION);
|
||||
property_context.last = true;
|
||||
property_value_out(
|
||||
&property_context, "%u", 3, "protobuf", "version", "minor", PROTOBUF_MINOR_VERSION);
|
||||
|
||||
furi_string_free(key);
|
||||
furi_string_free(value);
|
||||
}
|
||||
686
targets/f7/furi_hal/furi_hal_infrared.c
Normal file
686
targets/f7/furi_hal/furi_hal_infrared.c
Normal file
@@ -0,0 +1,686 @@
|
||||
#include <furi_hal_infrared.h>
|
||||
#include <furi_hal_interrupt.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_bus.h>
|
||||
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
#include <stm32wbxx_ll_dma.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <math.h>
|
||||
|
||||
// #define INFRARED_TX_DEBUG
|
||||
|
||||
#if defined INFRARED_TX_DEBUG
|
||||
#define gpio_infrared_tx gpio_ext_pa7
|
||||
#endif
|
||||
|
||||
#define INFRARED_TIM_TX_DMA_BUFFER_SIZE 200
|
||||
#define INFRARED_POLARITY_SHIFT 1
|
||||
|
||||
#define INFRARED_TX_CCMR_HIGH \
|
||||
(TIM_CCMR2_OC3PE | LL_TIM_OCMODE_PWM2) /* Mark time - enable PWM2 mode */
|
||||
#define INFRARED_TX_CCMR_LOW \
|
||||
(TIM_CCMR2_OC3PE | LL_TIM_OCMODE_FORCED_INACTIVE) /* Space time - force low */
|
||||
|
||||
/* DMA Channels definition */
|
||||
#define INFRARED_DMA DMA2
|
||||
#define INFRARED_DMA_CH1_CHANNEL LL_DMA_CHANNEL_1
|
||||
#define INFRARED_DMA_CH2_CHANNEL LL_DMA_CHANNEL_2
|
||||
#define INFRARED_DMA_CH1_IRQ FuriHalInterruptIdDma2Ch1
|
||||
#define INFRARED_DMA_CH2_IRQ FuriHalInterruptIdDma2Ch2
|
||||
#define INFRARED_DMA_CH1_DEF INFRARED_DMA, INFRARED_DMA_CH1_CHANNEL
|
||||
#define INFRARED_DMA_CH2_DEF INFRARED_DMA, INFRARED_DMA_CH2_CHANNEL
|
||||
|
||||
/* Timers definition */
|
||||
#define INFRARED_RX_TIMER TIM2
|
||||
#define INFRARED_DMA_TIMER TIM1
|
||||
#define INFRARED_RX_TIMER_BUS FuriHalBusTIM2
|
||||
#define INFRARED_DMA_TIMER_BUS FuriHalBusTIM1
|
||||
|
||||
/* Misc */
|
||||
#define INFRARED_RX_GPIO_ALT GpioAltFn1TIM2
|
||||
#define INFRARED_RX_IRQ FuriHalInterruptIdTIM2
|
||||
|
||||
typedef struct {
|
||||
FuriHalInfraredRxCaptureCallback capture_callback;
|
||||
void* capture_context;
|
||||
FuriHalInfraredRxTimeoutCallback timeout_callback;
|
||||
void* timeout_context;
|
||||
} InfraredTimRx;
|
||||
|
||||
typedef struct {
|
||||
uint8_t* polarity;
|
||||
uint16_t* data;
|
||||
size_t size;
|
||||
bool packet_end;
|
||||
bool last_packet_end;
|
||||
} InfraredTxBuf;
|
||||
|
||||
typedef struct {
|
||||
float cycle_duration;
|
||||
FuriHalInfraredTxGetDataISRCallback data_callback;
|
||||
FuriHalInfraredTxSignalSentISRCallback signal_sent_callback;
|
||||
void* data_context;
|
||||
void* signal_sent_context;
|
||||
InfraredTxBuf buffer[2];
|
||||
FuriSemaphore* stop_semaphore;
|
||||
uint32_t
|
||||
tx_timing_rest_duration; /** if timing is too long (> 0xFFFF), send it in few iterations */
|
||||
bool tx_timing_rest_level;
|
||||
FuriHalInfraredTxGetDataState tx_timing_rest_status;
|
||||
} InfraredTimTx;
|
||||
|
||||
typedef enum {
|
||||
InfraredStateIdle, /** Furi Hal Infrared is ready to start RX or TX */
|
||||
InfraredStateAsyncRx, /** Async RX started */
|
||||
InfraredStateAsyncTx, /** Async TX started, DMA and timer is on */
|
||||
InfraredStateAsyncTxStopReq, /** Async TX started, async stop request received */
|
||||
InfraredStateAsyncTxStopInProgress, /** Async TX started, stop request is processed and we wait for last data to be sent */
|
||||
InfraredStateAsyncTxStopped, /** Async TX complete, cleanup needed */
|
||||
InfraredStateMAX,
|
||||
} InfraredState;
|
||||
|
||||
static volatile InfraredState furi_hal_infrared_state = InfraredStateIdle;
|
||||
static InfraredTimTx infrared_tim_tx;
|
||||
static InfraredTimRx infrared_tim_rx;
|
||||
|
||||
static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift);
|
||||
static void furi_hal_infrared_async_tx_free_resources(void);
|
||||
static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift);
|
||||
static void furi_hal_infrared_tx_dma_set_buffer(uint8_t buf_num);
|
||||
static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num);
|
||||
static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void);
|
||||
static void furi_hal_infrared_tx_dma_polarity_isr();
|
||||
static void furi_hal_infrared_tx_dma_isr();
|
||||
|
||||
static void furi_hal_infrared_tim_rx_isr() {
|
||||
static uint32_t previous_captured_ch2 = 0;
|
||||
|
||||
/* Timeout */
|
||||
if(LL_TIM_IsActiveFlag_CC3(INFRARED_RX_TIMER)) {
|
||||
LL_TIM_ClearFlag_CC3(INFRARED_RX_TIMER);
|
||||
furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx);
|
||||
|
||||
/* Timers CNT register starts to counting from 0 to ARR, but it is
|
||||
* reseted when Channel 1 catches interrupt. It is not reseted by
|
||||
* channel 2, though, so we have to distract it's values (see TimerIRQSourceCCI1 ISR).
|
||||
* This can cause false timeout: when time is over, but we started
|
||||
* receiving new signal few microseconds ago, because CNT register
|
||||
* is reseted once per period, not per sample. */
|
||||
if(LL_GPIO_IsInputPinSet(gpio_infrared_rx.port, gpio_infrared_rx.pin) != 0) {
|
||||
if(infrared_tim_rx.timeout_callback)
|
||||
infrared_tim_rx.timeout_callback(infrared_tim_rx.timeout_context);
|
||||
}
|
||||
}
|
||||
|
||||
/* Rising Edge */
|
||||
if(LL_TIM_IsActiveFlag_CC1(INFRARED_RX_TIMER)) {
|
||||
LL_TIM_ClearFlag_CC1(INFRARED_RX_TIMER);
|
||||
furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx);
|
||||
|
||||
if(READ_BIT(INFRARED_RX_TIMER->CCMR1, TIM_CCMR1_CC1S)) {
|
||||
/* Low pin level is a Mark state of INFRARED signal. Invert level for further processing. */
|
||||
uint32_t duration = LL_TIM_IC_GetCaptureCH1(INFRARED_RX_TIMER) - previous_captured_ch2;
|
||||
if(infrared_tim_rx.capture_callback)
|
||||
infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 1, duration);
|
||||
} else {
|
||||
furi_assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Falling Edge */
|
||||
if(LL_TIM_IsActiveFlag_CC2(INFRARED_RX_TIMER)) {
|
||||
LL_TIM_ClearFlag_CC2(INFRARED_RX_TIMER);
|
||||
furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx);
|
||||
|
||||
if(READ_BIT(INFRARED_RX_TIMER->CCMR1, TIM_CCMR1_CC2S)) {
|
||||
/* High pin level is a Space state of INFRARED signal. Invert level for further processing. */
|
||||
uint32_t duration = LL_TIM_IC_GetCaptureCH2(INFRARED_RX_TIMER);
|
||||
previous_captured_ch2 = duration;
|
||||
if(infrared_tim_rx.capture_callback)
|
||||
infrared_tim_rx.capture_callback(infrared_tim_rx.capture_context, 0, duration);
|
||||
} else {
|
||||
furi_assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_rx_start(void) {
|
||||
furi_assert(furi_hal_infrared_state == InfraredStateIdle);
|
||||
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_infrared_rx,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedLow,
|
||||
INFRARED_RX_GPIO_ALT);
|
||||
|
||||
furi_hal_bus_enable(INFRARED_RX_TIMER_BUS);
|
||||
|
||||
LL_TIM_InitTypeDef TIM_InitStruct = {0};
|
||||
TIM_InitStruct.Prescaler = 64 - 1;
|
||||
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
|
||||
TIM_InitStruct.Autoreload = 0x7FFFFFFE;
|
||||
TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
|
||||
LL_TIM_Init(INFRARED_RX_TIMER, &TIM_InitStruct);
|
||||
|
||||
LL_TIM_SetClockSource(INFRARED_RX_TIMER, LL_TIM_CLOCKSOURCE_INTERNAL);
|
||||
LL_TIM_DisableARRPreload(INFRARED_RX_TIMER);
|
||||
LL_TIM_SetTriggerInput(INFRARED_RX_TIMER, LL_TIM_TS_TI1FP1);
|
||||
LL_TIM_SetSlaveMode(INFRARED_RX_TIMER, LL_TIM_SLAVEMODE_RESET);
|
||||
LL_TIM_CC_DisableChannel(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2);
|
||||
LL_TIM_IC_SetFilter(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2, LL_TIM_IC_FILTER_FDIV1);
|
||||
LL_TIM_IC_SetPolarity(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2, LL_TIM_IC_POLARITY_FALLING);
|
||||
LL_TIM_DisableIT_TRIG(INFRARED_RX_TIMER);
|
||||
LL_TIM_DisableDMAReq_TRIG(INFRARED_RX_TIMER);
|
||||
LL_TIM_SetTriggerOutput(INFRARED_RX_TIMER, LL_TIM_TRGO_RESET);
|
||||
LL_TIM_EnableMasterSlaveMode(INFRARED_RX_TIMER);
|
||||
LL_TIM_IC_SetActiveInput(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_ACTIVEINPUT_DIRECTTI);
|
||||
LL_TIM_IC_SetPrescaler(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_ICPSC_DIV1);
|
||||
LL_TIM_IC_SetFilter(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_IC_FILTER_FDIV1);
|
||||
LL_TIM_IC_SetPolarity(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_IC_POLARITY_RISING);
|
||||
LL_TIM_IC_SetActiveInput(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2, LL_TIM_ACTIVEINPUT_INDIRECTTI);
|
||||
LL_TIM_IC_SetPrescaler(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2, LL_TIM_ICPSC_DIV1);
|
||||
|
||||
furi_hal_interrupt_set_isr(INFRARED_RX_IRQ, furi_hal_infrared_tim_rx_isr, NULL);
|
||||
furi_hal_infrared_state = InfraredStateAsyncRx;
|
||||
|
||||
LL_TIM_EnableIT_CC1(INFRARED_RX_TIMER);
|
||||
LL_TIM_EnableIT_CC2(INFRARED_RX_TIMER);
|
||||
LL_TIM_CC_EnableChannel(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH1);
|
||||
LL_TIM_CC_EnableChannel(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH2);
|
||||
|
||||
LL_TIM_SetCounter(INFRARED_RX_TIMER, 0);
|
||||
LL_TIM_EnableCounter(INFRARED_RX_TIMER);
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_rx_stop(void) {
|
||||
furi_assert(furi_hal_infrared_state == InfraredStateAsyncRx);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_hal_bus_disable(INFRARED_RX_TIMER_BUS);
|
||||
furi_hal_interrupt_set_isr(INFRARED_RX_IRQ, NULL, NULL);
|
||||
furi_hal_infrared_state = InfraredStateIdle;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_rx_set_timeout(uint32_t timeout_us) {
|
||||
LL_TIM_OC_SetCompareCH3(INFRARED_RX_TIMER, timeout_us);
|
||||
LL_TIM_OC_SetMode(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_ACTIVE);
|
||||
LL_TIM_CC_EnableChannel(INFRARED_RX_TIMER, LL_TIM_CHANNEL_CH3);
|
||||
LL_TIM_EnableIT_CC3(INFRARED_RX_TIMER);
|
||||
}
|
||||
|
||||
bool furi_hal_infrared_is_busy(void) {
|
||||
return furi_hal_infrared_state != InfraredStateIdle;
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_rx_set_capture_isr_callback(
|
||||
FuriHalInfraredRxCaptureCallback callback,
|
||||
void* ctx) {
|
||||
infrared_tim_rx.capture_callback = callback;
|
||||
infrared_tim_rx.capture_context = ctx;
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_rx_set_timeout_isr_callback(
|
||||
FuriHalInfraredRxTimeoutCallback callback,
|
||||
void* ctx) {
|
||||
infrared_tim_rx.timeout_callback = callback;
|
||||
infrared_tim_rx.timeout_context = ctx;
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_tx_dma_terminate(void) {
|
||||
LL_DMA_DisableIT_TC(INFRARED_DMA_CH1_DEF);
|
||||
LL_DMA_DisableIT_HT(INFRARED_DMA_CH2_DEF);
|
||||
LL_DMA_DisableIT_TC(INFRARED_DMA_CH2_DEF);
|
||||
|
||||
furi_assert(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress);
|
||||
|
||||
LL_DMA_DisableIT_TC(INFRARED_DMA_CH1_DEF);
|
||||
LL_DMA_DisableChannel(INFRARED_DMA_CH2_DEF);
|
||||
LL_DMA_DisableChannel(INFRARED_DMA_CH1_DEF);
|
||||
LL_TIM_DisableCounter(INFRARED_DMA_TIMER);
|
||||
FuriStatus status = furi_semaphore_release(infrared_tim_tx.stop_semaphore);
|
||||
furi_check(status == FuriStatusOk);
|
||||
furi_hal_infrared_state = InfraredStateAsyncTxStopped;
|
||||
}
|
||||
|
||||
static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) {
|
||||
uint8_t buf_num = 0;
|
||||
uint32_t buffer_adr = LL_DMA_GetMemoryAddress(INFRARED_DMA_CH2_DEF);
|
||||
if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[0].data) {
|
||||
buf_num = 0;
|
||||
} else if(buffer_adr == (uint32_t)infrared_tim_tx.buffer[1].data) {
|
||||
buf_num = 1;
|
||||
} else {
|
||||
furi_assert(0);
|
||||
}
|
||||
return buf_num;
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_tx_dma_polarity_isr() {
|
||||
#if INFRARED_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1
|
||||
if(LL_DMA_IsActiveFlag_TE1(INFRARED_DMA)) {
|
||||
LL_DMA_ClearFlag_TE1(INFRARED_DMA);
|
||||
furi_crash(NULL);
|
||||
}
|
||||
if(LL_DMA_IsActiveFlag_TC1(INFRARED_DMA) && LL_DMA_IsEnabledIT_TC(INFRARED_DMA_CH1_DEF)) {
|
||||
LL_DMA_ClearFlag_TC1(INFRARED_DMA);
|
||||
|
||||
furi_check(
|
||||
(furi_hal_infrared_state == InfraredStateAsyncTx) ||
|
||||
(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) ||
|
||||
(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress));
|
||||
/* actually TC2 is processed and buffer is next buffer */
|
||||
uint8_t next_buf_num = furi_hal_infrared_get_current_dma_tx_buffer();
|
||||
furi_hal_infrared_tx_dma_set_polarity(next_buf_num, 0);
|
||||
}
|
||||
#else
|
||||
#error Update this code. Would you kindly?
|
||||
#endif
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_tx_dma_isr() {
|
||||
#if INFRARED_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2
|
||||
if(LL_DMA_IsActiveFlag_TE2(INFRARED_DMA)) {
|
||||
LL_DMA_ClearFlag_TE2(INFRARED_DMA);
|
||||
furi_crash(NULL);
|
||||
}
|
||||
if(LL_DMA_IsActiveFlag_HT2(INFRARED_DMA) && LL_DMA_IsEnabledIT_HT(INFRARED_DMA_CH2_DEF)) {
|
||||
LL_DMA_ClearFlag_HT2(INFRARED_DMA);
|
||||
uint8_t buf_num = furi_hal_infrared_get_current_dma_tx_buffer();
|
||||
uint8_t next_buf_num = !buf_num;
|
||||
if(infrared_tim_tx.buffer[buf_num].last_packet_end) {
|
||||
LL_DMA_DisableIT_HT(INFRARED_DMA_CH2_DEF);
|
||||
} else if(
|
||||
!infrared_tim_tx.buffer[buf_num].packet_end ||
|
||||
(furi_hal_infrared_state == InfraredStateAsyncTx)) {
|
||||
furi_hal_infrared_tx_fill_buffer(next_buf_num, 0);
|
||||
if(infrared_tim_tx.buffer[next_buf_num].last_packet_end) {
|
||||
LL_DMA_DisableIT_HT(INFRARED_DMA_CH2_DEF);
|
||||
}
|
||||
} else if(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) {
|
||||
/* fallthrough */
|
||||
} else {
|
||||
furi_crash(NULL);
|
||||
}
|
||||
}
|
||||
if(LL_DMA_IsActiveFlag_TC2(INFRARED_DMA) && LL_DMA_IsEnabledIT_TC(INFRARED_DMA_CH2_DEF)) {
|
||||
LL_DMA_ClearFlag_TC2(INFRARED_DMA);
|
||||
furi_check(
|
||||
(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress) ||
|
||||
(furi_hal_infrared_state == InfraredStateAsyncTxStopReq) ||
|
||||
(furi_hal_infrared_state == InfraredStateAsyncTx));
|
||||
|
||||
uint8_t buf_num = furi_hal_infrared_get_current_dma_tx_buffer();
|
||||
uint8_t next_buf_num = !buf_num;
|
||||
if(furi_hal_infrared_state == InfraredStateAsyncTxStopInProgress) {
|
||||
furi_hal_infrared_tx_dma_terminate();
|
||||
} else if(
|
||||
infrared_tim_tx.buffer[buf_num].last_packet_end ||
|
||||
(infrared_tim_tx.buffer[buf_num].packet_end &&
|
||||
(furi_hal_infrared_state == InfraredStateAsyncTxStopReq))) {
|
||||
furi_hal_infrared_state = InfraredStateAsyncTxStopInProgress;
|
||||
furi_hal_infrared_tx_fill_buffer_last(next_buf_num);
|
||||
furi_hal_infrared_tx_dma_set_buffer(next_buf_num);
|
||||
} else {
|
||||
/* if it's not end of the packet - continue receiving */
|
||||
furi_hal_infrared_tx_dma_set_buffer(next_buf_num);
|
||||
}
|
||||
if(infrared_tim_tx.signal_sent_callback && infrared_tim_tx.buffer[buf_num].packet_end &&
|
||||
(furi_hal_infrared_state != InfraredStateAsyncTxStopped)) {
|
||||
infrared_tim_tx.signal_sent_callback(infrared_tim_tx.signal_sent_context);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#error Update this code. Would you kindly?
|
||||
#endif
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cycle) {
|
||||
LL_TIM_DisableCounter(INFRARED_DMA_TIMER);
|
||||
LL_TIM_SetRepetitionCounter(INFRARED_DMA_TIMER, 0);
|
||||
LL_TIM_SetCounter(INFRARED_DMA_TIMER, 0);
|
||||
LL_TIM_SetPrescaler(INFRARED_DMA_TIMER, 0);
|
||||
LL_TIM_SetCounterMode(INFRARED_DMA_TIMER, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_EnableARRPreload(INFRARED_DMA_TIMER);
|
||||
LL_TIM_SetAutoReload(
|
||||
INFRARED_DMA_TIMER,
|
||||
__LL_TIM_CALC_ARR(SystemCoreClock, LL_TIM_GetPrescaler(INFRARED_DMA_TIMER), freq));
|
||||
#if defined INFRARED_TX_DEBUG
|
||||
LL_TIM_OC_SetCompareCH1(
|
||||
INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle)));
|
||||
LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1);
|
||||
/* LL_TIM_OCMODE_PWM2 set by DMA */
|
||||
LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE);
|
||||
LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH);
|
||||
LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1);
|
||||
LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1N);
|
||||
LL_TIM_DisableIT_CC1(INFRARED_DMA_TIMER);
|
||||
#else
|
||||
LL_TIM_OC_SetCompareCH3(
|
||||
INFRARED_DMA_TIMER, ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle)));
|
||||
LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3);
|
||||
/* LL_TIM_OCMODE_PWM2 set by DMA */
|
||||
LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE);
|
||||
LL_TIM_OC_SetPolarity(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N, LL_TIM_OCPOLARITY_HIGH);
|
||||
LL_TIM_OC_DisableFast(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3);
|
||||
LL_TIM_CC_EnableChannel(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3N);
|
||||
LL_TIM_DisableIT_CC3(INFRARED_DMA_TIMER);
|
||||
#endif
|
||||
LL_TIM_DisableMasterSlaveMode(INFRARED_DMA_TIMER);
|
||||
LL_TIM_EnableAllOutputs(INFRARED_DMA_TIMER);
|
||||
LL_TIM_DisableIT_UPDATE(INFRARED_DMA_TIMER);
|
||||
LL_TIM_EnableDMAReq_UPDATE(INFRARED_DMA_TIMER);
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_configure_tim_cmgr2_dma_tx(void) {
|
||||
LL_DMA_InitTypeDef dma_config = {0};
|
||||
#if defined INFRARED_TX_DEBUG
|
||||
dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR1);
|
||||
#else
|
||||
dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->CCMR2);
|
||||
#endif
|
||||
dma_config.MemoryOrM2MDstAddress = (uint32_t)NULL;
|
||||
dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
|
||||
dma_config.Mode = LL_DMA_MODE_NORMAL;
|
||||
dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
||||
dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
||||
/* fill word to have other bits set to 0 */
|
||||
dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD;
|
||||
dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_BYTE;
|
||||
dma_config.NbData = 0;
|
||||
dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP;
|
||||
dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH;
|
||||
LL_DMA_Init(INFRARED_DMA_CH1_DEF, &dma_config);
|
||||
|
||||
#if INFRARED_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1
|
||||
LL_DMA_ClearFlag_TE1(INFRARED_DMA);
|
||||
LL_DMA_ClearFlag_TC1(INFRARED_DMA);
|
||||
#else
|
||||
#error Update this code. Would you kindly?
|
||||
#endif
|
||||
|
||||
LL_DMA_EnableIT_TE(INFRARED_DMA_CH1_DEF);
|
||||
LL_DMA_EnableIT_TC(INFRARED_DMA_CH1_DEF);
|
||||
|
||||
furi_hal_interrupt_set_isr_ex(
|
||||
INFRARED_DMA_CH1_IRQ, 4, furi_hal_infrared_tx_dma_polarity_isr, NULL);
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_configure_tim_rcr_dma_tx(void) {
|
||||
LL_DMA_InitTypeDef dma_config = {0};
|
||||
dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (INFRARED_DMA_TIMER->RCR);
|
||||
dma_config.MemoryOrM2MDstAddress = (uint32_t)NULL;
|
||||
dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH;
|
||||
dma_config.Mode = LL_DMA_MODE_NORMAL;
|
||||
dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT;
|
||||
dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT;
|
||||
dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_HALFWORD;
|
||||
dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_HALFWORD;
|
||||
dma_config.NbData = 0;
|
||||
dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM1_UP;
|
||||
dma_config.Priority = LL_DMA_PRIORITY_MEDIUM;
|
||||
LL_DMA_Init(INFRARED_DMA_CH2_DEF, &dma_config);
|
||||
|
||||
#if INFRARED_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2
|
||||
LL_DMA_ClearFlag_TC2(INFRARED_DMA);
|
||||
LL_DMA_ClearFlag_HT2(INFRARED_DMA);
|
||||
LL_DMA_ClearFlag_TE2(INFRARED_DMA);
|
||||
#else
|
||||
#error Update this code. Would you kindly?
|
||||
#endif
|
||||
|
||||
LL_DMA_EnableIT_TC(INFRARED_DMA_CH2_DEF);
|
||||
LL_DMA_EnableIT_HT(INFRARED_DMA_CH2_DEF);
|
||||
LL_DMA_EnableIT_TE(INFRARED_DMA_CH2_DEF);
|
||||
|
||||
furi_hal_interrupt_set_isr_ex(INFRARED_DMA_CH2_IRQ, 5, furi_hal_infrared_tx_dma_isr, NULL);
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_tx_fill_buffer_last(uint8_t buf_num) {
|
||||
furi_assert(buf_num < 2);
|
||||
furi_assert(furi_hal_infrared_state != InfraredStateAsyncRx);
|
||||
furi_assert(furi_hal_infrared_state < InfraredStateMAX);
|
||||
furi_assert(infrared_tim_tx.data_callback);
|
||||
InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num];
|
||||
furi_assert(buffer->data != NULL);
|
||||
(void)buffer->data;
|
||||
furi_assert(buffer->polarity != NULL);
|
||||
(void)buffer->polarity;
|
||||
|
||||
infrared_tim_tx.buffer[buf_num].data[0] = 0; // 1 pulse
|
||||
infrared_tim_tx.buffer[buf_num].polarity[0] = INFRARED_TX_CCMR_LOW;
|
||||
infrared_tim_tx.buffer[buf_num].data[1] = 0; // 1 pulse
|
||||
infrared_tim_tx.buffer[buf_num].polarity[1] = INFRARED_TX_CCMR_LOW;
|
||||
infrared_tim_tx.buffer[buf_num].size = 2;
|
||||
infrared_tim_tx.buffer[buf_num].last_packet_end = true;
|
||||
infrared_tim_tx.buffer[buf_num].packet_end = true;
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_tx_fill_buffer(uint8_t buf_num, uint8_t polarity_shift) {
|
||||
furi_assert(buf_num < 2);
|
||||
furi_assert(furi_hal_infrared_state != InfraredStateAsyncRx);
|
||||
furi_assert(furi_hal_infrared_state < InfraredStateMAX);
|
||||
furi_assert(infrared_tim_tx.data_callback);
|
||||
InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num];
|
||||
furi_assert(buffer->data != NULL);
|
||||
furi_assert(buffer->polarity != NULL);
|
||||
|
||||
FuriHalInfraredTxGetDataState status = FuriHalInfraredTxGetDataStateOk;
|
||||
uint32_t duration = 0;
|
||||
bool level = 0;
|
||||
size_t* size = &buffer->size;
|
||||
size_t polarity_counter = 0;
|
||||
while(polarity_shift--) {
|
||||
buffer->polarity[polarity_counter++] = INFRARED_TX_CCMR_LOW;
|
||||
}
|
||||
|
||||
for(*size = 0; (*size < INFRARED_TIM_TX_DMA_BUFFER_SIZE) &&
|
||||
(status == FuriHalInfraredTxGetDataStateOk);) {
|
||||
if(infrared_tim_tx.tx_timing_rest_duration > 0) {
|
||||
if(infrared_tim_tx.tx_timing_rest_duration > 0xFFFF) {
|
||||
buffer->data[*size] = 0xFFFF;
|
||||
status = FuriHalInfraredTxGetDataStateOk;
|
||||
} else {
|
||||
buffer->data[*size] = infrared_tim_tx.tx_timing_rest_duration;
|
||||
status = infrared_tim_tx.tx_timing_rest_status;
|
||||
}
|
||||
infrared_tim_tx.tx_timing_rest_duration -= buffer->data[*size];
|
||||
buffer->polarity[polarity_counter] = infrared_tim_tx.tx_timing_rest_level ?
|
||||
INFRARED_TX_CCMR_HIGH :
|
||||
INFRARED_TX_CCMR_LOW;
|
||||
++(*size);
|
||||
++polarity_counter;
|
||||
continue;
|
||||
}
|
||||
|
||||
status = infrared_tim_tx.data_callback(infrared_tim_tx.data_context, &duration, &level);
|
||||
|
||||
uint32_t num_of_impulses = roundf(duration / infrared_tim_tx.cycle_duration);
|
||||
|
||||
if(num_of_impulses == 0) {
|
||||
if((*size == 0) && (status == FuriHalInfraredTxGetDataStateDone)) {
|
||||
/* if this is one sample in current buffer, but we
|
||||
* have more to send - continue
|
||||
*/
|
||||
status = FuriHalInfraredTxGetDataStateOk;
|
||||
}
|
||||
} else if((num_of_impulses - 1) > 0xFFFF) {
|
||||
infrared_tim_tx.tx_timing_rest_duration = num_of_impulses - 1;
|
||||
infrared_tim_tx.tx_timing_rest_status = status;
|
||||
infrared_tim_tx.tx_timing_rest_level = level;
|
||||
status = FuriHalInfraredTxGetDataStateOk;
|
||||
} else {
|
||||
buffer->polarity[polarity_counter] = level ? INFRARED_TX_CCMR_HIGH :
|
||||
INFRARED_TX_CCMR_LOW;
|
||||
buffer->data[*size] = num_of_impulses - 1;
|
||||
++(*size);
|
||||
++polarity_counter;
|
||||
}
|
||||
}
|
||||
|
||||
buffer->last_packet_end = (status == FuriHalInfraredTxGetDataStateLastDone);
|
||||
buffer->packet_end = buffer->last_packet_end || (status == FuriHalInfraredTxGetDataStateDone);
|
||||
|
||||
if(*size == 0) {
|
||||
buffer->data[0] = 0; // 1 pulse
|
||||
buffer->polarity[0] = INFRARED_TX_CCMR_LOW;
|
||||
buffer->size = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_tx_dma_set_polarity(uint8_t buf_num, uint8_t polarity_shift) {
|
||||
furi_assert(buf_num < 2);
|
||||
furi_assert(furi_hal_infrared_state < InfraredStateMAX);
|
||||
InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num];
|
||||
furi_assert(buffer->polarity != NULL);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
bool channel_enabled = LL_DMA_IsEnabledChannel(INFRARED_DMA_CH1_DEF);
|
||||
if(channel_enabled) {
|
||||
LL_DMA_DisableChannel(INFRARED_DMA_CH1_DEF);
|
||||
}
|
||||
LL_DMA_SetMemoryAddress(INFRARED_DMA_CH1_DEF, (uint32_t)buffer->polarity);
|
||||
LL_DMA_SetDataLength(INFRARED_DMA_CH1_DEF, buffer->size + polarity_shift);
|
||||
if(channel_enabled) {
|
||||
LL_DMA_EnableChannel(INFRARED_DMA_CH1_DEF);
|
||||
}
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_tx_dma_set_buffer(uint8_t buf_num) {
|
||||
furi_assert(buf_num < 2);
|
||||
furi_assert(furi_hal_infrared_state < InfraredStateMAX);
|
||||
InfraredTxBuf* buffer = &infrared_tim_tx.buffer[buf_num];
|
||||
furi_assert(buffer->data != NULL);
|
||||
|
||||
/* non-circular mode requires disabled channel before setup */
|
||||
FURI_CRITICAL_ENTER();
|
||||
bool channel_enabled = LL_DMA_IsEnabledChannel(INFRARED_DMA_CH2_DEF);
|
||||
if(channel_enabled) {
|
||||
LL_DMA_DisableChannel(INFRARED_DMA_CH2_DEF);
|
||||
}
|
||||
LL_DMA_SetMemoryAddress(INFRARED_DMA_CH2_DEF, (uint32_t)buffer->data);
|
||||
LL_DMA_SetDataLength(INFRARED_DMA_CH2_DEF, buffer->size);
|
||||
if(channel_enabled) {
|
||||
LL_DMA_EnableChannel(INFRARED_DMA_CH2_DEF);
|
||||
}
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
static void furi_hal_infrared_async_tx_free_resources(void) {
|
||||
furi_assert(
|
||||
(furi_hal_infrared_state == InfraredStateIdle) ||
|
||||
(furi_hal_infrared_state == InfraredStateAsyncTxStopped));
|
||||
|
||||
furi_hal_gpio_init(&gpio_infrared_tx, GpioModeAnalog, GpioPullDown, GpioSpeedLow);
|
||||
furi_hal_interrupt_set_isr(INFRARED_DMA_CH1_IRQ, NULL, NULL);
|
||||
furi_hal_interrupt_set_isr(INFRARED_DMA_CH2_IRQ, NULL, NULL);
|
||||
|
||||
furi_hal_bus_disable(INFRARED_DMA_TIMER_BUS);
|
||||
|
||||
furi_semaphore_free(infrared_tim_tx.stop_semaphore);
|
||||
free(infrared_tim_tx.buffer[0].data);
|
||||
free(infrared_tim_tx.buffer[1].data);
|
||||
free(infrared_tim_tx.buffer[0].polarity);
|
||||
free(infrared_tim_tx.buffer[1].polarity);
|
||||
|
||||
infrared_tim_tx.buffer[0].data = NULL;
|
||||
infrared_tim_tx.buffer[1].data = NULL;
|
||||
infrared_tim_tx.buffer[0].polarity = NULL;
|
||||
infrared_tim_tx.buffer[1].polarity = NULL;
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) {
|
||||
if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > INFRARED_MAX_FREQUENCY) ||
|
||||
(freq < INFRARED_MIN_FREQUENCY) || (infrared_tim_tx.data_callback == NULL)) {
|
||||
furi_crash(NULL);
|
||||
}
|
||||
|
||||
furi_assert(furi_hal_infrared_state == InfraredStateIdle);
|
||||
furi_assert(infrared_tim_tx.buffer[0].data == NULL);
|
||||
furi_assert(infrared_tim_tx.buffer[1].data == NULL);
|
||||
furi_assert(infrared_tim_tx.buffer[0].polarity == NULL);
|
||||
furi_assert(infrared_tim_tx.buffer[1].polarity == NULL);
|
||||
|
||||
size_t alloc_size_data = INFRARED_TIM_TX_DMA_BUFFER_SIZE * sizeof(uint16_t);
|
||||
infrared_tim_tx.buffer[0].data = malloc(alloc_size_data);
|
||||
infrared_tim_tx.buffer[1].data = malloc(alloc_size_data);
|
||||
|
||||
size_t alloc_size_polarity =
|
||||
(INFRARED_TIM_TX_DMA_BUFFER_SIZE + INFRARED_POLARITY_SHIFT) * sizeof(uint8_t);
|
||||
infrared_tim_tx.buffer[0].polarity = malloc(alloc_size_polarity);
|
||||
infrared_tim_tx.buffer[1].polarity = malloc(alloc_size_polarity);
|
||||
|
||||
infrared_tim_tx.stop_semaphore = furi_semaphore_alloc(1, 0);
|
||||
infrared_tim_tx.cycle_duration = 1000000.0 / freq;
|
||||
infrared_tim_tx.tx_timing_rest_duration = 0;
|
||||
|
||||
furi_hal_infrared_tx_fill_buffer(0, INFRARED_POLARITY_SHIFT);
|
||||
|
||||
furi_hal_bus_enable(INFRARED_DMA_TIMER_BUS);
|
||||
|
||||
furi_hal_infrared_configure_tim_pwm_tx(freq, duty_cycle);
|
||||
furi_hal_infrared_configure_tim_cmgr2_dma_tx();
|
||||
furi_hal_infrared_configure_tim_rcr_dma_tx();
|
||||
furi_hal_infrared_tx_dma_set_polarity(0, INFRARED_POLARITY_SHIFT);
|
||||
furi_hal_infrared_tx_dma_set_buffer(0);
|
||||
|
||||
furi_hal_infrared_state = InfraredStateAsyncTx;
|
||||
|
||||
LL_TIM_ClearFlag_UPDATE(INFRARED_DMA_TIMER);
|
||||
LL_DMA_EnableChannel(INFRARED_DMA_CH1_DEF);
|
||||
LL_DMA_EnableChannel(INFRARED_DMA_CH2_DEF);
|
||||
furi_delay_us(5);
|
||||
LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* DMA -> TIMx_RCR */
|
||||
furi_delay_us(5);
|
||||
LL_GPIO_ResetOutputPin(
|
||||
gpio_infrared_tx.port, gpio_infrared_tx.pin); /* when disable it prevents false pulse */
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_infrared_tx, GpioModeAltFunctionPushPull, GpioPullUp, GpioSpeedHigh, GpioAltFn1TIM1);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
LL_TIM_GenerateEvent_UPDATE(INFRARED_DMA_TIMER); /* TIMx_RCR -> Repetition counter */
|
||||
LL_TIM_EnableCounter(INFRARED_DMA_TIMER);
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_tx_wait_termination(void) {
|
||||
furi_assert(furi_hal_infrared_state >= InfraredStateAsyncTx);
|
||||
furi_assert(furi_hal_infrared_state < InfraredStateMAX);
|
||||
|
||||
FuriStatus status;
|
||||
status = furi_semaphore_acquire(infrared_tim_tx.stop_semaphore, FuriWaitForever);
|
||||
furi_check(status == FuriStatusOk);
|
||||
furi_hal_infrared_async_tx_free_resources();
|
||||
furi_hal_infrared_state = InfraredStateIdle;
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_tx_stop(void) {
|
||||
furi_assert(furi_hal_infrared_state >= InfraredStateAsyncTx);
|
||||
furi_assert(furi_hal_infrared_state < InfraredStateMAX);
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
if(furi_hal_infrared_state == InfraredStateAsyncTx)
|
||||
furi_hal_infrared_state = InfraredStateAsyncTxStopReq;
|
||||
FURI_CRITICAL_EXIT();
|
||||
|
||||
furi_hal_infrared_async_tx_wait_termination();
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_tx_set_data_isr_callback(
|
||||
FuriHalInfraredTxGetDataISRCallback callback,
|
||||
void* context) {
|
||||
furi_assert(furi_hal_infrared_state == InfraredStateIdle);
|
||||
infrared_tim_tx.data_callback = callback;
|
||||
infrared_tim_tx.data_context = context;
|
||||
}
|
||||
|
||||
void furi_hal_infrared_async_tx_set_signal_sent_isr_callback(
|
||||
FuriHalInfraredTxSignalSentISRCallback callback,
|
||||
void* context) {
|
||||
infrared_tim_tx.signal_sent_callback = callback;
|
||||
infrared_tim_tx.signal_sent_context = context;
|
||||
}
|
||||
330
targets/f7/furi_hal/furi_hal_interrupt.c
Normal file
330
targets/f7/furi_hal/furi_hal_interrupt.c
Normal file
@@ -0,0 +1,330 @@
|
||||
#include <furi_hal_interrupt.h>
|
||||
#include <furi_hal_os.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include <stm32wbxx.h>
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
#include <stm32wbxx_ll_cortex.h>
|
||||
|
||||
#define TAG "FuriHalInterrupt"
|
||||
|
||||
#define FURI_HAL_INTERRUPT_DEFAULT_PRIORITY 5
|
||||
|
||||
typedef struct {
|
||||
FuriHalInterruptISR isr;
|
||||
void* context;
|
||||
} FuriHalInterruptISRPair;
|
||||
|
||||
FuriHalInterruptISRPair furi_hal_interrupt_isr[FuriHalInterruptIdMax] = {0};
|
||||
|
||||
const IRQn_Type furi_hal_interrupt_irqn[FuriHalInterruptIdMax] = {
|
||||
// TIM1, TIM16, TIM17
|
||||
[FuriHalInterruptIdTim1TrgComTim17] = TIM1_TRG_COM_TIM17_IRQn,
|
||||
[FuriHalInterruptIdTim1Cc] = TIM1_CC_IRQn,
|
||||
[FuriHalInterruptIdTim1UpTim16] = TIM1_UP_TIM16_IRQn,
|
||||
|
||||
// TIM2
|
||||
[FuriHalInterruptIdTIM2] = TIM2_IRQn,
|
||||
|
||||
// DMA1
|
||||
[FuriHalInterruptIdDma1Ch1] = DMA1_Channel1_IRQn,
|
||||
[FuriHalInterruptIdDma1Ch2] = DMA1_Channel2_IRQn,
|
||||
[FuriHalInterruptIdDma1Ch3] = DMA1_Channel3_IRQn,
|
||||
[FuriHalInterruptIdDma1Ch4] = DMA1_Channel4_IRQn,
|
||||
[FuriHalInterruptIdDma1Ch5] = DMA1_Channel5_IRQn,
|
||||
[FuriHalInterruptIdDma1Ch6] = DMA1_Channel6_IRQn,
|
||||
[FuriHalInterruptIdDma1Ch7] = DMA1_Channel7_IRQn,
|
||||
|
||||
// DMA2
|
||||
[FuriHalInterruptIdDma2Ch1] = DMA2_Channel1_IRQn,
|
||||
[FuriHalInterruptIdDma2Ch2] = DMA2_Channel2_IRQn,
|
||||
[FuriHalInterruptIdDma2Ch3] = DMA2_Channel3_IRQn,
|
||||
[FuriHalInterruptIdDma2Ch4] = DMA2_Channel4_IRQn,
|
||||
[FuriHalInterruptIdDma2Ch5] = DMA2_Channel5_IRQn,
|
||||
[FuriHalInterruptIdDma2Ch6] = DMA2_Channel6_IRQn,
|
||||
[FuriHalInterruptIdDma2Ch7] = DMA2_Channel7_IRQn,
|
||||
|
||||
// RCC
|
||||
[FuriHalInterruptIdRcc] = RCC_IRQn,
|
||||
|
||||
// COMP
|
||||
[FuriHalInterruptIdCOMP] = COMP_IRQn,
|
||||
|
||||
// HSEM
|
||||
[FuriHalInterruptIdHsem] = HSEM_IRQn,
|
||||
|
||||
// LPTIMx
|
||||
[FuriHalInterruptIdLpTim1] = LPTIM1_IRQn,
|
||||
[FuriHalInterruptIdLpTim2] = LPTIM2_IRQn,
|
||||
};
|
||||
|
||||
__attribute__((always_inline)) static inline void
|
||||
furi_hal_interrupt_call(FuriHalInterruptId index) {
|
||||
furi_check(furi_hal_interrupt_isr[index].isr);
|
||||
furi_hal_interrupt_isr[index].isr(furi_hal_interrupt_isr[index].context);
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) static inline void
|
||||
furi_hal_interrupt_enable(FuriHalInterruptId index, uint16_t priority) {
|
||||
NVIC_SetPriority(
|
||||
furi_hal_interrupt_irqn[index],
|
||||
NVIC_EncodePriority(NVIC_GetPriorityGrouping(), priority, 0));
|
||||
NVIC_EnableIRQ(furi_hal_interrupt_irqn[index]);
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) static inline void
|
||||
furi_hal_interrupt_clear_pending(FuriHalInterruptId index) {
|
||||
NVIC_ClearPendingIRQ(furi_hal_interrupt_irqn[index]);
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) static inline void
|
||||
furi_hal_interrupt_get_pending(FuriHalInterruptId index) {
|
||||
NVIC_GetPendingIRQ(furi_hal_interrupt_irqn[index]);
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) static inline void
|
||||
furi_hal_interrupt_set_pending(FuriHalInterruptId index) {
|
||||
NVIC_SetPendingIRQ(furi_hal_interrupt_irqn[index]);
|
||||
}
|
||||
|
||||
__attribute__((always_inline)) static inline void
|
||||
furi_hal_interrupt_disable(FuriHalInterruptId index) {
|
||||
NVIC_DisableIRQ(furi_hal_interrupt_irqn[index]);
|
||||
}
|
||||
|
||||
void furi_hal_interrupt_init() {
|
||||
NVIC_SetPriority(
|
||||
TAMP_STAMP_LSECSS_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
|
||||
NVIC_EnableIRQ(TAMP_STAMP_LSECSS_IRQn);
|
||||
|
||||
NVIC_SetPriority(PendSV_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 15, 0));
|
||||
|
||||
NVIC_SetPriority(FPU_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 15, 0));
|
||||
NVIC_EnableIRQ(FPU_IRQn);
|
||||
|
||||
LL_SYSCFG_DisableIT_FPU_IOC();
|
||||
LL_SYSCFG_DisableIT_FPU_DZC();
|
||||
LL_SYSCFG_DisableIT_FPU_UFC();
|
||||
LL_SYSCFG_DisableIT_FPU_OFC();
|
||||
LL_SYSCFG_DisableIT_FPU_IDC();
|
||||
LL_SYSCFG_DisableIT_FPU_IXC();
|
||||
|
||||
LL_HANDLER_EnableFault(LL_HANDLER_FAULT_USG);
|
||||
LL_HANDLER_EnableFault(LL_HANDLER_FAULT_BUS);
|
||||
LL_HANDLER_EnableFault(LL_HANDLER_FAULT_MEM);
|
||||
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
void furi_hal_interrupt_set_isr(FuriHalInterruptId index, FuriHalInterruptISR isr, void* context) {
|
||||
furi_hal_interrupt_set_isr_ex(index, FURI_HAL_INTERRUPT_DEFAULT_PRIORITY, isr, context);
|
||||
}
|
||||
|
||||
void furi_hal_interrupt_set_isr_ex(
|
||||
FuriHalInterruptId index,
|
||||
uint16_t priority,
|
||||
FuriHalInterruptISR isr,
|
||||
void* context) {
|
||||
furi_check(index < FuriHalInterruptIdMax);
|
||||
furi_check(priority <= 15);
|
||||
|
||||
if(isr) {
|
||||
// Pre ISR set
|
||||
furi_check(furi_hal_interrupt_isr[index].isr == NULL);
|
||||
} else {
|
||||
// Pre ISR clear
|
||||
furi_hal_interrupt_disable(index);
|
||||
furi_hal_interrupt_clear_pending(index);
|
||||
}
|
||||
|
||||
furi_hal_interrupt_isr[index].isr = isr;
|
||||
furi_hal_interrupt_isr[index].context = context;
|
||||
__DMB();
|
||||
|
||||
if(isr) {
|
||||
// Post ISR set
|
||||
furi_hal_interrupt_clear_pending(index);
|
||||
furi_hal_interrupt_enable(index, priority);
|
||||
} else {
|
||||
// Post ISR clear
|
||||
}
|
||||
}
|
||||
|
||||
/* Timer 2 */
|
||||
void TIM2_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdTIM2);
|
||||
}
|
||||
|
||||
/* Timer 1 Update */
|
||||
void TIM1_UP_TIM16_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdTim1UpTim16);
|
||||
}
|
||||
|
||||
void TIM1_TRG_COM_TIM17_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdTim1TrgComTim17);
|
||||
}
|
||||
|
||||
void TIM1_CC_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdTim1Cc);
|
||||
}
|
||||
|
||||
/* DMA 1 */
|
||||
void DMA1_Channel1_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma1Ch1);
|
||||
}
|
||||
|
||||
void DMA1_Channel2_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma1Ch2);
|
||||
}
|
||||
|
||||
void DMA1_Channel3_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma1Ch3);
|
||||
}
|
||||
|
||||
void DMA1_Channel4_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma1Ch4);
|
||||
}
|
||||
|
||||
void DMA1_Channel5_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma1Ch5);
|
||||
}
|
||||
|
||||
void DMA1_Channel6_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma1Ch6);
|
||||
}
|
||||
|
||||
void DMA1_Channel7_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma1Ch7);
|
||||
}
|
||||
|
||||
/* DMA 2 */
|
||||
void DMA2_Channel1_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma2Ch1);
|
||||
}
|
||||
|
||||
void DMA2_Channel2_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma2Ch2);
|
||||
}
|
||||
|
||||
void DMA2_Channel3_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma2Ch3);
|
||||
}
|
||||
|
||||
void DMA2_Channel4_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma2Ch4);
|
||||
}
|
||||
|
||||
void DMA2_Channel5_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma2Ch5);
|
||||
}
|
||||
|
||||
void DMA2_Channel6_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma2Ch6);
|
||||
}
|
||||
|
||||
void DMA2_Channel7_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdDma2Ch7);
|
||||
}
|
||||
|
||||
void HSEM_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdHsem);
|
||||
}
|
||||
|
||||
void TAMP_STAMP_LSECSS_IRQHandler(void) {
|
||||
if(LL_RCC_IsActiveFlag_LSECSS()) {
|
||||
LL_RCC_ClearFlag_LSECSS();
|
||||
if(!LL_RCC_LSE_IsReady()) {
|
||||
FURI_LOG_E(TAG, "LSE CSS fired: resetting system");
|
||||
NVIC_SystemReset();
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "LSE CSS fired: but LSE is alive");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RCC_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdRcc);
|
||||
}
|
||||
|
||||
void NMI_Handler() {
|
||||
if(LL_RCC_IsActiveFlag_HSECSS()) {
|
||||
LL_RCC_ClearFlag_HSECSS();
|
||||
FURI_LOG_E(TAG, "HSE CSS fired: resetting system");
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
}
|
||||
|
||||
void HardFault_Handler() {
|
||||
furi_crash("HardFault");
|
||||
}
|
||||
|
||||
void MemManage_Handler() {
|
||||
if(FURI_BIT(SCB->CFSR, SCB_CFSR_MMARVALID_Pos)) {
|
||||
uint32_t memfault_address = SCB->MMFAR;
|
||||
if(memfault_address < (1024 * 1024)) {
|
||||
// from 0x00 to 1MB, see FuriHalMpuRegionNULL
|
||||
furi_crash("NULL pointer dereference");
|
||||
} else {
|
||||
// write or read of MPU region 1 (FuriHalMpuRegionStack)
|
||||
furi_crash("MPU fault, possibly stack overflow");
|
||||
}
|
||||
} else if(FURI_BIT(SCB->CFSR, SCB_CFSR_MSTKERR_Pos)) {
|
||||
// push to stack on MPU region 1 (FuriHalMpuRegionStack)
|
||||
furi_crash("MemManage fault, possibly stack overflow");
|
||||
}
|
||||
|
||||
furi_crash("MemManage");
|
||||
}
|
||||
|
||||
void BusFault_Handler() {
|
||||
furi_crash("BusFault");
|
||||
}
|
||||
|
||||
void UsageFault_Handler() {
|
||||
furi_crash("UsageFault");
|
||||
}
|
||||
|
||||
void DebugMon_Handler() {
|
||||
}
|
||||
|
||||
#include "usbd_core.h"
|
||||
|
||||
extern usbd_device udev;
|
||||
|
||||
extern void HW_IPCC_Tx_Handler();
|
||||
extern void HW_IPCC_Rx_Handler();
|
||||
|
||||
void SysTick_Handler() {
|
||||
furi_hal_os_tick();
|
||||
}
|
||||
|
||||
void USB_LP_IRQHandler() {
|
||||
#ifndef FURI_RAM_EXEC
|
||||
usbd_poll(&udev);
|
||||
#endif
|
||||
}
|
||||
|
||||
void USB_HP_IRQHandler() {
|
||||
}
|
||||
|
||||
void IPCC_C1_TX_IRQHandler() {
|
||||
HW_IPCC_Tx_Handler();
|
||||
}
|
||||
|
||||
void IPCC_C1_RX_IRQHandler() {
|
||||
HW_IPCC_Rx_Handler();
|
||||
}
|
||||
|
||||
void FPU_IRQHandler() {
|
||||
furi_crash("FpuFault");
|
||||
}
|
||||
|
||||
void LPTIM1_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdLpTim1);
|
||||
}
|
||||
|
||||
void LPTIM2_IRQHandler() {
|
||||
furi_hal_interrupt_call(FuriHalInterruptIdLpTim2);
|
||||
}
|
||||
82
targets/f7/furi_hal/furi_hal_interrupt.h
Normal file
82
targets/f7/furi_hal/furi_hal_interrupt.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Timer ISR */
|
||||
typedef void (*FuriHalInterruptISR)(void* context);
|
||||
|
||||
typedef enum {
|
||||
// TIM1, TIM16, TIM17
|
||||
FuriHalInterruptIdTim1TrgComTim17,
|
||||
FuriHalInterruptIdTim1Cc,
|
||||
FuriHalInterruptIdTim1UpTim16,
|
||||
|
||||
// TIM2
|
||||
FuriHalInterruptIdTIM2,
|
||||
|
||||
// DMA1
|
||||
FuriHalInterruptIdDma1Ch1,
|
||||
FuriHalInterruptIdDma1Ch2,
|
||||
FuriHalInterruptIdDma1Ch3,
|
||||
FuriHalInterruptIdDma1Ch4,
|
||||
FuriHalInterruptIdDma1Ch5,
|
||||
FuriHalInterruptIdDma1Ch6,
|
||||
FuriHalInterruptIdDma1Ch7,
|
||||
|
||||
// DMA2
|
||||
FuriHalInterruptIdDma2Ch1,
|
||||
FuriHalInterruptIdDma2Ch2,
|
||||
FuriHalInterruptIdDma2Ch3,
|
||||
FuriHalInterruptIdDma2Ch4,
|
||||
FuriHalInterruptIdDma2Ch5,
|
||||
FuriHalInterruptIdDma2Ch6,
|
||||
FuriHalInterruptIdDma2Ch7,
|
||||
|
||||
// RCC
|
||||
FuriHalInterruptIdRcc,
|
||||
|
||||
// Comp
|
||||
FuriHalInterruptIdCOMP,
|
||||
|
||||
// HSEM
|
||||
FuriHalInterruptIdHsem,
|
||||
|
||||
// LPTIMx
|
||||
FuriHalInterruptIdLpTim1,
|
||||
FuriHalInterruptIdLpTim2,
|
||||
|
||||
// Service value
|
||||
FuriHalInterruptIdMax,
|
||||
} FuriHalInterruptId;
|
||||
|
||||
/** Initialize interrupt subsystem */
|
||||
void furi_hal_interrupt_init();
|
||||
|
||||
/** Set ISR and enable interrupt with default priority
|
||||
* We don't clear interrupt flags for you, do it by your self.
|
||||
* @param index - interrupt ID
|
||||
* @param isr - your interrupt service routine or use NULL to clear
|
||||
* @param context - isr context
|
||||
*/
|
||||
void furi_hal_interrupt_set_isr(FuriHalInterruptId index, FuriHalInterruptISR isr, void* context);
|
||||
|
||||
/** Set ISR and enable interrupt with custom priority
|
||||
* We don't clear interrupt flags for you, do it by your self.
|
||||
* @param index - interrupt ID
|
||||
* @param priority - 0 to 15, 0 highest
|
||||
* @param isr - your interrupt service routine or use NULL to clear
|
||||
* @param context - isr context
|
||||
*/
|
||||
void furi_hal_interrupt_set_isr_ex(
|
||||
FuriHalInterruptId index,
|
||||
uint16_t priority,
|
||||
FuriHalInterruptISR isr,
|
||||
void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
116
targets/f7/furi_hal/furi_hal_light.c
Normal file
116
targets/f7/furi_hal/furi_hal_light.c
Normal file
@@ -0,0 +1,116 @@
|
||||
#include <core/common_defines.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_light.h>
|
||||
#include <lp5562.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define LED_CURRENT_RED 50
|
||||
#define LED_CURRENT_GREEN 50
|
||||
#define LED_CURRENT_BLUE 50
|
||||
#define LED_CURRENT_WHITE 150
|
||||
|
||||
void furi_hal_light_init() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
|
||||
lp5562_reset(&furi_hal_i2c_handle_power);
|
||||
|
||||
lp5562_set_channel_current(&furi_hal_i2c_handle_power, LP5562ChannelRed, LED_CURRENT_RED);
|
||||
lp5562_set_channel_current(&furi_hal_i2c_handle_power, LP5562ChannelGreen, LED_CURRENT_GREEN);
|
||||
lp5562_set_channel_current(&furi_hal_i2c_handle_power, LP5562ChannelBlue, LED_CURRENT_BLUE);
|
||||
lp5562_set_channel_current(&furi_hal_i2c_handle_power, LP5562ChannelWhite, LED_CURRENT_WHITE);
|
||||
|
||||
lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, 0x00);
|
||||
lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, 0x00);
|
||||
lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, 0x00);
|
||||
lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite, 0x00);
|
||||
|
||||
lp5562_enable(&furi_hal_i2c_handle_power);
|
||||
lp5562_configure(&furi_hal_i2c_handle_power);
|
||||
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
void furi_hal_light_set(Light light, uint8_t value) {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
if(light & LightRed) {
|
||||
lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelRed, value);
|
||||
}
|
||||
if(light & LightGreen) {
|
||||
lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelGreen, value);
|
||||
}
|
||||
if(light & LightBlue) {
|
||||
lp5562_set_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelBlue, value);
|
||||
}
|
||||
if(light & LightBacklight) {
|
||||
uint8_t prev = lp5562_get_channel_value(&furi_hal_i2c_handle_power, LP5562ChannelWhite);
|
||||
lp5562_execute_ramp(
|
||||
&furi_hal_i2c_handle_power, LP5562Engine1, LP5562ChannelWhite, prev, value, 100);
|
||||
}
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
void furi_hal_light_blink_start(Light light, uint8_t brightness, uint16_t on_time, uint16_t period) {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
lp5562_set_channel_src(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562ChannelRed | LP5562ChannelGreen | LP5562ChannelBlue,
|
||||
LP5562Direct);
|
||||
LP5562Channel led_ch = 0;
|
||||
if(light & LightRed) led_ch |= LP5562ChannelRed;
|
||||
if(light & LightGreen) led_ch |= LP5562ChannelGreen;
|
||||
if(light & LightBlue) led_ch |= LP5562ChannelBlue;
|
||||
lp5562_execute_blink(
|
||||
&furi_hal_i2c_handle_power, LP5562Engine2, led_ch, on_time, period, brightness);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
void furi_hal_light_blink_stop() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
lp5562_set_channel_src(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562ChannelRed | LP5562ChannelGreen | LP5562ChannelBlue,
|
||||
LP5562Direct);
|
||||
lp5562_stop_program(&furi_hal_i2c_handle_power, LP5562Engine2);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
void furi_hal_light_blink_set_color(Light light) {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
LP5562Channel led_ch = 0;
|
||||
lp5562_set_channel_src(
|
||||
&furi_hal_i2c_handle_power,
|
||||
LP5562ChannelRed | LP5562ChannelGreen | LP5562ChannelBlue,
|
||||
LP5562Direct);
|
||||
if(light & LightRed) led_ch |= LP5562ChannelRed;
|
||||
if(light & LightGreen) led_ch |= LP5562ChannelGreen;
|
||||
if(light & LightBlue) led_ch |= LP5562ChannelBlue;
|
||||
lp5562_set_channel_src(&furi_hal_i2c_handle_power, led_ch, LP5562Engine2);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
void furi_hal_light_sequence(const char* sequence) {
|
||||
do {
|
||||
if(*sequence == 'R') {
|
||||
furi_hal_light_set(LightRed, 0xFF);
|
||||
} else if(*sequence == 'r') {
|
||||
furi_hal_light_set(LightRed, 0x00);
|
||||
} else if(*sequence == 'G') {
|
||||
furi_hal_light_set(LightGreen, 0xFF);
|
||||
} else if(*sequence == 'g') {
|
||||
furi_hal_light_set(LightGreen, 0x00);
|
||||
} else if(*sequence == 'B') {
|
||||
furi_hal_light_set(LightBlue, 0xFF);
|
||||
} else if(*sequence == 'b') {
|
||||
furi_hal_light_set(LightBlue, 0x00);
|
||||
} else if(*sequence == 'W') {
|
||||
furi_hal_light_set(LightBacklight, 0xFF);
|
||||
} else if(*sequence == 'w') {
|
||||
furi_hal_light_set(LightBacklight, 0x00);
|
||||
} else if(*sequence == '.') {
|
||||
furi_delay_ms(250);
|
||||
} else if(*sequence == '-') {
|
||||
furi_delay_ms(500);
|
||||
}
|
||||
sequence++;
|
||||
} while(*sequence != 0);
|
||||
}
|
||||
129
targets/f7/furi_hal/furi_hal_memory.c
Normal file
129
targets/f7/furi_hal/furi_hal_memory.c
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_memory.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
#define TAG "FuriHalMemory"
|
||||
|
||||
typedef enum {
|
||||
SRAM_A,
|
||||
SRAM_B,
|
||||
SRAM_MAX,
|
||||
} SRAM;
|
||||
|
||||
typedef struct {
|
||||
void* start;
|
||||
uint32_t size;
|
||||
} FuriHalMemoryRegion;
|
||||
|
||||
typedef struct {
|
||||
FuriHalMemoryRegion region[SRAM_MAX];
|
||||
} FuriHalMemory;
|
||||
|
||||
static FuriHalMemory* furi_hal_memory = NULL;
|
||||
|
||||
extern const void __sram2a_start__;
|
||||
extern const void __sram2a_free__;
|
||||
extern const void __sram2b_start__;
|
||||
|
||||
void furi_hal_memory_init() {
|
||||
if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) {
|
||||
return;
|
||||
}
|
||||
|
||||
FuriHalMemory* memory = malloc(sizeof(FuriHalMemory));
|
||||
|
||||
uint32_t sbrsa = (FLASH->SRRVR & FLASH_SRRVR_SBRSA_Msk) >> FLASH_SRRVR_SBRSA_Pos;
|
||||
uint32_t snbrsa = (FLASH->SRRVR & FLASH_SRRVR_SNBRSA_Msk) >> FLASH_SRRVR_SNBRSA_Pos;
|
||||
|
||||
// STM(TM) Copro(TM) bug(TM): SNBRSA is incorrect if stack version is higher than 1.13 and lower than 1.17.2+
|
||||
// Radio core started, but not yet ready, so we'll try to guess
|
||||
// This will be true only if BLE light radio stack used,
|
||||
// 0x0D is known to be incorrect, 0x0B is known to be correct since 1.17.2+
|
||||
// Lower value by 2 pages to match real memory layout
|
||||
if(snbrsa > 0x0B) {
|
||||
FURI_LOG_E(TAG, "SNBRSA workaround");
|
||||
snbrsa -= 2;
|
||||
}
|
||||
|
||||
uint32_t sram2a_busy_size = (uint32_t)&__sram2a_free__ - (uint32_t)&__sram2a_start__;
|
||||
uint32_t sram2a_unprotected_size = (sbrsa)*1024;
|
||||
uint32_t sram2b_unprotected_size = (snbrsa)*1024;
|
||||
|
||||
memory->region[SRAM_A].start = (uint8_t*)&__sram2a_free__;
|
||||
memory->region[SRAM_B].start = (uint8_t*)&__sram2b_start__;
|
||||
|
||||
if(sram2a_unprotected_size > sram2a_busy_size) {
|
||||
memory->region[SRAM_A].size = sram2a_unprotected_size - sram2a_busy_size;
|
||||
} else {
|
||||
memory->region[SRAM_A].size = 0;
|
||||
}
|
||||
memory->region[SRAM_B].size = sram2b_unprotected_size;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG, "SRAM2A: 0x%p, %lu", memory->region[SRAM_A].start, memory->region[SRAM_A].size);
|
||||
FURI_LOG_I(
|
||||
TAG, "SRAM2B: 0x%p, %lu", memory->region[SRAM_B].start, memory->region[SRAM_B].size);
|
||||
|
||||
if((memory->region[SRAM_A].size > 0) || (memory->region[SRAM_B].size > 0)) {
|
||||
if((memory->region[SRAM_A].size > 0)) {
|
||||
FURI_LOG_I(TAG, "SRAM2A clear");
|
||||
memset(memory->region[SRAM_A].start, 0, memory->region[SRAM_A].size);
|
||||
}
|
||||
if((memory->region[SRAM_B].size > 0)) {
|
||||
FURI_LOG_I(TAG, "SRAM2B clear");
|
||||
memset(memory->region[SRAM_B].start, 0, memory->region[SRAM_B].size);
|
||||
}
|
||||
furi_hal_memory = memory;
|
||||
FURI_LOG_I(TAG, "Enabled");
|
||||
} else {
|
||||
free(memory);
|
||||
FURI_LOG_E(TAG, "No SRAM2 available");
|
||||
}
|
||||
}
|
||||
|
||||
void* furi_hal_memory_alloc(size_t size) {
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
furi_crash("memmgt in ISR");
|
||||
}
|
||||
|
||||
if(furi_hal_memory == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* allocated_memory = NULL;
|
||||
FURI_CRITICAL_ENTER();
|
||||
for(int i = 0; i < SRAM_MAX; i++) {
|
||||
if(furi_hal_memory->region[i].size >= size) {
|
||||
void* ptr = furi_hal_memory->region[i].start;
|
||||
furi_hal_memory->region[i].start += size;
|
||||
furi_hal_memory->region[i].size -= size;
|
||||
allocated_memory = ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
FURI_CRITICAL_EXIT();
|
||||
|
||||
return allocated_memory;
|
||||
}
|
||||
|
||||
size_t furi_hal_memory_get_free() {
|
||||
if(furi_hal_memory == NULL) return 0;
|
||||
|
||||
size_t free = 0;
|
||||
for(int i = 0; i < SRAM_MAX; i++) {
|
||||
free += furi_hal_memory->region[i].size;
|
||||
}
|
||||
return free;
|
||||
}
|
||||
|
||||
size_t furi_hal_memory_max_pool_block() {
|
||||
if(furi_hal_memory == NULL) return 0;
|
||||
|
||||
size_t max = 0;
|
||||
for(int i = 0; i < SRAM_MAX; i++) {
|
||||
if(furi_hal_memory->region[i].size > max) {
|
||||
max = furi_hal_memory->region[i].size;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
66
targets/f7/furi_hal/furi_hal_mpu.c
Normal file
66
targets/f7/furi_hal/furi_hal_mpu.c
Normal file
@@ -0,0 +1,66 @@
|
||||
#include <furi_hal_mpu.h>
|
||||
#include <stm32wbxx_ll_cortex.h>
|
||||
|
||||
#define FURI_HAL_MPU_ATTRIBUTES \
|
||||
(LL_MPU_ACCESS_BUFFERABLE | LL_MPU_ACCESS_CACHEABLE | LL_MPU_ACCESS_SHAREABLE | \
|
||||
LL_MPU_TEX_LEVEL1 | LL_MPU_INSTRUCTION_ACCESS_ENABLE)
|
||||
|
||||
#define FURI_HAL_MPU_STACK_PROTECT_REGION FuriHalMPURegionSize32B
|
||||
|
||||
void furi_hal_mpu_init() {
|
||||
furi_hal_mpu_enable();
|
||||
|
||||
// NULL pointer dereference protection
|
||||
furi_hal_mpu_protect_no_access(FuriHalMpuRegionNULL, 0x00, FuriHalMPURegionSize1MB);
|
||||
}
|
||||
|
||||
void furi_hal_mpu_enable() {
|
||||
LL_MPU_Enable(LL_MPU_CTRL_PRIVILEGED_DEFAULT);
|
||||
}
|
||||
|
||||
void furi_hal_mpu_disable() {
|
||||
LL_MPU_Disable();
|
||||
}
|
||||
|
||||
void furi_hal_mpu_protect_no_access(
|
||||
FuriHalMpuRegion region,
|
||||
uint32_t address,
|
||||
FuriHalMPURegionSize size) {
|
||||
uint32_t size_ll = size;
|
||||
size_ll = size_ll << MPU_RASR_SIZE_Pos;
|
||||
|
||||
furi_hal_mpu_disable();
|
||||
LL_MPU_ConfigRegion(
|
||||
region, 0x00, address, FURI_HAL_MPU_ATTRIBUTES | LL_MPU_REGION_NO_ACCESS | size_ll);
|
||||
furi_hal_mpu_enable();
|
||||
}
|
||||
|
||||
void furi_hal_mpu_protect_read_only(
|
||||
FuriHalMpuRegion region,
|
||||
uint32_t address,
|
||||
FuriHalMPURegionSize size) {
|
||||
uint32_t size_ll = size;
|
||||
size_ll = size_ll << MPU_RASR_SIZE_Pos;
|
||||
|
||||
furi_hal_mpu_disable();
|
||||
LL_MPU_ConfigRegion(
|
||||
region, 0x00, address, FURI_HAL_MPU_ATTRIBUTES | LL_MPU_REGION_PRIV_RO_URO | size_ll);
|
||||
furi_hal_mpu_enable();
|
||||
}
|
||||
|
||||
void furi_hal_mpu_protect_disable(FuriHalMpuRegion region) {
|
||||
furi_hal_mpu_disable();
|
||||
LL_MPU_DisableRegion(region);
|
||||
furi_hal_mpu_enable();
|
||||
}
|
||||
|
||||
void furi_hal_mpu_set_stack_protection(uint32_t* stack) {
|
||||
// Protection area address must be aligned to region size
|
||||
uint32_t stack_ptr = (uint32_t)stack;
|
||||
uint32_t mask = ((1 << (FURI_HAL_MPU_STACK_PROTECT_REGION + 2)) - 1);
|
||||
stack_ptr &= ~mask;
|
||||
if(stack_ptr < (uint32_t)stack) stack_ptr += (mask + 1);
|
||||
|
||||
furi_hal_mpu_protect_read_only(
|
||||
FuriHalMpuRegionStack, stack_ptr, FURI_HAL_MPU_STACK_PROTECT_REGION);
|
||||
}
|
||||
621
targets/f7/furi_hal/furi_hal_nfc.c
Normal file
621
targets/f7/furi_hal/furi_hal_nfc.c
Normal file
@@ -0,0 +1,621 @@
|
||||
#include "furi_hal_nfc_i.h"
|
||||
#include "furi_hal_nfc_tech_i.h"
|
||||
|
||||
#include <lib/drivers/st25r3916.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_spi.h>
|
||||
|
||||
#define TAG "FuriHalNfc"
|
||||
|
||||
const FuriHalNfcTechBase* furi_hal_nfc_tech[FuriHalNfcTechNum] = {
|
||||
[FuriHalNfcTechIso14443a] = &furi_hal_nfc_iso14443a,
|
||||
[FuriHalNfcTechIso14443b] = &furi_hal_nfc_iso14443b,
|
||||
[FuriHalNfcTechIso15693] = &furi_hal_nfc_iso15693,
|
||||
[FuriHalNfcTechFelica] = &furi_hal_nfc_felica,
|
||||
// Add new technologies here
|
||||
};
|
||||
|
||||
FuriHalNfc furi_hal_nfc;
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_turn_on_osc(FuriHalSpiBusHandle* handle) {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
furi_hal_nfc_event_start();
|
||||
|
||||
if(!st25r3916_check_reg(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
ST25R3916_REG_OP_CONTROL_en,
|
||||
ST25R3916_REG_OP_CONTROL_en)) {
|
||||
st25r3916_mask_irq(handle, ~ST25R3916_IRQ_MASK_OSC);
|
||||
st25r3916_set_reg_bits(handle, ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_en);
|
||||
furi_hal_nfc_event_wait_for_specific_irq(handle, ST25R3916_IRQ_MASK_OSC, 10);
|
||||
}
|
||||
// Disable IRQs
|
||||
st25r3916_mask_irq(handle, ST25R3916_IRQ_MASK_ALL);
|
||||
|
||||
bool osc_on = st25r3916_check_reg(
|
||||
handle,
|
||||
ST25R3916_REG_AUX_DISPLAY,
|
||||
ST25R3916_REG_AUX_DISPLAY_osc_ok,
|
||||
ST25R3916_REG_AUX_DISPLAY_osc_ok);
|
||||
if(!osc_on) {
|
||||
error = FuriHalNfcErrorOscillator;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_is_hal_ready() {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
|
||||
do {
|
||||
error = furi_hal_nfc_acquire();
|
||||
if(error != FuriHalNfcErrorNone) break;
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
uint8_t chip_id = 0;
|
||||
st25r3916_read_reg(handle, ST25R3916_REG_IC_IDENTITY, &chip_id);
|
||||
if((chip_id & ST25R3916_REG_IC_IDENTITY_ic_type_mask) !=
|
||||
ST25R3916_REG_IC_IDENTITY_ic_type_st25r3916) {
|
||||
FURI_LOG_E(TAG, "Wrong chip id");
|
||||
error = FuriHalNfcErrorCommunication;
|
||||
}
|
||||
|
||||
furi_hal_nfc_release();
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_init() {
|
||||
furi_assert(furi_hal_nfc.mutex == NULL);
|
||||
|
||||
furi_hal_nfc.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
|
||||
furi_hal_nfc_event_init();
|
||||
furi_hal_nfc_event_start();
|
||||
|
||||
do {
|
||||
error = furi_hal_nfc_acquire();
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
furi_hal_nfc_low_power_mode_start();
|
||||
}
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
// Set default state
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_SET_DEFAULT);
|
||||
// Increase IO driver strength of MISO and IRQ
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_IO_CONF2, ST25R3916_REG_IO_CONF2_io_drv_lvl);
|
||||
// Check chip ID
|
||||
uint8_t chip_id = 0;
|
||||
st25r3916_read_reg(handle, ST25R3916_REG_IC_IDENTITY, &chip_id);
|
||||
if((chip_id & ST25R3916_REG_IC_IDENTITY_ic_type_mask) !=
|
||||
ST25R3916_REG_IC_IDENTITY_ic_type_st25r3916) {
|
||||
FURI_LOG_E(TAG, "Wrong chip id");
|
||||
error = FuriHalNfcErrorCommunication;
|
||||
furi_hal_nfc_low_power_mode_start();
|
||||
furi_hal_nfc_release();
|
||||
break;
|
||||
}
|
||||
// Clear interrupts
|
||||
st25r3916_get_irq(handle);
|
||||
// Mask all interrupts
|
||||
st25r3916_mask_irq(handle, ST25R3916_IRQ_MASK_ALL);
|
||||
// Enable interrupts
|
||||
furi_hal_nfc_init_gpio_isr();
|
||||
// Disable internal overheat protection
|
||||
st25r3916_change_test_reg_bits(handle, 0x04, 0x10, 0x10);
|
||||
|
||||
error = furi_hal_nfc_turn_on_osc(handle);
|
||||
if(error != FuriHalNfcErrorNone) {
|
||||
furi_hal_nfc_low_power_mode_start();
|
||||
furi_hal_nfc_release();
|
||||
break;
|
||||
}
|
||||
|
||||
// Measure voltage
|
||||
// Set measure power supply voltage source
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_REGULATOR_CONTROL,
|
||||
ST25R3916_REG_REGULATOR_CONTROL_mpsv_mask,
|
||||
ST25R3916_REG_REGULATOR_CONTROL_mpsv_vdd);
|
||||
// Enable timer and interrupt register
|
||||
st25r3916_mask_irq(handle, ~ST25R3916_IRQ_MASK_DCT);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_MEASURE_VDD);
|
||||
furi_hal_nfc_event_wait_for_specific_irq(handle, ST25R3916_IRQ_MASK_DCT, 100);
|
||||
st25r3916_mask_irq(handle, ST25R3916_IRQ_MASK_ALL);
|
||||
uint8_t ad_res = 0;
|
||||
st25r3916_read_reg(handle, ST25R3916_REG_AD_RESULT, &ad_res);
|
||||
uint16_t mV = ((uint16_t)ad_res) * 23U;
|
||||
mV += (((((uint16_t)ad_res) * 4U) + 5U) / 10U);
|
||||
|
||||
if(mV < 3600) {
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_IO_CONF2,
|
||||
ST25R3916_REG_IO_CONF2_sup3V,
|
||||
ST25R3916_REG_IO_CONF2_sup3V_3V);
|
||||
} else {
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_IO_CONF2,
|
||||
ST25R3916_REG_IO_CONF2_sup3V,
|
||||
ST25R3916_REG_IO_CONF2_sup3V_5V);
|
||||
}
|
||||
|
||||
// Disable MCU CLK
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_IO_CONF1,
|
||||
ST25R3916_REG_IO_CONF1_out_cl_mask | ST25R3916_REG_IO_CONF1_lf_clk_off,
|
||||
0x07);
|
||||
// Disable MISO pull-down
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_IO_CONF2,
|
||||
ST25R3916_REG_IO_CONF2_miso_pd1 | ST25R3916_REG_IO_CONF2_miso_pd2,
|
||||
0x00);
|
||||
// Set tx driver resistance to 1 Om
|
||||
st25r3916_change_reg_bits(
|
||||
handle, ST25R3916_REG_TX_DRIVER, ST25R3916_REG_TX_DRIVER_d_res_mask, 0x00);
|
||||
// Use minimum non-overlap
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_RES_AM_MOD,
|
||||
ST25R3916_REG_RES_AM_MOD_fa3_f,
|
||||
ST25R3916_REG_RES_AM_MOD_fa3_f);
|
||||
|
||||
// Set activation threashold
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_ACTV,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_mask,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_ACTV_trg_105mV);
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_ACTV,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_mask,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_ACTV_rfe_105mV);
|
||||
// Set deactivation threashold
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_DEACTV,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_mask,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_DEACTV_trg_75mV);
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_DEACTV,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_mask,
|
||||
ST25R3916_REG_FIELD_THRESHOLD_DEACTV_rfe_75mV);
|
||||
// Enable external load modulation
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_AUX_MOD,
|
||||
ST25R3916_REG_AUX_MOD_lm_ext,
|
||||
ST25R3916_REG_AUX_MOD_lm_ext);
|
||||
// Enable internal load modulation
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_AUX_MOD,
|
||||
ST25R3916_REG_AUX_MOD_lm_dri,
|
||||
ST25R3916_REG_AUX_MOD_lm_dri);
|
||||
// Adjust FDT
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_PASSIVE_TARGET,
|
||||
ST25R3916_REG_PASSIVE_TARGET_fdel_mask,
|
||||
(5U << ST25R3916_REG_PASSIVE_TARGET_fdel_shift));
|
||||
// Reduce RFO resistance in Modulated state
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_PT_MOD,
|
||||
ST25R3916_REG_PT_MOD_ptm_res_mask | ST25R3916_REG_PT_MOD_pt_res_mask,
|
||||
0x0f);
|
||||
// Enable RX start on first 4 bits
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_EMD_SUP_CONF,
|
||||
ST25R3916_REG_EMD_SUP_CONF_rx_start_emv,
|
||||
ST25R3916_REG_EMD_SUP_CONF_rx_start_emv_on);
|
||||
// Set antena tunning
|
||||
st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_A, 0xff, 0x82);
|
||||
st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_B, 0xff, 0x82);
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
ST25R3916_REG_OP_CONTROL_en_fd_mask,
|
||||
ST25R3916_REG_OP_CONTROL_en_fd_auto_efd);
|
||||
|
||||
// Perform calibration
|
||||
if(st25r3916_check_reg(
|
||||
handle,
|
||||
ST25R3916_REG_REGULATOR_CONTROL,
|
||||
ST25R3916_REG_REGULATOR_CONTROL_reg_s,
|
||||
0x00)) {
|
||||
FURI_LOG_I(TAG, "Adjusting regulators");
|
||||
// Reset logic
|
||||
st25r3916_set_reg_bits(
|
||||
handle, ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s);
|
||||
st25r3916_clear_reg_bits(
|
||||
handle, ST25R3916_REG_REGULATOR_CONTROL, ST25R3916_REG_REGULATOR_CONTROL_reg_s);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_ADJUST_REGULATORS);
|
||||
furi_delay_ms(6);
|
||||
}
|
||||
|
||||
furi_hal_nfc_low_power_mode_start();
|
||||
furi_hal_nfc_release();
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static bool furi_hal_nfc_is_mine() {
|
||||
return (furi_mutex_get_owner(furi_hal_nfc.mutex) == furi_thread_get_current_id());
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_acquire() {
|
||||
furi_check(furi_hal_nfc.mutex);
|
||||
|
||||
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_nfc);
|
||||
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
if(furi_mutex_acquire(furi_hal_nfc.mutex, 100) != FuriStatusOk) {
|
||||
furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc);
|
||||
error = FuriHalNfcErrorBusy;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_release() {
|
||||
furi_check(furi_hal_nfc.mutex);
|
||||
furi_check(furi_hal_nfc_is_mine());
|
||||
furi_check(furi_mutex_release(furi_hal_nfc.mutex) == FuriStatusOk);
|
||||
|
||||
furi_hal_spi_release(&furi_hal_spi_bus_handle_nfc);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_low_power_mode_start() {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP);
|
||||
st25r3916_clear_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
(ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en |
|
||||
ST25R3916_REG_OP_CONTROL_wu | ST25R3916_REG_OP_CONTROL_tx_en |
|
||||
ST25R3916_REG_OP_CONTROL_en_fd_mask));
|
||||
furi_hal_nfc_deinit_gpio_isr();
|
||||
furi_hal_nfc_timers_deinit();
|
||||
furi_hal_nfc_event_stop();
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_low_power_mode_stop() {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
do {
|
||||
furi_hal_nfc_init_gpio_isr();
|
||||
furi_hal_nfc_timers_init();
|
||||
error = furi_hal_nfc_turn_on_osc(handle);
|
||||
if(error != FuriHalNfcErrorNone) break;
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
ST25R3916_REG_OP_CONTROL_en_fd_mask,
|
||||
ST25R3916_REG_OP_CONTROL_en_fd_auto_efd);
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_poller_init_common(FuriHalSpiBusHandle* handle) {
|
||||
// Disable wake up
|
||||
st25r3916_clear_reg_bits(handle, ST25R3916_REG_OP_CONTROL, ST25R3916_REG_OP_CONTROL_wu);
|
||||
// Enable correlator
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_AUX,
|
||||
ST25R3916_REG_AUX_dis_corr,
|
||||
ST25R3916_REG_AUX_dis_corr_correlator);
|
||||
|
||||
st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_A, 0xff, 0x82);
|
||||
st25r3916_change_reg_bits(handle, ST25R3916_REG_ANT_TUNE_B, 0xFF, 0x82);
|
||||
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_OVERSHOOT_CONF1, 0x00);
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_OVERSHOOT_CONF2, 0x00);
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_UNDERSHOOT_CONF1, 0x00);
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_UNDERSHOOT_CONF2, 0x00);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_listener_init_common(FuriHalSpiBusHandle* handle) {
|
||||
UNUSED(handle);
|
||||
// No common listener configuration
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_set_mode(FuriHalNfcMode mode, FuriHalNfcTech tech) {
|
||||
furi_assert(mode < FuriHalNfcModeNum);
|
||||
furi_assert(tech < FuriHalNfcTechNum);
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
|
||||
if(mode == FuriHalNfcModePoller) {
|
||||
do {
|
||||
error = furi_hal_nfc_poller_init_common(handle);
|
||||
if(error != FuriHalNfcErrorNone) break;
|
||||
error = furi_hal_nfc_tech[tech]->poller.init(handle);
|
||||
} while(false);
|
||||
|
||||
} else if(mode == FuriHalNfcModeListener) {
|
||||
do {
|
||||
error = furi_hal_nfc_listener_init_common(handle);
|
||||
if(error != FuriHalNfcErrorNone) break;
|
||||
error = furi_hal_nfc_tech[tech]->listener.init(handle);
|
||||
} while(false);
|
||||
}
|
||||
|
||||
furi_hal_nfc.mode = mode;
|
||||
furi_hal_nfc.tech = tech;
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_reset_mode() {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP);
|
||||
|
||||
const FuriHalNfcMode mode = furi_hal_nfc.mode;
|
||||
const FuriHalNfcTech tech = furi_hal_nfc.tech;
|
||||
if(mode == FuriHalNfcModePoller) {
|
||||
error = furi_hal_nfc_tech[tech]->poller.deinit(handle);
|
||||
} else if(mode == FuriHalNfcModeListener) {
|
||||
error = furi_hal_nfc_tech[tech]->listener.deinit(handle);
|
||||
}
|
||||
// Set default value in mode register
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_om0);
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_STREAM_MODE, 0);
|
||||
st25r3916_clear_reg_bits(handle, ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx);
|
||||
st25r3916_clear_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_BIT_RATE,
|
||||
ST25R3916_REG_BIT_RATE_txrate_mask | ST25R3916_REG_BIT_RATE_rxrate_mask);
|
||||
|
||||
// Write default values
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF1, 0);
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_RX_CONF2,
|
||||
ST25R3916_REG_RX_CONF2_sqm_dyn | ST25R3916_REG_RX_CONF2_agc_en |
|
||||
ST25R3916_REG_RX_CONF2_agc_m);
|
||||
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_CORR_CONF1,
|
||||
ST25R3916_REG_CORR_CONF1_corr_s7 | ST25R3916_REG_CORR_CONF1_corr_s4 |
|
||||
ST25R3916_REG_CORR_CONF1_corr_s1 | ST25R3916_REG_CORR_CONF1_corr_s0);
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_field_detect_start() {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_en_fd_mask);
|
||||
st25r3916_write_reg(
|
||||
handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ | ST25R3916_REG_MODE_om0);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_field_detect_stop() {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
st25r3916_clear_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
(ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_en_fd_mask));
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
bool furi_hal_nfc_field_is_present() {
|
||||
bool is_present = false;
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
if(st25r3916_check_reg(
|
||||
handle,
|
||||
ST25R3916_REG_AUX_DISPLAY,
|
||||
ST25R3916_REG_AUX_DISPLAY_efd_o,
|
||||
ST25R3916_REG_AUX_DISPLAY_efd_o)) {
|
||||
is_present = true;
|
||||
}
|
||||
|
||||
return is_present;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_poller_field_on() {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
if(!st25r3916_check_reg(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
ST25R3916_REG_OP_CONTROL_tx_en,
|
||||
ST25R3916_REG_OP_CONTROL_tx_en)) {
|
||||
// Set min guard time
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_FIELD_ON_GT, 0);
|
||||
// Enable tx rx
|
||||
st25r3916_set_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
(ST25R3916_REG_OP_CONTROL_rx_en | ST25R3916_REG_OP_CONTROL_tx_en));
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_poller_tx_common(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_bits) {
|
||||
furi_assert(tx_data);
|
||||
|
||||
FuriHalNfcError err = FuriHalNfcErrorNone;
|
||||
|
||||
// Prepare tx
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO);
|
||||
st25r3916_clear_reg_bits(
|
||||
handle, ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv);
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_ISO14443A_NFC,
|
||||
(ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par),
|
||||
(ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off));
|
||||
uint32_t interrupts =
|
||||
(ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS |
|
||||
ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC |
|
||||
ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE);
|
||||
// Clear interrupts
|
||||
st25r3916_get_irq(handle);
|
||||
// Enable interrupts
|
||||
st25r3916_mask_irq(handle, ~interrupts);
|
||||
|
||||
st25r3916_write_fifo(handle, tx_data, tx_bits);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WITHOUT_CRC);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_common_fifo_tx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_bits) {
|
||||
FuriHalNfcError err = FuriHalNfcErrorNone;
|
||||
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO);
|
||||
st25r3916_write_fifo(handle, tx_data, tx_bits);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WITHOUT_CRC);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits) {
|
||||
furi_assert(furi_hal_nfc.mode == FuriHalNfcModePoller);
|
||||
furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum);
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.tx(handle, tx_data, tx_bits);
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_poller_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) {
|
||||
furi_assert(furi_hal_nfc.mode == FuriHalNfcModePoller);
|
||||
furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum);
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.rx(handle, rx_data, rx_data_size, rx_bits);
|
||||
}
|
||||
|
||||
FuriHalNfcEvent furi_hal_nfc_poller_wait_event(uint32_t timeout_ms) {
|
||||
furi_assert(furi_hal_nfc.mode == FuriHalNfcModePoller);
|
||||
furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum);
|
||||
|
||||
return furi_hal_nfc_tech[furi_hal_nfc.tech]->poller.wait_event(timeout_ms);
|
||||
}
|
||||
|
||||
FuriHalNfcEvent furi_hal_nfc_listener_wait_event(uint32_t timeout_ms) {
|
||||
furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener);
|
||||
furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum);
|
||||
|
||||
return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.wait_event(timeout_ms);
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_listener_tx(const uint8_t* tx_data, size_t tx_bits) {
|
||||
furi_assert(tx_data);
|
||||
|
||||
furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener);
|
||||
furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum);
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.tx(handle, tx_data, tx_bits);
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_common_fifo_rx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* rx_data,
|
||||
size_t rx_data_size,
|
||||
size_t* rx_bits) {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
|
||||
if(!st25r3916_read_fifo(handle, rx_data, rx_data_size, rx_bits)) {
|
||||
error = FuriHalNfcErrorBufferOverflow;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_listener_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) {
|
||||
furi_assert(rx_data);
|
||||
furi_assert(rx_bits);
|
||||
|
||||
furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener);
|
||||
furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum);
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.rx(
|
||||
handle, rx_data, rx_data_size, rx_bits);
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_trx_reset() {
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_listener_sleep() {
|
||||
furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener);
|
||||
furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum);
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.sleep(handle);
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_listener_idle() {
|
||||
furi_assert(furi_hal_nfc.mode == FuriHalNfcModeListener);
|
||||
furi_assert(furi_hal_nfc.tech < FuriHalNfcTechNum);
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
return furi_hal_nfc_tech[furi_hal_nfc.tech]->listener.idle(handle);
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_listener_enable_rx() {
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
117
targets/f7/furi_hal/furi_hal_nfc_event.c
Normal file
117
targets/f7/furi_hal/furi_hal_nfc_event.c
Normal file
@@ -0,0 +1,117 @@
|
||||
#include <furi_hal_nfc_i.h>
|
||||
|
||||
FuriHalNfcEventInternal* furi_hal_nfc_event = NULL;
|
||||
|
||||
void furi_hal_nfc_event_init() {
|
||||
furi_hal_nfc_event = malloc(sizeof(FuriHalNfcEventInternal));
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_event_start() {
|
||||
furi_assert(furi_hal_nfc_event);
|
||||
|
||||
furi_hal_nfc_event->thread = furi_thread_get_current_id();
|
||||
furi_thread_flags_clear(FURI_HAL_NFC_EVENT_INTERNAL_ALL);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_event_stop() {
|
||||
furi_assert(furi_hal_nfc_event);
|
||||
|
||||
furi_hal_nfc_event->thread = NULL;
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
void furi_hal_nfc_event_set(FuriHalNfcEventInternalType event) {
|
||||
furi_assert(furi_hal_nfc_event);
|
||||
|
||||
if(furi_hal_nfc_event->thread) {
|
||||
furi_thread_flags_set(furi_hal_nfc_event->thread, event);
|
||||
}
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_abort() {
|
||||
furi_hal_nfc_event_set(FuriHalNfcEventInternalTypeAbort);
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms) {
|
||||
furi_assert(furi_hal_nfc_event);
|
||||
furi_assert(furi_hal_nfc_event->thread);
|
||||
|
||||
FuriHalNfcEvent event = 0;
|
||||
uint32_t event_timeout = timeout_ms == FURI_HAL_NFC_EVENT_WAIT_FOREVER ? FuriWaitForever :
|
||||
timeout_ms;
|
||||
uint32_t event_flag =
|
||||
furi_thread_flags_wait(FURI_HAL_NFC_EVENT_INTERNAL_ALL, FuriFlagWaitAny, event_timeout);
|
||||
if(event_flag != (unsigned)FuriFlagErrorTimeout) {
|
||||
if(event_flag & FuriHalNfcEventInternalTypeIrq) {
|
||||
furi_thread_flags_clear(FuriHalNfcEventInternalTypeIrq);
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
uint32_t irq = furi_hal_nfc_get_irq(handle);
|
||||
if(irq & ST25R3916_IRQ_MASK_OSC) {
|
||||
event |= FuriHalNfcEventOscOn;
|
||||
}
|
||||
if(irq & ST25R3916_IRQ_MASK_TXE) {
|
||||
event |= FuriHalNfcEventTxEnd;
|
||||
}
|
||||
if(irq & ST25R3916_IRQ_MASK_RXS) {
|
||||
event |= FuriHalNfcEventRxStart;
|
||||
}
|
||||
if(irq & ST25R3916_IRQ_MASK_RXE) {
|
||||
event |= FuriHalNfcEventRxEnd;
|
||||
}
|
||||
if(irq & ST25R3916_IRQ_MASK_COL) {
|
||||
event |= FuriHalNfcEventCollision;
|
||||
}
|
||||
if(irq & ST25R3916_IRQ_MASK_EON) {
|
||||
event |= FuriHalNfcEventFieldOn;
|
||||
}
|
||||
if(irq & ST25R3916_IRQ_MASK_EOF) {
|
||||
event |= FuriHalNfcEventFieldOff;
|
||||
}
|
||||
if(irq & ST25R3916_IRQ_MASK_WU_A) {
|
||||
event |= FuriHalNfcEventListenerActive;
|
||||
}
|
||||
if(irq & ST25R3916_IRQ_MASK_WU_A_X) {
|
||||
event |= FuriHalNfcEventListenerActive;
|
||||
}
|
||||
}
|
||||
if(event_flag & FuriHalNfcEventInternalTypeTimerFwtExpired) {
|
||||
event |= FuriHalNfcEventTimerFwtExpired;
|
||||
furi_thread_flags_clear(FuriHalNfcEventInternalTypeTimerFwtExpired);
|
||||
}
|
||||
if(event_flag & FuriHalNfcEventInternalTypeTimerBlockTxExpired) {
|
||||
event |= FuriHalNfcEventTimerBlockTxExpired;
|
||||
furi_thread_flags_clear(FuriHalNfcEventInternalTypeTimerBlockTxExpired);
|
||||
}
|
||||
if(event_flag & FuriHalNfcEventInternalTypeAbort) {
|
||||
event |= FuriHalNfcEventAbortRequest;
|
||||
furi_thread_flags_clear(FuriHalNfcEventInternalTypeAbort);
|
||||
}
|
||||
} else {
|
||||
event = FuriHalNfcEventTimeout;
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
bool furi_hal_nfc_event_wait_for_specific_irq(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint32_t mask,
|
||||
uint32_t timeout_ms) {
|
||||
furi_assert(furi_hal_nfc_event);
|
||||
furi_assert(furi_hal_nfc_event->thread);
|
||||
|
||||
bool irq_received = false;
|
||||
uint32_t event_flag =
|
||||
furi_thread_flags_wait(FuriHalNfcEventInternalTypeIrq, FuriFlagWaitAny, timeout_ms);
|
||||
if(event_flag == FuriHalNfcEventInternalTypeIrq) {
|
||||
uint32_t irq = furi_hal_nfc_get_irq(handle);
|
||||
irq_received = ((irq & mask) == mask);
|
||||
furi_thread_flags_clear(FuriHalNfcEventInternalTypeIrq);
|
||||
}
|
||||
|
||||
return irq_received;
|
||||
}
|
||||
69
targets/f7/furi_hal/furi_hal_nfc_felica.c
Normal file
69
targets/f7/furi_hal/furi_hal_nfc_felica.c
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "furi_hal_nfc_i.h"
|
||||
#include "furi_hal_nfc_tech_i.h"
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_felica_poller_init(FuriHalSpiBusHandle* handle) {
|
||||
// Enable Felica mode, AM modulation
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_MODE,
|
||||
ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am,
|
||||
ST25R3916_REG_MODE_om_felica | ST25R3916_REG_MODE_tr_am_am);
|
||||
|
||||
// 10% ASK modulation
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_TX_DRIVER,
|
||||
ST25R3916_REG_TX_DRIVER_am_mod_mask,
|
||||
ST25R3916_REG_TX_DRIVER_am_mod_10percent);
|
||||
|
||||
// Use regulator AM, resistive AM disabled
|
||||
st25r3916_clear_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_AUX_MOD,
|
||||
ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am);
|
||||
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_BIT_RATE,
|
||||
ST25R3916_REG_BIT_RATE_txrate_mask | ST25R3916_REG_BIT_RATE_rxrate_mask,
|
||||
ST25R3916_REG_BIT_RATE_txrate_212 | ST25R3916_REG_BIT_RATE_rxrate_212);
|
||||
|
||||
// Receive configuration
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_RX_CONF1,
|
||||
ST25R3916_REG_RX_CONF1_lp0 | ST25R3916_REG_RX_CONF1_hz_12_80khz);
|
||||
|
||||
// Correlator setup
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_CORR_CONF1,
|
||||
ST25R3916_REG_CORR_CONF1_corr_s6 | ST25R3916_REG_CORR_CONF1_corr_s4 |
|
||||
ST25R3916_REG_CORR_CONF1_corr_s3);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_felica_poller_deinit(FuriHalSpiBusHandle* handle) {
|
||||
UNUSED(handle);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
const FuriHalNfcTechBase furi_hal_nfc_felica = {
|
||||
.poller =
|
||||
{
|
||||
.compensation =
|
||||
{
|
||||
.fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC,
|
||||
.fwt = FURI_HAL_NFC_POLLER_FWT_COMP_FC,
|
||||
},
|
||||
.init = furi_hal_nfc_felica_poller_init,
|
||||
.deinit = furi_hal_nfc_felica_poller_deinit,
|
||||
.wait_event = furi_hal_nfc_wait_event_common,
|
||||
.tx = furi_hal_nfc_poller_tx_common,
|
||||
.rx = furi_hal_nfc_common_fifo_rx,
|
||||
},
|
||||
|
||||
.listener = {},
|
||||
};
|
||||
191
targets/f7/furi_hal/furi_hal_nfc_i.h
Normal file
191
targets/f7/furi_hal/furi_hal_nfc_i.h
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* @file furi_hal_nfc_i.h
|
||||
* @brief NFC HAL library (private definitions).
|
||||
*
|
||||
* This file is an implementation detail. It must not be included in
|
||||
* any public API-related headers.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <furi_hal_spi.h>
|
||||
|
||||
#include <drivers/st25r3916.h>
|
||||
#include <drivers/st25r3916_reg.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @brief Common frame delay time compensation for pollers. */
|
||||
#define FURI_HAL_NFC_POLLER_FDT_COMP_FC (-500)
|
||||
/** @brief Common frame wait time compensation for pollers. */
|
||||
#define FURI_HAL_NFC_POLLER_FWT_COMP_FC (FURI_HAL_NFC_POLLER_FDT_COMP_FC)
|
||||
|
||||
/**
|
||||
* @brief Enumeration containing bitmask values for NFC HAL internal events.
|
||||
*/
|
||||
typedef enum {
|
||||
FuriHalNfcEventInternalTypeAbort = (1U << 0), /**< Abort waiting for hardware events. */
|
||||
FuriHalNfcEventInternalTypeIrq = (1U << 1), /**< NFC hardware interrupt has occurred. */
|
||||
FuriHalNfcEventInternalTypeTimerFwtExpired =
|
||||
(1U << 2), /**< Frame wait time timeout has expired. */
|
||||
FuriHalNfcEventInternalTypeTimerBlockTxExpired =
|
||||
(1U << 3), /**< Transmission block timeout has expired. */
|
||||
FuriHalNfcEventInternalTypeTransparentDataReceived =
|
||||
(1U << 4), /**< Data was received in transparent mode. */
|
||||
} FuriHalNfcEventInternalType;
|
||||
|
||||
/** @brief Special bitmask value of all internal events. */
|
||||
#define FURI_HAL_NFC_EVENT_INTERNAL_ALL \
|
||||
((FuriHalNfcEventInternalTypeAbort | FuriHalNfcEventInternalTypeIrq | \
|
||||
FuriHalNfcEventInternalTypeTimerFwtExpired | \
|
||||
FuriHalNfcEventInternalTypeTimerBlockTxExpired | \
|
||||
FuriHalNfcEventInternalTypeTransparentDataReceived))
|
||||
|
||||
/**
|
||||
* @brief NFC HAL internal event structure.
|
||||
*/
|
||||
typedef struct {
|
||||
FuriThreadId thread; /**< Identifier of the thread that will be receiving events. */
|
||||
void* context; /**< Pointer to the user-provided context (will be passed to the event callback). */
|
||||
} FuriHalNfcEventInternal;
|
||||
|
||||
/**
|
||||
* @brief NFC HAL global state structure.
|
||||
*/
|
||||
typedef struct {
|
||||
FuriMutex* mutex; /**< Pointer to the mutex serving as global NFC HAL lock. */
|
||||
FuriHalNfcMode mode; /**< Currently selected operating mode. */
|
||||
FuriHalNfcTech tech; /**< Currently selected NFC technology. */
|
||||
} FuriHalNfc;
|
||||
|
||||
/**
|
||||
* @brief NFC HAL global state object declaration.
|
||||
*/
|
||||
extern FuriHalNfc furi_hal_nfc;
|
||||
|
||||
/**
|
||||
* @brief Initialise NFC HAL event system.
|
||||
*/
|
||||
void furi_hal_nfc_event_init();
|
||||
|
||||
/**
|
||||
* @brief Forcibly emit (a) particular internal event(s).
|
||||
*
|
||||
* @param[in] event bitmask of one or more events to be emitted.
|
||||
*/
|
||||
void furi_hal_nfc_event_set(FuriHalNfcEventInternalType event);
|
||||
|
||||
/**
|
||||
* @brief Initialise GPIO to generate an interrupt from the NFC hardware.
|
||||
*/
|
||||
void furi_hal_nfc_init_gpio_isr();
|
||||
|
||||
/**
|
||||
* @brief Disable interrupts from the NFC hardware.
|
||||
*/
|
||||
void furi_hal_nfc_deinit_gpio_isr();
|
||||
|
||||
/**
|
||||
* @brief Initialise all NFC timers.
|
||||
*/
|
||||
void furi_hal_nfc_timers_init();
|
||||
|
||||
/**
|
||||
* @brief Disable all NFC timers.
|
||||
*/
|
||||
void furi_hal_nfc_timers_deinit();
|
||||
|
||||
/**
|
||||
* @brief Get the interrupt bitmask from the NFC hardware.
|
||||
*
|
||||
* @param[in,out] handle pointer to the SPI handle associated with the NFC chip.
|
||||
* @returns bitmask of zero or more occurred interrupts.
|
||||
*/
|
||||
uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/**
|
||||
* @brief Wait until a specified type of interrupt occurs.
|
||||
*
|
||||
* @param[in,out] handle pointer to the SPI handle associated with the NFC chip.
|
||||
* @param[in] mask bitmask of one or more interrupts to wait for.
|
||||
* @param[in] timeout_ms maximum time to wait for an interrupt, in milliseconds.
|
||||
* @returns true if specified interrupt(s) have occured within timeout, false otherwise.
|
||||
*/
|
||||
bool furi_hal_nfc_event_wait_for_specific_irq(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint32_t mask,
|
||||
uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Wait for any event to occur.
|
||||
*
|
||||
* This function is common to all technologies.
|
||||
*
|
||||
* @param[in] timeout_ms maximum time to wait for an event, in milliseconds.
|
||||
* @returns bitmask of zero or more occurred events.
|
||||
*/
|
||||
FuriHalNfcEvent furi_hal_nfc_wait_event_common(uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Start reception in listener mode.
|
||||
*
|
||||
* This function is common to all technologies.
|
||||
*
|
||||
* @param[in,out] handle pointer to the SPI handle associated with the NFC chip.
|
||||
* @returns FuriHalNfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
FuriHalNfcError furi_hal_nfc_common_listener_rx_start(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/**
|
||||
* @brief Transmit data using on-chip FIFO.
|
||||
*
|
||||
* This function is common to all technologies.
|
||||
*
|
||||
* @param[in,out] handle pointer to the SPI handle associated with the NFC chip.
|
||||
* @param[in] tx_data pointer to a byte array containing the data to be transmitted.
|
||||
* @param[in] tx_bits transmit data size, in bits.
|
||||
* @returns FuriHalNfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
FuriHalNfcError furi_hal_nfc_common_fifo_tx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_bits);
|
||||
|
||||
/**
|
||||
* @brief Receive data using on-chip FIFO.
|
||||
*
|
||||
* This function is common to all technologies.
|
||||
*
|
||||
* @param[in,out] handle pointer to the SPI handle associated with the NFC chip.
|
||||
* @param[out] rx_data pointer to a byte array to be filled with received data.
|
||||
* @param[in] rx_data_size maximum received data size, in bytes.
|
||||
* @param[out] rx_bits pointer to the variable to contain the received data size, in bits.
|
||||
* @returns FuriHalNfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
FuriHalNfcError furi_hal_nfc_common_fifo_rx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* rx_data,
|
||||
size_t rx_data_size,
|
||||
size_t* rx_bits);
|
||||
|
||||
/**
|
||||
* @brief Transmit data in poller mode.
|
||||
*
|
||||
* This function is common to all technologies.
|
||||
*
|
||||
* @param[in,out] handle pointer to the SPI handle associated with the NFC chip.
|
||||
* @param[in] tx_data pointer to a byte array containing the data to be transmitted.
|
||||
* @param[in] tx_bits transmit data size, in bits.
|
||||
* @returns FuriHalNfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
FuriHalNfcError furi_hal_nfc_poller_tx_common(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_bits);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
28
targets/f7/furi_hal/furi_hal_nfc_irq.c
Normal file
28
targets/f7/furi_hal/furi_hal_nfc_irq.c
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "furi_hal_nfc_i.h"
|
||||
|
||||
#include <lib/drivers/st25r3916.h>
|
||||
#include <furi_hal_resources.h>
|
||||
|
||||
static void furi_hal_nfc_int_callback() {
|
||||
furi_hal_nfc_event_set(FuriHalNfcEventInternalTypeIrq);
|
||||
}
|
||||
|
||||
uint32_t furi_hal_nfc_get_irq(FuriHalSpiBusHandle* handle) {
|
||||
uint32_t irq = 0;
|
||||
while(furi_hal_gpio_read_port_pin(gpio_nfc_irq_rfid_pull.port, gpio_nfc_irq_rfid_pull.pin)) {
|
||||
irq |= st25r3916_get_irq(handle);
|
||||
}
|
||||
return irq;
|
||||
}
|
||||
|
||||
void furi_hal_nfc_init_gpio_isr() {
|
||||
furi_hal_gpio_init(
|
||||
&gpio_nfc_irq_rfid_pull, GpioModeInterruptRise, GpioPullDown, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_add_int_callback(&gpio_nfc_irq_rfid_pull, furi_hal_nfc_int_callback, NULL);
|
||||
furi_hal_gpio_enable_int_callback(&gpio_nfc_irq_rfid_pull);
|
||||
}
|
||||
|
||||
void furi_hal_nfc_deinit_gpio_isr() {
|
||||
furi_hal_gpio_remove_int_callback(&gpio_nfc_irq_rfid_pull);
|
||||
furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||
}
|
||||
356
targets/f7/furi_hal/furi_hal_nfc_iso14443a.c
Normal file
356
targets/f7/furi_hal/furi_hal_nfc_iso14443a.c
Normal file
@@ -0,0 +1,356 @@
|
||||
#include "furi_hal_nfc_i.h"
|
||||
#include "furi_hal_nfc_tech_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_resources.h>
|
||||
|
||||
#include <digital_signal/presets/nfc/iso14443_3a_signal.h>
|
||||
|
||||
#define TAG "FuriHalIso14443a"
|
||||
|
||||
// Prevent FDT timer from starting
|
||||
#define FURI_HAL_NFC_ISO14443A_LISTENER_FDT_COMP_FC (INT32_MAX)
|
||||
|
||||
static Iso14443_3aSignal* iso14443_3a_signal = NULL;
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso14443a_common_init(FuriHalSpiBusHandle* handle) {
|
||||
// Common NFC-A settings, 106 kbps
|
||||
|
||||
// 1st stage zero = 600kHz, 3rd stage zero = 200 kHz
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF1, ST25R3916_REG_RX_CONF1_z600k);
|
||||
// AGC enabled, ratio 3:1, squelch after TX
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_RX_CONF2,
|
||||
ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_m |
|
||||
ST25R3916_REG_RX_CONF2_agc_en | ST25R3916_REG_RX_CONF2_sqm_dyn);
|
||||
// HF operation, full gain on AM and PM channels
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00);
|
||||
// No gain reduction on AM and PM channels
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00);
|
||||
// Correlator config
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_CORR_CONF1,
|
||||
ST25R3916_REG_CORR_CONF1_corr_s0 | ST25R3916_REG_CORR_CONF1_corr_s4 |
|
||||
ST25R3916_REG_CORR_CONF1_corr_s6);
|
||||
// Sleep mode disable, 424kHz mode off
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0x00);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso14443a_poller_init(FuriHalSpiBusHandle* handle) {
|
||||
// Enable ISO14443A mode, OOK modulation
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_MODE,
|
||||
ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am,
|
||||
ST25R3916_REG_MODE_om_iso14443a | ST25R3916_REG_MODE_tr_am_ook);
|
||||
|
||||
// Overshoot protection - is this necessary here?
|
||||
st25r3916_change_reg_bits(handle, ST25R3916_REG_OVERSHOOT_CONF1, 0xff, 0x40);
|
||||
st25r3916_change_reg_bits(handle, ST25R3916_REG_OVERSHOOT_CONF2, 0xff, 0x03);
|
||||
st25r3916_change_reg_bits(handle, ST25R3916_REG_UNDERSHOOT_CONF1, 0xff, 0x40);
|
||||
st25r3916_change_reg_bits(handle, ST25R3916_REG_UNDERSHOOT_CONF2, 0xff, 0x03);
|
||||
|
||||
return furi_hal_nfc_iso14443a_common_init(handle);
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso14443a_poller_deinit(FuriHalSpiBusHandle* handle) {
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_ISO14443A_NFC,
|
||||
(ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par),
|
||||
(ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off));
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso14443a_listener_init(FuriHalSpiBusHandle* handle) {
|
||||
furi_check(iso14443_3a_signal == NULL);
|
||||
iso14443_3a_signal = iso14443_3a_signal_alloc(&gpio_spi_r_mosi);
|
||||
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en |
|
||||
ST25R3916_REG_OP_CONTROL_en_fd_auto_efd);
|
||||
st25r3916_write_reg(
|
||||
handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ_targ | ST25R3916_REG_MODE_om0);
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_PASSIVE_TARGET,
|
||||
ST25R3916_REG_PASSIVE_TARGET_fdel_2 | ST25R3916_REG_PASSIVE_TARGET_fdel_0 |
|
||||
ST25R3916_REG_PASSIVE_TARGET_d_ac_ap2p | ST25R3916_REG_PASSIVE_TARGET_d_212_424_1r);
|
||||
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_MASK_RX_TIMER, 0x02);
|
||||
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP);
|
||||
uint32_t interrupts =
|
||||
(ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS |
|
||||
ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC |
|
||||
ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE |
|
||||
ST25R3916_IRQ_MASK_EON | ST25R3916_IRQ_MASK_EOF | ST25R3916_IRQ_MASK_WU_A_X |
|
||||
ST25R3916_IRQ_MASK_WU_A);
|
||||
// Clear interrupts
|
||||
st25r3916_get_irq(handle);
|
||||
// Enable interrupts
|
||||
st25r3916_mask_irq(handle, ~interrupts);
|
||||
// Enable auto collision resolution
|
||||
st25r3916_clear_reg_bits(
|
||||
handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SENSE);
|
||||
|
||||
return furi_hal_nfc_iso14443a_common_init(handle);
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso14443a_listener_deinit(FuriHalSpiBusHandle* handle) {
|
||||
UNUSED(handle);
|
||||
|
||||
if(iso14443_3a_signal) {
|
||||
iso14443_3a_signal_free(iso14443_3a_signal);
|
||||
iso14443_3a_signal = NULL;
|
||||
}
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static FuriHalNfcEvent furi_hal_nfc_iso14443_3a_listener_wait_event(uint32_t timeout_ms) {
|
||||
FuriHalNfcEvent event = furi_hal_nfc_wait_event_common(timeout_ms);
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
if(event & FuriHalNfcEventListenerActive) {
|
||||
st25r3916_set_reg_bits(
|
||||
handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a);
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_iso14443a_poller_trx_short_frame(FuriHalNfcaShortFrame frame) {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
// Disable crc check
|
||||
st25r3916_set_reg_bits(handle, ST25R3916_REG_AUX, ST25R3916_REG_AUX_no_crc_rx);
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_ISO14443A_NFC,
|
||||
(ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par),
|
||||
(ST25R3916_REG_ISO14443A_NFC_no_tx_par_off | ST25R3916_REG_ISO14443A_NFC_no_rx_par_off));
|
||||
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_NUM_TX_BYTES2, 0);
|
||||
uint32_t interrupts =
|
||||
(ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS |
|
||||
ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC |
|
||||
ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE);
|
||||
// Clear interrupts
|
||||
st25r3916_get_irq(handle);
|
||||
// Enable interrupts
|
||||
st25r3916_mask_irq(handle, ~interrupts);
|
||||
if(frame == FuriHalNfcaShortFrameAllReq) {
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_REQA);
|
||||
} else {
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WUPA);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_iso14443a_tx_sdd_frame(const uint8_t* tx_data, size_t tx_bits) {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
// No anticollision is supported in this version of library
|
||||
error = furi_hal_nfc_poller_tx(tx_data, tx_bits);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError
|
||||
furi_hal_nfc_iso14443a_rx_sdd_frame(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits) {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
UNUSED(rx_data);
|
||||
UNUSED(rx_bits);
|
||||
UNUSED(rx_data_size);
|
||||
|
||||
error = furi_hal_nfc_poller_rx(rx_data, rx_data_size, rx_bits);
|
||||
// No anticollision is supported in this version of library
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError
|
||||
furi_hal_nfc_iso14443a_poller_tx_custom_parity(const uint8_t* tx_data, size_t tx_bits) {
|
||||
furi_assert(tx_data);
|
||||
|
||||
FuriHalNfcError err = FuriHalNfcErrorNone;
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
// Prepare tx
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_CLEAR_FIFO);
|
||||
st25r3916_clear_reg_bits(
|
||||
handle, ST25R3916_REG_TIMER_EMV_CONTROL, ST25R3916_REG_TIMER_EMV_CONTROL_nrt_emv);
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_ISO14443A_NFC,
|
||||
(ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par),
|
||||
(ST25R3916_REG_ISO14443A_NFC_no_tx_par | ST25R3916_REG_ISO14443A_NFC_no_rx_par));
|
||||
uint32_t interrupts =
|
||||
(ST25R3916_IRQ_MASK_FWL | ST25R3916_IRQ_MASK_TXE | ST25R3916_IRQ_MASK_RXS |
|
||||
ST25R3916_IRQ_MASK_RXE | ST25R3916_IRQ_MASK_PAR | ST25R3916_IRQ_MASK_CRC |
|
||||
ST25R3916_IRQ_MASK_ERR1 | ST25R3916_IRQ_MASK_ERR2 | ST25R3916_IRQ_MASK_NRE);
|
||||
// Clear interrupts
|
||||
st25r3916_get_irq(handle);
|
||||
// Enable interrupts
|
||||
st25r3916_mask_irq(handle, ~interrupts);
|
||||
|
||||
st25r3916_write_fifo(handle, tx_data, tx_bits);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSMIT_WITHOUT_CRC);
|
||||
return err;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_iso14443a_listener_set_col_res_data(
|
||||
uint8_t* uid,
|
||||
uint8_t uid_len,
|
||||
uint8_t* atqa,
|
||||
uint8_t sak) {
|
||||
furi_assert(uid);
|
||||
furi_assert(atqa);
|
||||
UNUSED(uid_len);
|
||||
UNUSED(sak);
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
// Set 4 or 7 bytes UID
|
||||
if(uid_len == 4) {
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_AUX,
|
||||
ST25R3916_REG_AUX_nfc_id_mask,
|
||||
ST25R3916_REG_AUX_nfc_id_4bytes);
|
||||
} else {
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_AUX,
|
||||
ST25R3916_REG_AUX_nfc_id_mask,
|
||||
ST25R3916_REG_AUX_nfc_id_7bytes);
|
||||
}
|
||||
// Write PT Memory
|
||||
uint8_t pt_memory[15] = {};
|
||||
memcpy(pt_memory, uid, uid_len);
|
||||
pt_memory[10] = atqa[0];
|
||||
pt_memory[11] = atqa[1];
|
||||
if(uid_len == 4) {
|
||||
pt_memory[12] = sak & ~0x04;
|
||||
} else {
|
||||
pt_memory[12] = 0x04;
|
||||
}
|
||||
pt_memory[13] = sak & ~0x04;
|
||||
pt_memory[14] = sak & ~0x04;
|
||||
|
||||
st25r3916_write_pta_mem(handle, pt_memory, sizeof(pt_memory));
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_iso4443a_listener_tx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_bits) {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
|
||||
do {
|
||||
error = furi_hal_nfc_common_fifo_tx(handle, tx_data, tx_bits);
|
||||
if(error != FuriHalNfcErrorNone) break;
|
||||
|
||||
bool tx_end = furi_hal_nfc_event_wait_for_specific_irq(handle, ST25R3916_IRQ_MASK_TXE, 10);
|
||||
if(!tx_end) {
|
||||
error = FuriHalNfcErrorCommunicationTimeout;
|
||||
break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_iso14443a_listener_tx_custom_parity(
|
||||
const uint8_t* tx_data,
|
||||
const uint8_t* tx_parity,
|
||||
size_t tx_bits) {
|
||||
furi_assert(tx_data);
|
||||
furi_assert(tx_parity);
|
||||
|
||||
furi_assert(iso14443_3a_signal);
|
||||
|
||||
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_nfc;
|
||||
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE);
|
||||
// Reconfigure gpio for Transparent mode
|
||||
furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc);
|
||||
|
||||
// Send signal
|
||||
iso14443_3a_signal_tx(iso14443_3a_signal, tx_data, tx_parity, tx_bits);
|
||||
|
||||
// Exit transparent mode
|
||||
furi_hal_gpio_write(&gpio_spi_r_mosi, false);
|
||||
|
||||
// Configure gpio back to SPI and exit transparent
|
||||
furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_sleep(FuriHalSpiBusHandle* handle) {
|
||||
// Enable auto collision resolution
|
||||
st25r3916_clear_reg_bits(
|
||||
handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SLEEP);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_iso14443_3a_listener_idle(FuriHalSpiBusHandle* handle) {
|
||||
// Enable auto collision resolution
|
||||
st25r3916_clear_reg_bits(
|
||||
handle, ST25R3916_REG_PASSIVE_TARGET, ST25R3916_REG_PASSIVE_TARGET_d_106_ac_a);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_STOP);
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_GOTO_SENSE);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
const FuriHalNfcTechBase furi_hal_nfc_iso14443a = {
|
||||
.poller =
|
||||
{
|
||||
.compensation =
|
||||
{
|
||||
.fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC,
|
||||
.fwt = FURI_HAL_NFC_POLLER_FWT_COMP_FC,
|
||||
},
|
||||
.init = furi_hal_nfc_iso14443a_poller_init,
|
||||
.deinit = furi_hal_nfc_iso14443a_poller_deinit,
|
||||
.wait_event = furi_hal_nfc_wait_event_common,
|
||||
.tx = furi_hal_nfc_poller_tx_common,
|
||||
.rx = furi_hal_nfc_common_fifo_rx,
|
||||
},
|
||||
|
||||
.listener =
|
||||
{
|
||||
.compensation =
|
||||
{
|
||||
.fdt = FURI_HAL_NFC_ISO14443A_LISTENER_FDT_COMP_FC,
|
||||
},
|
||||
.init = furi_hal_nfc_iso14443a_listener_init,
|
||||
.deinit = furi_hal_nfc_iso14443a_listener_deinit,
|
||||
.wait_event = furi_hal_nfc_iso14443_3a_listener_wait_event,
|
||||
.tx = furi_hal_nfc_iso4443a_listener_tx,
|
||||
.rx = furi_hal_nfc_common_fifo_rx,
|
||||
.sleep = furi_hal_nfc_iso14443_3a_listener_sleep,
|
||||
.idle = furi_hal_nfc_iso14443_3a_listener_idle,
|
||||
},
|
||||
};
|
||||
108
targets/f7/furi_hal/furi_hal_nfc_iso14443b.c
Normal file
108
targets/f7/furi_hal/furi_hal_nfc_iso14443b.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "furi_hal_nfc_i.h"
|
||||
#include "furi_hal_nfc_tech_i.h"
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso14443b_common_init(FuriHalSpiBusHandle* handle) {
|
||||
// Common NFC-B settings, 106kbps
|
||||
|
||||
// 1st stage zero = 60kHz, 3rd stage zero = 200 kHz
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF1, ST25R3916_REG_RX_CONF1_h200);
|
||||
|
||||
// Enable AGC
|
||||
// AGC Ratio 6
|
||||
// AGC algorithm with RESET (recommended for ISO14443-B)
|
||||
// AGC operation during complete receive period
|
||||
// Squelch ratio 6/3 (recommended for ISO14443-B)
|
||||
// Squelch automatic activation on TX end
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_RX_CONF2,
|
||||
ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_alg |
|
||||
ST25R3916_REG_RX_CONF2_agc_m | ST25R3916_REG_RX_CONF2_agc_en |
|
||||
ST25R3916_REG_RX_CONF2_pulz_61 | ST25R3916_REG_RX_CONF2_sqm_dyn);
|
||||
|
||||
// HF operation, full gain on AM and PM channels
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00);
|
||||
// No gain reduction on AM and PM channels
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00);
|
||||
|
||||
// Subcarrier end detector enabled
|
||||
// Subcarrier end detection level = 66%
|
||||
// BPSK start 33 pilot pulses
|
||||
// AM & PM summation before digitizing on
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_CORR_CONF1,
|
||||
ST25R3916_REG_CORR_CONF1_corr_s0 | ST25R3916_REG_CORR_CONF1_corr_s1 |
|
||||
ST25R3916_REG_CORR_CONF1_corr_s3 | ST25R3916_REG_CORR_CONF1_corr_s4);
|
||||
// Sleep mode disable, 424kHz mode off
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, 0x00);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso14443b_poller_init(FuriHalSpiBusHandle* handle) {
|
||||
// Enable ISO14443B mode, AM modulation
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_MODE,
|
||||
ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am,
|
||||
ST25R3916_REG_MODE_om_iso14443b | ST25R3916_REG_MODE_tr_am_am);
|
||||
|
||||
// 10% ASK modulation
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_TX_DRIVER,
|
||||
ST25R3916_REG_TX_DRIVER_am_mod_mask,
|
||||
ST25R3916_REG_TX_DRIVER_am_mod_10percent);
|
||||
|
||||
// Use regulator AM, resistive AM disabled
|
||||
st25r3916_clear_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_AUX_MOD,
|
||||
ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am);
|
||||
|
||||
// EGT = 0 etu
|
||||
// SOF = 10 etu LOW + 2 etu HIGH
|
||||
// EOF = 10 etu
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_ISO14443B_1,
|
||||
ST25R3916_REG_ISO14443B_1_egt_mask | ST25R3916_REG_ISO14443B_1_sof_mask |
|
||||
ST25R3916_REG_ISO14443B_1_eof,
|
||||
(0U << ST25R3916_REG_ISO14443B_1_egt_shift) | ST25R3916_REG_ISO14443B_1_sof_0_10etu |
|
||||
ST25R3916_REG_ISO14443B_1_sof_1_2etu | ST25R3916_REG_ISO14443B_1_eof_10etu);
|
||||
|
||||
// TR1 = 80 / fs
|
||||
// B' mode off (no_sof & no_eof = 0)
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_ISO14443B_2,
|
||||
ST25R3916_REG_ISO14443B_2_tr1_mask | ST25R3916_REG_ISO14443B_2_no_sof |
|
||||
ST25R3916_REG_ISO14443B_2_no_eof,
|
||||
ST25R3916_REG_ISO14443B_2_tr1_80fs80fs);
|
||||
|
||||
return furi_hal_nfc_iso14443b_common_init(handle);
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso14443b_poller_deinit(FuriHalSpiBusHandle* handle) {
|
||||
UNUSED(handle);
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
const FuriHalNfcTechBase furi_hal_nfc_iso14443b = {
|
||||
.poller =
|
||||
{
|
||||
.compensation =
|
||||
{
|
||||
.fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC,
|
||||
.fwt = FURI_HAL_NFC_POLLER_FWT_COMP_FC,
|
||||
},
|
||||
.init = furi_hal_nfc_iso14443b_poller_init,
|
||||
.deinit = furi_hal_nfc_iso14443b_poller_deinit,
|
||||
.wait_event = furi_hal_nfc_wait_event_common,
|
||||
.tx = furi_hal_nfc_poller_tx_common,
|
||||
.rx = furi_hal_nfc_common_fifo_rx,
|
||||
},
|
||||
|
||||
.listener = {},
|
||||
};
|
||||
463
targets/f7/furi_hal/furi_hal_nfc_iso15693.c
Normal file
463
targets/f7/furi_hal/furi_hal_nfc_iso15693.c
Normal file
@@ -0,0 +1,463 @@
|
||||
#include "furi_hal_nfc_i.h"
|
||||
#include "furi_hal_nfc_tech_i.h"
|
||||
|
||||
#include <digital_signal/presets/nfc/iso15693_signal.h>
|
||||
#include <signal_reader/parsers/iso15693/iso15693_parser.h>
|
||||
|
||||
#include <furi_hal_resources.h>
|
||||
|
||||
#define FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE (1024U)
|
||||
#define FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE (64)
|
||||
|
||||
#define FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE (5)
|
||||
#define FURI_HAL_NFC_ISO15693_RESP_EOF_SIZE (5)
|
||||
#define FURI_HAL_NFC_ISO15693_RESP_SOF_MASK (0x1FU)
|
||||
#define FURI_HAL_NFC_ISO15693_RESP_SOF_PATTERN (0x17U)
|
||||
#define FURI_HAL_NFC_ISO15693_RESP_EOF_PATTERN (0x1DU)
|
||||
|
||||
#define FURI_HAL_NFC_ISO15693_RESP_PATTERN_MASK (0x03U)
|
||||
#define FURI_HAL_NFC_ISO15693_RESP_PATTERN_0 (0x01U)
|
||||
#define FURI_HAL_NFC_ISO15693_RESP_PATTERN_1 (0x02U)
|
||||
|
||||
// Derived experimentally
|
||||
#define FURI_HAL_NFC_ISO15693_POLLER_FWT_COMP_FC (-1300)
|
||||
#define FURI_HAL_NFC_ISO15693_LISTENER_FDT_COMP_FC (2850)
|
||||
|
||||
#define BITS_IN_BYTE (8U)
|
||||
|
||||
#define TAG "FuriHalIso15693"
|
||||
|
||||
typedef struct {
|
||||
Iso15693Signal* signal;
|
||||
Iso15693Parser* parser;
|
||||
} FuriHalNfcIso15693Listener;
|
||||
|
||||
typedef struct {
|
||||
// 4 bits per data bit on transmit
|
||||
uint8_t fifo_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * 4];
|
||||
size_t fifo_buf_bits;
|
||||
uint8_t frame_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * 2];
|
||||
size_t frame_buf_bits;
|
||||
} FuriHalNfcIso15693Poller;
|
||||
|
||||
static FuriHalNfcIso15693Listener* furi_hal_nfc_iso15693_listener = NULL;
|
||||
static FuriHalNfcIso15693Poller* furi_hal_nfc_iso15693_poller = NULL;
|
||||
|
||||
static FuriHalNfcIso15693Listener* furi_hal_nfc_iso15693_listener_alloc() {
|
||||
FuriHalNfcIso15693Listener* instance = malloc(sizeof(FuriHalNfcIso15693Listener));
|
||||
|
||||
instance->signal = iso15693_signal_alloc(&gpio_spi_r_mosi);
|
||||
instance->parser =
|
||||
iso15693_parser_alloc(&gpio_nfc_irq_rfid_pull, FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_iso15693_listener_free(FuriHalNfcIso15693Listener* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
iso15693_signal_free(instance->signal);
|
||||
iso15693_parser_free(instance->parser);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static FuriHalNfcIso15693Poller* furi_hal_nfc_iso15693_poller_alloc() {
|
||||
FuriHalNfcIso15693Poller* instance = malloc(sizeof(FuriHalNfcIso15693Poller));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_iso15693_poller_free(FuriHalNfcIso15693Poller* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso15693_common_init(FuriHalSpiBusHandle* handle) {
|
||||
// Common NFC-V settings, 26.48 kbps
|
||||
|
||||
// 1st stage zero = 12 kHz, 3rd stage zero = 80 kHz, low-pass = 600 kHz
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_RX_CONF1,
|
||||
ST25R3916_REG_RX_CONF1_z12k | ST25R3916_REG_RX_CONF1_h80 |
|
||||
ST25R3916_REG_RX_CONF1_lp_600khz);
|
||||
|
||||
// Enable AGC
|
||||
// AGC Ratio 6
|
||||
// AGC algorithm with RESET (recommended for ISO15693)
|
||||
// AGC operation during complete receive period
|
||||
// Squelch automatic activation on TX end
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_RX_CONF2,
|
||||
ST25R3916_REG_RX_CONF2_agc6_3 | ST25R3916_REG_RX_CONF2_agc_m |
|
||||
ST25R3916_REG_RX_CONF2_agc_en | ST25R3916_REG_RX_CONF2_sqm_dyn);
|
||||
|
||||
// HF operation, full gain on AM and PM channels
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF3, 0x00);
|
||||
// No gain reduction on AM and PM channels
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_RX_CONF4, 0x00);
|
||||
|
||||
// Collision detection level 53%
|
||||
// AM & PM summation before digitizing on
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_CORR_CONF1,
|
||||
ST25R3916_REG_CORR_CONF1_corr_s0 | ST25R3916_REG_CORR_CONF1_corr_s1 |
|
||||
ST25R3916_REG_CORR_CONF1_corr_s4);
|
||||
// 424 kHz subcarrier stream mode on
|
||||
st25r3916_write_reg(handle, ST25R3916_REG_CORR_CONF2, ST25R3916_REG_CORR_CONF2_corr_s8);
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso15693_poller_init(FuriHalSpiBusHandle* handle) {
|
||||
furi_assert(furi_hal_nfc_iso15693_poller == NULL);
|
||||
|
||||
furi_hal_nfc_iso15693_poller = furi_hal_nfc_iso15693_poller_alloc();
|
||||
|
||||
// Enable Subcarrier Stream mode, OOK modulation
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_MODE,
|
||||
ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am,
|
||||
ST25R3916_REG_MODE_om_subcarrier_stream | ST25R3916_REG_MODE_tr_am_ook);
|
||||
|
||||
// Subcarrier 424 kHz mode
|
||||
// 8 sub-carrier pulses in report period
|
||||
st25r3916_write_reg(
|
||||
handle,
|
||||
ST25R3916_REG_STREAM_MODE,
|
||||
ST25R3916_REG_STREAM_MODE_scf_sc424 | ST25R3916_REG_STREAM_MODE_stx_106 |
|
||||
ST25R3916_REG_STREAM_MODE_scp_8pulses);
|
||||
|
||||
// Use regulator AM, resistive AM disabled
|
||||
st25r3916_clear_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_AUX_MOD,
|
||||
ST25R3916_REG_AUX_MOD_dis_reg_am | ST25R3916_REG_AUX_MOD_res_am);
|
||||
|
||||
return furi_hal_nfc_iso15693_common_init(handle);
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso15693_poller_deinit(FuriHalSpiBusHandle* handle) {
|
||||
UNUSED(handle);
|
||||
furi_assert(furi_hal_nfc_iso15693_poller);
|
||||
|
||||
furi_hal_nfc_iso15693_poller_free(furi_hal_nfc_iso15693_poller);
|
||||
furi_hal_nfc_iso15693_poller = NULL;
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static void iso15693_3_poller_encode_frame(
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_bits,
|
||||
uint8_t* frame_buf,
|
||||
size_t frame_buf_size,
|
||||
size_t* frame_buf_bits) {
|
||||
static const uint8_t bit_patterns_1_out_of_4[] = {0x02, 0x08, 0x20, 0x80};
|
||||
size_t frame_buf_size_calc = (tx_bits / 2) + 2;
|
||||
furi_assert(frame_buf_size >= frame_buf_size_calc);
|
||||
|
||||
// Add SOF 1 out of 4
|
||||
frame_buf[0] = 0x21;
|
||||
|
||||
size_t byte_pos = 1;
|
||||
for(size_t i = 0; i < tx_bits / BITS_IN_BYTE; ++i) {
|
||||
for(size_t j = 0; j < BITS_IN_BYTE; j += (BITS_IN_BYTE) / 4) {
|
||||
const uint8_t bit_pair = (tx_data[i] >> j) & 0x03;
|
||||
frame_buf[byte_pos++] = bit_patterns_1_out_of_4[bit_pair];
|
||||
}
|
||||
}
|
||||
// Add EOF
|
||||
frame_buf[byte_pos++] = 0x04;
|
||||
*frame_buf_bits = byte_pos * BITS_IN_BYTE;
|
||||
}
|
||||
|
||||
static FuriHalNfcError iso15693_3_poller_decode_frame(
|
||||
const uint8_t* buf,
|
||||
size_t buf_bits,
|
||||
uint8_t* buf_decoded,
|
||||
size_t buf_decoded_size,
|
||||
size_t* buf_decoded_bits) {
|
||||
FuriHalNfcError ret = FuriHalNfcErrorDataFormat;
|
||||
size_t bit_pos = 0;
|
||||
memset(buf_decoded, 0, buf_decoded_size);
|
||||
|
||||
do {
|
||||
if(buf_bits == 0) break;
|
||||
// Check SOF
|
||||
if((buf[0] & FURI_HAL_NFC_ISO15693_RESP_SOF_MASK) !=
|
||||
FURI_HAL_NFC_ISO15693_RESP_SOF_PATTERN)
|
||||
break;
|
||||
|
||||
if(buf_bits == BITS_IN_BYTE) {
|
||||
ret = FuriHalNfcErrorIncompleteFrame;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2 response bits = 1 data bit
|
||||
for(uint32_t i = FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE;
|
||||
i < buf_bits - FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE;
|
||||
i += BITS_IN_BYTE / 4) {
|
||||
const size_t byte_index = i / BITS_IN_BYTE;
|
||||
const size_t bit_offset = i % BITS_IN_BYTE;
|
||||
const uint8_t resp_byte = (buf[byte_index] >> bit_offset) |
|
||||
(buf[byte_index + 1] << (BITS_IN_BYTE - bit_offset));
|
||||
|
||||
// Check EOF
|
||||
if(resp_byte == FURI_HAL_NFC_ISO15693_RESP_EOF_PATTERN) {
|
||||
ret = FuriHalNfcErrorNone;
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t bit_pattern = resp_byte & FURI_HAL_NFC_ISO15693_RESP_PATTERN_MASK;
|
||||
|
||||
if(bit_pattern == FURI_HAL_NFC_ISO15693_RESP_PATTERN_0) {
|
||||
bit_pos++;
|
||||
} else if(bit_pattern == FURI_HAL_NFC_ISO15693_RESP_PATTERN_1) {
|
||||
buf_decoded[bit_pos / BITS_IN_BYTE] |= 1 << (bit_pos % BITS_IN_BYTE);
|
||||
bit_pos++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if(bit_pos / BITS_IN_BYTE > buf_decoded_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
if(ret == FuriHalNfcErrorNone) {
|
||||
*buf_decoded_bits = bit_pos;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso15693_poller_tx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_bits) {
|
||||
FuriHalNfcIso15693Poller* instance = furi_hal_nfc_iso15693_poller;
|
||||
iso15693_3_poller_encode_frame(
|
||||
tx_data,
|
||||
tx_bits,
|
||||
instance->frame_buf,
|
||||
sizeof(instance->frame_buf),
|
||||
&instance->frame_buf_bits);
|
||||
return furi_hal_nfc_poller_tx_common(handle, instance->frame_buf, instance->frame_buf_bits);
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso15693_poller_rx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* rx_data,
|
||||
size_t rx_data_size,
|
||||
size_t* rx_bits) {
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
FuriHalNfcIso15693Poller* instance = furi_hal_nfc_iso15693_poller;
|
||||
|
||||
do {
|
||||
error = furi_hal_nfc_common_fifo_rx(
|
||||
handle, instance->fifo_buf, sizeof(instance->fifo_buf), &instance->fifo_buf_bits);
|
||||
if(error != FuriHalNfcErrorNone) break;
|
||||
|
||||
error = iso15693_3_poller_decode_frame(
|
||||
instance->fifo_buf,
|
||||
instance->fifo_buf_bits,
|
||||
instance->frame_buf,
|
||||
sizeof(instance->frame_buf),
|
||||
&instance->frame_buf_bits);
|
||||
if(error != FuriHalNfcErrorNone) break;
|
||||
|
||||
if(rx_data_size < instance->frame_buf_bits / BITS_IN_BYTE) {
|
||||
error = FuriHalNfcErrorBufferOverflow;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(rx_data, instance->frame_buf, instance->frame_buf_bits / BITS_IN_BYTE);
|
||||
*rx_bits = instance->frame_buf_bits;
|
||||
} while(false);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_iso15693_listener_transparent_mode_enter(FuriHalSpiBusHandle* handle) {
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_TRANSPARENT_MODE);
|
||||
|
||||
furi_hal_spi_bus_handle_deinit(handle);
|
||||
furi_hal_nfc_deinit_gpio_isr();
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_iso15693_listener_transparent_mode_exit(FuriHalSpiBusHandle* handle) {
|
||||
// Configure gpio back to SPI and exit transparent mode
|
||||
furi_hal_nfc_init_gpio_isr();
|
||||
furi_hal_spi_bus_handle_init(handle);
|
||||
|
||||
st25r3916_direct_cmd(handle, ST25R3916_CMD_UNMASK_RECEIVE_DATA);
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso15693_listener_init(FuriHalSpiBusHandle* handle) {
|
||||
furi_assert(furi_hal_nfc_iso15693_listener == NULL);
|
||||
|
||||
furi_hal_nfc_iso15693_listener = furi_hal_nfc_iso15693_listener_alloc();
|
||||
|
||||
// Set default operation mode
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_MODE,
|
||||
ST25R3916_REG_MODE_om_mask | ST25R3916_REG_MODE_tr_am,
|
||||
ST25R3916_REG_MODE_om_targ_nfca | ST25R3916_REG_MODE_tr_am_ook);
|
||||
|
||||
st25r3916_change_reg_bits(
|
||||
handle,
|
||||
ST25R3916_REG_OP_CONTROL,
|
||||
ST25R3916_REG_OP_CONTROL_rx_en,
|
||||
ST25R3916_REG_OP_CONTROL_rx_en);
|
||||
|
||||
// Enable passive target mode
|
||||
st25r3916_change_reg_bits(
|
||||
handle, ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ, ST25R3916_REG_MODE_targ_targ);
|
||||
|
||||
FuriHalNfcError error = furi_hal_nfc_iso15693_common_init(handle);
|
||||
|
||||
furi_hal_nfc_iso15693_listener_transparent_mode_enter(handle);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso15693_listener_deinit(FuriHalSpiBusHandle* handle) {
|
||||
furi_assert(furi_hal_nfc_iso15693_listener);
|
||||
|
||||
furi_hal_nfc_iso15693_listener_transparent_mode_exit(handle);
|
||||
|
||||
furi_hal_nfc_iso15693_listener_free(furi_hal_nfc_iso15693_listener);
|
||||
furi_hal_nfc_iso15693_listener = NULL;
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static FuriHalNfcError
|
||||
furi_hal_nfc_iso15693_listener_tx_transparent(const uint8_t* data, size_t data_size) {
|
||||
iso15693_signal_tx(
|
||||
furi_hal_nfc_iso15693_listener->signal, Iso15693SignalDataRateHi, data, data_size);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_iso15693_parser_callback(Iso15693ParserEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
if(event == Iso15693ParserEventDataReceived) {
|
||||
FuriThreadId thread_id = context;
|
||||
furi_thread_flags_set(thread_id, FuriHalNfcEventInternalTypeTransparentDataReceived);
|
||||
}
|
||||
}
|
||||
|
||||
static FuriHalNfcEvent furi_hal_nfc_iso15693_wait_event(uint32_t timeout_ms) {
|
||||
FuriHalNfcEvent event = 0;
|
||||
|
||||
FuriThreadId thread_id = furi_thread_get_current_id();
|
||||
iso15693_parser_start(
|
||||
furi_hal_nfc_iso15693_listener->parser, furi_hal_nfc_iso15693_parser_callback, thread_id);
|
||||
|
||||
while(true) {
|
||||
uint32_t flag = furi_thread_flags_wait(
|
||||
FuriHalNfcEventInternalTypeAbort | FuriHalNfcEventInternalTypeTransparentDataReceived,
|
||||
FuriFlagWaitAny,
|
||||
timeout_ms);
|
||||
furi_thread_flags_clear(flag);
|
||||
|
||||
if(flag & FuriHalNfcEventInternalTypeAbort) {
|
||||
event = FuriHalNfcEventAbortRequest;
|
||||
break;
|
||||
}
|
||||
if(flag & FuriHalNfcEventInternalTypeTransparentDataReceived) {
|
||||
if(iso15693_parser_run(furi_hal_nfc_iso15693_listener->parser)) {
|
||||
event = FuriHalNfcEventRxEnd;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
iso15693_parser_stop(furi_hal_nfc_iso15693_listener->parser);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso15693_listener_tx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
const uint8_t* tx_data,
|
||||
size_t tx_bits) {
|
||||
UNUSED(handle);
|
||||
furi_assert(furi_hal_nfc_iso15693_listener);
|
||||
|
||||
FuriHalNfcError error = FuriHalNfcErrorNone;
|
||||
|
||||
error = furi_hal_nfc_iso15693_listener_tx_transparent(tx_data, tx_bits / BITS_IN_BYTE);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_iso15693_listener_tx_sof() {
|
||||
iso15693_signal_tx_sof(furi_hal_nfc_iso15693_listener->signal, Iso15693SignalDataRateHi);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
static FuriHalNfcError furi_hal_nfc_iso15693_listener_rx(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* rx_data,
|
||||
size_t rx_data_size,
|
||||
size_t* rx_bits) {
|
||||
furi_assert(furi_hal_nfc_iso15693_listener);
|
||||
UNUSED(handle);
|
||||
|
||||
if(rx_data_size <
|
||||
iso15693_parser_get_data_size_bytes(furi_hal_nfc_iso15693_listener->parser)) {
|
||||
return FuriHalNfcErrorBufferOverflow;
|
||||
}
|
||||
|
||||
iso15693_parser_get_data(
|
||||
furi_hal_nfc_iso15693_listener->parser, rx_data, rx_data_size, rx_bits);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
FuriHalNfcError furi_hal_nfc_iso15693_listener_sleep(FuriHalSpiBusHandle* handle) {
|
||||
UNUSED(handle);
|
||||
|
||||
return FuriHalNfcErrorNone;
|
||||
}
|
||||
|
||||
const FuriHalNfcTechBase furi_hal_nfc_iso15693 = {
|
||||
.poller =
|
||||
{
|
||||
.compensation =
|
||||
{
|
||||
.fdt = FURI_HAL_NFC_POLLER_FDT_COMP_FC,
|
||||
.fwt = FURI_HAL_NFC_ISO15693_POLLER_FWT_COMP_FC,
|
||||
},
|
||||
.init = furi_hal_nfc_iso15693_poller_init,
|
||||
.deinit = furi_hal_nfc_iso15693_poller_deinit,
|
||||
.wait_event = furi_hal_nfc_wait_event_common,
|
||||
.tx = furi_hal_nfc_iso15693_poller_tx,
|
||||
.rx = furi_hal_nfc_iso15693_poller_rx,
|
||||
},
|
||||
|
||||
.listener =
|
||||
{
|
||||
.compensation =
|
||||
{
|
||||
.fdt = FURI_HAL_NFC_ISO15693_LISTENER_FDT_COMP_FC,
|
||||
},
|
||||
.init = furi_hal_nfc_iso15693_listener_init,
|
||||
.deinit = furi_hal_nfc_iso15693_listener_deinit,
|
||||
.wait_event = furi_hal_nfc_iso15693_wait_event,
|
||||
.tx = furi_hal_nfc_iso15693_listener_tx,
|
||||
.rx = furi_hal_nfc_iso15693_listener_rx,
|
||||
.sleep = furi_hal_nfc_iso15693_listener_sleep,
|
||||
.idle = furi_hal_nfc_iso15693_listener_sleep,
|
||||
},
|
||||
};
|
||||
167
targets/f7/furi_hal/furi_hal_nfc_tech_i.h
Normal file
167
targets/f7/furi_hal/furi_hal_nfc_tech_i.h
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @file furi_hal_nfc_tech_i.h
|
||||
* @brief NFC HAL technology-related private definitions.
|
||||
*
|
||||
* This file is an implementation detail. It must not be included in
|
||||
* any public API-related headers.
|
||||
*
|
||||
* This file is to be changed in an unlikely event of adding support
|
||||
* for a new NFC technology.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
#include <furi_hal_spi.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Configure the NFC chip for use with this technology.
|
||||
*
|
||||
* Used for init() and deinit() functions.
|
||||
*
|
||||
* @param[in,out] handle pointer to the NFC chip SPI handle.
|
||||
* @returns FuriHalNfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
typedef FuriHalNfcError (*FuriHalNfcChipConfig)(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/**
|
||||
* @brief Transmit data using technology-specific framing and timings.
|
||||
*
|
||||
* @param[in,out] handle pointer to the NFC chip SPI handle.
|
||||
* @param[in] tx_data pointer to a byte array containing the data to be transmitted.
|
||||
* @param[in] tx_bits transmit data size, in bits.
|
||||
* @returns FuriHalNfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
typedef FuriHalNfcError (
|
||||
*FuriHalNfcTx)(FuriHalSpiBusHandle* handle, const uint8_t* tx_data, size_t tx_bits);
|
||||
|
||||
/**
|
||||
* @brief Receive data using technology-specific framing and timings.
|
||||
*
|
||||
* @param[in,out] handle pointer to the NFC chip SPI handle.
|
||||
* @param[out] rx_data pointer to a byte array to be filled with received data.
|
||||
* @param[in] rx_data_size maximum received data length, in bytes.
|
||||
* @param[out] rx_bits pointer to a variable to contain received data length, in bits.
|
||||
* @returns FuriHalNfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
typedef FuriHalNfcError (*FuriHalNfcRx)(
|
||||
FuriHalSpiBusHandle* handle,
|
||||
uint8_t* rx_data,
|
||||
size_t rx_data_size,
|
||||
size_t* rx_bits);
|
||||
|
||||
/**
|
||||
* @brief Wait for an event using technology-specific method.
|
||||
*
|
||||
* @param[in] timeout_ms maximum time to wait, in milliseconds.
|
||||
* @return bitmask of occurred events.
|
||||
*/
|
||||
typedef FuriHalNfcEvent (*FuriHalNfcWaitEvent)(uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Go to sleep in listener mode.
|
||||
*
|
||||
* Puts the passive target logic into Sleep (Halt) state.
|
||||
*
|
||||
* @param[in,out] handle pointer to the NFC chip SPI handle.
|
||||
* @returns FuriHalNfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
typedef FuriHalNfcError (*FuriHalNfcSleep)(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/**
|
||||
* @brief Go to idle in listener mode.
|
||||
*
|
||||
* Puts the passive target logic into Sense (Idle) state.
|
||||
*
|
||||
* @param[in,out] handle pointer to the NFC chip SPI handle.
|
||||
* @returns FuriHalNfcErrorNone on success, any other error code on failure.
|
||||
*/
|
||||
typedef FuriHalNfcError (*FuriHalNfcIdle)(FuriHalSpiBusHandle* handle);
|
||||
|
||||
/**
|
||||
* @brief Technology-specific compenstaion values for pollers.
|
||||
*
|
||||
* Timing compensations are needed due to execution delays not accounted for
|
||||
* in standards, they are usually found out experimentally.
|
||||
*
|
||||
* The compensation value will be subtracted from the respective timer running
|
||||
* time, so positive values shorten timeouts, and negative ones make them longer.
|
||||
*/
|
||||
typedef struct {
|
||||
int32_t fdt; /**< Frame delay time compensation, in carrier cycles. */
|
||||
int32_t fwt; /**< Frame wait time compensaton, in carrier cycles. */
|
||||
} FuriHalNfcPollerCompensation;
|
||||
|
||||
/**
|
||||
* @brief Abstract technology-specific poller structure.
|
||||
*/
|
||||
typedef struct {
|
||||
FuriHalNfcPollerCompensation compensation; /**< Compensation values in poller mode. */
|
||||
FuriHalNfcChipConfig init; /**< Pointer to the init() function. */
|
||||
FuriHalNfcChipConfig deinit; /**< Pointer to the deinit() function. */
|
||||
FuriHalNfcWaitEvent wait_event; /**< Pointer to the wait_event() function. */
|
||||
FuriHalNfcTx tx; /**< Pointer to the tx() function. */
|
||||
FuriHalNfcRx rx; /**< Pointer to the rx() function. */
|
||||
} FuriHalNfcTechPollerBase;
|
||||
|
||||
/**
|
||||
* @brief Technology-specific compenstaion values for listeners.
|
||||
*
|
||||
* Same considerations apply as with FuriHalNfcPollerCompensation.
|
||||
*/
|
||||
typedef struct {
|
||||
int32_t fdt; /**< Frame delay time compensation, in carrier cycles. */
|
||||
} FuriHalNfcListenerCompensation;
|
||||
|
||||
/**
|
||||
* @brief Abstract technology-specific listener structure.
|
||||
*
|
||||
* If the listener operating mode is not supported for a particular
|
||||
* technology, fill this structure with zeroes.
|
||||
*/
|
||||
typedef struct {
|
||||
FuriHalNfcListenerCompensation compensation; /**< Compensation values in listener mode. */
|
||||
FuriHalNfcChipConfig init; /**< Pointer to the init() function. */
|
||||
FuriHalNfcChipConfig deinit; /**< Pointer to the deinit() function. */
|
||||
FuriHalNfcWaitEvent wait_event; /**< Pointer to the wait_event() function. */
|
||||
FuriHalNfcTx tx; /**< Pointer to the tx() function. */
|
||||
FuriHalNfcRx rx; /**< Pointer to the rx() function. */
|
||||
FuriHalNfcSleep sleep; /**< Pointer to the sleep() function. */
|
||||
FuriHalNfcIdle idle; /**< Pointer to the idle() function. */
|
||||
} FuriHalNfcTechListenerBase;
|
||||
|
||||
/**
|
||||
* @brief Abstract NFC technology definition structure.
|
||||
*
|
||||
* Each concrete technology implementation must fill this structure
|
||||
* with its proper functions and constants.
|
||||
*/
|
||||
typedef struct {
|
||||
FuriHalNfcTechPollerBase poller; /**< Structure containing the poller definition. */
|
||||
FuriHalNfcTechListenerBase listener; /**< Structure containing the listener definition. */
|
||||
} FuriHalNfcTechBase;
|
||||
|
||||
/** @brief Technology declaration for ISO14443 (Type A). */
|
||||
extern const FuriHalNfcTechBase furi_hal_nfc_iso14443a;
|
||||
/** @brief Technology declaration for ISO14443 (Type B). */
|
||||
extern const FuriHalNfcTechBase furi_hal_nfc_iso14443b;
|
||||
/** @brief Technology declaration for ISO15693. */
|
||||
extern const FuriHalNfcTechBase furi_hal_nfc_iso15693;
|
||||
/** @brief Technology declaration for FeliCa. */
|
||||
extern const FuriHalNfcTechBase furi_hal_nfc_felica;
|
||||
/* Declare new tehcnologies here. */
|
||||
|
||||
/**
|
||||
* @brief Array of pointers to every supported technology.
|
||||
*
|
||||
* This variable is defined in furi_hal_nfc.c. It will need to be modified
|
||||
* in case when a new technology is to be added.
|
||||
*/
|
||||
extern const FuriHalNfcTechBase* furi_hal_nfc_tech[];
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
229
targets/f7/furi_hal/furi_hal_nfc_timer.c
Normal file
229
targets/f7/furi_hal/furi_hal_nfc_timer.c
Normal file
@@ -0,0 +1,229 @@
|
||||
#include "furi_hal_nfc_i.h"
|
||||
#include "furi_hal_nfc_tech_i.h"
|
||||
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
|
||||
#include <furi_hal_interrupt.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_bus.h>
|
||||
|
||||
#define TAG "FuriHalNfcTimer"
|
||||
|
||||
#define FURI_HAL_NFC_TIMER_US_IN_S (1000000UL)
|
||||
|
||||
/**
|
||||
* To enable timer debug output on GPIO, define the FURI_HAL_NFC_TIMER_DEBUG macro
|
||||
* Example: ./fbt --extra-define=FURI_HAL_NFC_TIMER_DEBUG
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
FuriHalNfcTimerFwt,
|
||||
FuriHalNfcTimerBlockTx,
|
||||
FuriHalNfcTimerCount,
|
||||
} FuriHalNfcTimer;
|
||||
|
||||
typedef struct {
|
||||
TIM_TypeDef* timer;
|
||||
FuriHalBus bus;
|
||||
uint32_t prescaler;
|
||||
uint32_t freq_khz;
|
||||
FuriHalNfcEventInternalType event;
|
||||
FuriHalInterruptId irq_id;
|
||||
IRQn_Type irq_type;
|
||||
#ifdef FURI_HAL_NFC_TIMER_DEBUG
|
||||
const GpioPin* pin;
|
||||
#endif
|
||||
} FuriHalNfcTimerConfig;
|
||||
|
||||
static const FuriHalNfcTimerConfig furi_hal_nfc_timers[FuriHalNfcTimerCount] = {
|
||||
[FuriHalNfcTimerFwt] =
|
||||
{
|
||||
.timer = TIM1,
|
||||
.bus = FuriHalBusTIM1,
|
||||
.event = FuriHalNfcEventInternalTypeTimerFwtExpired,
|
||||
.irq_id = FuriHalInterruptIdTim1UpTim16,
|
||||
.irq_type = TIM1_UP_TIM16_IRQn,
|
||||
#ifdef FURI_HAL_NFC_TIMER_DEBUG
|
||||
.pin = &gpio_ext_pa7,
|
||||
#endif
|
||||
},
|
||||
[FuriHalNfcTimerBlockTx] =
|
||||
{
|
||||
.timer = TIM17,
|
||||
.bus = FuriHalBusTIM17,
|
||||
.event = FuriHalNfcEventInternalTypeTimerBlockTxExpired,
|
||||
.irq_id = FuriHalInterruptIdTim1TrgComTim17,
|
||||
.irq_type = TIM1_TRG_COM_TIM17_IRQn,
|
||||
#ifdef FURI_HAL_NFC_TIMER_DEBUG
|
||||
.pin = &gpio_ext_pa6,
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
static void furi_hal_nfc_timer_irq_callback(void* context) {
|
||||
// Returning removed const-ness
|
||||
const FuriHalNfcTimerConfig* config = context;
|
||||
if(LL_TIM_IsActiveFlag_UPDATE(config->timer)) {
|
||||
LL_TIM_ClearFlag_UPDATE(config->timer);
|
||||
furi_hal_nfc_event_set(config->event);
|
||||
#ifdef FURI_HAL_NFC_TIMER_DEBUG
|
||||
furi_hal_gpio_write(timer_config->pin, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_timer_init(FuriHalNfcTimer timer) {
|
||||
const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer];
|
||||
|
||||
furi_hal_bus_enable(config->bus);
|
||||
|
||||
LL_TIM_SetOnePulseMode(config->timer, LL_TIM_ONEPULSEMODE_SINGLE);
|
||||
LL_TIM_EnableUpdateEvent(config->timer);
|
||||
LL_TIM_SetCounterMode(config->timer, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_SetClockSource(config->timer, LL_TIM_CLOCKSOURCE_INTERNAL);
|
||||
|
||||
furi_hal_interrupt_set_isr(
|
||||
config->irq_id,
|
||||
furi_hal_nfc_timer_irq_callback,
|
||||
// Warning: casting const-ness away
|
||||
(FuriHalNfcTimerConfig*)config);
|
||||
NVIC_SetPriority(config->irq_type, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 5, 0));
|
||||
NVIC_EnableIRQ(config->irq_type);
|
||||
#ifdef FURI_HAL_NFC_TIMER_DEBUG
|
||||
furi_hal_gpio_init(config->pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
furi_hal_gpio_write(config->pin, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_timer_deinit(FuriHalNfcTimer timer) {
|
||||
const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer];
|
||||
|
||||
LL_TIM_ClearFlag_UPDATE(config->timer);
|
||||
furi_hal_interrupt_set_isr(config->irq_id, NULL, NULL);
|
||||
NVIC_DisableIRQ(config->irq_type);
|
||||
|
||||
if(furi_hal_bus_is_enabled(config->bus)) {
|
||||
furi_hal_bus_disable(config->bus);
|
||||
}
|
||||
#ifdef FURI_HAL_NFC_TIMER_DEBUG
|
||||
furi_hal_gpio_init_simple(config->pin, GpioModeAnalog);
|
||||
furi_hal_gpio_write(config->pin, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int32_t furi_hal_nfc_timer_get_compensation(FuriHalNfcTimer timer) {
|
||||
const FuriHalNfcTechBase* current_tech = furi_hal_nfc_tech[furi_hal_nfc.tech];
|
||||
|
||||
if(furi_hal_nfc.mode == FuriHalNfcModePoller) {
|
||||
const FuriHalNfcPollerCompensation* comp = ¤t_tech->poller.compensation;
|
||||
if(timer == FuriHalNfcTimerFwt)
|
||||
return comp->fwt;
|
||||
else if(timer == FuriHalNfcTimerBlockTx)
|
||||
return comp->fdt;
|
||||
|
||||
} else if(furi_hal_nfc.mode == FuriHalNfcModeListener) {
|
||||
const FuriHalNfcListenerCompensation* comp = ¤t_tech->listener.compensation;
|
||||
if(timer == FuriHalNfcTimerBlockTx) return comp->fdt;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool furi_hal_nfc_timer_is_running(FuriHalNfcTimer timer) {
|
||||
return LL_TIM_IsEnabledCounter(furi_hal_nfc_timers[timer].timer) != 0;
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_timer_start_core_ticks(FuriHalNfcTimer timer, uint64_t core_ticks) {
|
||||
furi_check(!furi_hal_nfc_timer_is_running(timer));
|
||||
|
||||
const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer];
|
||||
furi_check(furi_hal_bus_is_enabled(config->bus));
|
||||
|
||||
const uint32_t prescaler = (core_ticks - 1) / UINT16_MAX;
|
||||
furi_check(prescaler <= UINT16_MAX);
|
||||
|
||||
const uint32_t arr_reg = core_ticks / (prescaler + 1);
|
||||
furi_check(arr_reg <= UINT16_MAX);
|
||||
|
||||
LL_TIM_DisableIT_UPDATE(config->timer);
|
||||
|
||||
LL_TIM_SetPrescaler(config->timer, prescaler);
|
||||
LL_TIM_SetAutoReload(config->timer, arr_reg);
|
||||
|
||||
LL_TIM_GenerateEvent_UPDATE(config->timer);
|
||||
while(!LL_TIM_IsActiveFlag_UPDATE(config->timer))
|
||||
;
|
||||
LL_TIM_ClearFlag_UPDATE(config->timer);
|
||||
|
||||
LL_TIM_EnableIT_UPDATE(config->timer);
|
||||
LL_TIM_EnableCounter(config->timer);
|
||||
#ifdef FURI_HAL_NFC_TIMER_DEBUG
|
||||
furi_hal_gpio_write(config->pin, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_timer_start_us(FuriHalNfcTimer timer, uint32_t time_us) {
|
||||
furi_hal_nfc_timer_start_core_ticks(
|
||||
timer, SystemCoreClock / FURI_HAL_NFC_TIMER_US_IN_S * time_us);
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_timer_start_fc(FuriHalNfcTimer timer, uint32_t time_fc) {
|
||||
const int32_t comp_fc = furi_hal_nfc_timer_get_compensation(timer);
|
||||
// Not starting the timer if the compensation value is greater than the requested delay
|
||||
if(comp_fc >= (int32_t)time_fc) return;
|
||||
|
||||
furi_hal_nfc_timer_start_core_ticks(
|
||||
timer, ((uint64_t)SystemCoreClock * (time_fc - comp_fc)) / FURI_HAL_NFC_CARRIER_HZ);
|
||||
}
|
||||
|
||||
static void furi_hal_nfc_timer_stop(FuriHalNfcTimer timer) {
|
||||
const FuriHalNfcTimerConfig* config = &furi_hal_nfc_timers[timer];
|
||||
|
||||
LL_TIM_DisableIT_UPDATE(config->timer);
|
||||
LL_TIM_DisableCounter(config->timer);
|
||||
LL_TIM_SetCounter(config->timer, 0);
|
||||
LL_TIM_SetAutoReload(config->timer, 0);
|
||||
|
||||
if(LL_TIM_IsActiveFlag_UPDATE(config->timer)) {
|
||||
LL_TIM_ClearFlag_UPDATE(config->timer);
|
||||
}
|
||||
#ifdef FURI_HAL_NFC_TIMER_DEBUG
|
||||
furi_hal_gpio_write(config->pin, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void furi_hal_nfc_timers_init() {
|
||||
for(size_t i = 0; i < FuriHalNfcTimerCount; i++) {
|
||||
furi_hal_nfc_timer_init(i);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_nfc_timers_deinit() {
|
||||
for(size_t i = 0; i < FuriHalNfcTimerCount; i++) {
|
||||
furi_hal_nfc_timer_deinit(i);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_nfc_timer_fwt_start(uint32_t time_fc) {
|
||||
furi_hal_nfc_timer_start_fc(FuriHalNfcTimerFwt, time_fc);
|
||||
}
|
||||
|
||||
void furi_hal_nfc_timer_fwt_stop() {
|
||||
furi_hal_nfc_timer_stop(FuriHalNfcTimerFwt);
|
||||
}
|
||||
|
||||
void furi_hal_nfc_timer_block_tx_start(uint32_t time_fc) {
|
||||
furi_hal_nfc_timer_start_fc(FuriHalNfcTimerBlockTx, time_fc);
|
||||
}
|
||||
|
||||
void furi_hal_nfc_timer_block_tx_start_us(uint32_t time_us) {
|
||||
furi_hal_nfc_timer_start_us(FuriHalNfcTimerBlockTx, time_us);
|
||||
}
|
||||
|
||||
void furi_hal_nfc_timer_block_tx_stop() {
|
||||
furi_hal_nfc_timer_stop(FuriHalNfcTimerBlockTx);
|
||||
}
|
||||
|
||||
bool furi_hal_nfc_timer_block_tx_is_running() {
|
||||
return furi_hal_nfc_timer_is_running(FuriHalNfcTimerBlockTx);
|
||||
}
|
||||
212
targets/f7/furi_hal/furi_hal_os.c
Normal file
212
targets/f7/furi_hal/furi_hal_os.c
Normal file
@@ -0,0 +1,212 @@
|
||||
#include <furi_hal_os.h>
|
||||
#include <furi_hal_clock.h>
|
||||
#include <furi_hal_console.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_gpio.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_idle_timer.h>
|
||||
|
||||
#include <stm32wbxx_ll_cortex.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "FuriHalOs"
|
||||
|
||||
#define FURI_HAL_IDLE_TIMER_CLK_HZ 32768
|
||||
#define FURI_HAL_OS_TICK_HZ configTICK_RATE_HZ
|
||||
|
||||
#define FURI_HAL_OS_IDLE_CNT_TO_TICKS(x) (((x)*FURI_HAL_OS_TICK_HZ) / FURI_HAL_IDLE_TIMER_CLK_HZ)
|
||||
#define FURI_HAL_OS_TICKS_TO_IDLE_CNT(x) (((x)*FURI_HAL_IDLE_TIMER_CLK_HZ) / FURI_HAL_OS_TICK_HZ)
|
||||
|
||||
#define FURI_HAL_IDLE_TIMER_TICK_PER_EPOCH (FURI_HAL_OS_IDLE_CNT_TO_TICKS(FURI_HAL_IDLE_TIMER_MAX))
|
||||
#define FURI_HAL_OS_MAX_SLEEP (FURI_HAL_IDLE_TIMER_TICK_PER_EPOCH - 1)
|
||||
|
||||
#define FURI_HAL_OS_NVIC_IS_PENDING() (NVIC->ISPR[0] || NVIC->ISPR[1])
|
||||
#define FURI_HAL_OS_EXTI_LINE_0_31 0
|
||||
#define FURI_HAL_OS_EXTI_LINE_32_63 1
|
||||
|
||||
// Arbitrary (but small) number for better tick consistency
|
||||
#define FURI_HAL_OS_EXTRA_CNT 3
|
||||
|
||||
#ifndef FURI_HAL_OS_DEBUG_AWAKE_GPIO
|
||||
#define FURI_HAL_OS_DEBUG_AWAKE_GPIO (&gpio_ext_pa7)
|
||||
#endif
|
||||
|
||||
#ifndef FURI_HAL_OS_DEBUG_TICK_GPIO
|
||||
#define FURI_HAL_OS_DEBUG_TICK_GPIO (&gpio_ext_pa6)
|
||||
#endif
|
||||
|
||||
#ifndef FURI_HAL_OS_DEBUG_SECOND_GPIO
|
||||
#define FURI_HAL_OS_DEBUG_SECOND_GPIO (&gpio_ext_pa4)
|
||||
#endif
|
||||
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
#include <stm32wbxx_ll_gpio.h>
|
||||
|
||||
void furi_hal_os_timer_callback() {
|
||||
furi_hal_gpio_write(
|
||||
FURI_HAL_OS_DEBUG_SECOND_GPIO, !furi_hal_gpio_read(FURI_HAL_OS_DEBUG_SECOND_GPIO));
|
||||
}
|
||||
#endif
|
||||
|
||||
extern void xPortSysTickHandler();
|
||||
|
||||
static volatile uint32_t furi_hal_os_skew;
|
||||
|
||||
void furi_hal_os_init() {
|
||||
furi_hal_idle_timer_init();
|
||||
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
furi_hal_gpio_init_simple(FURI_HAL_OS_DEBUG_AWAKE_GPIO, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_init_simple(FURI_HAL_OS_DEBUG_TICK_GPIO, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_init_simple(FURI_HAL_OS_DEBUG_SECOND_GPIO, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(FURI_HAL_OS_DEBUG_AWAKE_GPIO, 1);
|
||||
|
||||
FuriTimer* second_timer =
|
||||
furi_timer_alloc(furi_hal_os_timer_callback, FuriTimerTypePeriodic, NULL);
|
||||
furi_timer_start(second_timer, FURI_HAL_OS_TICK_HZ);
|
||||
#endif
|
||||
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
void furi_hal_os_tick() {
|
||||
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
furi_hal_gpio_write(
|
||||
FURI_HAL_OS_DEBUG_TICK_GPIO, !furi_hal_gpio_read(FURI_HAL_OS_DEBUG_TICK_GPIO));
|
||||
#endif
|
||||
xPortSysTickHandler();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
// Find out the IRQ number while debugging
|
||||
static void furi_hal_os_nvic_dbg_trap() {
|
||||
for(int32_t i = WWDG_IRQn; i <= DMAMUX1_OVR_IRQn; i++) {
|
||||
if(NVIC_GetPendingIRQ(i)) {
|
||||
(void)i;
|
||||
// Break here
|
||||
__NOP();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find out the EXTI line number while debugging
|
||||
static void furi_hal_os_exti_dbg_trap(uint32_t exti, uint32_t val) {
|
||||
for(uint32_t i = 0; val; val >>= 1U, ++i) {
|
||||
if(val & 1U) {
|
||||
(void)exti;
|
||||
(void)i;
|
||||
// Break here
|
||||
__NOP();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline bool furi_hal_os_is_pending_irq() {
|
||||
if(FURI_HAL_OS_NVIC_IS_PENDING()) {
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
furi_hal_os_nvic_dbg_trap();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t exti_lines_active;
|
||||
if((exti_lines_active = LL_EXTI_ReadFlag_0_31(LL_EXTI_LINE_ALL_0_31))) {
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
furi_hal_os_exti_dbg_trap(FURI_HAL_OS_EXTI_LINE_0_31, exti_lines_active);
|
||||
#endif
|
||||
return true;
|
||||
} else if((exti_lines_active = LL_EXTI_ReadFlag_32_63(LL_EXTI_LINE_ALL_32_63))) {
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
furi_hal_os_exti_dbg_trap(FURI_HAL_OS_EXTI_LINE_32_63, exti_lines_active);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline uint32_t furi_hal_os_sleep(TickType_t expected_idle_ticks) {
|
||||
// Stop ticks
|
||||
furi_hal_clock_suspend_tick();
|
||||
|
||||
// Start wakeup timer
|
||||
furi_hal_idle_timer_start(FURI_HAL_OS_TICKS_TO_IDLE_CNT(expected_idle_ticks));
|
||||
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
furi_hal_gpio_write(FURI_HAL_OS_DEBUG_AWAKE_GPIO, 0);
|
||||
#endif
|
||||
|
||||
// Go to sleep mode
|
||||
furi_hal_power_sleep();
|
||||
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
furi_hal_gpio_write(FURI_HAL_OS_DEBUG_AWAKE_GPIO, 1);
|
||||
#endif
|
||||
|
||||
// Calculate how much time we spent in the sleep
|
||||
uint32_t after_cnt = furi_hal_idle_timer_get_cnt() + furi_hal_os_skew + FURI_HAL_OS_EXTRA_CNT;
|
||||
uint32_t after_tick = FURI_HAL_OS_IDLE_CNT_TO_TICKS(after_cnt);
|
||||
furi_hal_os_skew = after_cnt - FURI_HAL_OS_TICKS_TO_IDLE_CNT(after_tick);
|
||||
|
||||
bool cmpm = LL_LPTIM_IsActiveFlag_CMPM(FURI_HAL_IDLE_TIMER);
|
||||
bool arrm = LL_LPTIM_IsActiveFlag_ARRM(FURI_HAL_IDLE_TIMER);
|
||||
if(cmpm && arrm) after_tick += expected_idle_ticks;
|
||||
|
||||
// Prepare tick timer for new round
|
||||
furi_hal_idle_timer_reset();
|
||||
|
||||
// Resume ticks
|
||||
furi_hal_clock_resume_tick();
|
||||
return after_tick;
|
||||
}
|
||||
|
||||
void vPortSuppressTicksAndSleep(TickType_t expected_idle_ticks) {
|
||||
if(!furi_hal_power_sleep_available()) {
|
||||
__WFI();
|
||||
return;
|
||||
}
|
||||
|
||||
// Core2 shenanigans takes extra time, so we want to compensate tick skew by reducing sleep duration by 1 tick
|
||||
TickType_t unexpected_idle_ticks = expected_idle_ticks - 1;
|
||||
|
||||
// Limit amount of ticks to maximum that timer can count
|
||||
if(unexpected_idle_ticks > FURI_HAL_OS_MAX_SLEEP) {
|
||||
unexpected_idle_ticks = FURI_HAL_OS_MAX_SLEEP;
|
||||
}
|
||||
|
||||
// Stop IRQ handling, no one should disturb us till we finish
|
||||
__disable_irq();
|
||||
do {
|
||||
// Confirm OS that sleep is still possible
|
||||
if(eTaskConfirmSleepModeStatus() == eAbortSleep || furi_hal_os_is_pending_irq()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Sleep and track how much ticks we spent sleeping
|
||||
uint32_t completed_ticks = furi_hal_os_sleep(unexpected_idle_ticks);
|
||||
// Notify system about time spent in sleep
|
||||
if(completed_ticks > 0) {
|
||||
if(completed_ticks > expected_idle_ticks) {
|
||||
#ifdef FURI_HAL_OS_DEBUG
|
||||
furi_hal_console_printf(">%lu\r\n", completed_ticks - expected_idle_ticks);
|
||||
#endif
|
||||
completed_ticks = expected_idle_ticks;
|
||||
}
|
||||
vTaskStepTick(completed_ticks);
|
||||
}
|
||||
} while(0);
|
||||
// Reenable IRQ
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName) {
|
||||
UNUSED(xTask);
|
||||
furi_hal_console_puts("\r\n\r\n stack overflow in ");
|
||||
furi_hal_console_puts(pcTaskName);
|
||||
furi_hal_console_puts("\r\n\r\n");
|
||||
furi_crash("StackOverflow");
|
||||
}
|
||||
20
targets/f7/furi_hal/furi_hal_os.h
Normal file
20
targets/f7/furi_hal/furi_hal_os.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Initialize OS helpers
|
||||
* Configure and start tick timer
|
||||
*/
|
||||
void furi_hal_os_init();
|
||||
|
||||
/* Advance OS tick counter
|
||||
*/
|
||||
void furi_hal_os_tick();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
720
targets/f7/furi_hal/furi_hal_power.c
Normal file
720
targets/f7/furi_hal/furi_hal_power.c
Normal file
@@ -0,0 +1,720 @@
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_clock.h>
|
||||
#include <furi_hal_bt.h>
|
||||
#include <furi_hal_vibro.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_uart.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
#include <furi_hal_debug.h>
|
||||
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
#include <stm32wbxx_ll_pwr.h>
|
||||
#include <stm32wbxx_ll_hsem.h>
|
||||
#include <stm32wbxx_ll_cortex.h>
|
||||
#include <stm32wbxx_ll_gpio.h>
|
||||
|
||||
#include <hsem_map.h>
|
||||
#include <bq27220.h>
|
||||
#include <bq27220_data_memory.h>
|
||||
#include <bq25896.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "FuriHalPower"
|
||||
|
||||
#ifndef FURI_HAL_POWER_DEBUG_WFI_GPIO
|
||||
#define FURI_HAL_POWER_DEBUG_WFI_GPIO (&gpio_ext_pb2)
|
||||
#endif
|
||||
|
||||
#ifndef FURI_HAL_POWER_DEBUG_STOP_GPIO
|
||||
#define FURI_HAL_POWER_DEBUG_STOP_GPIO (&gpio_ext_pc3)
|
||||
#endif
|
||||
|
||||
#ifndef FURI_HAL_POWER_STOP_MODE
|
||||
#define FURI_HAL_POWER_STOP_MODE (LL_PWR_MODE_STOP2)
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
volatile uint8_t insomnia;
|
||||
volatile uint8_t suppress_charge;
|
||||
|
||||
bool gauge_ok;
|
||||
bool charger_ok;
|
||||
} FuriHalPower;
|
||||
|
||||
static volatile FuriHalPower furi_hal_power = {
|
||||
.insomnia = 0,
|
||||
.suppress_charge = 0,
|
||||
.gauge_ok = false,
|
||||
.charger_ok = false,
|
||||
};
|
||||
|
||||
extern const BQ27220DMData furi_hal_power_gauge_data_memory[];
|
||||
|
||||
void furi_hal_power_init() {
|
||||
#ifdef FURI_HAL_POWER_DEBUG
|
||||
furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_WFI_GPIO, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_init_simple(FURI_HAL_POWER_DEBUG_STOP_GPIO, GpioModeOutputPushPull);
|
||||
furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 0);
|
||||
furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 0);
|
||||
#endif
|
||||
|
||||
LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE1);
|
||||
LL_PWR_SMPS_SetMode(LL_PWR_SMPS_STEP_DOWN);
|
||||
|
||||
LL_PWR_SetPowerMode(FURI_HAL_POWER_STOP_MODE);
|
||||
LL_C2_PWR_SetPowerMode(FURI_HAL_POWER_STOP_MODE);
|
||||
|
||||
#if FURI_HAL_POWER_STOP_MODE == LL_PWR_MODE_STOP0
|
||||
LL_RCC_HSI_EnableInStopMode(); // Ensure that MR is capable of work in STOP0
|
||||
#endif
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
// Find and init gauge
|
||||
if(bq27220_init(&furi_hal_i2c_handle_power)) {
|
||||
furi_hal_power.gauge_ok = bq27220_apply_data_memory(
|
||||
&furi_hal_i2c_handle_power, furi_hal_power_gauge_data_memory);
|
||||
}
|
||||
// Find and init charger
|
||||
furi_hal_power.charger_ok = bq25896_init(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
|
||||
FURI_LOG_I(TAG, "Init OK");
|
||||
}
|
||||
|
||||
bool furi_hal_power_gauge_is_ok() {
|
||||
bool ret = true;
|
||||
|
||||
BatteryStatus battery_status;
|
||||
OperationStatus operation_status;
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
|
||||
if(!bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) ||
|
||||
!bq27220_get_operation_status(&furi_hal_i2c_handle_power, &operation_status)) {
|
||||
ret = false;
|
||||
} else {
|
||||
ret &= battery_status.BATTPRES;
|
||||
ret &= operation_status.INITCOMP;
|
||||
ret &= furi_hal_power.gauge_ok;
|
||||
}
|
||||
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool furi_hal_power_is_shutdown_requested() {
|
||||
bool ret = false;
|
||||
|
||||
BatteryStatus battery_status;
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
|
||||
if(bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) != BQ27220_ERROR) {
|
||||
ret = battery_status.SYSDWN;
|
||||
}
|
||||
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint16_t furi_hal_power_insomnia_level() {
|
||||
return furi_hal_power.insomnia;
|
||||
}
|
||||
|
||||
void furi_hal_power_insomnia_enter() {
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_assert(furi_hal_power.insomnia < UINT8_MAX);
|
||||
furi_hal_power.insomnia++;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_power_insomnia_exit() {
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_assert(furi_hal_power.insomnia > 0);
|
||||
furi_hal_power.insomnia--;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
bool furi_hal_power_sleep_available() {
|
||||
return furi_hal_power.insomnia == 0;
|
||||
}
|
||||
|
||||
static inline bool furi_hal_power_deep_sleep_available() {
|
||||
return furi_hal_bt_is_alive() && !furi_hal_rtc_is_flag_set(FuriHalRtcFlagLegacySleep) &&
|
||||
!furi_hal_debug_is_gdb_session_active();
|
||||
}
|
||||
|
||||
static inline void furi_hal_power_light_sleep() {
|
||||
__WFI();
|
||||
}
|
||||
|
||||
static inline void furi_hal_power_suspend_aux_periphs() {
|
||||
// Disable USART
|
||||
furi_hal_uart_suspend(FuriHalUartIdUSART1);
|
||||
furi_hal_uart_suspend(FuriHalUartIdLPUART1);
|
||||
}
|
||||
|
||||
static inline void furi_hal_power_resume_aux_periphs() {
|
||||
// Re-enable USART
|
||||
furi_hal_uart_resume(FuriHalUartIdUSART1);
|
||||
furi_hal_uart_resume(FuriHalUartIdLPUART1);
|
||||
}
|
||||
|
||||
static inline void furi_hal_power_deep_sleep() {
|
||||
furi_hal_power_suspend_aux_periphs();
|
||||
|
||||
if(!furi_hal_clock_switch_pll2hse()) {
|
||||
// Hello core2 my old friend
|
||||
return;
|
||||
}
|
||||
|
||||
while(LL_HSEM_1StepLock(HSEM, CFG_HW_RCC_SEMID))
|
||||
;
|
||||
|
||||
if(!LL_HSEM_1StepLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID)) {
|
||||
if(LL_PWR_IsActiveFlag_C2DS() || LL_PWR_IsActiveFlag_C2SB()) {
|
||||
// Release ENTRY_STOP_MODE semaphore
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID, 0);
|
||||
|
||||
// The switch on HSI before entering Stop Mode is required
|
||||
furi_hal_clock_switch_hse2hsi();
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* The switch on HSI before entering Stop Mode is required
|
||||
*/
|
||||
furi_hal_clock_switch_hse2hsi();
|
||||
}
|
||||
|
||||
/* Release RCC semaphore */
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_RCC_SEMID, 0);
|
||||
|
||||
// Prepare deep sleep
|
||||
LL_LPM_EnableDeepSleep();
|
||||
|
||||
#if defined(__CC_ARM)
|
||||
// Force store operations
|
||||
__force_stores();
|
||||
#endif
|
||||
|
||||
__WFI();
|
||||
|
||||
LL_LPM_EnableSleep();
|
||||
|
||||
/* Release ENTRY_STOP_MODE semaphore */
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID, 0);
|
||||
|
||||
while(LL_HSEM_1StepLock(HSEM, CFG_HW_RCC_SEMID))
|
||||
;
|
||||
|
||||
if(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSI) {
|
||||
furi_hal_clock_switch_hsi2hse();
|
||||
} else {
|
||||
// Ensure that we are already on HSE
|
||||
furi_check(LL_RCC_GetSysClkSource() == LL_RCC_SYS_CLKSOURCE_STATUS_HSE);
|
||||
}
|
||||
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_RCC_SEMID, 0);
|
||||
|
||||
furi_check(furi_hal_clock_switch_hse2pll());
|
||||
|
||||
furi_hal_power_resume_aux_periphs();
|
||||
furi_hal_rtc_sync_shadow();
|
||||
}
|
||||
|
||||
void furi_hal_power_sleep() {
|
||||
if(furi_hal_power_deep_sleep_available()) {
|
||||
#ifdef FURI_HAL_POWER_DEBUG
|
||||
furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 1);
|
||||
#endif
|
||||
furi_hal_power_deep_sleep();
|
||||
#ifdef FURI_HAL_POWER_DEBUG
|
||||
furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_STOP_GPIO, 0);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef FURI_HAL_POWER_DEBUG
|
||||
furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 1);
|
||||
#endif
|
||||
furi_hal_power_light_sleep();
|
||||
#ifdef FURI_HAL_POWER_DEBUG
|
||||
furi_hal_gpio_write(FURI_HAL_POWER_DEBUG_WFI_GPIO, 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t furi_hal_power_get_pct() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
uint8_t ret = bq27220_get_state_of_charge(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t furi_hal_power_get_bat_health_pct() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
uint8_t ret = bq27220_get_state_of_health(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool furi_hal_power_is_charging() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bool ret = bq25896_is_charging(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool furi_hal_power_is_charging_done() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bool ret = bq25896_is_charging_done(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_hal_power_shutdown() {
|
||||
furi_hal_power_insomnia_enter();
|
||||
|
||||
furi_hal_bt_reinit();
|
||||
|
||||
while(LL_HSEM_1StepLock(HSEM, CFG_HW_RCC_SEMID))
|
||||
;
|
||||
|
||||
if(!LL_HSEM_1StepLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID)) {
|
||||
if(LL_PWR_IsActiveFlag_C2DS() || LL_PWR_IsActiveFlag_C2SB()) {
|
||||
// Release ENTRY_STOP_MODE semaphore
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_ENTRY_STOP_MODE_SEMID, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare Wakeup pin
|
||||
LL_PWR_SetWakeUpPinPolarityLow(LL_PWR_WAKEUP_PIN2);
|
||||
LL_PWR_EnableWakeUpPin(LL_PWR_WAKEUP_PIN2);
|
||||
LL_C2_PWR_EnableWakeUpPin(LL_PWR_WAKEUP_PIN2);
|
||||
|
||||
/* Release RCC semaphore */
|
||||
LL_HSEM_ReleaseLock(HSEM, CFG_HW_RCC_SEMID, 0);
|
||||
|
||||
LL_PWR_DisableBootC2();
|
||||
LL_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
|
||||
LL_C2_PWR_SetPowerMode(LL_PWR_MODE_SHUTDOWN);
|
||||
LL_LPM_EnableDeepSleep();
|
||||
|
||||
__WFI();
|
||||
furi_crash("Insomniac core2");
|
||||
}
|
||||
|
||||
void furi_hal_power_off() {
|
||||
// Crutch: shutting down with ext 3V3 off is causing LSE to stop
|
||||
furi_hal_power_enable_external_3_3v();
|
||||
furi_hal_vibro_on(true);
|
||||
furi_delay_us(50000);
|
||||
// Send poweroff to charger
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bq25896_poweroff(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
furi_hal_vibro_on(false);
|
||||
}
|
||||
|
||||
void furi_hal_power_reset() {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool furi_hal_power_enable_otg() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bq25896_set_boost_lim(&furi_hal_i2c_handle_power, BoostLim_2150);
|
||||
bq25896_enable_otg(&furi_hal_i2c_handle_power);
|
||||
furi_delay_ms(30);
|
||||
bool ret = bq25896_is_otg_enabled(&furi_hal_i2c_handle_power);
|
||||
bq25896_set_boost_lim(&furi_hal_i2c_handle_power, BoostLim_1400);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_hal_power_disable_otg() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bq25896_disable_otg(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
bool furi_hal_power_is_otg_enabled() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bool ret = bq25896_is_otg_enabled(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
float furi_hal_power_get_battery_charge_voltage_limit() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
float ret = (float)bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power) / 1000.0f;
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_hal_power_set_battery_charge_voltage_limit(float voltage) {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
// Adding 0.0005 is necessary because 4.016f is 4.015999794000, which gets truncated
|
||||
bq25896_set_vreg_voltage(&furi_hal_i2c_handle_power, (uint16_t)(voltage * 1000.0f + 0.0005f));
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
bool furi_hal_power_check_otg_fault() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bool ret = bq25896_check_otg_fault(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_hal_power_check_otg_status() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
if(bq25896_check_otg_fault(&furi_hal_i2c_handle_power))
|
||||
bq25896_disable_otg(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
|
||||
uint32_t furi_hal_power_get_battery_remaining_capacity() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
uint32_t ret = bq27220_get_remaining_capacity(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t furi_hal_power_get_battery_full_capacity() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
uint32_t ret = bq27220_get_full_charge_capacity(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t furi_hal_power_get_battery_design_capacity() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
uint32_t ret = bq27220_get_design_capacity(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
float furi_hal_power_get_battery_voltage(FuriHalPowerIC ic) {
|
||||
float ret = 0.0f;
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
if(ic == FuriHalPowerICCharger) {
|
||||
ret = (float)bq25896_get_vbat_voltage(&furi_hal_i2c_handle_power) / 1000.0f;
|
||||
} else if(ic == FuriHalPowerICFuelGauge) {
|
||||
ret = (float)bq27220_get_voltage(&furi_hal_i2c_handle_power) / 1000.0f;
|
||||
}
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
float furi_hal_power_get_battery_current(FuriHalPowerIC ic) {
|
||||
float ret = 0.0f;
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
if(ic == FuriHalPowerICCharger) {
|
||||
ret = (float)bq25896_get_vbat_current(&furi_hal_i2c_handle_power) / 1000.0f;
|
||||
} else if(ic == FuriHalPowerICFuelGauge) {
|
||||
ret = (float)bq27220_get_current(&furi_hal_i2c_handle_power) / 1000.0f;
|
||||
}
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static float furi_hal_power_get_battery_temperature_internal(FuriHalPowerIC ic) {
|
||||
float ret = 0.0f;
|
||||
|
||||
if(ic == FuriHalPowerICCharger) {
|
||||
// Linear approximation, +/- 5 C
|
||||
ret = (71.0f - (float)bq25896_get_ntc_mpct(&furi_hal_i2c_handle_power) / 1000) / 0.6f;
|
||||
} else if(ic == FuriHalPowerICFuelGauge) {
|
||||
ret = ((float)bq27220_get_temperature(&furi_hal_i2c_handle_power) - 2731.0f) / 10.0f;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
float furi_hal_power_get_battery_temperature(FuriHalPowerIC ic) {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
float ret = furi_hal_power_get_battery_temperature_internal(ic);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
float furi_hal_power_get_usb_voltage() {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
float ret = (float)bq25896_get_vbus_voltage(&furi_hal_i2c_handle_power) / 1000.0f;
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_hal_power_enable_external_3_3v() {
|
||||
furi_hal_gpio_write(&gpio_periph_power, 1);
|
||||
}
|
||||
|
||||
void furi_hal_power_disable_external_3_3v() {
|
||||
furi_hal_gpio_write(&gpio_periph_power, 0);
|
||||
}
|
||||
|
||||
void furi_hal_power_suppress_charge_enter() {
|
||||
vTaskSuspendAll();
|
||||
bool disable_charging = furi_hal_power.suppress_charge == 0;
|
||||
furi_hal_power.suppress_charge++;
|
||||
xTaskResumeAll();
|
||||
|
||||
if(disable_charging) {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bq25896_disable_charging(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_power_suppress_charge_exit() {
|
||||
vTaskSuspendAll();
|
||||
furi_hal_power.suppress_charge--;
|
||||
bool enable_charging = furi_hal_power.suppress_charge == 0;
|
||||
xTaskResumeAll();
|
||||
|
||||
if(enable_charging) {
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
bq25896_enable_charging(&furi_hal_i2c_handle_power);
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_power_info_get(PropertyValueCallback out, char sep, void* context) {
|
||||
furi_assert(out);
|
||||
|
||||
FuriString* value = furi_string_alloc();
|
||||
FuriString* key = furi_string_alloc();
|
||||
|
||||
PropertyValueContext property_context = {
|
||||
.key = key, .value = value, .out = out, .sep = sep, .last = false, .context = context};
|
||||
|
||||
if(sep == '.') {
|
||||
property_value_out(&property_context, NULL, 2, "format", "major", "2");
|
||||
property_value_out(&property_context, NULL, 2, "format", "minor", "1");
|
||||
} else {
|
||||
property_value_out(&property_context, NULL, 3, "power", "info", "major", "2");
|
||||
property_value_out(&property_context, NULL, 3, "power", "info", "minor", "1");
|
||||
}
|
||||
|
||||
uint8_t charge = furi_hal_power_get_pct();
|
||||
property_value_out(&property_context, "%u", 2, "charge", "level", charge);
|
||||
|
||||
const char* charge_state;
|
||||
if(furi_hal_power_is_charging()) {
|
||||
if((charge < 100) && (!furi_hal_power_is_charging_done())) {
|
||||
charge_state = "charging";
|
||||
} else {
|
||||
charge_state = "charged";
|
||||
}
|
||||
} else {
|
||||
charge_state = "discharging";
|
||||
}
|
||||
|
||||
property_value_out(&property_context, NULL, 2, "charge", "state", charge_state);
|
||||
uint16_t charge_voltage_limit =
|
||||
(uint16_t)(furi_hal_power_get_battery_charge_voltage_limit() * 1000.f);
|
||||
property_value_out(
|
||||
&property_context, "%u", 3, "charge", "voltage", "limit", charge_voltage_limit);
|
||||
uint16_t voltage =
|
||||
(uint16_t)(furi_hal_power_get_battery_voltage(FuriHalPowerICFuelGauge) * 1000.f);
|
||||
property_value_out(&property_context, "%u", 2, "battery", "voltage", voltage);
|
||||
int16_t current =
|
||||
(int16_t)(furi_hal_power_get_battery_current(FuriHalPowerICFuelGauge) * 1000.f);
|
||||
property_value_out(&property_context, "%d", 2, "battery", "current", current);
|
||||
int16_t temperature = (int16_t)furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge);
|
||||
property_value_out(&property_context, "%d", 2, "battery", "temp", temperature);
|
||||
property_value_out(
|
||||
&property_context, "%u", 2, "battery", "health", furi_hal_power_get_bat_health_pct());
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%lu",
|
||||
2,
|
||||
"capacity",
|
||||
"remain",
|
||||
furi_hal_power_get_battery_remaining_capacity());
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%lu",
|
||||
2,
|
||||
"capacity",
|
||||
"full",
|
||||
furi_hal_power_get_battery_full_capacity());
|
||||
property_context.last = true;
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%lu",
|
||||
2,
|
||||
"capacity",
|
||||
"design",
|
||||
furi_hal_power_get_battery_design_capacity());
|
||||
|
||||
furi_string_free(key);
|
||||
furi_string_free(value);
|
||||
}
|
||||
|
||||
void furi_hal_power_debug_get(PropertyValueCallback out, void* context) {
|
||||
furi_assert(out);
|
||||
|
||||
FuriString* value = furi_string_alloc();
|
||||
FuriString* key = furi_string_alloc();
|
||||
|
||||
PropertyValueContext property_context = {
|
||||
.key = key, .value = value, .out = out, .sep = '.', .last = false, .context = context};
|
||||
|
||||
BatteryStatus battery_status;
|
||||
OperationStatus operation_status;
|
||||
|
||||
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
|
||||
|
||||
// Power Debug version
|
||||
property_value_out(&property_context, NULL, 2, "format", "major", "1");
|
||||
property_value_out(&property_context, NULL, 2, "format", "minor", "0");
|
||||
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
2,
|
||||
"charger",
|
||||
"vbus",
|
||||
bq25896_get_vbus_voltage(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
2,
|
||||
"charger",
|
||||
"vsys",
|
||||
bq25896_get_vsys_voltage(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
2,
|
||||
"charger",
|
||||
"vbat",
|
||||
bq25896_get_vbat_voltage(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
2,
|
||||
"charger",
|
||||
"vreg",
|
||||
bq25896_get_vreg_voltage(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
2,
|
||||
"charger",
|
||||
"current",
|
||||
bq25896_get_vbat_current(&furi_hal_i2c_handle_power));
|
||||
|
||||
const uint32_t ntc_mpct = bq25896_get_ntc_mpct(&furi_hal_i2c_handle_power);
|
||||
|
||||
if(bq27220_get_battery_status(&furi_hal_i2c_handle_power, &battery_status) &&
|
||||
bq27220_get_operation_status(&furi_hal_i2c_handle_power, &operation_status)) {
|
||||
property_value_out(&property_context, "%lu", 2, "charger", "ntc", ntc_mpct);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "calmd", operation_status.CALMD);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "sec", operation_status.SEC);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "edv2", operation_status.EDV2);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "vdq", operation_status.VDQ);
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "gauge", "initcomp", operation_status.INITCOMP);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "smth", operation_status.SMTH);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "btpint", operation_status.BTPINT);
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "gauge", "cfgupdate", operation_status.CFGUPDATE);
|
||||
|
||||
// Battery status register, part 1
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "chginh", battery_status.CHGINH);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "fc", battery_status.FC);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "otd", battery_status.OTD);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "otc", battery_status.OTC);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "sleep", battery_status.SLEEP);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "ocvfail", battery_status.OCVFAIL);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "ocvcomp", battery_status.OCVCOMP);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "fd", battery_status.FD);
|
||||
|
||||
// Battery status register, part 2
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "dsg", battery_status.DSG);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "sysdwn", battery_status.SYSDWN);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "tda", battery_status.TDA);
|
||||
property_value_out(
|
||||
&property_context, "%d", 2, "gauge", "battpres", battery_status.BATTPRES);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "authgd", battery_status.AUTH_GD);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "ocvgd", battery_status.OCVGD);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "tca", battery_status.TCA);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "rsvd", battery_status.RSVD);
|
||||
|
||||
// Voltage and current info
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
3,
|
||||
"gauge",
|
||||
"capacity",
|
||||
"full",
|
||||
bq27220_get_full_charge_capacity(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
3,
|
||||
"gauge",
|
||||
"capacity",
|
||||
"design",
|
||||
bq27220_get_design_capacity(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
3,
|
||||
"gauge",
|
||||
"capacity",
|
||||
"remain",
|
||||
bq27220_get_remaining_capacity(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
3,
|
||||
"gauge",
|
||||
"state",
|
||||
"charge",
|
||||
bq27220_get_state_of_charge(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
3,
|
||||
"gauge",
|
||||
"state",
|
||||
"health",
|
||||
bq27220_get_state_of_health(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
2,
|
||||
"gauge",
|
||||
"voltage",
|
||||
bq27220_get_voltage(&furi_hal_i2c_handle_power));
|
||||
property_value_out(
|
||||
&property_context,
|
||||
"%d",
|
||||
2,
|
||||
"gauge",
|
||||
"current",
|
||||
bq27220_get_current(&furi_hal_i2c_handle_power));
|
||||
|
||||
property_context.last = true;
|
||||
const int battery_temp =
|
||||
(int)furi_hal_power_get_battery_temperature_internal(FuriHalPowerICFuelGauge);
|
||||
property_value_out(&property_context, "%d", 2, "gauge", "temperature", battery_temp);
|
||||
} else {
|
||||
property_context.last = true;
|
||||
property_value_out(&property_context, "%lu", 2, "charger", "ntc", ntc_mpct);
|
||||
}
|
||||
|
||||
furi_string_free(key);
|
||||
furi_string_free(value);
|
||||
|
||||
furi_hal_i2c_release(&furi_hal_i2c_handle_power);
|
||||
}
|
||||
149
targets/f7/furi_hal/furi_hal_power_config.c
Normal file
149
targets/f7/furi_hal/furi_hal_power_config.c
Normal file
@@ -0,0 +1,149 @@
|
||||
#include <bq27220_data_memory.h>
|
||||
|
||||
const BQ27220DMGaugingConfig furi_hal_power_gauge_data_memory_gauging_config = {
|
||||
.CCT = 1,
|
||||
.CSYNC = 0,
|
||||
.EDV_CMP = 0,
|
||||
.SC = 1,
|
||||
.FIXED_EDV0 = 1,
|
||||
.FCC_LIM = 1,
|
||||
.FC_FOR_VDQ = 1,
|
||||
.IGNORE_SD = 1,
|
||||
.SME0 = 0,
|
||||
};
|
||||
|
||||
const BQ27220DMData furi_hal_power_gauge_data_memory[] = {
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1GaugingConfig,
|
||||
.type = BQ27220DMTypePtr16,
|
||||
.value.u32 = (uint32_t)&furi_hal_power_gauge_data_memory_gauging_config,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1FullChargeCapacity,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 2100,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1DesignCapacity,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 2100,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1EMF,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3679,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1C0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 430,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1R0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 334,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1T0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 4626,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1R1,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 408,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1TC,
|
||||
.type = BQ27220DMTypeU8,
|
||||
.value.u8 = 11,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1C1,
|
||||
.type = BQ27220DMTypeU8,
|
||||
.value.u8 = 0,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 4044,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD10,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3905,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD20,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3807,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD30,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3718,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD40,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3642,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD50,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3585,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD60,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3546,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD70,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3514,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD80,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3477,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD90,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3411,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1StartDOD100,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3299,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1EDV0,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3300,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1EDV1,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3321,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressGasGaugingCEDVProfile1EDV2,
|
||||
.type = BQ27220DMTypeU16,
|
||||
.value.u16 = 3355,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressCalibrationCurrentDeadband,
|
||||
.type = BQ27220DMTypeU8,
|
||||
.value.u8 = 1,
|
||||
},
|
||||
{
|
||||
.address = BQ27220DMAddressConfigurationPowerSleepCurrent,
|
||||
.type = BQ27220DMTypeI16,
|
||||
.value.i16 = 1,
|
||||
},
|
||||
{
|
||||
.type = BQ27220DMTypeEnd,
|
||||
},
|
||||
};
|
||||
138
targets/f7/furi_hal/furi_hal_pwm.c
Normal file
138
targets/f7/furi_hal/furi_hal_pwm.c
Normal file
@@ -0,0 +1,138 @@
|
||||
#include <furi_hal_pwm.h>
|
||||
#include <furi_hal_resources.h>
|
||||
#include <furi_hal_bus.h>
|
||||
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
#include <stm32wbxx_ll_lptim.h>
|
||||
#include <stm32wbxx_ll_rcc.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
const uint32_t lptim_psc_table[] = {
|
||||
LL_LPTIM_PRESCALER_DIV1,
|
||||
LL_LPTIM_PRESCALER_DIV2,
|
||||
LL_LPTIM_PRESCALER_DIV4,
|
||||
LL_LPTIM_PRESCALER_DIV8,
|
||||
LL_LPTIM_PRESCALER_DIV16,
|
||||
LL_LPTIM_PRESCALER_DIV32,
|
||||
LL_LPTIM_PRESCALER_DIV64,
|
||||
LL_LPTIM_PRESCALER_DIV128,
|
||||
};
|
||||
|
||||
void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) {
|
||||
if(channel == FuriHalPwmOutputIdTim1PA7) {
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_ext_pa7,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn1TIM1);
|
||||
|
||||
furi_hal_bus_enable(FuriHalBusTIM1);
|
||||
|
||||
LL_TIM_SetCounterMode(TIM1, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_SetRepetitionCounter(TIM1, 0);
|
||||
LL_TIM_SetClockDivision(TIM1, LL_TIM_CLOCKDIVISION_DIV1);
|
||||
LL_TIM_SetClockSource(TIM1, LL_TIM_CLOCKSOURCE_INTERNAL);
|
||||
LL_TIM_EnableARRPreload(TIM1);
|
||||
|
||||
LL_TIM_OC_EnablePreload(TIM1, LL_TIM_CHANNEL_CH1);
|
||||
LL_TIM_OC_SetMode(TIM1, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_PWM1);
|
||||
LL_TIM_OC_SetPolarity(TIM1, LL_TIM_CHANNEL_CH1N, LL_TIM_OCPOLARITY_HIGH);
|
||||
LL_TIM_OC_DisableFast(TIM1, LL_TIM_CHANNEL_CH1);
|
||||
LL_TIM_CC_EnableChannel(TIM1, LL_TIM_CHANNEL_CH1N);
|
||||
|
||||
LL_TIM_EnableAllOutputs(TIM1);
|
||||
|
||||
furi_hal_pwm_set_params(channel, freq, duty);
|
||||
|
||||
LL_TIM_EnableCounter(TIM1);
|
||||
} else if(channel == FuriHalPwmOutputIdLptim2PA4) {
|
||||
furi_hal_gpio_init_ex(
|
||||
&gpio_ext_pa4,
|
||||
GpioModeAltFunctionPushPull,
|
||||
GpioPullNo,
|
||||
GpioSpeedVeryHigh,
|
||||
GpioAltFn14LPTIM2);
|
||||
|
||||
furi_hal_bus_enable(FuriHalBusLPTIM2);
|
||||
|
||||
LL_LPTIM_SetUpdateMode(LPTIM2, LL_LPTIM_UPDATE_MODE_ENDOFPERIOD);
|
||||
LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_PCLK1);
|
||||
LL_LPTIM_SetClockSource(LPTIM2, LL_LPTIM_CLK_SOURCE_INTERNAL);
|
||||
LL_LPTIM_ConfigOutput(
|
||||
LPTIM2, LL_LPTIM_OUTPUT_WAVEFORM_PWM, LL_LPTIM_OUTPUT_POLARITY_INVERSE);
|
||||
LL_LPTIM_SetCounterMode(LPTIM2, LL_LPTIM_COUNTER_MODE_INTERNAL);
|
||||
|
||||
LL_LPTIM_Enable(LPTIM2);
|
||||
|
||||
furi_hal_pwm_set_params(channel, freq, duty);
|
||||
|
||||
LL_LPTIM_StartCounter(LPTIM2, LL_LPTIM_OPERATING_MODE_CONTINUOUS);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_hal_pwm_stop(FuriHalPwmOutputId channel) {
|
||||
if(channel == FuriHalPwmOutputIdTim1PA7) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pa7, GpioModeAnalog);
|
||||
furi_hal_bus_disable(FuriHalBusTIM1);
|
||||
} else if(channel == FuriHalPwmOutputIdLptim2PA4) {
|
||||
furi_hal_gpio_init_simple(&gpio_ext_pa4, GpioModeAnalog);
|
||||
furi_hal_bus_disable(FuriHalBusLPTIM2);
|
||||
}
|
||||
}
|
||||
|
||||
bool furi_hal_pwm_is_running(FuriHalPwmOutputId channel) {
|
||||
if(channel == FuriHalPwmOutputIdTim1PA7) {
|
||||
return furi_hal_bus_is_enabled(FuriHalBusTIM1);
|
||||
} else if(channel == FuriHalPwmOutputIdLptim2PA4) {
|
||||
return furi_hal_bus_is_enabled(FuriHalBusLPTIM2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty) {
|
||||
furi_assert(freq > 0);
|
||||
uint32_t freq_div = 64000000LU / freq;
|
||||
|
||||
if(channel == FuriHalPwmOutputIdTim1PA7) {
|
||||
uint32_t prescaler = freq_div / 0x10000LU;
|
||||
uint32_t period = freq_div / (prescaler + 1);
|
||||
uint32_t compare = period * duty / 100;
|
||||
|
||||
LL_TIM_SetPrescaler(TIM1, prescaler);
|
||||
LL_TIM_SetAutoReload(TIM1, period - 1);
|
||||
LL_TIM_OC_SetCompareCH1(TIM1, compare);
|
||||
} else if(channel == FuriHalPwmOutputIdLptim2PA4) {
|
||||
uint32_t prescaler = 0;
|
||||
uint32_t period = 0;
|
||||
|
||||
bool clock_lse = false;
|
||||
|
||||
do {
|
||||
period = freq_div / (1UL << prescaler);
|
||||
if(period <= 0xFFFF) {
|
||||
break;
|
||||
}
|
||||
prescaler++;
|
||||
if(prescaler > 7) {
|
||||
prescaler = 0;
|
||||
clock_lse = true;
|
||||
period = 32768LU / freq;
|
||||
break;
|
||||
}
|
||||
} while(1);
|
||||
|
||||
uint32_t compare = period * duty / 100;
|
||||
|
||||
LL_LPTIM_SetPrescaler(LPTIM2, lptim_psc_table[prescaler]);
|
||||
LL_LPTIM_SetAutoReload(LPTIM2, period);
|
||||
LL_LPTIM_SetCompare(LPTIM2, compare);
|
||||
|
||||
if(clock_lse) {
|
||||
LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_LSE);
|
||||
} else {
|
||||
LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM2_CLKSOURCE_PCLK1);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
targets/f7/furi_hal/furi_hal_pwm.h
Normal file
50
targets/f7/furi_hal/furi_hal_pwm.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file furi_hal_pwm.h
|
||||
* PWM contol HAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
FuriHalPwmOutputIdTim1PA7,
|
||||
FuriHalPwmOutputIdLptim2PA4,
|
||||
} FuriHalPwmOutputId;
|
||||
|
||||
/** Enable PWM channel and set parameters
|
||||
*
|
||||
* @param[in] channel PWM channel (FuriHalPwmOutputId)
|
||||
* @param[in] freq Frequency in Hz
|
||||
* @param[in] duty Duty cycle value in %
|
||||
*/
|
||||
void furi_hal_pwm_start(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty);
|
||||
|
||||
/** Disable PWM channel
|
||||
*
|
||||
* @param[in] channel PWM channel (FuriHalPwmOutputId)
|
||||
*/
|
||||
void furi_hal_pwm_stop(FuriHalPwmOutputId channel);
|
||||
|
||||
/** Set PWM channel parameters
|
||||
*
|
||||
* @param[in] channel PWM channel (FuriHalPwmOutputId)
|
||||
* @param[in] freq Frequency in Hz
|
||||
* @param[in] duty Duty cycle value in %
|
||||
*/
|
||||
void furi_hal_pwm_set_params(FuriHalPwmOutputId channel, uint32_t freq, uint8_t duty);
|
||||
|
||||
/** Is PWM channel running?
|
||||
*
|
||||
* @param[in] channel PWM channel (FuriHalPwmOutputId)
|
||||
* @return bool - true if running
|
||||
*/
|
||||
bool furi_hal_pwm_is_running(FuriHalPwmOutputId channel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user