Merge branch 'fz-dev' into dev

This commit is contained in:
MX 2022-11-30 15:27:55 +03:00
commit a66f94d22e
No known key found for this signature in database
GPG Key ID: 6C4C311DFD4B4AB5
5 changed files with 204 additions and 44 deletions

View File

@ -209,6 +209,149 @@ MU_TEST(subghz_keystore_test) {
"Test keystore error"); "Test keystore error");
} }
typedef enum {
SubGhzHalAsyncTxTestTypeNormal,
SubGhzHalAsyncTxTestTypeInvalidStart,
SubGhzHalAsyncTxTestTypeInvalidMid,
SubGhzHalAsyncTxTestTypeInvalidEnd,
SubGhzHalAsyncTxTestTypeResetStart,
SubGhzHalAsyncTxTestTypeResetMid,
SubGhzHalAsyncTxTestTypeResetEnd,
} SubGhzHalAsyncTxTestType;
typedef struct {
SubGhzHalAsyncTxTestType type;
size_t pos;
} SubGhzHalAsyncTxTest;
#define SUBGHZ_HAL_TEST_DURATION 1
static LevelDuration subghz_hal_async_tx_test_yield(void* context) {
SubGhzHalAsyncTxTest* test = context;
bool is_odd = test->pos % 2;
if(test->type == SubGhzHalAsyncTxTestTypeNormal) {
if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeInvalidStart) {
if(test->pos == 0) {
test->pos++;
return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeInvalidMid) {
if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
test->pos++;
return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeInvalidEnd) {
if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
test->pos++;
return level_duration_make(!is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * 8) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeResetStart) {
if(test->pos == 0) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeResetMid) {
if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF / 2) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else if(test->type == SubGhzHalAsyncTxTestTypeResetEnd) {
if(test->pos < API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
test->pos++;
return level_duration_make(is_odd, SUBGHZ_HAL_TEST_DURATION);
} else if(test->pos == API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL - 1) {
test->pos++;
return level_duration_reset();
} else {
furi_crash("Yield after reset");
}
} else {
furi_crash("Programming error");
}
}
bool subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestType type) {
SubGhzHalAsyncTxTest test = {0};
test.type = type;
furi_hal_subghz_reset();
furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async);
furi_hal_subghz_set_frequency_and_path(433920000);
furi_hal_subghz_start_async_tx(subghz_hal_async_tx_test_yield, &test);
while(!furi_hal_subghz_is_async_tx_complete()) {
furi_delay_ms(10);
}
furi_hal_subghz_stop_async_tx();
furi_hal_subghz_sleep();
return true;
}
MU_TEST(subghz_hal_async_tx_test) {
mu_assert(
subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeNormal),
"Test furi_hal_async_tx normal");
mu_assert(
subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeInvalidStart),
"Test furi_hal_async_tx invalid start");
mu_assert(
subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeInvalidMid),
"Test furi_hal_async_tx invalid mid");
mu_assert(
subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeInvalidEnd),
"Test furi_hal_async_tx invalid end");
mu_assert(
subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeResetStart),
"Test furi_hal_async_tx reset start");
mu_assert(
subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeResetMid),
"Test furi_hal_async_tx reset mid");
mu_assert(
subghz_hal_async_tx_test_run(SubGhzHalAsyncTxTestTypeResetEnd),
"Test furi_hal_async_tx reset end");
}
//test decoders //test decoders
MU_TEST(subghz_decoder_came_atomo_test) { MU_TEST(subghz_decoder_came_atomo_test) {
mu_assert( mu_assert(
@ -579,6 +722,8 @@ MU_TEST_SUITE(subghz) {
subghz_test_init(); subghz_test_init();
MU_RUN_TEST(subghz_keystore_test); MU_RUN_TEST(subghz_keystore_test);
MU_RUN_TEST(subghz_hal_async_tx_test);
MU_RUN_TEST(subghz_decoder_came_atomo_test); MU_RUN_TEST(subghz_decoder_came_atomo_test);
MU_RUN_TEST(subghz_decoder_came_test); MU_RUN_TEST(subghz_decoder_came_test);
MU_RUN_TEST(subghz_decoder_came_twee_test); MU_RUN_TEST(subghz_decoder_came_twee_test);

View File

@ -1,6 +1,7 @@
#include <furi_hal_power.h> #include <furi_hal_power.h>
#include <furi_hal_clock.h> #include <furi_hal_clock.h>
#include <furi_hal_bt.h> #include <furi_hal_bt.h>
#include <furi_hal_vibro.h>
#include <furi_hal_resources.h> #include <furi_hal_resources.h>
#include <furi_hal_uart.h> #include <furi_hal_uart.h>
@ -308,11 +309,13 @@ void furi_hal_power_shutdown() {
void furi_hal_power_off() { void furi_hal_power_off() {
// Crutch: shutting down with ext 3V3 off is causing LSE to stop // Crutch: shutting down with ext 3V3 off is causing LSE to stop
furi_hal_power_enable_external_3_3v(); furi_hal_power_enable_external_3_3v();
furi_delay_us(1000); furi_hal_vibro_on(true);
furi_delay_us(50000);
// Send poweroff to charger // Send poweroff to charger
furi_hal_i2c_acquire(&furi_hal_i2c_handle_power); furi_hal_i2c_acquire(&furi_hal_i2c_handle_power);
bq25896_poweroff(&furi_hal_i2c_handle_power); bq25896_poweroff(&furi_hal_i2c_handle_power);
furi_hal_i2c_release(&furi_hal_i2c_handle_power); furi_hal_i2c_release(&furi_hal_i2c_handle_power);
furi_hal_vibro_on(false);
} }
void furi_hal_power_reset() { void furi_hal_power_reset() {

View File

@ -522,13 +522,9 @@ void furi_hal_subghz_stop_async_rx() {
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
} }
#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL (256)
#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF (API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL / 2)
#define API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME 333
typedef struct { typedef struct {
uint32_t* buffer; uint32_t* buffer;
bool flip_flop; LevelDuration carry_ld;
FuriHalSubGhzAsyncTxCallback callback; FuriHalSubGhzAsyncTxCallback callback;
void* callback_context; void* callback_context;
uint64_t duty_high; uint64_t duty_high;
@ -538,37 +534,48 @@ typedef struct {
static FuriHalSubGhzAsyncTx furi_hal_subghz_async_tx = {0}; static FuriHalSubGhzAsyncTx furi_hal_subghz_async_tx = {0};
static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) {
furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx);
while(samples > 0) { while(samples > 0) {
bool is_odd = samples % 2; bool is_odd = samples % 2;
LevelDuration ld = LevelDuration ld;
furi_hal_subghz_async_tx.callback(furi_hal_subghz_async_tx.callback_context); if(level_duration_is_reset(furi_hal_subghz_async_tx.carry_ld)) {
ld = furi_hal_subghz_async_tx.callback(furi_hal_subghz_async_tx.callback_context);
} else {
ld = furi_hal_subghz_async_tx.carry_ld;
furi_hal_subghz_async_tx.carry_ld = level_duration_reset();
}
if(level_duration_is_wait(ld)) { if(level_duration_is_wait(ld)) {
return; *buffer = API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME;
buffer++;
samples--;
} else if(level_duration_is_reset(ld)) { } else if(level_duration_is_reset(ld)) {
// One more even sample required to end at low level *buffer = 0;
if(is_odd) { buffer++;
*buffer = API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; samples--;
buffer++; LL_DMA_DisableIT_HT(DMA1, LL_DMA_CHANNEL_1);
samples--; LL_DMA_DisableIT_TC(DMA1, LL_DMA_CHANNEL_1);
furi_hal_subghz_async_tx.duty_low += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; LL_TIM_EnableIT_UPDATE(TIM2);
}
break; break;
} else { } else {
// Inject guard time if level is incorrect
bool level = level_duration_get_level(ld); bool level = level_duration_get_level(ld);
if(is_odd == level) {
// Inject guard time if level is incorrect
if(is_odd != level) {
*buffer = API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; *buffer = API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME;
buffer++; buffer++;
samples--; samples--;
if(!level) { if(is_odd) {
furi_hal_subghz_async_tx.duty_high += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; furi_hal_subghz_async_tx.duty_high += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME;
} else { } else {
furi_hal_subghz_async_tx.duty_low += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME; furi_hal_subghz_async_tx.duty_low += API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME;
} }
// This code must be invoked only once: when encoder starts with low level.
// Otherwise whole thing will crash. // Special case: prevent buffer overflow if sample is last
furi_check(samples > 0); if(samples == 0) {
furi_hal_subghz_async_tx.carry_ld = ld;
break;
}
} }
uint32_t duration = level_duration_get_duration(ld); uint32_t duration = level_duration_get_duration(ld);
@ -577,22 +584,17 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) {
buffer++; buffer++;
samples--; samples--;
if(level) { if(is_odd) {
furi_hal_subghz_async_tx.duty_high += duration; furi_hal_subghz_async_tx.duty_high += duration;
} else { } else {
furi_hal_subghz_async_tx.duty_low += duration; furi_hal_subghz_async_tx.duty_low += duration;
} }
} }
} }
memset(buffer, 0, samples * sizeof(uint32_t));
} }
static void furi_hal_subghz_async_tx_dma_isr() { static void furi_hal_subghz_async_tx_dma_isr() {
furi_assert( furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx);
furi_hal_subghz.state == SubGhzStateAsyncTx ||
furi_hal_subghz.state == SubGhzStateAsyncTxEnd ||
furi_hal_subghz.state == SubGhzStateAsyncTxLast);
if(LL_DMA_IsActiveFlag_HT1(DMA1)) { if(LL_DMA_IsActiveFlag_HT1(DMA1)) {
LL_DMA_ClearFlag_HT1(DMA1); LL_DMA_ClearFlag_HT1(DMA1);
furi_hal_subghz_async_tx_refill( furi_hal_subghz_async_tx_refill(
@ -612,11 +614,14 @@ static void furi_hal_subghz_async_tx_timer_isr() {
if(LL_TIM_GetAutoReload(TIM2) == 0) { if(LL_TIM_GetAutoReload(TIM2) == 0) {
if(furi_hal_subghz.state == SubGhzStateAsyncTx) { if(furi_hal_subghz.state == SubGhzStateAsyncTx) {
furi_hal_subghz.state = SubGhzStateAsyncTxLast; furi_hal_subghz.state = SubGhzStateAsyncTxLast;
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
} else if(furi_hal_subghz.state == SubGhzStateAsyncTxLast) {
furi_hal_subghz.state = SubGhzStateAsyncTxEnd;
//forcibly pulls the pin to the ground so that there is no carrier //forcibly pulls the pin to the ground so that there is no carrier
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullDown, GpioSpeedLow);
} else {
furi_hal_subghz.state = SubGhzStateAsyncTxEnd;
LL_TIM_DisableCounter(TIM2); LL_TIM_DisableCounter(TIM2);
} else {
furi_crash(NULL);
} }
} }
} }
@ -639,8 +644,6 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void*
furi_hal_subghz_async_tx.buffer = furi_hal_subghz_async_tx.buffer =
malloc(API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t)); malloc(API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t));
furi_hal_subghz_async_tx_refill(
furi_hal_subghz_async_tx.buffer, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL);
// Connect CC1101_GD0 to TIM2 as output // Connect CC1101_GD0 to TIM2 as output
furi_hal_gpio_init_ex( furi_hal_gpio_init_ex(
@ -681,14 +684,16 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void*
TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE; TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE;
TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE; TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE;
TIM_OC_InitStruct.CompareValue = 0; TIM_OC_InitStruct.CompareValue = 0;
TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH; TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_LOW;
LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH2, &TIM_OC_InitStruct); LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH2, &TIM_OC_InitStruct);
LL_TIM_OC_DisableFast(TIM2, LL_TIM_CHANNEL_CH2); LL_TIM_OC_DisableFast(TIM2, LL_TIM_CHANNEL_CH2);
LL_TIM_DisableMasterSlaveMode(TIM2); LL_TIM_DisableMasterSlaveMode(TIM2);
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_subghz_async_tx_timer_isr, NULL); furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, furi_hal_subghz_async_tx_timer_isr, NULL);
LL_TIM_EnableIT_UPDATE(TIM2); furi_hal_subghz_async_tx_refill(
furi_hal_subghz_async_tx.buffer, API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL);
LL_TIM_EnableDMAReq_UPDATE(TIM2); LL_TIM_EnableDMAReq_UPDATE(TIM2);
LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2);
@ -707,8 +712,8 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void*
&SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); &SUBGHZ_DEBUG_CC1101_PIN, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
const GpioPin* gpio = &SUBGHZ_DEBUG_CC1101_PIN; const GpioPin* gpio = &SUBGHZ_DEBUG_CC1101_PIN;
subghz_debug_gpio_buff[0] = gpio->pin; subghz_debug_gpio_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER;
subghz_debug_gpio_buff[1] = (uint32_t)gpio->pin << GPIO_NUMBER; subghz_debug_gpio_buff[1] = gpio->pin;
dma_config.MemoryOrM2MDstAddress = (uint32_t)subghz_debug_gpio_buff; dma_config.MemoryOrM2MDstAddress = (uint32_t)subghz_debug_gpio_buff;
dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR);

View File

@ -14,6 +14,11 @@
extern "C" { extern "C" {
#endif #endif
/** Low level buffer dimensions and guard times */
#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL (256)
#define API_HAL_SUBGHZ_ASYNC_TX_BUFFER_HALF (API_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL / 2)
#define API_HAL_SUBGHZ_ASYNC_TX_GUARD_TIME 999
/** Radio Presets */ /** Radio Presets */
typedef enum { typedef enum {
FuriHalSubGhzPresetIDLE, /**< default configuration */ FuriHalSubGhzPresetIDLE, /**< default configuration */

View File

@ -18,6 +18,7 @@ struct SubGhzFileEncoderWorker {
volatile bool worker_running; volatile bool worker_running;
volatile bool worker_stoping; volatile bool worker_stoping;
bool level; bool level;
bool is_storage_slow;
FuriString* str_data; FuriString* str_data;
FuriString* file_path; FuriString* file_path;
@ -98,7 +99,7 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) {
if(ret == sizeof(int32_t)) { if(ret == sizeof(int32_t)) {
LevelDuration level_duration = {.level = LEVEL_DURATION_RESET}; LevelDuration level_duration = {.level = LEVEL_DURATION_RESET};
if(duration < 0) { if(duration < 0) {
level_duration = level_duration_make(false, duration * -1); level_duration = level_duration_make(false, -duration);
} else if(duration > 0) { } else if(duration > 0) {
level_duration = level_duration_make(true, duration); level_duration = level_duration_make(true, duration);
} else if(duration == 0) { } else if(duration == 0) {
@ -108,7 +109,7 @@ LevelDuration subghz_file_encoder_worker_get_level_duration(void* context) {
} }
return level_duration; return level_duration;
} else { } else {
FURI_LOG_E(TAG, "Slow flash read"); instance->is_storage_slow = true;
return level_duration_wait(); return level_duration_wait();
} }
} }
@ -122,6 +123,7 @@ static int32_t subghz_file_encoder_worker_thread(void* context) {
SubGhzFileEncoderWorker* instance = context; SubGhzFileEncoderWorker* instance = context;
FURI_LOG_I(TAG, "Worker start"); FURI_LOG_I(TAG, "Worker start");
bool res = false; bool res = false;
instance->is_storage_slow = false;
Stream* stream = flipper_format_get_raw_stream(instance->flipper_format); Stream* stream = flipper_format_get_raw_stream(instance->flipper_format);
do { do {
if(!flipper_format_file_open_existing( if(!flipper_format_file_open_existing(
@ -151,21 +153,21 @@ static int32_t subghz_file_encoder_worker_thread(void* context) {
furi_string_trim(instance->str_data); furi_string_trim(instance->str_data);
if(!subghz_file_encoder_worker_data_parse( if(!subghz_file_encoder_worker_data_parse(
instance, furi_string_get_cstr(instance->str_data))) { instance, furi_string_get_cstr(instance->str_data))) {
//to stop DMA correctly
subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);
subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);
break; break;
} }
} else { } else {
subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);
subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET); subghz_file_encoder_worker_add_level_duration(instance, LEVEL_DURATION_RESET);
break; break;
} }
} else {
furi_delay_ms(1);
} }
furi_delay_ms(5);
} }
//waiting for the end of the transfer //waiting for the end of the transfer
if(instance->is_storage_slow) {
FURI_LOG_E(TAG, "Storage is slow");
}
FURI_LOG_I(TAG, "End read file"); FURI_LOG_I(TAG, "End read file");
while(!furi_hal_subghz_is_async_tx_complete() && instance->worker_running) { while(!furi_hal_subghz_is_async_tx_complete() && instance->worker_running) {
furi_delay_ms(5); furi_delay_ms(5);