Skip to content

Commit fcd778b

Browse files
lib: bm_spi_mngr: add bare metal SPI transaction manager
Add a bare metal SPI transaction manager library on top of the nrfx SPIM driver. The library serializes SPIM work on a single hardware instance by keeping pending transactions in a FIFO queue and running them back-to-back on the bus. Each transaction is a sequence of one or more TX/RX transfers, with optional begin and end callbacks. The library is functionally equivalent to the legacy nRF5 SDK nrf_spi_mngr module, ported to NCS conventions: nrfx_spim instead of nrf_drv_spi, Zephyr ring buffer instead of nrf_queue, irq_lock around shared queue access, and int return codes mapped from nrfx. It exposes both a non-blocking API (bm_spi_mngr_schedule) and a blocking API (bm_spi_mngr_perform) on top of a shared scheduling engine. Each transaction may carry its own SPIM configuration, in which case the driver is reinitialized before the first transfer if the configuration differs from the one currently in use. bm_spi_mngr_perform asserts that its idle hook is not called from interrupt context, as a precaution against silent deadlocks where the SPIM interrupt that ends the transaction would not be able to run. Signed-off-by: Martynas Smilingis <martynas.smilingis@nordicsemi.no>
1 parent b272386 commit fcd778b

13 files changed

Lines changed: 961 additions & 2 deletions

File tree

boards/nordic/bm_nrf54l15dk/include/board-config.h

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,42 @@ extern "C" {
6363
#define BOARD_CONSOLE_UARTE_PIN_CTS NRF_PIN_PORT_TO_PIN_NUMBER(7, 1)
6464
#endif
6565

66+
/* Application SPI master configuration */
67+
#ifndef BOARD_APP_SPIM_INST
68+
#define BOARD_APP_SPIM_INST NRF_SPIM21
69+
#endif
70+
71+
#ifndef BOARD_APP_SPIM_PIN_SCK
72+
#define BOARD_APP_SPIM_PIN_SCK NRF_PIN_PORT_TO_PIN_NUMBER(11, 1)
73+
#endif
74+
#ifndef BOARD_APP_SPIM_PIN_MOSI
75+
#define BOARD_APP_SPIM_PIN_MOSI NRF_PIN_PORT_TO_PIN_NUMBER(12, 1)
76+
#endif
77+
#ifndef BOARD_APP_SPIM_PIN_MISO
78+
#define BOARD_APP_SPIM_PIN_MISO NRF_PIN_PORT_TO_PIN_NUMBER(13, 1)
79+
#endif
80+
#ifndef BOARD_APP_SPIM_PIN_CSN
81+
#define BOARD_APP_SPIM_PIN_CSN NRF_PIN_PORT_TO_PIN_NUMBER(14, 1)
82+
#endif
83+
84+
/* Application SPI slave configuration */
85+
#ifndef BOARD_APP_SPIS_INST
86+
#define BOARD_APP_SPIS_INST NRF_SPIS30
87+
#endif
88+
89+
#ifndef BOARD_APP_SPIS_PIN_SCK
90+
#define BOARD_APP_SPIS_PIN_SCK NRF_PIN_PORT_TO_PIN_NUMBER(0, 0)
91+
#endif
92+
#ifndef BOARD_APP_SPIS_PIN_MOSI
93+
#define BOARD_APP_SPIS_PIN_MOSI NRF_PIN_PORT_TO_PIN_NUMBER(1, 0)
94+
#endif
95+
#ifndef BOARD_APP_SPIS_PIN_MISO
96+
#define BOARD_APP_SPIS_PIN_MISO NRF_PIN_PORT_TO_PIN_NUMBER(2, 0)
97+
#endif
98+
#ifndef BOARD_APP_SPIS_PIN_CSN
99+
#define BOARD_APP_SPIS_PIN_CSN NRF_PIN_PORT_TO_PIN_NUMBER(3, 0)
100+
#endif
101+
66102
/* Application UART configuration */
67103
#ifndef BOARD_APP_UARTE_INST
68104
#define BOARD_APP_UARTE_INST NRF_UARTE30
@@ -104,17 +140,20 @@ extern "C" {
104140
#define BOARD_APP_LPUARTE_INST NRF_UARTE21
105141
#endif
106142

143+
/* Shared with SPI master SCK. */
107144
#ifndef BOARD_APP_LPUARTE_PIN_TX
108145
#define BOARD_APP_LPUARTE_PIN_TX NRF_PIN_PORT_TO_PIN_NUMBER(11, 1)
109146
#endif
147+
/* Shared with SPI master MOSI. */
110148
#ifndef BOARD_APP_LPUARTE_PIN_RX
111149
#define BOARD_APP_LPUARTE_PIN_RX NRF_PIN_PORT_TO_PIN_NUMBER(12, 1)
112150
#endif
113151
#ifndef BOARD_APP_LPUARTE_PIN_REQ
114152
#define BOARD_APP_LPUARTE_PIN_REQ NRF_PIN_PORT_TO_PIN_NUMBER(4, 0) /* Shared with button 3. */
115153
#endif
154+
/* Shared with LED 3 and SPI master CSN. */
116155
#ifndef BOARD_APP_LPUARTE_PIN_RDY
117-
#define BOARD_APP_LPUARTE_PIN_RDY NRF_PIN_PORT_TO_PIN_NUMBER(14, 1) /* Shared with LED 3. */
156+
#define BOARD_APP_LPUARTE_PIN_RDY NRF_PIN_PORT_TO_PIN_NUMBER(14, 1)
118157
#endif
119158

120159
#ifdef __cplusplus

boards/nordic/bm_nrf54lm20dk/include/board-config.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,42 @@ extern "C" {
6363
#define BOARD_CONSOLE_UARTE_PIN_CTS NRF_PIN_PORT_TO_PIN_NUMBER(19, 1)
6464
#endif
6565

66+
/* Application SPI master configuration */
67+
#ifndef BOARD_APP_SPIM_INST
68+
#define BOARD_APP_SPIM_INST NRF_SPIM21
69+
#endif
70+
71+
#ifndef BOARD_APP_SPIM_PIN_SCK
72+
#define BOARD_APP_SPIM_PIN_SCK NRF_PIN_PORT_TO_PIN_NUMBER(11, 1)
73+
#endif
74+
#ifndef BOARD_APP_SPIM_PIN_MOSI
75+
#define BOARD_APP_SPIM_PIN_MOSI NRF_PIN_PORT_TO_PIN_NUMBER(12, 1)
76+
#endif
77+
#ifndef BOARD_APP_SPIM_PIN_MISO
78+
#define BOARD_APP_SPIM_PIN_MISO NRF_PIN_PORT_TO_PIN_NUMBER(13, 1)
79+
#endif
80+
#ifndef BOARD_APP_SPIM_PIN_CSN
81+
#define BOARD_APP_SPIM_PIN_CSN NRF_PIN_PORT_TO_PIN_NUMBER(14, 1)
82+
#endif
83+
84+
/* Application SPI slave configuration */
85+
#ifndef BOARD_APP_SPIS_INST
86+
#define BOARD_APP_SPIS_INST NRF_SPIS30
87+
#endif
88+
89+
#ifndef BOARD_APP_SPIS_PIN_SCK
90+
#define BOARD_APP_SPIS_PIN_SCK NRF_PIN_PORT_TO_PIN_NUMBER(0, 0)
91+
#endif
92+
#ifndef BOARD_APP_SPIS_PIN_MOSI
93+
#define BOARD_APP_SPIS_PIN_MOSI NRF_PIN_PORT_TO_PIN_NUMBER(1, 0)
94+
#endif
95+
#ifndef BOARD_APP_SPIS_PIN_MISO
96+
#define BOARD_APP_SPIS_PIN_MISO NRF_PIN_PORT_TO_PIN_NUMBER(2, 0)
97+
#endif
98+
#ifndef BOARD_APP_SPIS_PIN_CSN
99+
#define BOARD_APP_SPIS_PIN_CSN NRF_PIN_PORT_TO_PIN_NUMBER(3, 0)
100+
#endif
101+
66102
/* Application UART configuration */
67103
#ifndef BOARD_APP_UARTE_INST
68104
#define BOARD_APP_UARTE_INST NRF_UARTE30

boards/nordic/bm_nrf54ls05dk/include/board-config.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,42 @@ extern "C" {
6363
#define BOARD_CONSOLE_UARTE_PIN_CTS NRF_PIN_PORT_TO_PIN_NUMBER(7, 1)
6464
#endif
6565

66+
/* Application SPI master configuration */
67+
#ifndef BOARD_APP_SPIM_INST
68+
#define BOARD_APP_SPIM_INST NRF_SPIM21
69+
#endif
70+
71+
#ifndef BOARD_APP_SPIM_PIN_SCK
72+
#define BOARD_APP_SPIM_PIN_SCK NRF_PIN_PORT_TO_PIN_NUMBER(2, 1)
73+
#endif
74+
#ifndef BOARD_APP_SPIM_PIN_MOSI
75+
#define BOARD_APP_SPIM_PIN_MOSI NRF_PIN_PORT_TO_PIN_NUMBER(3, 1)
76+
#endif
77+
#ifndef BOARD_APP_SPIM_PIN_MISO
78+
#define BOARD_APP_SPIM_PIN_MISO NRF_PIN_PORT_TO_PIN_NUMBER(5, 1)
79+
#endif
80+
#ifndef BOARD_APP_SPIM_PIN_CSN
81+
#define BOARD_APP_SPIM_PIN_CSN NRF_PIN_PORT_TO_PIN_NUMBER(6, 1)
82+
#endif
83+
84+
/* Application SPI slave configuration */
85+
#ifndef BOARD_APP_SPIS_INST
86+
#define BOARD_APP_SPIS_INST NRF_SPIS22
87+
#endif
88+
89+
#ifndef BOARD_APP_SPIS_PIN_SCK
90+
#define BOARD_APP_SPIS_PIN_SCK NRF_PIN_PORT_TO_PIN_NUMBER(11, 1)
91+
#endif
92+
#ifndef BOARD_APP_SPIS_PIN_MOSI
93+
#define BOARD_APP_SPIS_PIN_MOSI NRF_PIN_PORT_TO_PIN_NUMBER(12, 1)
94+
#endif
95+
#ifndef BOARD_APP_SPIS_PIN_MISO
96+
#define BOARD_APP_SPIS_PIN_MISO NRF_PIN_PORT_TO_PIN_NUMBER(21, 1)
97+
#endif
98+
#ifndef BOARD_APP_SPIS_PIN_CSN
99+
#define BOARD_APP_SPIS_PIN_CSN NRF_PIN_PORT_TO_PIN_NUMBER(22, 1)
100+
#endif
101+
66102
/* Application UART configuration */
67103
#ifndef BOARD_APP_UARTE_INST
68104
#define BOARD_APP_UARTE_INST NRF_UARTE21

boards/nordic/bm_nrf54lv10dk/include/board-config.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,42 @@ extern "C" {
6363
#define BOARD_CONSOLE_UARTE_PIN_CTS NRF_PIN_PORT_TO_PIN_NUMBER(7, 1)
6464
#endif
6565

66+
/* Application SPI master configuration */
67+
#ifndef BOARD_APP_SPIM_INST
68+
#define BOARD_APP_SPIM_INST NRF_SPIM21
69+
#endif
70+
71+
#ifndef BOARD_APP_SPIM_PIN_SCK
72+
#define BOARD_APP_SPIM_PIN_SCK NRF_PIN_PORT_TO_PIN_NUMBER(0, 1)
73+
#endif
74+
#ifndef BOARD_APP_SPIM_PIN_MOSI
75+
#define BOARD_APP_SPIM_PIN_MOSI NRF_PIN_PORT_TO_PIN_NUMBER(1, 1)
76+
#endif
77+
#ifndef BOARD_APP_SPIM_PIN_MISO
78+
#define BOARD_APP_SPIM_PIN_MISO NRF_PIN_PORT_TO_PIN_NUMBER(2, 1)
79+
#endif
80+
#ifndef BOARD_APP_SPIM_PIN_CSN
81+
#define BOARD_APP_SPIM_PIN_CSN NRF_PIN_PORT_TO_PIN_NUMBER(3, 1)
82+
#endif
83+
84+
/* Application SPI slave configuration */
85+
#ifndef BOARD_APP_SPIS_INST
86+
#define BOARD_APP_SPIS_INST NRF_SPIS21
87+
#endif
88+
89+
#ifndef BOARD_APP_SPIS_PIN_SCK
90+
#define BOARD_APP_SPIS_PIN_SCK NRF_PIN_PORT_TO_PIN_NUMBER(0, 1)
91+
#endif
92+
#ifndef BOARD_APP_SPIS_PIN_MOSI
93+
#define BOARD_APP_SPIS_PIN_MOSI NRF_PIN_PORT_TO_PIN_NUMBER(1, 1)
94+
#endif
95+
#ifndef BOARD_APP_SPIS_PIN_MISO
96+
#define BOARD_APP_SPIS_PIN_MISO NRF_PIN_PORT_TO_PIN_NUMBER(2, 1)
97+
#endif
98+
#ifndef BOARD_APP_SPIS_PIN_CSN
99+
#define BOARD_APP_SPIS_PIN_CSN NRF_PIN_PORT_TO_PIN_NUMBER(3, 1)
100+
#endif
101+
66102
/* Application UART configuration */
67103
#ifndef BOARD_APP_UARTE_INST
68104
#define BOARD_APP_UARTE_INST NRF_UARTE30

doc/nrf-bm/api/api.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ Bare Metal Zephyr Memory Storage (ZMS)
145145

146146
.. doxygengroup:: bm_zms
147147

148+
.. _api_bm_spi_mngr:
149+
150+
SPI transaction manager
151+
=======================
152+
153+
.. doxygengroup:: bm_spi_mngr
154+
148155
.. _api_ble_gatt_queue:
149156

150157
GATT Queue
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
.. _lib_bm_spi_mngr:
2+
3+
SPI transaction manager
4+
#######################
5+
6+
.. contents::
7+
:local:
8+
:depth: 2
9+
10+
The SPI transaction manager library serializes SPI master (SPIM) work on one hardware instance.
11+
Applications describe work as *transactions* (one or more TX/RX *transfers* in order), and the library keeps pending transactions in a FIFO queue and runs them back-to-back on the bus.
12+
13+
Overview
14+
********
15+
16+
The library sits on top of the nrfx SPIM driver and uses a Zephyr ring buffer as a FIFO queue of transaction pointers.
17+
The queue is accessed both from the application (when scheduling) and from the SPIM interrupt handler (when one transaction finishes and the next one is started), so internal queue operations run with interrupts locked.
18+
19+
The library exposes a non-blocking API and a blocking API on top of the same scheduling engine:
20+
21+
* :c:func:`bm_spi_mngr_schedule` enqueues a :c:struct:`bm_spi_mngr_transaction_t` and returns immediately.
22+
Optional begin and end callbacks on the descriptor run from the SPIM event handler in interrupt context, around the transaction.
23+
24+
* :c:func:`bm_spi_mngr_perform` builds an internal transaction for a one-shot multi-transfer job, schedules it through the same path, and blocks until completion.
25+
While waiting, it may call an optional idle function (for example ``k_cpu_idle``).
26+
This idle function must not be called from ISR context, because the SPIM interrupt that ends the transaction must still run to release the wait loop.
27+
Calling it from an ISR would deadlock silently, and the library asserts on this case as a precaution.
28+
29+
All user-supplied callbacks run in interrupt context, so they should do as little work as possible, typically just setting a flag or releasing a lock that the application checks from its main loop.
30+
31+
Each transaction may supply its own :c:member:`bm_spi_mngr_transaction_t.p_required_spim_cfg` pointer.
32+
If that member is NULL, the library uses the SPIM configuration passed to :c:func:`bm_spi_mngr_init`.
33+
If it points at a different :c:struct:`nrfx_spim_config_t`, the driver is reinitialized when the configuration does not match the one already active.
34+
This allows changing pins, clock, or mode between devices on the same bus, for example to drive a different software chip select line per peripheral.
35+
36+
A typical use is sharing one SPIM instance between multiple peripherals, where each peripheral has its own chip select pin.
37+
Each peripheral defines its own :c:struct:`nrfx_spim_config_t` with the matching chip select, and transactions targeted at that peripheral point :c:member:`bm_spi_mngr_transaction_t.p_required_spim_cfg` at it.
38+
The manager then reconfigures the SPIM instance on the fly as transactions for different peripherals are pulled from the queue.
39+
40+
Before calling :c:func:`bm_spi_mngr_init`, connect and enable the SPIM interrupt for the chosen instance (for example using :c:macro:`BM_IRQ_DIRECT_CONNECT`), as required by nrfx SPIM.
41+
42+
Configuration
43+
*************
44+
45+
Enable the library with the :kconfig:option:`CONFIG_BM_SPI_MNGR` Kconfig option.
46+
47+
The option depends on :kconfig:option:`CONFIG_NRFX_SPIM` and selects :kconfig:option:`CONFIG_RING_BUFFER` for the internal FIFO.
48+
49+
Defining an instance
50+
====================
51+
52+
Use the :c:macro:`BM_SPI_MNGR_DEF` macro once per logical bus manager.
53+
It allocates the queue backing store, control block, :c:struct:`nrfx_spim_t` instance, and a const :c:struct:`bm_spi_mngr_t` handle.
54+
55+
The macro argument ``_queue_size`` is the number of *pending* transaction slots, not counting the transaction currently executing.
56+
With queue depth ``N``, you can have at most ``N`` waiting transactions while one runs, so up to ``N + 1`` transactions may be in flight in total.
57+
58+
Initialization
59+
==============
60+
61+
Call :c:func:`bm_spi_mngr_init` with the manager handle and the SPIM configuration the instance should use when a scheduled transaction leaves :c:member:`bm_spi_mngr_transaction_t.p_required_spim_cfg` as NULL.
62+
The library copies that configuration into its control block and uses it as the default for any transaction that does not provide its own.
63+
64+
Call :c:func:`bm_spi_mngr_uninit` to shut down SPIM and reset the queue.
65+
66+
Scheduling transactions
67+
=======================
68+
69+
Fill in a :c:struct:`bm_spi_mngr_transaction_t` with:
70+
71+
* :c:member:`bm_spi_mngr_transaction_t.p_transfers` -- array of :c:struct:`bm_spi_mngr_transfer_t` segments.
72+
* :c:member:`bm_spi_mngr_transaction_t.number_of_transfers` -- length of that array.
73+
* Optional :c:member:`bm_spi_mngr_transaction_t.begin_callback`, called from the SPIM event handler context just before the first transfer of the transaction is started on the bus.
74+
* Optional :c:member:`bm_spi_mngr_transaction_t.end_callback`, called from the SPIM event handler context when the transaction completes or aborts after an error. The result is passed as the first argument.
75+
* Optional :c:member:`bm_spi_mngr_transaction_t.p_user_data`, an opaque pointer passed through to both callbacks.
76+
* Optional :c:member:`bm_spi_mngr_transaction_t.p_required_spim_cfg` -- per-transaction SPIM settings, or NULL to use the configuration from :c:func:`bm_spi_mngr_init`.
77+
78+
Use the :c:macro:`BM_SPI_MNGR_TRANSFER` macro to initialize simple transfer descriptors.
79+
80+
The transaction descriptor (and any non-NULL configuration pointer it carries) must stay valid until the transaction completes, because the library only stores a pointer to it in the queue.
81+
82+
Pass the descriptor to :c:func:`bm_spi_mngr_schedule`.
83+
The function returns :c:macro:`NRF_ERROR_NO_MEM` if the queue is full.
84+
On success, the transaction is started immediately if the bus is idle, otherwise it runs back-to-back after the transactions ahead of it.
85+
86+
Because both callbacks run from interrupt context, keep them short and use them only to signal completion to the application, for example by setting a flag or releasing a lock that protects the transaction descriptor.
87+
88+
Blocking helper
89+
===============
90+
91+
:c:func:`bm_spi_mngr_perform` is a convenience wrapper around :c:func:`bm_spi_mngr_schedule` for a single synthetic transaction.
92+
Its ``p_config`` argument maps to :c:member:`bm_spi_mngr_transaction_t.p_required_spim_cfg`, where NULL means use the configuration from :c:func:`bm_spi_mngr_init`.
93+
The optional ``idle_fn`` argument is called repeatedly while the function waits for the transaction to finish, and must not be called from ISR context.
94+
95+
Idle detection
96+
==============
97+
98+
:c:func:`bm_spi_mngr_is_idle` returns true when the manager has no transaction in progress and the pending queue is empty.
99+
The library is careful not to report a false idle state between back-to-back transactions, so the return value can be used as a reliable signal that all scheduled work has finished.
100+
101+
Dependencies
102+
************
103+
104+
* nrfx SPIM (:kconfig:option:`CONFIG_NRFX_SPIM`)
105+
* Zephyr ring buffer (:kconfig:option:`CONFIG_RING_BUFFER`), pulled in automatically when the library is enabled
106+
* Zephyr kernel symbols for the ISR assertion in :c:func:`bm_spi_mngr_perform` (``k_is_in_isr``)
107+
108+
API documentation
109+
*****************
110+
111+
| Header file: :file:`include/bm/bm_spi_mngr.h`
112+
| Source files: :file:`lib/bm_spi_mngr/`
113+
114+
:ref:`SPI transaction manager API reference <api_bm_spi_mngr>`

doc/nrf-bm/release_notes/release_notes_changelog.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ No changes since the latest nRF Connect SDK Bare Metal release.
7171
Libraries
7272
=========
7373

74-
No changes since the latest nRF Connect SDK Bare Metal release.
74+
* Added the :ref:`lib_bm_spi_mngr` library for queued SPI master transactions on a single SPIM instance.
75+
Enable it with the :kconfig:option:`CONFIG_BM_SPI_MNGR` Kconfig option.
76+
See :ref:`lib_bm_spi_mngr` for an overview and :ref:`SPI transaction manager API reference <api_bm_spi_mngr>` for the full API.
7577

7678
Bluetooth LE Services
7779
---------------------

0 commit comments

Comments
 (0)