|
| 1 | +#include "softspi.h" |
| 2 | +#include <stm32wbxx_ll_tim.h> |
| 3 | +#include <furi_hal_bus.h> |
| 4 | +#include <furi_hal_gpio.h> |
| 5 | +#include <furi_hal_resources.h> |
| 6 | +#include <furi_hal_interrupt.h> |
| 7 | + |
| 8 | +#define SOFTSPI_TIM TIM17 |
| 9 | +#define SOFTSPI_TIM_BUS FuriHalBusTIM17 |
| 10 | +#define SOFTSPI_TIM_IRQ FuriHalInterruptIdTim1TrgComTim17 |
| 11 | +#define SOFTSPI_TIM_FQ_KHZ 64000UL |
| 12 | + |
| 13 | +typedef struct { |
| 14 | + SoftspiConfig* config; |
| 15 | + const uint8_t* tx_buffer; |
| 16 | + uint8_t* rx_buffer; |
| 17 | + FuriSemaphore* done_semaphore; |
| 18 | + uint8_t |
| 19 | + sck_level : 1; // <! which edge (1=rising, 0=falling) to transmit on; reception is done on the opposite edge |
| 20 | + uint8_t done : 1; |
| 21 | + uint8_t out_level : 1; |
| 22 | + uint8_t clk_polarity : 1; |
| 23 | + size_t size; |
| 24 | + uint8_t bit; |
| 25 | +} SoftspiTimerIsrContext; |
| 26 | + |
| 27 | +void softspi_acquire(SoftspiConfig* config) { |
| 28 | + furi_check(config); |
| 29 | + furi_hal_gpio_write(config->cs, true); |
| 30 | + furi_hal_gpio_write(config->sck, config->clk_polarity); |
| 31 | + furi_hal_gpio_write(config->mosi, false); |
| 32 | + furi_hal_gpio_init(config->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); |
| 33 | + furi_hal_gpio_init(config->sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); |
| 34 | + furi_hal_gpio_init(config->miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh); |
| 35 | + furi_hal_gpio_init(config->cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); |
| 36 | + furi_hal_gpio_write(config->cs, false); |
| 37 | +} |
| 38 | + |
| 39 | +void softspi_release(SoftspiConfig* config) { |
| 40 | + furi_check(config); |
| 41 | + furi_hal_gpio_write(config->cs, true); |
| 42 | + furi_hal_gpio_init(config->cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow); |
| 43 | + furi_hal_gpio_init(config->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow); |
| 44 | + furi_hal_gpio_init(config->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow); |
| 45 | + furi_hal_gpio_init(config->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow); |
| 46 | +} |
| 47 | + |
| 48 | +static void furi_hal_spi_softbus_timer_isr(void* param) { |
| 49 | + if(!LL_TIM_IsActiveFlag_UPDATE(SOFTSPI_TIM)) return; |
| 50 | + |
| 51 | + do { |
| 52 | + SoftspiTimerIsrContext* context = param; |
| 53 | + |
| 54 | + furi_hal_gpio_write(context->config->sck, context->sck_level); |
| 55 | + if(context->done) break; |
| 56 | + |
| 57 | + if(context->sck_level == context->out_level) { |
| 58 | + // TX edge |
| 59 | + if(context->tx_buffer) |
| 60 | + furi_hal_gpio_write( |
| 61 | + context->config->mosi, (*context->tx_buffer >> context->bit) & 1); |
| 62 | + } else { |
| 63 | + // RX edge |
| 64 | + if(context->rx_buffer) |
| 65 | + *context->rx_buffer |= furi_hal_gpio_read(context->config->miso) << context->bit; |
| 66 | + |
| 67 | + if(context->bit == 0) { |
| 68 | + // entire byte transmitted |
| 69 | + if(context->tx_buffer) context->tx_buffer++; |
| 70 | + if(context->rx_buffer) context->rx_buffer++; |
| 71 | + if(!--context->size) { |
| 72 | + furi_semaphore_release(context->done_semaphore); |
| 73 | + context->done = 1; |
| 74 | + } else { |
| 75 | + context->bit = 7; |
| 76 | + } |
| 77 | + } else { |
| 78 | + context->bit--; |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + context->sck_level = !context->sck_level; |
| 83 | + } while(0); |
| 84 | + |
| 85 | + LL_TIM_ClearFlag_UPDATE(SOFTSPI_TIM); |
| 86 | +} |
| 87 | + |
| 88 | +void softspi_trx( |
| 89 | + SoftspiConfig* config, |
| 90 | + const uint8_t* tx_buffer, |
| 91 | + uint8_t* rx_buffer, |
| 92 | + size_t size, |
| 93 | + uint32_t timeout) { |
| 94 | + furi_check(config); |
| 95 | + // NOTE: serious low-level optimization is needed in order to push this further |
| 96 | + // this check is here to prevent a lockup caused by never-ending timer interrupts |
| 97 | + furi_check(config->clk_fq_khz <= 200); |
| 98 | + if(size == 0) return; |
| 99 | + |
| 100 | + SoftspiTimerIsrContext context = { |
| 101 | + .config = config, |
| 102 | + .tx_buffer = tx_buffer, |
| 103 | + .rx_buffer = rx_buffer, |
| 104 | + .size = size, |
| 105 | + .bit = 7, |
| 106 | + .done_semaphore = furi_semaphore_alloc(1, 0), |
| 107 | + .sck_level = config->clk_polarity, |
| 108 | + .done = 0, |
| 109 | + .out_level = config->clk_polarity ^ config->clk_phase, |
| 110 | + }; |
| 111 | + |
| 112 | + furi_hal_bus_enable(SOFTSPI_TIM_BUS); |
| 113 | + furi_hal_interrupt_set_isr_ex( |
| 114 | + SOFTSPI_TIM_IRQ, FuriHalInterruptPriorityHighest, furi_hal_spi_softbus_timer_isr, &context); |
| 115 | + LL_TIM_SetPrescaler(SOFTSPI_TIM, 0); |
| 116 | + LL_TIM_SetCounterMode(SOFTSPI_TIM, LL_TIM_COUNTERMODE_UP); |
| 117 | + LL_TIM_SetAutoReload( |
| 118 | + SOFTSPI_TIM, SOFTSPI_TIM_FQ_KHZ / config->clk_fq_khz / 2); // f_ISR = 2 f_CLK |
| 119 | + LL_TIM_DisableARRPreload(SOFTSPI_TIM); |
| 120 | + LL_TIM_SetRepetitionCounter(SOFTSPI_TIM, 0); |
| 121 | + LL_TIM_SetClockDivision(SOFTSPI_TIM, LL_TIM_CLOCKDIVISION_DIV1); |
| 122 | + LL_TIM_SetClockSource(SOFTSPI_TIM, LL_TIM_CLOCKSOURCE_INTERNAL); |
| 123 | + LL_TIM_GenerateEvent_UPDATE(SOFTSPI_TIM); |
| 124 | + LL_TIM_EnableIT_UPDATE(SOFTSPI_TIM); |
| 125 | + LL_TIM_EnableCounter(SOFTSPI_TIM); |
| 126 | + |
| 127 | + furi_semaphore_acquire(context.done_semaphore, timeout); |
| 128 | + |
| 129 | + furi_hal_interrupt_set_isr(SOFTSPI_TIM_IRQ, NULL, NULL); |
| 130 | + furi_hal_bus_disable(SOFTSPI_TIM_BUS); |
| 131 | + furi_semaphore_free(context.done_semaphore); |
| 132 | +} |
| 133 | + |
| 134 | +void softspi_tx(SoftspiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout) { |
| 135 | + furi_check(buffer); |
| 136 | + softspi_trx(config, buffer, NULL, size, timeout); |
| 137 | +} |
| 138 | + |
| 139 | +void softspi_rx(SoftspiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout) { |
| 140 | + furi_check(buffer); |
| 141 | + softspi_trx(config, NULL, buffer, size, timeout); |
| 142 | +} |
0 commit comments