From 2503f1f37a96540fff285769490155e86ceed4b1 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 29 Aug 2024 17:00:08 +0300 Subject: [PATCH 1/7] feat: softspi --- applications/debug/spi_test/application.fam | 10 ++ applications/debug/spi_test/spi_test.c | 129 ++++++++++++++++++ lib/SConscript | 1 + lib/softspi/SConscript | 22 +++ lib/softspi/softspi.c | 142 ++++++++++++++++++++ lib/softspi/softspi.h | 74 ++++++++++ targets/f18/api_symbols.csv | 20 ++- targets/f7/api_symbols.csv | 8 +- targets/furi_hal_include/furi_hal_spi.h | 2 +- 9 files changed, 399 insertions(+), 9 deletions(-) create mode 100644 applications/debug/spi_test/application.fam create mode 100644 applications/debug/spi_test/spi_test.c create mode 100644 lib/softspi/SConscript create mode 100644 lib/softspi/softspi.c create mode 100644 lib/softspi/softspi.h diff --git a/applications/debug/spi_test/application.fam b/applications/debug/spi_test/application.fam new file mode 100644 index 00000000000..9995b765299 --- /dev/null +++ b/applications/debug/spi_test/application.fam @@ -0,0 +1,10 @@ +App( + appid="spi_test", + name="SPI Test", + apptype=FlipperAppType.DEBUG, + entry_point="spi_test_app", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Debug", + fap_libs=["softspi"] +) diff --git a/applications/debug/spi_test/spi_test.c b/applications/debug/spi_test/spi_test.c new file mode 100644 index 00000000000..cdb1f9e3ea0 --- /dev/null +++ b/applications/debug/spi_test/spi_test.c @@ -0,0 +1,129 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define TAG "SpiTest" + +typedef struct { + Gui* gui; + ViewDispatcher* view_dispatcher; + Submenu* submenu; +} SpiTest; + +typedef enum { + SpiTestViewSubmenu, +} SpiTestView; + +typedef enum { + SpiTestSubmenuHardwareTx, + SpiTestSubmenuSoftwareTx, +} SpiTestSubmenu; + +static void spi_test_submenu_callback(void* context, uint32_t index) { + SpiTest* instance = (SpiTest*)context; + UNUSED(instance); + + uint8_t tx_buffer[] = {0x55, 0xAA}; + uint8_t rx_buffer[sizeof(tx_buffer)] = {0}; + + if(index == SpiTestSubmenuHardwareTx) { + FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_external; + furi_hal_spi_bus_handle_init(handle); + furi_hal_spi_acquire(handle); + furi_hal_spi_bus_trx(handle, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever); + furi_hal_spi_release(handle); + furi_hal_spi_bus_handle_deinit(handle); + } else { + SoftspiConfig config = { + .miso = &gpio_ext_pa6, + .mosi = &gpio_ext_pa7, + .sck = &gpio_ext_pb3, + .cs = &gpio_ext_pa4, + .clk_fq_khz = 100, + .clk_polarity = 0, + .clk_phase = 0, + }; + softspi_acquire(&config); + softspi_trx(&config, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever); + softspi_release(&config); + } + + FURI_LOG_I( + TAG, + "sent %02hhx %02hhX, received %02hhx %02hhX", + tx_buffer[0], + tx_buffer[1], + rx_buffer[0], + rx_buffer[1]); +} + +static uint32_t spi_test_exit_callback(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +SpiTest* spi_test_alloc(void) { + SpiTest* instance = malloc(sizeof(SpiTest)); + + View* view = NULL; + + instance->gui = furi_record_open(RECORD_GUI); + instance->view_dispatcher = view_dispatcher_alloc(); + view_dispatcher_attach_to_gui( + instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen); + + // Menu + instance->submenu = submenu_alloc(); + view = submenu_get_view(instance->submenu); + view_set_previous_callback(view, spi_test_exit_callback); + view_dispatcher_add_view(instance->view_dispatcher, SpiTestViewSubmenu, view); + submenu_add_item( + instance->submenu, + "Hardware TRX", + SpiTestSubmenuHardwareTx, + spi_test_submenu_callback, + instance); + submenu_add_item( + instance->submenu, + "Software TRX", + SpiTestSubmenuSoftwareTx, + spi_test_submenu_callback, + instance); + + return instance; +} + +void spi_test_free(SpiTest* instance) { + view_dispatcher_remove_view(instance->view_dispatcher, SpiTestViewSubmenu); + submenu_free(instance->submenu); + + view_dispatcher_free(instance->view_dispatcher); + furi_record_close(RECORD_GUI); + + free(instance); +} + +int32_t spi_test_run(SpiTest* instance) { + view_dispatcher_switch_to_view(instance->view_dispatcher, SpiTestViewSubmenu); + view_dispatcher_run(instance->view_dispatcher); + return 0; +} + +int32_t spi_test_app(void* p) { + UNUSED(p); + + SpiTest* instance = spi_test_alloc(); + + int32_t ret = spi_test_run(instance); + + spi_test_free(instance); + + return ret; +} diff --git a/lib/SConscript b/lib/SConscript index fb0473f8d48..49af4df9436 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -43,6 +43,7 @@ libs = env.BuildModules( "ble_profile", "bit_lib", "datetime", + "softspi" ], ) diff --git a/lib/softspi/SConscript b/lib/softspi/SConscript new file mode 100644 index 00000000000..81315494790 --- /dev/null +++ b/lib/softspi/SConscript @@ -0,0 +1,22 @@ +Import("env") + +env.Append( + LINT_SOURCES=[ + Dir("."), + ], + CPPPATH=[ + "#/lib/softspi", + ], + SDK_HEADERS=[ + File("softspi.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="softspi") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/softspi/softspi.c b/lib/softspi/softspi.c new file mode 100644 index 00000000000..aee6eab3910 --- /dev/null +++ b/lib/softspi/softspi.c @@ -0,0 +1,142 @@ +#include "softspi.h" +#include +#include +#include +#include +#include + +#define SOFTSPI_TIM TIM17 +#define SOFTSPI_TIM_BUS FuriHalBusTIM17 +#define SOFTSPI_TIM_IRQ FuriHalInterruptIdTim1TrgComTim17 +#define SOFTSPI_TIM_FQ_KHZ 64000UL + +typedef struct { + SoftspiConfig* config; + const uint8_t* tx_buffer; + uint8_t* rx_buffer; + FuriSemaphore* done_semaphore; + uint8_t + sck_level : 1; // cs, true); + furi_hal_gpio_write(config->sck, config->clk_polarity); + furi_hal_gpio_write(config->mosi, false); + furi_hal_gpio_init(config->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(config->sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(config->miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(config->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(config->cs, false); +} + +void softspi_release(SoftspiConfig* config) { + furi_check(config); + furi_hal_gpio_write(config->cs, true); + furi_hal_gpio_init(config->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(config->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(config->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(config->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); +} + +static void furi_hal_spi_softbus_timer_isr(void* param) { + if(!LL_TIM_IsActiveFlag_UPDATE(SOFTSPI_TIM)) return; + + do { + SoftspiTimerIsrContext* context = param; + + furi_hal_gpio_write(context->config->sck, context->sck_level); + if(context->done) break; + + if(context->sck_level == context->out_level) { + // TX edge + if(context->tx_buffer) + furi_hal_gpio_write( + context->config->mosi, (*context->tx_buffer >> context->bit) & 1); + } else { + // RX edge + if(context->rx_buffer) + *context->rx_buffer |= furi_hal_gpio_read(context->config->miso) << context->bit; + + if(context->bit == 0) { + // entire byte transmitted + if(context->tx_buffer) context->tx_buffer++; + if(context->rx_buffer) context->rx_buffer++; + if(!--context->size) { + furi_semaphore_release(context->done_semaphore); + context->done = 1; + } else { + context->bit = 7; + } + } else { + context->bit--; + } + } + + context->sck_level = !context->sck_level; + } while(0); + + LL_TIM_ClearFlag_UPDATE(SOFTSPI_TIM); +} + +void softspi_trx( + SoftspiConfig* config, + const uint8_t* tx_buffer, + uint8_t* rx_buffer, + size_t size, + uint32_t timeout) { + furi_check(config); + // NOTE: serious low-level optimization is needed in order to push this further + // this check is here to prevent a lockup caused by never-ending timer interrupts + furi_check(config->clk_fq_khz <= 200); + if(size == 0) return; + + SoftspiTimerIsrContext context = { + .config = config, + .tx_buffer = tx_buffer, + .rx_buffer = rx_buffer, + .size = size, + .bit = 7, + .done_semaphore = furi_semaphore_alloc(1, 0), + .sck_level = config->clk_polarity, + .done = 0, + .out_level = config->clk_polarity ^ config->clk_phase, + }; + + furi_hal_bus_enable(SOFTSPI_TIM_BUS); + furi_hal_interrupt_set_isr_ex( + SOFTSPI_TIM_IRQ, FuriHalInterruptPriorityHighest, furi_hal_spi_softbus_timer_isr, &context); + LL_TIM_SetPrescaler(SOFTSPI_TIM, 0); + LL_TIM_SetCounterMode(SOFTSPI_TIM, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetAutoReload( + SOFTSPI_TIM, SOFTSPI_TIM_FQ_KHZ / config->clk_fq_khz / 2); // f_ISR = 2 f_CLK + LL_TIM_DisableARRPreload(SOFTSPI_TIM); + LL_TIM_SetRepetitionCounter(SOFTSPI_TIM, 0); + LL_TIM_SetClockDivision(SOFTSPI_TIM, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetClockSource(SOFTSPI_TIM, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_GenerateEvent_UPDATE(SOFTSPI_TIM); + LL_TIM_EnableIT_UPDATE(SOFTSPI_TIM); + LL_TIM_EnableCounter(SOFTSPI_TIM); + + furi_semaphore_acquire(context.done_semaphore, timeout); + + furi_hal_interrupt_set_isr(SOFTSPI_TIM_IRQ, NULL, NULL); + furi_hal_bus_disable(SOFTSPI_TIM_BUS); + furi_semaphore_free(context.done_semaphore); +} + +void softspi_tx(SoftspiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout) { + furi_check(buffer); + softspi_trx(config, buffer, NULL, size, timeout); +} + +void softspi_rx(SoftspiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout) { + furi_check(buffer); + softspi_trx(config, NULL, buffer, size, timeout); +} diff --git a/lib/softspi/softspi.h b/lib/softspi/softspi.h new file mode 100644 index 00000000000..9e27880ac8d --- /dev/null +++ b/lib/softspi/softspi.h @@ -0,0 +1,74 @@ +#pragma once + +/** + * @file softspi.h + * Software (bit-banged) SPI implementation. Master-only. Supports all 4 modes + * with clock rates of up to 200kHz. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * Software SPI bus configuration + */ +typedef struct { + const GpioPin* miso; + const GpioPin* mosi; + const GpioPin* sck; + const GpioPin* cs; + uint32_t clk_fq_khz; + uint8_t clk_polarity : 1; + uint8_t clk_phase : 1; +} SoftspiConfig; + +/** + * @brief Initializes bus pins, brings the CS low + */ +void softspi_acquire(SoftspiConfig* config); + +/** + * @brief Brings the CS high, resets bus pins + */ +void softspi_release(SoftspiConfig* config); + +/** + * @brief Simultaneously transmits and receives a buffer on the software SPI bus + * @param [in] config Software SPI bus configuration + * @param [in] tx_buffer Buffer to transmit. May be NULL if transmission is not required. + * @param [in] rx_buffer Buffer to receive data into. May be NULL if reception is not required. + * @param size Buffer length (both buffers must be of the same size) + * @param timeout Timeout in ticks. Transaction will be interrupted abruptly if this timeout is reached. + */ +void softspi_trx( + SoftspiConfig* config, + const uint8_t* tx_buffer, + uint8_t* rx_buffer, + size_t size, + uint32_t timeout); + +/** + * @brief Transmits a buffer on the software SPI bus + * @param [in] config Software SPI bus configuration + * @param [in] buffer Buffer to transmit + * @param size Buffer length + * @param timeout Timeout in ticks. Transmission will be interrupted abruptly if this timeout is reached. + */ +void softspi_tx(SoftspiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout); + +/** + * @brief Receives a buffer from the software SPI bus + * @param [in] config Software SPI bus configuration + * @param [in] buffer Buffer to receive into + * @param size Buffer length + * @param timeout Timeout in ticks. Reception will be interrupted abruptly if this timeout is reached. + */ +void softspi_rx(SoftspiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 57cbd1d62e8..e180b12bddf 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,v,72.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -13,13 +13,13 @@ Header,+,applications/services/gui/icon_i.h,, Header,+,applications/services/gui/modules/button_menu.h,, Header,+,applications/services/gui/modules/button_panel.h,, Header,+,applications/services/gui/modules/byte_input.h,, -Header,+,applications/services/gui/modules/number_input.h,, Header,+,applications/services/gui/modules/dialog_ex.h,, Header,+,applications/services/gui/modules/empty_screen.h,, Header,+,applications/services/gui/modules/file_browser.h,, Header,+,applications/services/gui/modules/file_browser_worker.h,, Header,+,applications/services/gui/modules/loading.h,, Header,+,applications/services/gui/modules/menu.h,, +Header,+,applications/services/gui/modules/number_input.h,, Header,+,applications/services/gui/modules/popup.h,, Header,+,applications/services/gui/modules/submenu.h,, Header,+,applications/services/gui/modules/text_box.h,, @@ -120,6 +120,7 @@ Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/pulse_reader/pulse_reader.h,, Header,+,lib/signal_reader/signal_reader.h,, +Header,+,lib/softspi/softspi.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -723,11 +724,6 @@ Function,+,byte_input_free,void,ByteInput* Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" -Function,+,number_input_alloc,NumberInput*, -Function,+,number_input_free,void,NumberInput* -Function,+,number_input_get_view,View*,NumberInput* -Function,+,number_input_set_header_text,void,"NumberInput*, const char*" -Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t" Function,-,bzero,void,"void*, size_t" Function,+,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* @@ -2197,6 +2193,11 @@ Function,+,notification_internal_message_block,void,"NotificationApp*, const Not Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" Function,-,nrand48,long,unsigned short[3] +Function,+,number_input_alloc,NumberInput*, +Function,+,number_input_free,void,NumberInput* +Function,+,number_input_get_view,View*,NumberInput* +Function,+,number_input_set_header_text,void,"NumberInput*, const char*" +Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t" Function,-,on_exit,int,"void (*)(int, void*), void*" Function,+,onewire_host_alloc,OneWireHost*,const GpioPin* Function,+,onewire_host_free,void,OneWireHost* @@ -2454,6 +2455,11 @@ Function,-,siprintf,int,"char*, const char*, ..." Function,-,siscanf,int,"const char*, const char*, ..." Function,-,sniprintf,int,"char*, size_t, const char*, ..." Function,+,snprintf,int,"char*, size_t, const char*, ..." +Function,+,softspi_acquire,void,SoftspiConfig* +Function,+,softspi_release,void,SoftspiConfig* +Function,+,softspi_rx,void,"SoftspiConfig*, uint8_t*, size_t, uint32_t" +Function,+,softspi_trx,void,"SoftspiConfig*, const uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,softspi_tx,void,"SoftspiConfig*, const uint8_t*, size_t, uint32_t" Function,-,sprintf,int,"char*, const char*, ..." Function,-,sqrt,double,double Function,-,sqrtf,float,float diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e44b3356c67..a9797544222 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,72.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -174,6 +174,7 @@ Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/pulse_reader/pulse_reader.h,, Header,+,lib/signal_reader/signal_reader.h,, +Header,+,lib/softspi/softspi.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -3106,6 +3107,11 @@ Function,+,slix_type_supports_password,_Bool,"SlixType, SlixPasswordType" Function,+,slix_verify,_Bool,"SlixData*, const FuriString*" Function,-,sniprintf,int,"char*, size_t, const char*, ..." Function,+,snprintf,int,"char*, size_t, const char*, ..." +Function,+,softspi_acquire,void,SoftspiConfig* +Function,+,softspi_release,void,SoftspiConfig* +Function,+,softspi_rx,void,"SoftspiConfig*, uint8_t*, size_t, uint32_t" +Function,+,softspi_trx,void,"SoftspiConfig*, const uint8_t*, uint8_t*, size_t, uint32_t" +Function,+,softspi_tx,void,"SoftspiConfig*, const uint8_t*, size_t, uint32_t" Function,-,sprintf,int,"char*, const char*, ..." Function,-,sqrt,double,double Function,-,sqrtf,float,float diff --git a/targets/furi_hal_include/furi_hal_spi.h b/targets/furi_hal_include/furi_hal_spi.h index d497dff5c35..c55b05e86bf 100644 --- a/targets/furi_hal_include/furi_hal_spi.h +++ b/targets/furi_hal_include/furi_hal_spi.h @@ -66,7 +66,7 @@ void furi_hal_spi_release(FuriHalSpiBusHandle* handle); * @param size transaction size (buffer size) * @param timeout operation timeout in ms * - * @return true on sucess + * @return true on success */ bool furi_hal_spi_bus_rx( FuriHalSpiBusHandle* handle, From 829c80aee579dfc90335ba27b5ba7faf74a485d1 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Thu, 29 Aug 2024 17:14:53 +0300 Subject: [PATCH 2/7] build: fix errors --- applications/debug/spi_test/application.fam | 2 +- lib/SConscript | 2 +- lib/softspi/SConscript | 4 +--- lib/softspi/softspi.c | 4 ++-- targets/f18/api_symbols.csv | 8 +------- targets/f7/api_symbols.csv | 8 +------- 6 files changed, 7 insertions(+), 21 deletions(-) diff --git a/applications/debug/spi_test/application.fam b/applications/debug/spi_test/application.fam index 9995b765299..03c22db0e3b 100644 --- a/applications/debug/spi_test/application.fam +++ b/applications/debug/spi_test/application.fam @@ -6,5 +6,5 @@ App( requires=["gui"], stack_size=1 * 1024, fap_category="Debug", - fap_libs=["softspi"] + fap_libs=["softspi"], ) diff --git a/lib/SConscript b/lib/SConscript index 49af4df9436..f696ce33882 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -43,7 +43,7 @@ libs = env.BuildModules( "ble_profile", "bit_lib", "datetime", - "softspi" + "softspi", ], ) diff --git a/lib/softspi/SConscript b/lib/softspi/SConscript index 81315494790..ddc1c4c8de0 100644 --- a/lib/softspi/SConscript +++ b/lib/softspi/SConscript @@ -7,9 +7,7 @@ env.Append( CPPPATH=[ "#/lib/softspi", ], - SDK_HEADERS=[ - File("softspi.h"), - ], + SDK_HEADERS=[], ) libenv = env.Clone(FW_LIB_NAME="softspi") diff --git a/lib/softspi/softspi.c b/lib/softspi/softspi.c index aee6eab3910..0c509198836 100644 --- a/lib/softspi/softspi.c +++ b/lib/softspi/softspi.c @@ -45,7 +45,7 @@ void softspi_release(SoftspiConfig* config) { furi_hal_gpio_init(config->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } -static void furi_hal_spi_softbus_timer_isr(void* param) { +static void softspi_timer_isr(void* param) { if(!LL_TIM_IsActiveFlag_UPDATE(SOFTSPI_TIM)) return; do { @@ -111,7 +111,7 @@ void softspi_trx( furi_hal_bus_enable(SOFTSPI_TIM_BUS); furi_hal_interrupt_set_isr_ex( - SOFTSPI_TIM_IRQ, FuriHalInterruptPriorityHighest, furi_hal_spi_softbus_timer_isr, &context); + SOFTSPI_TIM_IRQ, FuriHalInterruptPriorityHighest, softspi_timer_isr, &context); LL_TIM_SetPrescaler(SOFTSPI_TIM, 0); LL_TIM_SetCounterMode(SOFTSPI_TIM, LL_TIM_COUNTERMODE_UP); LL_TIM_SetAutoReload( diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index e180b12bddf..92904b3fbd7 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,v,72.2,, +Version,+,72.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -120,7 +120,6 @@ Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/pulse_reader/pulse_reader.h,, Header,+,lib/signal_reader/signal_reader.h,, -Header,+,lib/softspi/softspi.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -2455,11 +2454,6 @@ Function,-,siprintf,int,"char*, const char*, ..." Function,-,siscanf,int,"const char*, const char*, ..." Function,-,sniprintf,int,"char*, size_t, const char*, ..." Function,+,snprintf,int,"char*, size_t, const char*, ..." -Function,+,softspi_acquire,void,SoftspiConfig* -Function,+,softspi_release,void,SoftspiConfig* -Function,+,softspi_rx,void,"SoftspiConfig*, uint8_t*, size_t, uint32_t" -Function,+,softspi_trx,void,"SoftspiConfig*, const uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,softspi_tx,void,"SoftspiConfig*, const uint8_t*, size_t, uint32_t" Function,-,sprintf,int,"char*, const char*, ..." Function,-,sqrt,double,double Function,-,sqrtf,float,float diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index a9797544222..e44b3356c67 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.2,, +Version,+,72.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -174,7 +174,6 @@ Header,+,lib/one_wire/one_wire_slave.h,, Header,+,lib/print/wrappers.h,, Header,+,lib/pulse_reader/pulse_reader.h,, Header,+,lib/signal_reader/signal_reader.h,, -Header,+,lib/softspi/softspi.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_adc.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_bus.h,, Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_comp.h,, @@ -3107,11 +3106,6 @@ Function,+,slix_type_supports_password,_Bool,"SlixType, SlixPasswordType" Function,+,slix_verify,_Bool,"SlixData*, const FuriString*" Function,-,sniprintf,int,"char*, size_t, const char*, ..." Function,+,snprintf,int,"char*, size_t, const char*, ..." -Function,+,softspi_acquire,void,SoftspiConfig* -Function,+,softspi_release,void,SoftspiConfig* -Function,+,softspi_rx,void,"SoftspiConfig*, uint8_t*, size_t, uint32_t" -Function,+,softspi_trx,void,"SoftspiConfig*, const uint8_t*, uint8_t*, size_t, uint32_t" -Function,+,softspi_tx,void,"SoftspiConfig*, const uint8_t*, size_t, uint32_t" Function,-,sprintf,int,"char*, const char*, ..." Function,-,sqrt,double,double Function,-,sqrtf,float,float From 1df33dfb8f0a247291c21acc816cfe8af18f781e Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 6 Sep 2024 16:32:09 +0300 Subject: [PATCH 3/7] refactor: rename to softio --- applications/debug/spi_test/application.fam | 2 +- applications/debug/spi_test/spi_test.c | 10 ++++---- lib/SConscript | 2 +- lib/{softspi => softio}/SConscript | 4 ++-- .../softspi.c => softio/softio_spi.c} | 24 +++++++++---------- .../softspi.h => softio/softio_spi.h} | 16 ++++++------- 6 files changed, 29 insertions(+), 29 deletions(-) rename lib/{softspi => softio}/SConscript (81%) rename lib/{softspi/softspi.c => softio/softio_spi.c} (87%) rename lib/{softspi/softspi.h => softio/softio_spi.h} (82%) diff --git a/applications/debug/spi_test/application.fam b/applications/debug/spi_test/application.fam index 03c22db0e3b..30c0c268fc8 100644 --- a/applications/debug/spi_test/application.fam +++ b/applications/debug/spi_test/application.fam @@ -6,5 +6,5 @@ App( requires=["gui"], stack_size=1 * 1024, fap_category="Debug", - fap_libs=["softspi"], + fap_libs=["softio"], ) diff --git a/applications/debug/spi_test/spi_test.c b/applications/debug/spi_test/spi_test.c index cdb1f9e3ea0..12bcf3a4a48 100644 --- a/applications/debug/spi_test/spi_test.c +++ b/applications/debug/spi_test/spi_test.c @@ -7,7 +7,7 @@ #include #include -#include +#include #define TAG "SpiTest" @@ -41,7 +41,7 @@ static void spi_test_submenu_callback(void* context, uint32_t index) { furi_hal_spi_release(handle); furi_hal_spi_bus_handle_deinit(handle); } else { - SoftspiConfig config = { + SoftIoSpiConfig config = { .miso = &gpio_ext_pa6, .mosi = &gpio_ext_pa7, .sck = &gpio_ext_pb3, @@ -50,9 +50,9 @@ static void spi_test_submenu_callback(void* context, uint32_t index) { .clk_polarity = 0, .clk_phase = 0, }; - softspi_acquire(&config); - softspi_trx(&config, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever); - softspi_release(&config); + softio_spi_acquire(&config); + softio_spi_trx(&config, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever); + softio_spi_release(&config); } FURI_LOG_I( diff --git a/lib/SConscript b/lib/SConscript index f696ce33882..738c780ce86 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -43,7 +43,7 @@ libs = env.BuildModules( "ble_profile", "bit_lib", "datetime", - "softspi", + "softio", ], ) diff --git a/lib/softspi/SConscript b/lib/softio/SConscript similarity index 81% rename from lib/softspi/SConscript rename to lib/softio/SConscript index ddc1c4c8de0..409b90edeb3 100644 --- a/lib/softspi/SConscript +++ b/lib/softio/SConscript @@ -5,12 +5,12 @@ env.Append( Dir("."), ], CPPPATH=[ - "#/lib/softspi", + "#/lib/softio", ], SDK_HEADERS=[], ) -libenv = env.Clone(FW_LIB_NAME="softspi") +libenv = env.Clone(FW_LIB_NAME="softio") libenv.ApplyLibFlags() sources = libenv.GlobRecursive("*.c*") diff --git a/lib/softspi/softspi.c b/lib/softio/softio_spi.c similarity index 87% rename from lib/softspi/softspi.c rename to lib/softio/softio_spi.c index 0c509198836..d8d35855a88 100644 --- a/lib/softspi/softspi.c +++ b/lib/softio/softio_spi.c @@ -1,4 +1,4 @@ -#include "softspi.h" +#include "softio_spi.h" #include #include #include @@ -11,7 +11,7 @@ #define SOFTSPI_TIM_FQ_KHZ 64000UL typedef struct { - SoftspiConfig* config; + SoftIoSpiConfig* config; const uint8_t* tx_buffer; uint8_t* rx_buffer; FuriSemaphore* done_semaphore; @@ -24,7 +24,7 @@ typedef struct { uint8_t bit; } SoftspiTimerIsrContext; -void softspi_acquire(SoftspiConfig* config) { +void softio_spi_acquire(SoftIoSpiConfig* config) { furi_check(config); furi_hal_gpio_write(config->cs, true); furi_hal_gpio_write(config->sck, config->clk_polarity); @@ -36,7 +36,7 @@ void softspi_acquire(SoftspiConfig* config) { furi_hal_gpio_write(config->cs, false); } -void softspi_release(SoftspiConfig* config) { +void softio_spi_release(SoftIoSpiConfig* config) { furi_check(config); furi_hal_gpio_write(config->cs, true); furi_hal_gpio_init(config->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); @@ -45,7 +45,7 @@ void softspi_release(SoftspiConfig* config) { furi_hal_gpio_init(config->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); } -static void softspi_timer_isr(void* param) { +static void softio_spi_timer_isr(void* param) { if(!LL_TIM_IsActiveFlag_UPDATE(SOFTSPI_TIM)) return; do { @@ -85,8 +85,8 @@ static void softspi_timer_isr(void* param) { LL_TIM_ClearFlag_UPDATE(SOFTSPI_TIM); } -void softspi_trx( - SoftspiConfig* config, +void softio_spi_trx( + SoftIoSpiConfig* config, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, @@ -111,7 +111,7 @@ void softspi_trx( furi_hal_bus_enable(SOFTSPI_TIM_BUS); furi_hal_interrupt_set_isr_ex( - SOFTSPI_TIM_IRQ, FuriHalInterruptPriorityHighest, softspi_timer_isr, &context); + SOFTSPI_TIM_IRQ, FuriHalInterruptPriorityHighest, softio_spi_timer_isr, &context); LL_TIM_SetPrescaler(SOFTSPI_TIM, 0); LL_TIM_SetCounterMode(SOFTSPI_TIM, LL_TIM_COUNTERMODE_UP); LL_TIM_SetAutoReload( @@ -131,12 +131,12 @@ void softspi_trx( furi_semaphore_free(context.done_semaphore); } -void softspi_tx(SoftspiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout) { +void softio_spi_tx(SoftIoSpiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout) { furi_check(buffer); - softspi_trx(config, buffer, NULL, size, timeout); + softio_spi_trx(config, buffer, NULL, size, timeout); } -void softspi_rx(SoftspiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout) { +void softio_spi_rx(SoftIoSpiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout) { furi_check(buffer); - softspi_trx(config, NULL, buffer, size, timeout); + softio_spi_trx(config, NULL, buffer, size, timeout); } diff --git a/lib/softspi/softspi.h b/lib/softio/softio_spi.h similarity index 82% rename from lib/softspi/softspi.h rename to lib/softio/softio_spi.h index 9e27880ac8d..e7adb1a6ccf 100644 --- a/lib/softspi/softspi.h +++ b/lib/softio/softio_spi.h @@ -1,7 +1,7 @@ #pragma once /** - * @file softspi.h + * @file softio.h * Software (bit-banged) SPI implementation. Master-only. Supports all 4 modes * with clock rates of up to 200kHz. */ @@ -24,17 +24,17 @@ typedef struct { uint32_t clk_fq_khz; uint8_t clk_polarity : 1; uint8_t clk_phase : 1; -} SoftspiConfig; +} SoftIoSpiConfig; /** * @brief Initializes bus pins, brings the CS low */ -void softspi_acquire(SoftspiConfig* config); +void softio_spi_acquire(SoftIoSpiConfig* config); /** * @brief Brings the CS high, resets bus pins */ -void softspi_release(SoftspiConfig* config); +void softio_spi_release(SoftIoSpiConfig* config); /** * @brief Simultaneously transmits and receives a buffer on the software SPI bus @@ -44,8 +44,8 @@ void softspi_release(SoftspiConfig* config); * @param size Buffer length (both buffers must be of the same size) * @param timeout Timeout in ticks. Transaction will be interrupted abruptly if this timeout is reached. */ -void softspi_trx( - SoftspiConfig* config, +void softio_spi_trx( + SoftIoSpiConfig* config, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, @@ -58,7 +58,7 @@ void softspi_trx( * @param size Buffer length * @param timeout Timeout in ticks. Transmission will be interrupted abruptly if this timeout is reached. */ -void softspi_tx(SoftspiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout); +void softio_spi_tx(SoftIoSpiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout); /** * @brief Receives a buffer from the software SPI bus @@ -67,7 +67,7 @@ void softspi_tx(SoftspiConfig* config, const uint8_t* buffer, size_t size, uint3 * @param size Buffer length * @param timeout Timeout in ticks. Reception will be interrupted abruptly if this timeout is reached. */ -void softspi_rx(SoftspiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout); +void softio_spi_rx(SoftIoSpiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout); #ifdef __cplusplus } From 85a1d426e2c47764e0f1cc3c388b57528eb1db90 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 6 Sep 2024 17:05:11 +0300 Subject: [PATCH 4/7] docs: fix file name --- lib/softio/softio_spi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/softio/softio_spi.h b/lib/softio/softio_spi.h index e7adb1a6ccf..cd2ef06dedf 100644 --- a/lib/softio/softio_spi.h +++ b/lib/softio/softio_spi.h @@ -1,7 +1,7 @@ #pragma once /** - * @file softio.h + * @file softio_spi.h * Software (bit-banged) SPI implementation. Master-only. Supports all 4 modes * with clock rates of up to 200kHz. */ From 4118ca6ab4f2af1b34522630f962a9cb6f5eca33 Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Wed, 11 Sep 2024 17:15:43 +0300 Subject: [PATCH 5/7] refactor: softspi --- applications/debug/spi_test/spi_test.c | 26 ++- lib/softio/softio_spi.c | 234 ++++++++++++++++++------- lib/softio/softio_spi.h | 114 ++++++++++-- 3 files changed, 287 insertions(+), 87 deletions(-) diff --git a/applications/debug/spi_test/spi_test.c b/applications/debug/spi_test/spi_test.c index 12bcf3a4a48..dca4217d478 100644 --- a/applications/debug/spi_test/spi_test.c +++ b/applications/debug/spi_test/spi_test.c @@ -41,18 +41,32 @@ static void spi_test_submenu_callback(void* context, uint32_t index) { furi_hal_spi_release(handle); furi_hal_spi_bus_handle_deinit(handle); } else { - SoftIoSpiConfig config = { + // initialize + SoftIoSpiBusConfig bus_cfg = { .miso = &gpio_ext_pa6, .mosi = &gpio_ext_pa7, .sck = &gpio_ext_pb3, - .cs = &gpio_ext_pa4, - .clk_fq_khz = 100, .clk_polarity = 0, + }; + SoftIoSpiSlaveConfig dev_cfg = { + .cs = &gpio_ext_pa4, + .clk_fq_khz = 200, .clk_phase = 0, }; - softio_spi_acquire(&config); - softio_spi_trx(&config, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever); - softio_spi_release(&config); + SoftIoSpiBus* bus = softio_spi_alloc(&bus_cfg); + SoftIoSpiSlave* device = softio_spi_attach_slave(bus, &dev_cfg); + softio_spi_init(bus); + furi_delay_ms(100); + + // transmit + softio_spi_acquire(device); + softio_spi_trx(device, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever); + softio_spi_release(device); + + // deinitialize + softio_spi_deinit(bus); + softio_spi_detach_slave(bus, device); + softio_spi_free(bus); } FURI_LOG_I( diff --git a/lib/softio/softio_spi.c b/lib/softio/softio_spi.c index d8d35855a88..be802f7ddf3 100644 --- a/lib/softio/softio_spi.c +++ b/lib/softio/softio_spi.c @@ -4,109 +4,209 @@ #include #include #include +#include #define SOFTSPI_TIM TIM17 #define SOFTSPI_TIM_BUS FuriHalBusTIM17 #define SOFTSPI_TIM_IRQ FuriHalInterruptIdTim1TrgComTim17 #define SOFTSPI_TIM_FQ_KHZ 64000UL +struct SoftIoSpiSlave { + SoftIoSpiSlaveConfig config; + SoftIoSpiBus* owner; + uint8_t is_active; +}; + +ARRAY_DEF(BusSlaves, SoftIoSpiSlave, M_POD_OPLIST); + +struct SoftIoSpiBus { + SoftIoSpiBusConfig config; + BusSlaves_t slaves; + uint8_t is_currently_initd; +}; + typedef struct { - SoftIoSpiConfig* config; + SoftIoSpiBusConfig config; const uint8_t* tx_buffer; uint8_t* rx_buffer; + size_t size; FuriSemaphore* done_semaphore; + uint8_t bit : 3; + uint8_t sck_level : 1; + uint8_t done : 1; uint8_t - sck_level : 1; // cs, true); - furi_hal_gpio_write(config->sck, config->clk_polarity); - furi_hal_gpio_write(config->mosi, false); - furi_hal_gpio_init(config->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(config->sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(config->miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_init(config->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); - furi_hal_gpio_write(config->cs, false); + furi_check(config->miso); + furi_check(config->mosi); + furi_check(config->sck); + + SoftIoSpiBus* bus = malloc(sizeof(SoftIoSpiBus)); + bus->config = *config; + BusSlaves_init(bus->slaves); + + return bus; +} + +void softio_spi_free(SoftIoSpiBus* bus) { + furi_check(bus); + furi_check(BusSlaves_empty_p(bus->slaves)); + furi_check(!bus->is_currently_initd); + + BusSlaves_clear(bus->slaves); + free(bus); } -void softio_spi_release(SoftIoSpiConfig* config) { +SoftIoSpiSlave* softio_spi_attach_slave(SoftIoSpiBus* bus, SoftIoSpiSlaveConfig* config) { + furi_check(bus); + furi_check(!bus->is_currently_initd); furi_check(config); - furi_hal_gpio_write(config->cs, true); - furi_hal_gpio_init(config->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(config->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(config->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - furi_hal_gpio_init(config->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_check(config->cs); + furi_check(config->clk_fq_khz > 0); + // Serious low-level optimization is needed in order to push this further. + // This check is here to prevent a lockup caused by never-ending timer interrupts: + furi_check(config->clk_fq_khz <= 200); + + BusSlaves_push_back( + bus->slaves, + (SoftIoSpiSlave){ + .owner = bus, + .config = *config, + .is_active = false, + }); + + return BusSlaves_back(bus->slaves); } -static void softio_spi_timer_isr(void* param) { - if(!LL_TIM_IsActiveFlag_UPDATE(SOFTSPI_TIM)) return; +void softio_spi_detach_slave(SoftIoSpiBus* bus, SoftIoSpiSlave* device) { + furi_check(bus); + furi_check(!bus->is_currently_initd); + furi_check(device); + furi_check(device->owner == bus); - do { - SoftspiTimerIsrContext* context = param; + BusSlaves_it_t iter; + for(BusSlaves_it(iter, bus->slaves); !BusSlaves_end_p(iter); BusSlaves_next(iter)) + if(BusSlaves_cref(iter) == device) break; - furi_hal_gpio_write(context->config->sck, context->sck_level); - if(context->done) break; + BusSlaves_remove(bus->slaves, iter); +} - if(context->sck_level == context->out_level) { - // TX edge - if(context->tx_buffer) - furi_hal_gpio_write( - context->config->mosi, (*context->tx_buffer >> context->bit) & 1); - } else { - // RX edge - if(context->rx_buffer) - *context->rx_buffer |= furi_hal_gpio_read(context->config->miso) << context->bit; - - if(context->bit == 0) { - // entire byte transmitted - if(context->tx_buffer) context->tx_buffer++; - if(context->rx_buffer) context->rx_buffer++; - if(!--context->size) { - furi_semaphore_release(context->done_semaphore); - context->done = 1; - } else { - context->bit = 7; - } +void softio_spi_init(SoftIoSpiBus* bus) { + furi_check(bus); + furi_check(!bus->is_currently_initd); + + furi_hal_gpio_write(bus->config.mosi, false); + furi_hal_gpio_write(bus->config.sck, bus->config.clk_polarity); + furi_hal_gpio_init(bus->config.mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(bus->config.sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_init(bus->config.miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); + + BusSlaves_it_t iter; + for(BusSlaves_it(iter, bus->slaves); !BusSlaves_end_p(iter); BusSlaves_next(iter)) { + const SoftIoSpiSlave* device = BusSlaves_cref(iter); + furi_hal_gpio_write(device->config.cs, true); + furi_hal_gpio_init( + device->config.cs, GpioModeOutputOpenDrain, GpioPullUp, GpioSpeedVeryHigh); + } + + bus->is_currently_initd = true; +} + +void softio_spi_deinit(SoftIoSpiBus* bus) { + furi_check(bus); + furi_check(bus->is_currently_initd); + + furi_hal_gpio_init(bus->config.mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(bus->config.sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + furi_hal_gpio_init(bus->config.miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + BusSlaves_it_t iter; + for(BusSlaves_it(iter, bus->slaves); !BusSlaves_end_p(iter); BusSlaves_next(iter)) { + const SoftIoSpiSlave* device = BusSlaves_cref(iter); + furi_hal_gpio_init(device->config.cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + } + + bus->is_currently_initd = false; +} + +void softio_spi_acquire(SoftIoSpiSlave* device) { + furi_check(device); + furi_check(device->owner->is_currently_initd); + furi_hal_gpio_write(device->config.cs, false); + device->is_active = true; +} + +void softio_spi_release(SoftIoSpiSlave* device) { + furi_check(device); + furi_check(device->owner->is_currently_initd); + furi_hal_gpio_write(device->config.cs, true); + device->is_active = false; +} + +static void softio_spi_timer_isr(void* param) { + furi_check(LL_TIM_IsActiveFlag_UPDATE(SOFTSPI_TIM)); + LL_TIM_ClearFlag_UPDATE(SOFTSPI_TIM); + + SoftspiTimerIsrContext* context = param; + + if(context->done) { + furi_hal_gpio_write(context->config.sck, context->config.clk_polarity); + return; + } else { + furi_hal_gpio_write(context->config.sck, context->sck_level); + } + + if(context->sck_level == context->out_level) { + // TX edge + if(context->tx_buffer) + furi_hal_gpio_write(context->config.mosi, (*context->tx_buffer >> context->bit) & 1); + } else { + // RX edge + if(context->rx_buffer) + *context->rx_buffer |= furi_hal_gpio_read(context->config.miso) << context->bit; + + if(context->bit == 0) { + // entire byte transmitted + if(context->tx_buffer) context->tx_buffer++; + if(context->rx_buffer) context->rx_buffer++; + if(!--context->size) { + furi_semaphore_release(context->done_semaphore); + context->done = 1; } else { - context->bit--; + context->bit = 7; } + } else { + context->bit--; } + } - context->sck_level = !context->sck_level; - } while(0); - - LL_TIM_ClearFlag_UPDATE(SOFTSPI_TIM); + context->sck_level = !context->sck_level; } void softio_spi_trx( - SoftIoSpiConfig* config, + SoftIoSpiSlave* device, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, uint32_t timeout) { - furi_check(config); - // NOTE: serious low-level optimization is needed in order to push this further - // this check is here to prevent a lockup caused by never-ending timer interrupts - furi_check(config->clk_fq_khz <= 200); - if(size == 0) return; + furi_check(device); + furi_check(device->owner->is_currently_initd); + furi_check(device->is_active); + furi_assert(size); SoftspiTimerIsrContext context = { - .config = config, + .config = device->owner->config, .tx_buffer = tx_buffer, .rx_buffer = rx_buffer, .size = size, .bit = 7, .done_semaphore = furi_semaphore_alloc(1, 0), - .sck_level = config->clk_polarity, + .sck_level = device->owner->config.clk_polarity, .done = 0, - .out_level = config->clk_polarity ^ config->clk_phase, + .out_level = device->owner->config.clk_polarity ^ device->config.clk_phase, }; furi_hal_bus_enable(SOFTSPI_TIM_BUS); @@ -115,7 +215,7 @@ void softio_spi_trx( LL_TIM_SetPrescaler(SOFTSPI_TIM, 0); LL_TIM_SetCounterMode(SOFTSPI_TIM, LL_TIM_COUNTERMODE_UP); LL_TIM_SetAutoReload( - SOFTSPI_TIM, SOFTSPI_TIM_FQ_KHZ / config->clk_fq_khz / 2); // f_ISR = 2 f_CLK + SOFTSPI_TIM, SOFTSPI_TIM_FQ_KHZ / device->config.clk_fq_khz / 2); // f_ISR = 2 f_CLK LL_TIM_DisableARRPreload(SOFTSPI_TIM); LL_TIM_SetRepetitionCounter(SOFTSPI_TIM, 0); LL_TIM_SetClockDivision(SOFTSPI_TIM, LL_TIM_CLOCKDIVISION_DIV1); @@ -131,12 +231,12 @@ void softio_spi_trx( furi_semaphore_free(context.done_semaphore); } -void softio_spi_tx(SoftIoSpiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout) { +void softio_spi_tx(SoftIoSpiSlave* device, const uint8_t* buffer, size_t size, uint32_t timeout) { furi_check(buffer); - softio_spi_trx(config, buffer, NULL, size, timeout); + softio_spi_trx(device, buffer, NULL, size, timeout); } -void softio_spi_rx(SoftIoSpiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout) { +void softio_spi_rx(SoftIoSpiSlave* device, uint8_t* buffer, size_t size, uint32_t timeout) { furi_check(buffer); - softio_spi_trx(config, NULL, buffer, size, timeout); + softio_spi_trx(device, NULL, buffer, size, timeout); } diff --git a/lib/softio/softio_spi.h b/lib/softio/softio_spi.h index cd2ef06dedf..1207a973d0d 100644 --- a/lib/softio/softio_spi.h +++ b/lib/softio/softio_spi.h @@ -4,6 +4,36 @@ * @file softio_spi.h * Software (bit-banged) SPI implementation. Master-only. Supports all 4 modes * with clock rates of up to 200kHz. + * + * @example + * ```c + * // initialize + * SoftIoSpiBusConfig bus_cfg = { + * .miso = &gpio_ext_pa6, + * .mosi = &gpio_ext_pa7, + * .sck = &gpio_ext_pb3, + * .clk_polarity = 0, + * }; + * SoftIoSpiSlaveConfig dev_cfg = { + * .cs = &gpio_ext_pa4, + * .clk_fq_khz = 200, + * .clk_phase = 0, + * }; + * SoftIoSpiBus* bus = softio_spi_alloc(&bus_cfg); + * SoftIoSpiSlave* device = softio_spi_attach_slave(bus, &dev_cfg); + * softio_spi_init(bus); + * + * // transmit + * uint8_t buffer[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x55, 0xAA}; + * softio_spi_acquire(device); + * softio_spi_tx(device, buffer, sizeof(buffer), FuriWaitForever); + * softio_spi_release(device); + * + * // deinitialize + * softio_spi_deinit(bus); + * softio_spi_detach_slave(bus, device); + * softio_spi_free(bus); + * ``` */ #ifdef __cplusplus @@ -14,38 +44,94 @@ extern "C" { #include /** - * Software SPI bus configuration + * @brief Opaque software SPI bus handle + */ +typedef struct SoftIoSpiBus SoftIoSpiBus; + +/** + * @brief Opaque software SPI bus device handle + */ +typedef struct SoftIoSpiSlave SoftIoSpiSlave; + +/** + * @brief Software SPI bus configuration */ typedef struct { const GpioPin* miso; const GpioPin* mosi; const GpioPin* sck; + uint8_t clk_polarity : 1; +} SoftIoSpiBusConfig; + +/** + * @brief Software SPI bus device configuration + */ +typedef struct { const GpioPin* cs; uint32_t clk_fq_khz; - uint8_t clk_polarity : 1; - uint8_t clk_phase : 1; -} SoftIoSpiConfig; + uint8_t clk_phase : 1; +} SoftIoSpiSlaveConfig; + +/** + * @brief Allocates a software SPI bus with the specified configuration + * @param [in] config Pointer to the configuration structure. Does not have to remain valid after the function exits. + */ +SoftIoSpiBus* softio_spi_alloc(SoftIoSpiBusConfig* config); + +/** + * @brief Deallocates a software SPI bus + * @param [in] bus Software SPI bus + */ +void softio_spi_free(SoftIoSpiBus* bus); + +/** + * @brief Registers a slave with a software SPI bus + * @param [in] bus Software SPI bus + * @param [in] config Pointer to the configuration structure. Does not have to remain valid after the function exits. + */ +SoftIoSpiSlave* softio_spi_attach_slave(SoftIoSpiBus* bus, SoftIoSpiSlaveConfig* config); + +/** + * @brief Detaches a CS pin from a software SPI bus + * @param [in] bus Software SPI bus + * @param [in] device Software SPI bus device + */ +void softio_spi_detach_slave(SoftIoSpiBus* bus, SoftIoSpiSlave* device); + +/** + * @brief Initializes bus pins: MOSI, MISO and SCK and all CS pins + * @param [in] bus Software SPI bus + */ +void softio_spi_init(SoftIoSpiBus* bus); + +/** + * @brief Deinitializes bus pins: MOSI, MISO and SCK and all CS pins + * @param [in] bus Software SPI bus + */ +void softio_spi_deinit(SoftIoSpiBus* bus); /** - * @brief Initializes bus pins, brings the CS low + * @brief Brings the CS pin associated with a slave low + * @param [in] device Software SPI bus device */ -void softio_spi_acquire(SoftIoSpiConfig* config); +void softio_spi_acquire(SoftIoSpiSlave* device); /** - * @brief Brings the CS high, resets bus pins + * @brief Brings the CS pin associated with a slave high + * @param [in] device Software SPI bus device */ -void softio_spi_release(SoftIoSpiConfig* config); +void softio_spi_release(SoftIoSpiSlave* device); /** * @brief Simultaneously transmits and receives a buffer on the software SPI bus - * @param [in] config Software SPI bus configuration + * @param [in] device Software SPI bus device * @param [in] tx_buffer Buffer to transmit. May be NULL if transmission is not required. * @param [in] rx_buffer Buffer to receive data into. May be NULL if reception is not required. * @param size Buffer length (both buffers must be of the same size) * @param timeout Timeout in ticks. Transaction will be interrupted abruptly if this timeout is reached. */ void softio_spi_trx( - SoftIoSpiConfig* config, + SoftIoSpiSlave* device, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, @@ -53,21 +139,21 @@ void softio_spi_trx( /** * @brief Transmits a buffer on the software SPI bus - * @param [in] config Software SPI bus configuration + * @param [in] device Software SPI bus device * @param [in] buffer Buffer to transmit * @param size Buffer length * @param timeout Timeout in ticks. Transmission will be interrupted abruptly if this timeout is reached. */ -void softio_spi_tx(SoftIoSpiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout); +void softio_spi_tx(SoftIoSpiSlave* device, const uint8_t* buffer, size_t size, uint32_t timeout); /** * @brief Receives a buffer from the software SPI bus - * @param [in] config Software SPI bus configuration + * @param [in] device Software SPI bus device * @param [in] buffer Buffer to receive into * @param size Buffer length * @param timeout Timeout in ticks. Reception will be interrupted abruptly if this timeout is reached. */ -void softio_spi_rx(SoftIoSpiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout); +void softio_spi_rx(SoftIoSpiSlave* device, uint8_t* buffer, size_t size, uint32_t timeout); #ifdef __cplusplus } From 752b708c6380665c8f1f5f88974762d87a81ceee Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 20 Sep 2024 16:06:13 +0300 Subject: [PATCH 6/7] refactor: remove slave support --- applications/debug/spi_test/spi_test.c | 15 ++-- lib/softio/softio_spi.c | 99 +++----------------------- lib/softio/softio_spi.h | 54 +++----------- 3 files changed, 25 insertions(+), 143 deletions(-) diff --git a/applications/debug/spi_test/spi_test.c b/applications/debug/spi_test/spi_test.c index dca4217d478..b047f049d9c 100644 --- a/applications/debug/spi_test/spi_test.c +++ b/applications/debug/spi_test/spi_test.c @@ -47,25 +47,22 @@ static void spi_test_submenu_callback(void* context, uint32_t index) { .mosi = &gpio_ext_pa7, .sck = &gpio_ext_pb3, .clk_polarity = 0, - }; - SoftIoSpiSlaveConfig dev_cfg = { - .cs = &gpio_ext_pa4, .clk_fq_khz = 200, .clk_phase = 0, }; SoftIoSpiBus* bus = softio_spi_alloc(&bus_cfg); - SoftIoSpiSlave* device = softio_spi_attach_slave(bus, &dev_cfg); softio_spi_init(bus); - furi_delay_ms(100); + furi_hal_gpio_write(&gpio_ext_pa4, true); + furi_hal_gpio_init(&gpio_ext_pa4, GpioModeOutputOpenDrain, GpioPullUp, GpioSpeedVeryHigh); // transmit - softio_spi_acquire(device); - softio_spi_trx(device, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever); - softio_spi_release(device); + furi_hal_gpio_write(&gpio_ext_pa4, false); + softio_spi_trx(bus, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever); + furi_hal_gpio_write(&gpio_ext_pa4, true); // deinitialize + furi_hal_gpio_init(&gpio_ext_pa4, GpioModeAnalog, GpioPullNo, GpioSpeedLow); softio_spi_deinit(bus); - softio_spi_detach_slave(bus, device); softio_spi_free(bus); } diff --git a/lib/softio/softio_spi.c b/lib/softio/softio_spi.c index be802f7ddf3..16f193a1a10 100644 --- a/lib/softio/softio_spi.c +++ b/lib/softio/softio_spi.c @@ -4,24 +4,14 @@ #include #include #include -#include #define SOFTSPI_TIM TIM17 #define SOFTSPI_TIM_BUS FuriHalBusTIM17 #define SOFTSPI_TIM_IRQ FuriHalInterruptIdTim1TrgComTim17 #define SOFTSPI_TIM_FQ_KHZ 64000UL -struct SoftIoSpiSlave { - SoftIoSpiSlaveConfig config; - SoftIoSpiBus* owner; - uint8_t is_active; -}; - -ARRAY_DEF(BusSlaves, SoftIoSpiSlave, M_POD_OPLIST); - struct SoftIoSpiBus { SoftIoSpiBusConfig config; - BusSlaves_t slaves; uint8_t is_currently_initd; }; @@ -46,54 +36,16 @@ SoftIoSpiBus* softio_spi_alloc(SoftIoSpiBusConfig* config) { SoftIoSpiBus* bus = malloc(sizeof(SoftIoSpiBus)); bus->config = *config; - BusSlaves_init(bus->slaves); return bus; } void softio_spi_free(SoftIoSpiBus* bus) { furi_check(bus); - furi_check(BusSlaves_empty_p(bus->slaves)); furi_check(!bus->is_currently_initd); - - BusSlaves_clear(bus->slaves); free(bus); } -SoftIoSpiSlave* softio_spi_attach_slave(SoftIoSpiBus* bus, SoftIoSpiSlaveConfig* config) { - furi_check(bus); - furi_check(!bus->is_currently_initd); - furi_check(config); - furi_check(config->cs); - furi_check(config->clk_fq_khz > 0); - // Serious low-level optimization is needed in order to push this further. - // This check is here to prevent a lockup caused by never-ending timer interrupts: - furi_check(config->clk_fq_khz <= 200); - - BusSlaves_push_back( - bus->slaves, - (SoftIoSpiSlave){ - .owner = bus, - .config = *config, - .is_active = false, - }); - - return BusSlaves_back(bus->slaves); -} - -void softio_spi_detach_slave(SoftIoSpiBus* bus, SoftIoSpiSlave* device) { - furi_check(bus); - furi_check(!bus->is_currently_initd); - furi_check(device); - furi_check(device->owner == bus); - - BusSlaves_it_t iter; - for(BusSlaves_it(iter, bus->slaves); !BusSlaves_end_p(iter); BusSlaves_next(iter)) - if(BusSlaves_cref(iter) == device) break; - - BusSlaves_remove(bus->slaves, iter); -} - void softio_spi_init(SoftIoSpiBus* bus) { furi_check(bus); furi_check(!bus->is_currently_initd); @@ -104,14 +56,6 @@ void softio_spi_init(SoftIoSpiBus* bus) { furi_hal_gpio_init(bus->config.sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); furi_hal_gpio_init(bus->config.miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); - BusSlaves_it_t iter; - for(BusSlaves_it(iter, bus->slaves); !BusSlaves_end_p(iter); BusSlaves_next(iter)) { - const SoftIoSpiSlave* device = BusSlaves_cref(iter); - furi_hal_gpio_write(device->config.cs, true); - furi_hal_gpio_init( - device->config.cs, GpioModeOutputOpenDrain, GpioPullUp, GpioSpeedVeryHigh); - } - bus->is_currently_initd = true; } @@ -123,29 +67,9 @@ void softio_spi_deinit(SoftIoSpiBus* bus) { furi_hal_gpio_init(bus->config.sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(bus->config.miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - BusSlaves_it_t iter; - for(BusSlaves_it(iter, bus->slaves); !BusSlaves_end_p(iter); BusSlaves_next(iter)) { - const SoftIoSpiSlave* device = BusSlaves_cref(iter); - furi_hal_gpio_init(device->config.cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); - } - bus->is_currently_initd = false; } -void softio_spi_acquire(SoftIoSpiSlave* device) { - furi_check(device); - furi_check(device->owner->is_currently_initd); - furi_hal_gpio_write(device->config.cs, false); - device->is_active = true; -} - -void softio_spi_release(SoftIoSpiSlave* device) { - furi_check(device); - furi_check(device->owner->is_currently_initd); - furi_hal_gpio_write(device->config.cs, true); - device->is_active = false; -} - static void softio_spi_timer_isr(void* param) { furi_check(LL_TIM_IsActiveFlag_UPDATE(SOFTSPI_TIM)); LL_TIM_ClearFlag_UPDATE(SOFTSPI_TIM); @@ -187,26 +111,25 @@ static void softio_spi_timer_isr(void* param) { } void softio_spi_trx( - SoftIoSpiSlave* device, + SoftIoSpiBus* bus, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, uint32_t timeout) { - furi_check(device); - furi_check(device->owner->is_currently_initd); - furi_check(device->is_active); + furi_check(bus); + furi_check(bus->is_currently_initd); furi_assert(size); SoftspiTimerIsrContext context = { - .config = device->owner->config, + .config = bus->config, .tx_buffer = tx_buffer, .rx_buffer = rx_buffer, .size = size, .bit = 7, .done_semaphore = furi_semaphore_alloc(1, 0), - .sck_level = device->owner->config.clk_polarity, + .sck_level = bus->config.clk_polarity, .done = 0, - .out_level = device->owner->config.clk_polarity ^ device->config.clk_phase, + .out_level = bus->config.clk_polarity ^ bus->config.clk_phase, }; furi_hal_bus_enable(SOFTSPI_TIM_BUS); @@ -215,7 +138,7 @@ void softio_spi_trx( LL_TIM_SetPrescaler(SOFTSPI_TIM, 0); LL_TIM_SetCounterMode(SOFTSPI_TIM, LL_TIM_COUNTERMODE_UP); LL_TIM_SetAutoReload( - SOFTSPI_TIM, SOFTSPI_TIM_FQ_KHZ / device->config.clk_fq_khz / 2); // f_ISR = 2 f_CLK + SOFTSPI_TIM, SOFTSPI_TIM_FQ_KHZ / bus->config.clk_fq_khz / 2); // f_ISR = 2 f_CLK LL_TIM_DisableARRPreload(SOFTSPI_TIM); LL_TIM_SetRepetitionCounter(SOFTSPI_TIM, 0); LL_TIM_SetClockDivision(SOFTSPI_TIM, LL_TIM_CLOCKDIVISION_DIV1); @@ -231,12 +154,12 @@ void softio_spi_trx( furi_semaphore_free(context.done_semaphore); } -void softio_spi_tx(SoftIoSpiSlave* device, const uint8_t* buffer, size_t size, uint32_t timeout) { +void softio_spi_tx(SoftIoSpiBus* bus, const uint8_t* buffer, size_t size, uint32_t timeout) { furi_check(buffer); - softio_spi_trx(device, buffer, NULL, size, timeout); + softio_spi_trx(bus, buffer, NULL, size, timeout); } -void softio_spi_rx(SoftIoSpiSlave* device, uint8_t* buffer, size_t size, uint32_t timeout) { +void softio_spi_rx(SoftIoSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout) { furi_check(buffer); - softio_spi_trx(device, NULL, buffer, size, timeout); + softio_spi_trx(bus, NULL, buffer, size, timeout); } diff --git a/lib/softio/softio_spi.h b/lib/softio/softio_spi.h index 1207a973d0d..587f7a36eeb 100644 --- a/lib/softio/softio_spi.h +++ b/lib/softio/softio_spi.h @@ -48,11 +48,6 @@ extern "C" { */ typedef struct SoftIoSpiBus SoftIoSpiBus; -/** - * @brief Opaque software SPI bus device handle - */ -typedef struct SoftIoSpiSlave SoftIoSpiSlave; - /** * @brief Software SPI bus configuration */ @@ -60,18 +55,11 @@ typedef struct { const GpioPin* miso; const GpioPin* mosi; const GpioPin* sck; + uint32_t clk_fq_khz; + uint8_t clk_phase : 1; uint8_t clk_polarity : 1; } SoftIoSpiBusConfig; -/** - * @brief Software SPI bus device configuration - */ -typedef struct { - const GpioPin* cs; - uint32_t clk_fq_khz; - uint8_t clk_phase : 1; -} SoftIoSpiSlaveConfig; - /** * @brief Allocates a software SPI bus with the specified configuration * @param [in] config Pointer to the configuration structure. Does not have to remain valid after the function exits. @@ -84,20 +72,6 @@ SoftIoSpiBus* softio_spi_alloc(SoftIoSpiBusConfig* config); */ void softio_spi_free(SoftIoSpiBus* bus); -/** - * @brief Registers a slave with a software SPI bus - * @param [in] bus Software SPI bus - * @param [in] config Pointer to the configuration structure. Does not have to remain valid after the function exits. - */ -SoftIoSpiSlave* softio_spi_attach_slave(SoftIoSpiBus* bus, SoftIoSpiSlaveConfig* config); - -/** - * @brief Detaches a CS pin from a software SPI bus - * @param [in] bus Software SPI bus - * @param [in] device Software SPI bus device - */ -void softio_spi_detach_slave(SoftIoSpiBus* bus, SoftIoSpiSlave* device); - /** * @brief Initializes bus pins: MOSI, MISO and SCK and all CS pins * @param [in] bus Software SPI bus @@ -110,28 +84,16 @@ void softio_spi_init(SoftIoSpiBus* bus); */ void softio_spi_deinit(SoftIoSpiBus* bus); -/** - * @brief Brings the CS pin associated with a slave low - * @param [in] device Software SPI bus device - */ -void softio_spi_acquire(SoftIoSpiSlave* device); - -/** - * @brief Brings the CS pin associated with a slave high - * @param [in] device Software SPI bus device - */ -void softio_spi_release(SoftIoSpiSlave* device); - /** * @brief Simultaneously transmits and receives a buffer on the software SPI bus - * @param [in] device Software SPI bus device + * @param [in] bus Software SPI bus * @param [in] tx_buffer Buffer to transmit. May be NULL if transmission is not required. * @param [in] rx_buffer Buffer to receive data into. May be NULL if reception is not required. * @param size Buffer length (both buffers must be of the same size) * @param timeout Timeout in ticks. Transaction will be interrupted abruptly if this timeout is reached. */ void softio_spi_trx( - SoftIoSpiSlave* device, + SoftIoSpiBus* bus, const uint8_t* tx_buffer, uint8_t* rx_buffer, size_t size, @@ -139,21 +101,21 @@ void softio_spi_trx( /** * @brief Transmits a buffer on the software SPI bus - * @param [in] device Software SPI bus device + * @param [in] bus Software SPI bus * @param [in] buffer Buffer to transmit * @param size Buffer length * @param timeout Timeout in ticks. Transmission will be interrupted abruptly if this timeout is reached. */ -void softio_spi_tx(SoftIoSpiSlave* device, const uint8_t* buffer, size_t size, uint32_t timeout); +void softio_spi_tx(SoftIoSpiBus* bus, const uint8_t* buffer, size_t size, uint32_t timeout); /** * @brief Receives a buffer from the software SPI bus - * @param [in] device Software SPI bus device + * @param [in] bus Software SPI bus * @param [in] buffer Buffer to receive into * @param size Buffer length * @param timeout Timeout in ticks. Reception will be interrupted abruptly if this timeout is reached. */ -void softio_spi_rx(SoftIoSpiSlave* device, uint8_t* buffer, size_t size, uint32_t timeout); +void softio_spi_rx(SoftIoSpiBus* bus, uint8_t* buffer, size_t size, uint32_t timeout); #ifdef __cplusplus } From 1421156c363901b80530f28893f5fc0962d4b81f Mon Sep 17 00:00:00 2001 From: Anna Antonenko Date: Fri, 11 Oct 2024 15:47:35 +0300 Subject: [PATCH 7/7] fix: stop timer after transfer --- lib/softio/softio_spi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/softio/softio_spi.c b/lib/softio/softio_spi.c index 16f193a1a10..e68e579c1d9 100644 --- a/lib/softio/softio_spi.c +++ b/lib/softio/softio_spi.c @@ -78,6 +78,7 @@ static void softio_spi_timer_isr(void* param) { if(context->done) { furi_hal_gpio_write(context->config.sck, context->config.clk_polarity); + LL_TIM_DisableCounter(SOFTSPI_TIM); return; } else { furi_hal_gpio_write(context->config.sck, context->sck_level);