Environment:
- Board: nRF54L15DK (
nrf54l15dk/nrf54l15/cpuapp) - NCS Version: v3.1.0
- Zephyr OS: v3.7.99 (NCS fork)
NOTE: I am building this over peripheral uart example, so I want to keep that functionality intact besides this.
Overview: I am trying to drive a Waveshare 4.2" ePaper display using hardware SPI. I have assigned the SPI pins to Port 1 on the expansion header. However, when using the standard Zephyr SPI API (spi_write_dt), the SPIM clock and data signals never physically reach the header pins, despite the SPI driver initializing successfully with no errors.
If I configure those exact same pins as standard GPIO outputs and bit-bang the SPI protocol, the display works perfectly. This isolates the issue specifically to the SPIM peripheral's pin routing (GPIOMCU).
Pin Configuration (Port 1):
- SCK: P1.14
- MOSI (DIN): P1.08
- MISO: Not connected (display is write-only)
- CS: P1.09
- DC: P1.11 (GPIO)
- RST: P1.12 (GPIO)
- BUSY: P1.06 (GPIO Input)
What we implemented for Bit-Banging (Working): To prove the wiring and display were functional, we bypassed the SPI peripheral entirely. We configured P1.14, P1.08, and P1.09 using gpio_pin_configure_dt() as GPIO_OUTPUT_ACTIVE/INACTIVE and manually toggled the pins using gpio_pin_set() in a busy-wait loop (~500 kHz). Result: The display initializes and refreshes perfectly. The physical pins on the nRF54L15DK can definitely be driven by the application core.
What we implemented for Hardware SPI (Failing): We specifically chose SPIM22 (spi@c8000) because we noticed in the compiled zephyr.dts that SPIM21 has the cross-domain-pins-supported flag and belongs to a different hardware domain than Port 1, whereas SPIM22 routes directly to Port 1.
Here is our Devicetree overlay:
&pinctrl {
spi22_default: spi22_default {
group1 {
psels = <NRF_PSEL(NRF_FUN_SPIM_SCK, 1, 14)>,
<NRF_PSEL(NRF_FUN_SPIM_MOSI, 1, 8)>,
<NRF_PSEL(NRF_FUN_SPIM_MISO, 1, 31)>; /* Disconnected */
};
};
spi22_sleep: spi22_sleep {
group1 {
psels = <NRF_PSEL(NRF_FUN_SPIM_SCK, 1, 14)>,
<NRF_PSEL(NRF_FUN_SPIM_MOSI, 1, 8)>,
<NRF_PSEL(NRF_FUN_SPIM_MISO, 1, 31)>;
low-power-enable;
};
};
};
&spi22 {
status = "okay";
pinctrl-0 = <&spi22_default>;
pinctrl-1 = <&spi22_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
epaper: epaper@0 {
compatible = "waveshare,epaper-4in2";
reg = <0>;
spi-max-frequency = <2000000>;
dc-gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
busy-gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>;
};
};
In the C code, we use standard Zephyr calls:
static const struct spi_dt_spec spi_spec = SPI_DT_SPEC_GET(
DT_NODELABEL(epaper),
SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
0
);
// ... later in code ...
spi_write_dt(&spi_spec, &tx_set);
Result: spi_is_ready_dt() returns true. spi_write_dt() executes and returns 0 (success). However, a logic analyzer/multimeter shows that P1.14 (SCK) and P1.08 (MOSI) remain completely dead. The display's BUSY pin never goes high because it receives no clock or data.
(Note: We also tried bypassing Zephyr's SPI driver and initializing nrfx_spim directly, but this conflicts with Zephyr's driver state, throwing an assertion).
Questions for Nordic Support:
- Is there a known issue in NCS v3.1.0 regarding
pinctrlfailing to activate theGPIOMCUrouting matrix for SPIM peripherals on the nRF54L15? - Are there extra steps required in the DTS (e.g., specific domain bridges, clock requests, or power domain configurations) to allow SPIM22 to drive Port 1 pins?
- Is there a workaround to manually force the
GPIOMCUrouting registers for SCK and MOSI while still allowing the Zephyrspi_write_dt()API to handle the EasyDMA transfers?
Thank you!