Skip to content

Commit 2503f1f

Browse files
feat: softspi
1 parent 99655c1 commit 2503f1f

File tree

9 files changed

+399
-9
lines changed

9 files changed

+399
-9
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
App(
2+
appid="spi_test",
3+
name="SPI Test",
4+
apptype=FlipperAppType.DEBUG,
5+
entry_point="spi_test_app",
6+
requires=["gui"],
7+
stack_size=1 * 1024,
8+
fap_category="Debug",
9+
fap_libs=["softspi"]
10+
)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#include <furi_hal.h>
2+
#include <furi_hal_spi.h>
3+
#include <furi_hal_resources.h>
4+
#include <furi.h>
5+
6+
#include <gui/gui.h>
7+
#include <gui/view_dispatcher.h>
8+
#include <gui/modules/submenu.h>
9+
10+
#include <lib/softspi/softspi.h>
11+
12+
#define TAG "SpiTest"
13+
14+
typedef struct {
15+
Gui* gui;
16+
ViewDispatcher* view_dispatcher;
17+
Submenu* submenu;
18+
} SpiTest;
19+
20+
typedef enum {
21+
SpiTestViewSubmenu,
22+
} SpiTestView;
23+
24+
typedef enum {
25+
SpiTestSubmenuHardwareTx,
26+
SpiTestSubmenuSoftwareTx,
27+
} SpiTestSubmenu;
28+
29+
static void spi_test_submenu_callback(void* context, uint32_t index) {
30+
SpiTest* instance = (SpiTest*)context;
31+
UNUSED(instance);
32+
33+
uint8_t tx_buffer[] = {0x55, 0xAA};
34+
uint8_t rx_buffer[sizeof(tx_buffer)] = {0};
35+
36+
if(index == SpiTestSubmenuHardwareTx) {
37+
FuriHalSpiBusHandle* handle = &furi_hal_spi_bus_handle_external;
38+
furi_hal_spi_bus_handle_init(handle);
39+
furi_hal_spi_acquire(handle);
40+
furi_hal_spi_bus_trx(handle, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever);
41+
furi_hal_spi_release(handle);
42+
furi_hal_spi_bus_handle_deinit(handle);
43+
} else {
44+
SoftspiConfig config = {
45+
.miso = &gpio_ext_pa6,
46+
.mosi = &gpio_ext_pa7,
47+
.sck = &gpio_ext_pb3,
48+
.cs = &gpio_ext_pa4,
49+
.clk_fq_khz = 100,
50+
.clk_polarity = 0,
51+
.clk_phase = 0,
52+
};
53+
softspi_acquire(&config);
54+
softspi_trx(&config, tx_buffer, rx_buffer, sizeof(tx_buffer), FuriWaitForever);
55+
softspi_release(&config);
56+
}
57+
58+
FURI_LOG_I(
59+
TAG,
60+
"sent %02hhx %02hhX, received %02hhx %02hhX",
61+
tx_buffer[0],
62+
tx_buffer[1],
63+
rx_buffer[0],
64+
rx_buffer[1]);
65+
}
66+
67+
static uint32_t spi_test_exit_callback(void* context) {
68+
UNUSED(context);
69+
return VIEW_NONE;
70+
}
71+
72+
SpiTest* spi_test_alloc(void) {
73+
SpiTest* instance = malloc(sizeof(SpiTest));
74+
75+
View* view = NULL;
76+
77+
instance->gui = furi_record_open(RECORD_GUI);
78+
instance->view_dispatcher = view_dispatcher_alloc();
79+
view_dispatcher_attach_to_gui(
80+
instance->view_dispatcher, instance->gui, ViewDispatcherTypeFullscreen);
81+
82+
// Menu
83+
instance->submenu = submenu_alloc();
84+
view = submenu_get_view(instance->submenu);
85+
view_set_previous_callback(view, spi_test_exit_callback);
86+
view_dispatcher_add_view(instance->view_dispatcher, SpiTestViewSubmenu, view);
87+
submenu_add_item(
88+
instance->submenu,
89+
"Hardware TRX",
90+
SpiTestSubmenuHardwareTx,
91+
spi_test_submenu_callback,
92+
instance);
93+
submenu_add_item(
94+
instance->submenu,
95+
"Software TRX",
96+
SpiTestSubmenuSoftwareTx,
97+
spi_test_submenu_callback,
98+
instance);
99+
100+
return instance;
101+
}
102+
103+
void spi_test_free(SpiTest* instance) {
104+
view_dispatcher_remove_view(instance->view_dispatcher, SpiTestViewSubmenu);
105+
submenu_free(instance->submenu);
106+
107+
view_dispatcher_free(instance->view_dispatcher);
108+
furi_record_close(RECORD_GUI);
109+
110+
free(instance);
111+
}
112+
113+
int32_t spi_test_run(SpiTest* instance) {
114+
view_dispatcher_switch_to_view(instance->view_dispatcher, SpiTestViewSubmenu);
115+
view_dispatcher_run(instance->view_dispatcher);
116+
return 0;
117+
}
118+
119+
int32_t spi_test_app(void* p) {
120+
UNUSED(p);
121+
122+
SpiTest* instance = spi_test_alloc();
123+
124+
int32_t ret = spi_test_run(instance);
125+
126+
spi_test_free(instance);
127+
128+
return ret;
129+
}

lib/SConscript

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ libs = env.BuildModules(
4343
"ble_profile",
4444
"bit_lib",
4545
"datetime",
46+
"softspi"
4647
],
4748
)
4849

lib/softspi/SConscript

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Import("env")
2+
3+
env.Append(
4+
LINT_SOURCES=[
5+
Dir("."),
6+
],
7+
CPPPATH=[
8+
"#/lib/softspi",
9+
],
10+
SDK_HEADERS=[
11+
File("softspi.h"),
12+
],
13+
)
14+
15+
libenv = env.Clone(FW_LIB_NAME="softspi")
16+
libenv.ApplyLibFlags()
17+
18+
sources = libenv.GlobRecursive("*.c*")
19+
20+
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
21+
libenv.Install("${LIB_DIST_DIR}", lib)
22+
Return("lib")

lib/softspi/softspi.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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+
}

lib/softspi/softspi.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#pragma once
2+
3+
/**
4+
* @file softspi.h
5+
* Software (bit-banged) SPI implementation. Master-only. Supports all 4 modes
6+
* with clock rates of up to 200kHz.
7+
*/
8+
9+
#ifdef __cplusplus
10+
extern "C" {
11+
#endif
12+
13+
#include <stdint.h>
14+
#include <furi.h>
15+
16+
/**
17+
* Software SPI bus configuration
18+
*/
19+
typedef struct {
20+
const GpioPin* miso;
21+
const GpioPin* mosi;
22+
const GpioPin* sck;
23+
const GpioPin* cs;
24+
uint32_t clk_fq_khz;
25+
uint8_t clk_polarity : 1;
26+
uint8_t clk_phase : 1;
27+
} SoftspiConfig;
28+
29+
/**
30+
* @brief Initializes bus pins, brings the CS low
31+
*/
32+
void softspi_acquire(SoftspiConfig* config);
33+
34+
/**
35+
* @brief Brings the CS high, resets bus pins
36+
*/
37+
void softspi_release(SoftspiConfig* config);
38+
39+
/**
40+
* @brief Simultaneously transmits and receives a buffer on the software SPI bus
41+
* @param [in] config Software SPI bus configuration
42+
* @param [in] tx_buffer Buffer to transmit. May be NULL if transmission is not required.
43+
* @param [in] rx_buffer Buffer to receive data into. May be NULL if reception is not required.
44+
* @param size Buffer length (both buffers must be of the same size)
45+
* @param timeout Timeout in ticks. Transaction will be interrupted abruptly if this timeout is reached.
46+
*/
47+
void softspi_trx(
48+
SoftspiConfig* config,
49+
const uint8_t* tx_buffer,
50+
uint8_t* rx_buffer,
51+
size_t size,
52+
uint32_t timeout);
53+
54+
/**
55+
* @brief Transmits a buffer on the software SPI bus
56+
* @param [in] config Software SPI bus configuration
57+
* @param [in] buffer Buffer to transmit
58+
* @param size Buffer length
59+
* @param timeout Timeout in ticks. Transmission will be interrupted abruptly if this timeout is reached.
60+
*/
61+
void softspi_tx(SoftspiConfig* config, const uint8_t* buffer, size_t size, uint32_t timeout);
62+
63+
/**
64+
* @brief Receives a buffer from the software SPI bus
65+
* @param [in] config Software SPI bus configuration
66+
* @param [in] buffer Buffer to receive into
67+
* @param size Buffer length
68+
* @param timeout Timeout in ticks. Reception will be interrupted abruptly if this timeout is reached.
69+
*/
70+
void softspi_rx(SoftspiConfig* config, uint8_t* buffer, size_t size, uint32_t timeout);
71+
72+
#ifdef __cplusplus
73+
}
74+
#endif

0 commit comments

Comments
 (0)