nRF54L15DK (NCS v3.1.0): SPIM22 signals not routing to physical pins (GPIOMCU issue?)

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:

  1. Is there a known issue in NCS v3.1.0 regarding pinctrl failing to activate the GPIOMCU routing matrix for SPIM peripherals on the nRF54L15?
  2. 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?
  3. Is there a workaround to manually force the GPIOMCU routing registers for SCK and MOSI while still allowing the Zephyr spi_write_dt() API to handle the EasyDMA transfers?

Thank you!

Parents
  • Hello,

    I don't see anything wrong with your pin assignments, but I see you are using the NRF_FUN_* prefix in your pinctrl nodes. This prefix is already applied by the NRF_PSEL() macro. 

    So instead of this:

    			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 */

    it should be written like this:

    			psels = <NRF_PSEL(SPIM_SCK, 1, 14)>,
    			        <NRF_PSEL(SPIM_MOSI, 1, 8)>,
    			        <NRF_PSEL(SPIM_MISO, 1, 31)>; /* Disconnected */

    Please correct this and check again to see if it works.

    Best regards,

    Vidar

  • I am still seeing the same issue, where the BUSY signal never goes high even after MASTER activvation command sent

    epaper.c 

    /*
     * Copyright (c) 2024
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     *
     * Waveshare 4.2" e-Paper driver for the nRF54L15DK.
     *
     * Controller: SSD1683 (Waveshare V2 variant).
     * Resolution: 400 x 300 pixels, 1-bit monochrome.
     * Interface:  Selectable via CONFIG_EPAPER_SW_SPI / CONFIG_EPAPER_HW_SPI.
     *             See src/epaper_spi.h for the two-define selection mechanism.
     *
     * Pins:
     *   SPI (SCK P1.14 / DIN P1.08 / CS P1.09) — managed by the transport layer.
     *   DC  P1.11  RST P1.12  BUSY P1.06        — managed here.
     *
     * Framebuffer layout:
     *   - 15 000 bytes (400 * 300 / 8)
     *   - Byte index:  idx = (x / 8) + y * (EPD_WIDTH / 8)
     *   - Bit mask:    0x80 >> (x % 8)   [MSB = leftmost pixel in byte]
     *   - Bit value 1 = white, 0 = black
     *
     * Debug output:
     *   All [EPD] printk() lines go to the UART console (uart20, 115200 baud).
     *   LOG_INF/LOG_ERR lines go to RTT (connect via J-Link RTT Viewer).
     */
    
    #include "epaper.h"
    #include "epaper_spi.h"
    #include "font5x7.h"
    
    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/devicetree.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/logging/log.h>
    
    #include <string.h>
    
    LOG_MODULE_REGISTER(epaper, LOG_LEVEL_DBG);
    
    /* -------------------------------------------------------------------------
     * Timing constants
     * All values are conservative (larger than minimum) for wiring tolerance.
     * ------------------------------------------------------------------------- */
    #define EPD_POWER_ON_DELAY_MS   200  /* VDD stabilisation after power-on                     */
    #define EPD_RESET_HIGH_MS       100  /* RST high before asserting reset (V2 ref: 100 ms)     */
    #define EPD_RESET_PULSE_MS        2  /* RST low pulse width        (V2 ref: 2 ms)            */
    #define EPD_RESET_RELEASE_MS    100  /* Settle time after RST deasserted (V2 ref: 100 ms)    */
    #define EPD_SW_RESET_DELAY_MS    20  /* Wait after 0x12 SW_RESET command                     */
    #define EPD_BUSY_POLL_MS         10  /* Polling interval while waiting for BUSY              */
    #define EPD_BUSY_TIMEOUT_MS    6000  /* Maximum time to wait for BUSY to go LOW              */
    /* EPD_BUSY_POLL_MS is reused as the post-MASTER_ACTIVATION probe delay.
     * A genuine full refresh takes > 1 000 ms, so 10 ms is more than enough to
     * distinguish a connected (BUSY = 1) from a floating (BUSY = 0) signal. */
    
    /* -------------------------------------------------------------------------
     * Devicetree node aliases
     * ------------------------------------------------------------------------- */
    #define EPAPER_NODE DT_NODELABEL(epaper)
    
    /*
     * Thin adapter macros so the rest of this file is unchanged regardless of
     * which SPI transport is compiled.  All actual SPI logic lives in
     * epaper_spi_sw.c (software) or epaper_spi_hw.c (hardware SPIM + DMA).
     */
    #define epd_send_cmd(c)     epaper_spi_cmd((uint8_t)(c))
    #define epd_send_byte(d)    epaper_spi_data((uint8_t)(d))
    #define epd_send_buf(b, l)  epaper_spi_buf((b), (l))
    
    /* GPIO control lines — DC and CS are owned by the transport layer */
    static const struct gpio_dt_spec rst_gpio =
        GPIO_DT_SPEC_GET(EPAPER_NODE, reset_gpios);
    
    static const struct gpio_dt_spec busy_gpio =
        GPIO_DT_SPEC_GET(EPAPER_NODE, busy_gpios);
    
    /* -------------------------------------------------------------------------
     * Framebuffer  (15 000 bytes, static allocation in BSS)
     * ------------------------------------------------------------------------- */
    static uint8_t framebuf[EPD_BUF_SIZE];
    
    /* SPI transfers are forwarded to the selected transport via the
     * epd_send_cmd / epd_send_byte / epd_send_buf macros defined above. */
    
    /* -------------------------------------------------------------------------
     * Display control helpers
     * ------------------------------------------------------------------------- */
    
    /*
     * Poll BUSY until the display signals idle (LOW), with a hard timeout.
     *
     * BUSY = HIGH → display is busy / refreshing
     * BUSY = LOW  → display is idle, ready to accept commands
     *
     * Prints to UART:
     *   - Pin level on entry (helps detect always-HIGH / always-LOW faults)
     *   - Heartbeat every 500 ms while waiting (confirms MCU is alive)
     *   - Elapsed time when BUSY finally goes LOW
     *   - A clear ERROR line on timeout
     *
     * Returns true if idle was reached, false on timeout.
     */
    static bool epd_wait_busy(const char *context)
    {
        int64_t t_start    = k_uptime_get();
        int64_t deadline   = t_start + EPD_BUSY_TIMEOUT_MS;
        int64_t t_last_hb  = t_start;
        int     pin_entry  = gpio_pin_get_dt(&busy_gpio);
    
        printk("[EPD] wait_busy [%s]: BUSY pin = %d at entry  "
               "(1=busy HIGH, 0=idle LOW)\n", context, pin_entry);
        LOG_INF("wait_busy [%s]: BUSY pin = %d at entry", context, pin_entry);
    
        while (gpio_pin_get_dt(&busy_gpio) == 1) {
            int64_t now = k_uptime_get();
    
            if (now > deadline) {
                printk("[EPD] *** BUSY TIMEOUT *** [%s] after %d ms\n"
                       "      --> Is display powered? Is P1.06 connected to BUSY?\n"
                       "      --> Continuing anyway — subsequent commands may fail.\n",
                       context, EPD_BUSY_TIMEOUT_MS);
                LOG_ERR("BUSY timeout %d ms [%s] — check P1.06 wiring!",
                        EPD_BUSY_TIMEOUT_MS, context);
                return false;
            }
    
            if (now - t_last_hb >= 500) {
                printk("[EPD] ... still waiting [%s], elapsed %d ms, BUSY=%d\n",
                       context, (int)(now - t_start),
                       gpio_pin_get_dt(&busy_gpio));
                t_last_hb = now;
            }
    
            k_msleep(EPD_BUSY_POLL_MS);
        }
    
        int elapsed = (int)(k_uptime_get() - t_start);
    
        printk("[EPD] BUSY went LOW after %d ms [%s]  <-- OK\n", elapsed, context);
        LOG_INF("wait_busy [%s]: idle after %d ms", context, elapsed);
        return true;
    }
    
    /*
     * Pulse RST to hardware-reset the display controller.
     * After this function returns the controller is out of reset but still
     * performing its internal init — do NOT send any SPI command until
     * EPD_RESET_RELEASE_MS has elapsed (handled by the caller).
     */
    static void epd_hw_reset(void)
    {
        /* RST HIGH → LOW → HIGH, matching Waveshare V2 reference timing */
        printk("[EPD] HW reset: RST HIGH (%d ms)\n", EPD_RESET_HIGH_MS);
        LOG_INF("HW reset start");
        gpio_pin_set_dt(&rst_gpio, 1);
        k_msleep(EPD_RESET_HIGH_MS);                 /* 100 ms */
    
        printk("[EPD] HW reset: RST LOW pulse (%d ms)\n", EPD_RESET_PULSE_MS);
        gpio_pin_set_dt(&rst_gpio, 0);
        k_msleep(EPD_RESET_PULSE_MS);                /* 2 ms   */
    
        gpio_pin_set_dt(&rst_gpio, 1);
        k_msleep(EPD_RESET_RELEASE_MS);              /* 100 ms settle — controller init */
        printk("[EPD] HW reset: RST released, %d ms settle done\n",
               EPD_RESET_RELEASE_MS);
    
        printk("[EPD] HW reset: monitoring BUSY for 300 ms after RST release...\n");
        bool busy_ever_high = false;
        int  busy_rise_ms   = -1;
        int  busy_fall_ms   = -1;
        int  prev_b         = 0;
    
        for (int t = 0; t < 300; t++) {
            int b = gpio_pin_get_dt(&busy_gpio);
    
            if (b == 1 && prev_b == 0) {
                busy_rise_ms   = t;
                busy_ever_high = true;
                printk("[EPD]   BUSY rose  HIGH at %3d ms\n", t);
            } else if (b == 0 && prev_b == 1) {
                busy_fall_ms = t;
                printk("[EPD]   BUSY fell  LOW  at %3d ms  (reset complete)\n", t);
                break; /* display ready — no need to wait longer */
            }
    
            prev_b = b;
            k_msleep(1);
        }
    
        if (busy_ever_high) {
            printk("[EPD] HW reset: BUSY pulsed HIGH (%d ms) then LOW (%d ms) -- RST OK\n",
                   busy_rise_ms,
                   busy_fall_ms >= 0 ? busy_fall_ms : 300);
        } else {
            printk("[EPD] HW reset: BUSY never went HIGH in 300 ms.\n");
            printk("[EPD]   --> RST wire (P1.12 to display RST) may not be connected.\n");
            printk("[EPD]   --> The SPI init sequence will still proceed.\n");
            printk("[EPD]   --> If display stays blank: verify P1.12 <-> RST wire.\n");
            LOG_WRN("BUSY never HIGH after RST — check P1.12 RST wire");
        }
    
        printk("[EPD] HW reset: done\n");
        LOG_INF("HW reset done");
    }
    
    
    /* Set the RAM write window to the full panel and move cursor to (0, 0). */
    static void epd_set_window_and_cursor(void)
    {
        /* RAM X: byte 0 → byte 49  (8 pixels per byte × 50 = 400 pixels) */
        epd_send_cmd(0x44);
        epd_send_byte(0x00);
        epd_send_byte((EPD_WIDTH / 8U) - 1U);         /* 49 = 0x31 */
    
        /* RAM Y: line 0 → line 299 */
        epd_send_cmd(0x45);
        epd_send_byte(0x00);                           /* YStart low  */
        epd_send_byte(0x00);                           /* YStart high */
        epd_send_byte((EPD_HEIGHT - 1U) & 0xFF);       /* YEnd low  = 0x2B */
        epd_send_byte(((EPD_HEIGHT - 1U) >> 8) & 0xFF); /* YEnd high = 0x01 */
    
        /* RAM X counter = 0 */
        epd_send_cmd(0x4E);
        epd_send_byte(0x00);
    
        /* RAM Y counter = 0 */
        epd_send_cmd(0x4F);
        epd_send_byte(0x00);
        epd_send_byte(0x00);
    }
    
    /* -------------------------------------------------------------------------
     * SSD1683 full initialisation sequence
     * Mirrors Waveshare 4.2" V2 Python driver (epd4in2_V2.py) exactly.
     * SPI transfers are delegated to the selected transport (SW or HW).
     * ------------------------------------------------------------------------- */
    static int epd_init_hw(void)
    {
        printk("[EPD] ============================================\n");
        printk("[EPD]  SSD1683 init (V2 sequence)\n");
        printk("[EPD] ============================================\n");
    
        /*
         * Init sequence mirrors Waveshare 4.2" V2 Python driver (epd4in2_V2.py)
         * exactly — same commands, same data bytes, same order.
         *
         * Key corrections vs previous version:
         *   • 0x01 (DRIVER_OUTPUT_CONTROL) removed — not in V2 init()
         *   • 0x21 data fixed: 0x40,0x00 (was 0x00,0x80)
         *     0x80 in byte-2 sets LUTSEL=1 (custom LUT from registers).
         *     No custom LUT was loaded, so waveform engine was undefined.
         *     0x00 in byte-2 = LUTSEL=0 → use factory-calibrated OTP LUT.
         *   • 0x21 / 0x3C sent BEFORE RAM window (V2 order)
         *   • 0x18 (TEMPERATURE_SENSOR) removed — not in V2 init()
         */
    
        /* Step 0: power-on stabilisation */
        printk("[EPD] Step 0: power-on delay %d ms\n", EPD_POWER_ON_DELAY_MS);
        k_msleep(EPD_POWER_ON_DELAY_MS);
    
        /* Step 1: hardware reset */
        printk("[EPD] Step 1: hardware reset\n");
        epd_hw_reset();
    
        /* Step 1b: wait for BUSY LOW after reset (V2: ReadBusy after reset()) */
        printk("[EPD] Step 1b: wait BUSY LOW after HW reset\n");
        epd_wait_busy("post_hw_reset");
    
        /* Step 2: software reset (0x12) then wait BUSY LOW (V2: ReadBusy after 0x12) */
        printk("[EPD] Step 2: SW_RESET 0x12\n");
        epd_send_cmd(0x12);
        k_msleep(EPD_SW_RESET_DELAY_MS);
        printk("[EPD] Step 2b: wait BUSY LOW after SW_RESET\n");
        epd_wait_busy("SW_RESET");
    
        /* Step 3: Display Update Control — 0x21, data 0x40, 0x00
         * Byte 1: 0x40 — normal BW display mode
         * Byte 2: 0x00 — LUTSEL=0 (use OTP/factory LUT, NOT register LUT) */
        printk("[EPD] Step 3: DISPLAY_UPDATE_CTRL 0x21, data=0x40,0x00\n");
        epd_send_cmd(0x21);
        epd_send_byte(0x40);
        epd_send_byte(0x00);
    
        /* Step 4: Border Waveform Control */
        printk("[EPD] Step 4: BORDER_WAVEFORM 0x3C, data=0x05\n");
        epd_send_cmd(0x3C);
        epd_send_byte(0x05);
    
        /* Step 5: Data Entry Mode — X increment, Y increment */
        printk("[EPD] Step 5: DATA_ENTRY_MODE 0x11, data=0x03\n");
        epd_send_cmd(0x11);
        epd_send_byte(0x03);
    
        /* Step 6: RAM X window: byte 0 (X=0) to byte 0x31=49 (X=399) */
        printk("[EPD] Step 6: RAM X window 0x44: 0x00..0x31\n");
        epd_send_cmd(0x44);
        epd_send_byte(0x00);
        epd_send_byte(0x31);
    
        /* Step 7: RAM Y window: line 0 to line 299 (0x012B) */
        printk("[EPD] Step 7: RAM Y window 0x45: 0x0000..0x012B\n");
        epd_send_cmd(0x45);
        epd_send_byte(0x00);
        epd_send_byte(0x00);
        epd_send_byte(0x2B);
        epd_send_byte(0x01);
    
        /* Step 8: RAM X counter = 0 */
        printk("[EPD] Step 8: RAM cursor 0x4E=0x00, 0x4F=0x0000\n");
        epd_send_cmd(0x4E);
        epd_send_byte(0x00);
    
        /* Step 9: RAM Y counter = 0 */
        epd_send_cmd(0x4F);
        epd_send_byte(0x00);
        epd_send_byte(0x00);
    
        /* Step 10: final BUSY wait (V2: ReadBusy at end of init()) */
        printk("[EPD] Step 10: final wait BUSY LOW\n");
        epd_wait_busy("init_final");
    
        printk("[EPD] ============================================\n");
        printk("[EPD]  SSD1683 init COMPLETE — display ready\n");
        printk("[EPD] ============================================\n");
        LOG_INF("EPD init: complete");
        return 0;
    }
    
    /* -------------------------------------------------------------------------
     * Public API
     * ------------------------------------------------------------------------- */
    
    int epaper_init(void)
    {
        printk("[EPD] *** epaper_init() called ***\n");
        printk("[EPD] Display: Waveshare 4.2\" b/w, SSD1683, %ux%u px\n",
               EPD_WIDTH, EPD_HEIGHT);
        printk("[EPD] Transport: %s\n",
               EPAPER_TRANSPORT_HW ? "hardware SPIM22 + DMA (nrfx)"
                                   : "software SPI (bit-bang)");
        LOG_INF("epaper_init: starting");
    
        /* ---- SPI transport (DC + CS + SPI pins) ---- */
        int err = epaper_spi_init();
        if (err) {
            printk("[EPD] ERROR: epaper_spi_init() failed: %d\n", err);
            LOG_ERR("SPI transport init failed (%d)", err);
            return err;
        }
    
        /* ---- RST GPIO (P1.12) ---- */
        if (!gpio_is_ready_dt(&rst_gpio)) {
            printk("[EPD] ERROR: RST GPIO not ready\n");
            LOG_ERR("RST GPIO not ready");
            return -ENODEV;
        }
        printk("[EPD] RST GPIO (%s pin %u) ready\n",
               rst_gpio.port->name, rst_gpio.pin);
    
        /* ---- BUSY GPIO (P1.06) ---- */
        if (!gpio_is_ready_dt(&busy_gpio)) {
            printk("[EPD] ERROR: BUSY GPIO not ready\n");
            LOG_ERR("BUSY GPIO not ready");
            return -ENODEV;
        }
        printk("[EPD] BUSY GPIO (%s pin %u) ready\n",
               busy_gpio.port->name, busy_gpio.pin);
    
        /* RST: output, start HIGH (not in reset) */
        err = gpio_pin_configure_dt(&rst_gpio, GPIO_OUTPUT_ACTIVE);
        if (err) {
            printk("[EPD] ERROR: RST GPIO configure failed: %d\n", err);
            LOG_ERR("RST GPIO configure failed (%d)", err);
            return err;
        }
        printk("[EPD] RST GPIO configured OUTPUT HIGH\n");
    
        /*
         * BUSY: input with internal pull-down.
         * Pull-down ensures LOW (idle) when the BUSY wire is disconnected,
         * so epd_wait_busy() never hangs even without the wire present.
         */
        err = gpio_pin_configure_dt(&busy_gpio, GPIO_INPUT | GPIO_PULL_DOWN);
        if (err) {
            printk("[EPD] ERROR: BUSY GPIO configure failed: %d\n", err);
            LOG_ERR("BUSY GPIO configure failed (%d)", err);
            return err;
        }
        printk("[EPD] BUSY GPIO configured INPUT+PULL-DOWN  (current=%d)\n",
               gpio_pin_get_dt(&busy_gpio));
    
        LOG_INF("GPIOs configured — starting SSD1683 init");
    
        err = epd_init_hw();
        if (err) {
            printk("[EPD] ERROR: hardware init failed: %d\n", err);
            LOG_ERR("EPD hardware init failed (%d)", err);
            return err;
        }
    
        printk("[EPD] epaper_init() SUCCESS — %ux%u display ready\n",
               EPD_WIDTH, EPD_HEIGHT);
        LOG_INF("Waveshare 4.2\" e-Paper ready (%ux%u)", EPD_WIDTH, EPD_HEIGHT);
        return 0;
    }
    
    void epaper_clear(uint8_t color)
    {
        memset(framebuf, color, sizeof(framebuf));
    }
    
    void epaper_set_pixel(uint16_t x, uint16_t y, bool black)
    {
        if (x >= EPD_WIDTH || y >= EPD_HEIGHT) {
            return;
        }
    
        uint32_t idx  = (x / 8U) + (uint32_t)y * (EPD_WIDTH / 8U);
        uint8_t  mask = 0x80U >> (x % 8U);
    
        if (black) {
            framebuf[idx] &= ~mask; /* clear bit → black pixel */
        } else {
            framebuf[idx] |=  mask; /* set bit   → white pixel */
        }
    }
    
    void epaper_draw_char(uint16_t x, uint16_t y, char c,
                          uint8_t scale, bool black)
    {
        if ((uint8_t)c < 0x20U || (uint8_t)c > 0x7EU) {
            c = '?';
        }
    
        const uint8_t *glyph = font5x7[(uint8_t)c - 0x20U];
    
        for (uint8_t col = 0U; col < FONT5X7_COLS; col++) {
            uint8_t col_data = glyph[col];
    
            for (uint8_t row = 0U; row < FONT5X7_ROWS; row++) {
                if (col_data & (1U << row)) {
                    for (uint8_t sx = 0U; sx < scale; sx++) {
                        for (uint8_t sy = 0U; sy < scale; sy++) {
                            epaper_set_pixel(
                                x + (uint16_t)(col * scale + sx),
                                y + (uint16_t)(row * scale + sy),
                                black);
                        }
                    }
                }
            }
        }
    }
    
    void epaper_draw_string(uint16_t x, uint16_t y, const char *str,
                            uint8_t scale, bool black)
    {
        uint16_t cx      = x;
        uint16_t advance = (uint16_t)(FONT5X7_ADVANCE * scale);
    
        while (*str) {
            if (cx + (uint16_t)(FONT5X7_COLS * scale) > EPD_WIDTH) {
                break;
            }
            epaper_draw_char(cx, y, *str, scale, black);
            cx += advance;
            str++;
        }
    }
    
    void epaper_display(void)
    {
        printk("[EPD] epaper_display(): resetting cursor to (0,0)\n");
        LOG_INF("epaper_display: writing framebuffer");
    
        /* Reset write cursor to (0, 0) before streaming the framebuffer */
        epd_set_window_and_cursor();
    
        /*
         * Write the same framebuffer to BOTH RAM banks — required by V2.
         *   0x24 = BW RAM  (new / current frame)
         *   0x26 = RED RAM (previous frame — used by waveform engine for
         *                   differential e-ink drive; must not be garbage)
         * Waveshare V2 Python display() sends the image to both banks.
         */
        printk("[EPD] Writing BW RAM  (0x24): %u bytes...\n",
               (unsigned)sizeof(framebuf));
        epd_send_cmd(0x24);
        epd_send_buf(framebuf, sizeof(framebuf));
    
        printk("[EPD] Writing RED RAM (0x26): %u bytes (same data)...\n",
               (unsigned)sizeof(framebuf));
        epd_send_cmd(0x26);
        epd_send_buf(framebuf, sizeof(framebuf));
        printk("[EPD] Framebuffer write complete (both RAM banks)\n");
    
        /* Full update — LUT from OTP (temperature-compensated) */
        printk("[EPD] Sending DISPLAY_UPDATE_CONTROL_2 cmd 0x22, data=0xF7\n");
        epd_send_cmd(0x22);
        epd_send_byte(0xF7);
    
        printk("[EPD] Sending MASTER_ACTIVATION cmd 0x20  "
               "<-- e-ink waveform starts now\n");
        LOG_INF("epaper_display: triggering full refresh (may take ~2 s)");
        epd_send_cmd(0x20);
    
        /*
         * Monitor BUSY for up to 15 seconds after MASTER_ACTIVATION.
         *
         * A working SSD1683 drives BUSY HIGH within 1 ms of receiving 0x20 and
         * holds it HIGH for ~2-4 seconds during the full refresh waveform.
         * Polling every 50 ms is more than fine enough to catch it.
         *
         * If BUSY never goes HIGH in 15 seconds:
         *   (a) SPI data is not reaching the display controller.
         *   (b) The display is damaged.
         * The software SPI diagnostic earlier in epd_init_hw() distinguishes these.
         */
        printk("[EPD] Monitoring BUSY for up to 15 s after MASTER_ACTIVATION...\n");
        printk("[EPD]   (SSD1683 should drive BUSY HIGH within 1 ms of 0x20)\n");
    
        bool refresh_seen = false;
        int  last_print_s = 0;
    
        for (int t_ms = 0; t_ms <= 15000; t_ms += 50) {
            int b = gpio_pin_get_dt(&busy_gpio);
    
            /* Print a status line every 1000 ms */
            int s = t_ms / 1000;
            if (s > last_print_s || t_ms == 0) {
                last_print_s = s;
                printk("[EPD]   +%2d s : BUSY = %d\n", s, b);
            }
    
            if (b == 1) {
                refresh_seen = true;
                printk("[EPD] BUSY HIGH at +%d ms — display is refreshing!\n", t_ms);
                printk("[EPD] Waiting for BUSY to go LOW (refresh complete)...\n");
                epd_wait_busy("display_refresh");
                break;
            }
    
            k_msleep(50);
        }
    
        if (!refresh_seen) {
            printk("[EPD] *** BUSY never went HIGH in 15 s ***\n");
            printk("[EPD]   SPI data (0x20 MASTER_ACTIVATION) did not reach the display.\n");
            printk("[EPD]   Check wiring: SCK P1.14, DIN P1.08, CS P1.09, DC P1.11.\n");
        }
    
        printk("[EPD] epaper_display(): refresh COMPLETE — image should be visible\n");
        LOG_INF("epaper_display: refresh complete");
    }
    
    void epaper_sleep(void)
    {
        printk("[EPD] epaper_sleep(): sending DEEP_SLEEP cmd 0x10, data=0x01\n");
        epd_send_cmd(0x10);
        epd_send_byte(0x01); /* Deep sleep mode 1 — RAM contents retained */
        printk("[EPD] ePaper in deep sleep (RST required to wake)\n");
        LOG_INF("ePaper: deep sleep entered");
    }
    


    THhs the uart output while using SW SPI (bit bangging approach)

    [MAIN] ----------------------------------------
    [MAIN] Starting Waveshare 4.2" ePaper init
    [MAIN] ----------------------------------------
    [EPD] *** epaper_init() called ***
    [EPD] Display: Waveshare 4.2" b/w, SSD1683, 400x300 px
    [EPD] Transport: software SPI (bit-bang)
    [EPD] RST GPIO (gpio@d8200 pin 12) ready
    [EPD] BUSY GPIO (gpio@d8200 pin 6) ready
    [EPD] RST GPIO configured OUTPUT HIGH
    [EPD] BUSY GPIO configured INPUT+PULL-DOWN (current=0)
    [EPD] ============================================
    [EPD] SSD1683 init (V2 sequence)
    [EPD] ============================================
    [EPD] Step 0: power-on delay 200 ms
    [EPD] Step 1: hardware reset
    [EPD] HW reset: RST HIGH (100 ms)
    [EPD] HW reset: RST LOW pulse (2 ms)
    [EPD] HW reset: RST released, 100 ms settle done
    [EPD] HW reset: monitoring BUSY for 300 ms after RST release...
    [EPD] HW reset: BUSY never went HIGH in 300 ms.
    [EPD] --> RST wire (P1.12 to display RST) may not be connected.
    [EPD] --> The SPI init sequence will still proceed.
    [EPD] --> If display stays blank: verify P1.12 <-> RST wire.
    [EPD] HW reset: done
    [EPD] Step 1b: wait BUSY LOW after HW reset
    [EPD] wait_busy [post_hw_reset]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 7 ms [post_hw_reset] <-- OK
    [EPD] Step 2: SW_RESET 0x12
    [EPD] Step 2b: wait BUSY LOW after SW_RESET
    [EPD] wait_busy [SW_RESET]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 6 ms [SW_RESET] <-- OK
    [EPD] Step 3: DISPLAY_UPDATE_CTRL 0x21, data=0x40,0x00
    [EPD] Step 4: BORDER_WAVEFORM 0x3C, data=0x05
    [EPD] Step 5: DATA_ENTRY_MODE 0x11, data=0x03
    [EPD] Step 6: RAM X window 0x44: 0x00..0x31
    [EPD] Step 7: RAM Y window 0x45: 0x0000..0x012B
    [EPD] Step 8: RAM cursor 0x4E=0x00, 0x4F=0x0000
    [EPD] Step 10: final wait BUSY LOW
    [EPD] wait_busy [init_final]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 7 ms [init_final] <-- OK
    [EPD] ============================================
    [EPD] SSD1683 init COMPLETE — display ready
    [EPD] ============================================
    [EPD] epaper_init() SUCCESS — 400x300 display ready
    [MAIN] epaper_init OK — clearing framebuffer (white)
    [MAIN] Drawing 'Hello, Nugo!' at (92,120) scale=3
    [MAIN] Drawing 'nRF54L15DK' at (140,165) scale=2
    [MAIN] Calling epaper_display() — full refresh (~2 s)
    [EPD] epaper_display(): resetting cursor to (0,0)
    [EPD] Writing BW RAM (0x24): 15000 bytes...
    [EPD] Writing RED RAM (0x26): 15000 bytes (same data)...
    [EPD] Framebuffer write complete (both RAM banks)
    [EPD] Sending DISPLAY_UPDATE_CONTROL_2 cmd 0x22, data=0xF7
    [EPD] Sending MASTER_ACTIVATION cmd 0x20 <-- e-ink waveform starts now
    [EPD] Monitoring BUSY for up to 15 s after MASTER_ACTIVATION...
    [EPD] (SSD1683 should drive BUSY HIGH within 1 ms of 0x20)
    [EPD] + 0 s : BUSY = 1
    [EPD] BUSY HIGH at +0 ms — display is refreshing!
    [EPD] Waiting for BUSY to go LOW (refresh complete)...
    [EPD] wait_busy [display_refresh]: BUSY pin = 1 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] ... still waiting [display_refresh], elapsed 501 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 1001 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 1510 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 2010 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 2510 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 3020 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 3529 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 4029 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 4529 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 5039 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 5548 ms, BUSY=1
    [EPD] *** BUSY TIMEOUT *** [display_refresh] after 6000 ms
    --> Is display powered? Is P1.06 connected to BUSY?
    --> Continuing anyway — subsequent commands may fail.
    [EPD] epaper_display(): refresh COMPLETE — image should be visible
    [MAIN] epaper_display() returned — putting display to sleep
    [EPD] epaper_sleep(): sending DEEP_SLEEP cmd 0x10, data=0x01
    [EPD] ePaper in deep sleep (RST required to wake)
    [MAIN] ePaper sequence complete

    THis is my uart output for HW use case

    MAIN] ----------------------------------------
    [MAIN] Starting Waveshare 4.2" ePaper init
    [MAIN] ----------------------------------------
    [EPD] *** epaper_init() called ***
    [EPD] Display: Waveshare 4.2" b/w, SSD1683, 400x300 px
    [EPD] Transport: hardware SPIM22 + DMA (nrfx)
    [EPD-HW] SPIM22 ready via Zephyr SPI API (SCK=P1.14, DIN=P1.08, CS=P1.09 auto, DC=P1.11)
    [EPD] RST GPIO (gpio@d8200 pin 12) ready
    [EPD] BUSY GPIO (gpio@d8200 pin 6) ready
    [EPD] RST GPIO configured OUTPUT HIGH
    [EPD] BUSY GPIO configured INPUT+PULL-DOWN (current=0)
    [EPD] ============================================
    [EPD] SSD1683 init (V2 sequence)
    [EPD] ============================================
    [EPD] Step 0: power-on delay 200 ms
    [EPD] Step 1: hardware reset
    [EPD] HW reset: RST HIGH (100 ms)
    [EPD] HW reset: RST LOW pulse (2 ms)
    [EPD] HW reset: RST released, 100 ms settle done
    [EPD] HW reset: monitoring BUSY for 300 ms after RST release...
    [EPD] HW reset: BUSY never went HIGH in 300 ms.
    [EPD] --> RST wire (P1.12 to display RST) may not be connected.
    [EPD] --> The SPI init sequence will still proceed.
    [EPD] --> If display stays blank: verify P1.12 <-> RST wire.
    [EPD] HW reset: done
    [EPD] Step 1b: wait BUSY LOW after HW reset
    [EPD] wait_busy [post_hw_reset]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 7 ms [post_hw_reset] <-- OK
    [EPD] Step 2: SW_RESET 0x12
    [EPD] Step 2b: wait BUSY LOW after SW_RESET
    [EPD] wait_busy [SW_RESET]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 6 ms [SW_RESET] <-- OK
    [EPD] Step 3: DISPLAY_UPDATE_CTRL 0x21, data=0x40,0x00
    [EPD] Step 4: BORDER_WAVEFORM 0x3C, data=0x05
    [EPD] Step 5: DATA_ENTRY_MODE 0x11, data=0x03
    [EPD] Step 6: RAM X window 0x44: 0x00..0x31
    [EPD] Step 7: RAM Y window 0x45: 0x0000..0x012B
    [EPD] Step 8: RAM cursor 0x4E=0x00, 0x4F=0x0000
    [EPD] Step 10: final wait BUSY LOW
    [EPD] wait_busy [init_final]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 7 ms [init_final] <-- OK
    [EPD] ============================================
    [EPD] SSD1683 init COMPLETE — display ready
    [EPD] ============================================
    [EPD] epaper_init() SUCCESS — 400x300 display ready
    [MAIN] epaper_init OK — clearing framebuffer (white)
    [MAIN] Drawing 'Hello, Nugo!' at (92,120) scale=3
    [MAIN] Drawing 'nRF54L15DK' at (140,165) scale=2
    [MAIN] Calling epaper_display() — full refresh (~2 s)
    [EPD] epaper_display(): resetting cursor to (0,0)
    [EPD] Writing BW RAM (0x24): 15000 bytes...
    [EPD] Writing RED RAM (0x26): 15000 bytes (same data)...
    [EPD] Framebuffer write complete (both RAM banks)
    [EPD] Sending DISPLAY_UPDATE_CONTROL_2 cmd 0x22, data=0xF7
    [EPD] Sending MASTER_ACTIVATION cmd 0x20 <-- e-ink waveform starts now
    [EPD] Monitoring BUSY for up to 15 s after MASTER_ACTIVATION...
    [EPD] (SSD1683 should drive BUSY HIGH within 1 ms of 0x20)
    [EPD] + 0 s : BUSY = 0
    [EPD] + 1 s : BUSY = 0
    [EPD] + 2 s : BUSY = 0
    [EPD] + 3 s : BUSY = 0
    [EPD] + 4 s : BUSY = 0
    [EPD] + 5 s : BUSY = 0
    [EPD] + 6 s : BUSY = 0
    [EPD] + 7 s : BUSY = 0
    [EPD] + 8 s : BUSY = 0
    [EPD] + 9 s : BUSY = 0
    [EPD] +10 s : BUSY = 0
    [EPD] +11 s : BUSY = 0
    [EPD] +12 s : BUSY = 0
    [EPD] +13 s : BUSY = 0
    [EPD] +14 s : BUSY = 0
    [EPD] +15 s : BUSY = 0
    [EPD] *** BUSY never went HIGH in 15 s ***
    [EPD] SPI data (0x20 MASTER_ACTIVATION) did not reach the display.
    [EPD] Check wiring: SCK P1.14, DIN P1.08, CS P1.09, DC P1.11.
    [EPD] epaper_display(): refresh COMPLETE — image should be visible
    [MAIN] epaper_display() returned — putting display to sleep
    [EPD] epaper_sleep(): sending DEEP_SLEEP cmd 0x10, data=0x01
    [EPD] ePaper in deep sleep (RST required to wake)
    [MAIN] ePaper sequence complete
    [MAIN] ----------------------------------------



Reply
  • I am still seeing the same issue, where the BUSY signal never goes high even after MASTER activvation command sent

    epaper.c 

    /*
     * Copyright (c) 2024
     * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
     *
     * Waveshare 4.2" e-Paper driver for the nRF54L15DK.
     *
     * Controller: SSD1683 (Waveshare V2 variant).
     * Resolution: 400 x 300 pixels, 1-bit monochrome.
     * Interface:  Selectable via CONFIG_EPAPER_SW_SPI / CONFIG_EPAPER_HW_SPI.
     *             See src/epaper_spi.h for the two-define selection mechanism.
     *
     * Pins:
     *   SPI (SCK P1.14 / DIN P1.08 / CS P1.09) — managed by the transport layer.
     *   DC  P1.11  RST P1.12  BUSY P1.06        — managed here.
     *
     * Framebuffer layout:
     *   - 15 000 bytes (400 * 300 / 8)
     *   - Byte index:  idx = (x / 8) + y * (EPD_WIDTH / 8)
     *   - Bit mask:    0x80 >> (x % 8)   [MSB = leftmost pixel in byte]
     *   - Bit value 1 = white, 0 = black
     *
     * Debug output:
     *   All [EPD] printk() lines go to the UART console (uart20, 115200 baud).
     *   LOG_INF/LOG_ERR lines go to RTT (connect via J-Link RTT Viewer).
     */
    
    #include "epaper.h"
    #include "epaper_spi.h"
    #include "font5x7.h"
    
    #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <zephyr/devicetree.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/logging/log.h>
    
    #include <string.h>
    
    LOG_MODULE_REGISTER(epaper, LOG_LEVEL_DBG);
    
    /* -------------------------------------------------------------------------
     * Timing constants
     * All values are conservative (larger than minimum) for wiring tolerance.
     * ------------------------------------------------------------------------- */
    #define EPD_POWER_ON_DELAY_MS   200  /* VDD stabilisation after power-on                     */
    #define EPD_RESET_HIGH_MS       100  /* RST high before asserting reset (V2 ref: 100 ms)     */
    #define EPD_RESET_PULSE_MS        2  /* RST low pulse width        (V2 ref: 2 ms)            */
    #define EPD_RESET_RELEASE_MS    100  /* Settle time after RST deasserted (V2 ref: 100 ms)    */
    #define EPD_SW_RESET_DELAY_MS    20  /* Wait after 0x12 SW_RESET command                     */
    #define EPD_BUSY_POLL_MS         10  /* Polling interval while waiting for BUSY              */
    #define EPD_BUSY_TIMEOUT_MS    6000  /* Maximum time to wait for BUSY to go LOW              */
    /* EPD_BUSY_POLL_MS is reused as the post-MASTER_ACTIVATION probe delay.
     * A genuine full refresh takes > 1 000 ms, so 10 ms is more than enough to
     * distinguish a connected (BUSY = 1) from a floating (BUSY = 0) signal. */
    
    /* -------------------------------------------------------------------------
     * Devicetree node aliases
     * ------------------------------------------------------------------------- */
    #define EPAPER_NODE DT_NODELABEL(epaper)
    
    /*
     * Thin adapter macros so the rest of this file is unchanged regardless of
     * which SPI transport is compiled.  All actual SPI logic lives in
     * epaper_spi_sw.c (software) or epaper_spi_hw.c (hardware SPIM + DMA).
     */
    #define epd_send_cmd(c)     epaper_spi_cmd((uint8_t)(c))
    #define epd_send_byte(d)    epaper_spi_data((uint8_t)(d))
    #define epd_send_buf(b, l)  epaper_spi_buf((b), (l))
    
    /* GPIO control lines — DC and CS are owned by the transport layer */
    static const struct gpio_dt_spec rst_gpio =
        GPIO_DT_SPEC_GET(EPAPER_NODE, reset_gpios);
    
    static const struct gpio_dt_spec busy_gpio =
        GPIO_DT_SPEC_GET(EPAPER_NODE, busy_gpios);
    
    /* -------------------------------------------------------------------------
     * Framebuffer  (15 000 bytes, static allocation in BSS)
     * ------------------------------------------------------------------------- */
    static uint8_t framebuf[EPD_BUF_SIZE];
    
    /* SPI transfers are forwarded to the selected transport via the
     * epd_send_cmd / epd_send_byte / epd_send_buf macros defined above. */
    
    /* -------------------------------------------------------------------------
     * Display control helpers
     * ------------------------------------------------------------------------- */
    
    /*
     * Poll BUSY until the display signals idle (LOW), with a hard timeout.
     *
     * BUSY = HIGH → display is busy / refreshing
     * BUSY = LOW  → display is idle, ready to accept commands
     *
     * Prints to UART:
     *   - Pin level on entry (helps detect always-HIGH / always-LOW faults)
     *   - Heartbeat every 500 ms while waiting (confirms MCU is alive)
     *   - Elapsed time when BUSY finally goes LOW
     *   - A clear ERROR line on timeout
     *
     * Returns true if idle was reached, false on timeout.
     */
    static bool epd_wait_busy(const char *context)
    {
        int64_t t_start    = k_uptime_get();
        int64_t deadline   = t_start + EPD_BUSY_TIMEOUT_MS;
        int64_t t_last_hb  = t_start;
        int     pin_entry  = gpio_pin_get_dt(&busy_gpio);
    
        printk("[EPD] wait_busy [%s]: BUSY pin = %d at entry  "
               "(1=busy HIGH, 0=idle LOW)\n", context, pin_entry);
        LOG_INF("wait_busy [%s]: BUSY pin = %d at entry", context, pin_entry);
    
        while (gpio_pin_get_dt(&busy_gpio) == 1) {
            int64_t now = k_uptime_get();
    
            if (now > deadline) {
                printk("[EPD] *** BUSY TIMEOUT *** [%s] after %d ms\n"
                       "      --> Is display powered? Is P1.06 connected to BUSY?\n"
                       "      --> Continuing anyway — subsequent commands may fail.\n",
                       context, EPD_BUSY_TIMEOUT_MS);
                LOG_ERR("BUSY timeout %d ms [%s] — check P1.06 wiring!",
                        EPD_BUSY_TIMEOUT_MS, context);
                return false;
            }
    
            if (now - t_last_hb >= 500) {
                printk("[EPD] ... still waiting [%s], elapsed %d ms, BUSY=%d\n",
                       context, (int)(now - t_start),
                       gpio_pin_get_dt(&busy_gpio));
                t_last_hb = now;
            }
    
            k_msleep(EPD_BUSY_POLL_MS);
        }
    
        int elapsed = (int)(k_uptime_get() - t_start);
    
        printk("[EPD] BUSY went LOW after %d ms [%s]  <-- OK\n", elapsed, context);
        LOG_INF("wait_busy [%s]: idle after %d ms", context, elapsed);
        return true;
    }
    
    /*
     * Pulse RST to hardware-reset the display controller.
     * After this function returns the controller is out of reset but still
     * performing its internal init — do NOT send any SPI command until
     * EPD_RESET_RELEASE_MS has elapsed (handled by the caller).
     */
    static void epd_hw_reset(void)
    {
        /* RST HIGH → LOW → HIGH, matching Waveshare V2 reference timing */
        printk("[EPD] HW reset: RST HIGH (%d ms)\n", EPD_RESET_HIGH_MS);
        LOG_INF("HW reset start");
        gpio_pin_set_dt(&rst_gpio, 1);
        k_msleep(EPD_RESET_HIGH_MS);                 /* 100 ms */
    
        printk("[EPD] HW reset: RST LOW pulse (%d ms)\n", EPD_RESET_PULSE_MS);
        gpio_pin_set_dt(&rst_gpio, 0);
        k_msleep(EPD_RESET_PULSE_MS);                /* 2 ms   */
    
        gpio_pin_set_dt(&rst_gpio, 1);
        k_msleep(EPD_RESET_RELEASE_MS);              /* 100 ms settle — controller init */
        printk("[EPD] HW reset: RST released, %d ms settle done\n",
               EPD_RESET_RELEASE_MS);
    
        printk("[EPD] HW reset: monitoring BUSY for 300 ms after RST release...\n");
        bool busy_ever_high = false;
        int  busy_rise_ms   = -1;
        int  busy_fall_ms   = -1;
        int  prev_b         = 0;
    
        for (int t = 0; t < 300; t++) {
            int b = gpio_pin_get_dt(&busy_gpio);
    
            if (b == 1 && prev_b == 0) {
                busy_rise_ms   = t;
                busy_ever_high = true;
                printk("[EPD]   BUSY rose  HIGH at %3d ms\n", t);
            } else if (b == 0 && prev_b == 1) {
                busy_fall_ms = t;
                printk("[EPD]   BUSY fell  LOW  at %3d ms  (reset complete)\n", t);
                break; /* display ready — no need to wait longer */
            }
    
            prev_b = b;
            k_msleep(1);
        }
    
        if (busy_ever_high) {
            printk("[EPD] HW reset: BUSY pulsed HIGH (%d ms) then LOW (%d ms) -- RST OK\n",
                   busy_rise_ms,
                   busy_fall_ms >= 0 ? busy_fall_ms : 300);
        } else {
            printk("[EPD] HW reset: BUSY never went HIGH in 300 ms.\n");
            printk("[EPD]   --> RST wire (P1.12 to display RST) may not be connected.\n");
            printk("[EPD]   --> The SPI init sequence will still proceed.\n");
            printk("[EPD]   --> If display stays blank: verify P1.12 <-> RST wire.\n");
            LOG_WRN("BUSY never HIGH after RST — check P1.12 RST wire");
        }
    
        printk("[EPD] HW reset: done\n");
        LOG_INF("HW reset done");
    }
    
    
    /* Set the RAM write window to the full panel and move cursor to (0, 0). */
    static void epd_set_window_and_cursor(void)
    {
        /* RAM X: byte 0 → byte 49  (8 pixels per byte × 50 = 400 pixels) */
        epd_send_cmd(0x44);
        epd_send_byte(0x00);
        epd_send_byte((EPD_WIDTH / 8U) - 1U);         /* 49 = 0x31 */
    
        /* RAM Y: line 0 → line 299 */
        epd_send_cmd(0x45);
        epd_send_byte(0x00);                           /* YStart low  */
        epd_send_byte(0x00);                           /* YStart high */
        epd_send_byte((EPD_HEIGHT - 1U) & 0xFF);       /* YEnd low  = 0x2B */
        epd_send_byte(((EPD_HEIGHT - 1U) >> 8) & 0xFF); /* YEnd high = 0x01 */
    
        /* RAM X counter = 0 */
        epd_send_cmd(0x4E);
        epd_send_byte(0x00);
    
        /* RAM Y counter = 0 */
        epd_send_cmd(0x4F);
        epd_send_byte(0x00);
        epd_send_byte(0x00);
    }
    
    /* -------------------------------------------------------------------------
     * SSD1683 full initialisation sequence
     * Mirrors Waveshare 4.2" V2 Python driver (epd4in2_V2.py) exactly.
     * SPI transfers are delegated to the selected transport (SW or HW).
     * ------------------------------------------------------------------------- */
    static int epd_init_hw(void)
    {
        printk("[EPD] ============================================\n");
        printk("[EPD]  SSD1683 init (V2 sequence)\n");
        printk("[EPD] ============================================\n");
    
        /*
         * Init sequence mirrors Waveshare 4.2" V2 Python driver (epd4in2_V2.py)
         * exactly — same commands, same data bytes, same order.
         *
         * Key corrections vs previous version:
         *   • 0x01 (DRIVER_OUTPUT_CONTROL) removed — not in V2 init()
         *   • 0x21 data fixed: 0x40,0x00 (was 0x00,0x80)
         *     0x80 in byte-2 sets LUTSEL=1 (custom LUT from registers).
         *     No custom LUT was loaded, so waveform engine was undefined.
         *     0x00 in byte-2 = LUTSEL=0 → use factory-calibrated OTP LUT.
         *   • 0x21 / 0x3C sent BEFORE RAM window (V2 order)
         *   • 0x18 (TEMPERATURE_SENSOR) removed — not in V2 init()
         */
    
        /* Step 0: power-on stabilisation */
        printk("[EPD] Step 0: power-on delay %d ms\n", EPD_POWER_ON_DELAY_MS);
        k_msleep(EPD_POWER_ON_DELAY_MS);
    
        /* Step 1: hardware reset */
        printk("[EPD] Step 1: hardware reset\n");
        epd_hw_reset();
    
        /* Step 1b: wait for BUSY LOW after reset (V2: ReadBusy after reset()) */
        printk("[EPD] Step 1b: wait BUSY LOW after HW reset\n");
        epd_wait_busy("post_hw_reset");
    
        /* Step 2: software reset (0x12) then wait BUSY LOW (V2: ReadBusy after 0x12) */
        printk("[EPD] Step 2: SW_RESET 0x12\n");
        epd_send_cmd(0x12);
        k_msleep(EPD_SW_RESET_DELAY_MS);
        printk("[EPD] Step 2b: wait BUSY LOW after SW_RESET\n");
        epd_wait_busy("SW_RESET");
    
        /* Step 3: Display Update Control — 0x21, data 0x40, 0x00
         * Byte 1: 0x40 — normal BW display mode
         * Byte 2: 0x00 — LUTSEL=0 (use OTP/factory LUT, NOT register LUT) */
        printk("[EPD] Step 3: DISPLAY_UPDATE_CTRL 0x21, data=0x40,0x00\n");
        epd_send_cmd(0x21);
        epd_send_byte(0x40);
        epd_send_byte(0x00);
    
        /* Step 4: Border Waveform Control */
        printk("[EPD] Step 4: BORDER_WAVEFORM 0x3C, data=0x05\n");
        epd_send_cmd(0x3C);
        epd_send_byte(0x05);
    
        /* Step 5: Data Entry Mode — X increment, Y increment */
        printk("[EPD] Step 5: DATA_ENTRY_MODE 0x11, data=0x03\n");
        epd_send_cmd(0x11);
        epd_send_byte(0x03);
    
        /* Step 6: RAM X window: byte 0 (X=0) to byte 0x31=49 (X=399) */
        printk("[EPD] Step 6: RAM X window 0x44: 0x00..0x31\n");
        epd_send_cmd(0x44);
        epd_send_byte(0x00);
        epd_send_byte(0x31);
    
        /* Step 7: RAM Y window: line 0 to line 299 (0x012B) */
        printk("[EPD] Step 7: RAM Y window 0x45: 0x0000..0x012B\n");
        epd_send_cmd(0x45);
        epd_send_byte(0x00);
        epd_send_byte(0x00);
        epd_send_byte(0x2B);
        epd_send_byte(0x01);
    
        /* Step 8: RAM X counter = 0 */
        printk("[EPD] Step 8: RAM cursor 0x4E=0x00, 0x4F=0x0000\n");
        epd_send_cmd(0x4E);
        epd_send_byte(0x00);
    
        /* Step 9: RAM Y counter = 0 */
        epd_send_cmd(0x4F);
        epd_send_byte(0x00);
        epd_send_byte(0x00);
    
        /* Step 10: final BUSY wait (V2: ReadBusy at end of init()) */
        printk("[EPD] Step 10: final wait BUSY LOW\n");
        epd_wait_busy("init_final");
    
        printk("[EPD] ============================================\n");
        printk("[EPD]  SSD1683 init COMPLETE — display ready\n");
        printk("[EPD] ============================================\n");
        LOG_INF("EPD init: complete");
        return 0;
    }
    
    /* -------------------------------------------------------------------------
     * Public API
     * ------------------------------------------------------------------------- */
    
    int epaper_init(void)
    {
        printk("[EPD] *** epaper_init() called ***\n");
        printk("[EPD] Display: Waveshare 4.2\" b/w, SSD1683, %ux%u px\n",
               EPD_WIDTH, EPD_HEIGHT);
        printk("[EPD] Transport: %s\n",
               EPAPER_TRANSPORT_HW ? "hardware SPIM22 + DMA (nrfx)"
                                   : "software SPI (bit-bang)");
        LOG_INF("epaper_init: starting");
    
        /* ---- SPI transport (DC + CS + SPI pins) ---- */
        int err = epaper_spi_init();
        if (err) {
            printk("[EPD] ERROR: epaper_spi_init() failed: %d\n", err);
            LOG_ERR("SPI transport init failed (%d)", err);
            return err;
        }
    
        /* ---- RST GPIO (P1.12) ---- */
        if (!gpio_is_ready_dt(&rst_gpio)) {
            printk("[EPD] ERROR: RST GPIO not ready\n");
            LOG_ERR("RST GPIO not ready");
            return -ENODEV;
        }
        printk("[EPD] RST GPIO (%s pin %u) ready\n",
               rst_gpio.port->name, rst_gpio.pin);
    
        /* ---- BUSY GPIO (P1.06) ---- */
        if (!gpio_is_ready_dt(&busy_gpio)) {
            printk("[EPD] ERROR: BUSY GPIO not ready\n");
            LOG_ERR("BUSY GPIO not ready");
            return -ENODEV;
        }
        printk("[EPD] BUSY GPIO (%s pin %u) ready\n",
               busy_gpio.port->name, busy_gpio.pin);
    
        /* RST: output, start HIGH (not in reset) */
        err = gpio_pin_configure_dt(&rst_gpio, GPIO_OUTPUT_ACTIVE);
        if (err) {
            printk("[EPD] ERROR: RST GPIO configure failed: %d\n", err);
            LOG_ERR("RST GPIO configure failed (%d)", err);
            return err;
        }
        printk("[EPD] RST GPIO configured OUTPUT HIGH\n");
    
        /*
         * BUSY: input with internal pull-down.
         * Pull-down ensures LOW (idle) when the BUSY wire is disconnected,
         * so epd_wait_busy() never hangs even without the wire present.
         */
        err = gpio_pin_configure_dt(&busy_gpio, GPIO_INPUT | GPIO_PULL_DOWN);
        if (err) {
            printk("[EPD] ERROR: BUSY GPIO configure failed: %d\n", err);
            LOG_ERR("BUSY GPIO configure failed (%d)", err);
            return err;
        }
        printk("[EPD] BUSY GPIO configured INPUT+PULL-DOWN  (current=%d)\n",
               gpio_pin_get_dt(&busy_gpio));
    
        LOG_INF("GPIOs configured — starting SSD1683 init");
    
        err = epd_init_hw();
        if (err) {
            printk("[EPD] ERROR: hardware init failed: %d\n", err);
            LOG_ERR("EPD hardware init failed (%d)", err);
            return err;
        }
    
        printk("[EPD] epaper_init() SUCCESS — %ux%u display ready\n",
               EPD_WIDTH, EPD_HEIGHT);
        LOG_INF("Waveshare 4.2\" e-Paper ready (%ux%u)", EPD_WIDTH, EPD_HEIGHT);
        return 0;
    }
    
    void epaper_clear(uint8_t color)
    {
        memset(framebuf, color, sizeof(framebuf));
    }
    
    void epaper_set_pixel(uint16_t x, uint16_t y, bool black)
    {
        if (x >= EPD_WIDTH || y >= EPD_HEIGHT) {
            return;
        }
    
        uint32_t idx  = (x / 8U) + (uint32_t)y * (EPD_WIDTH / 8U);
        uint8_t  mask = 0x80U >> (x % 8U);
    
        if (black) {
            framebuf[idx] &= ~mask; /* clear bit → black pixel */
        } else {
            framebuf[idx] |=  mask; /* set bit   → white pixel */
        }
    }
    
    void epaper_draw_char(uint16_t x, uint16_t y, char c,
                          uint8_t scale, bool black)
    {
        if ((uint8_t)c < 0x20U || (uint8_t)c > 0x7EU) {
            c = '?';
        }
    
        const uint8_t *glyph = font5x7[(uint8_t)c - 0x20U];
    
        for (uint8_t col = 0U; col < FONT5X7_COLS; col++) {
            uint8_t col_data = glyph[col];
    
            for (uint8_t row = 0U; row < FONT5X7_ROWS; row++) {
                if (col_data & (1U << row)) {
                    for (uint8_t sx = 0U; sx < scale; sx++) {
                        for (uint8_t sy = 0U; sy < scale; sy++) {
                            epaper_set_pixel(
                                x + (uint16_t)(col * scale + sx),
                                y + (uint16_t)(row * scale + sy),
                                black);
                        }
                    }
                }
            }
        }
    }
    
    void epaper_draw_string(uint16_t x, uint16_t y, const char *str,
                            uint8_t scale, bool black)
    {
        uint16_t cx      = x;
        uint16_t advance = (uint16_t)(FONT5X7_ADVANCE * scale);
    
        while (*str) {
            if (cx + (uint16_t)(FONT5X7_COLS * scale) > EPD_WIDTH) {
                break;
            }
            epaper_draw_char(cx, y, *str, scale, black);
            cx += advance;
            str++;
        }
    }
    
    void epaper_display(void)
    {
        printk("[EPD] epaper_display(): resetting cursor to (0,0)\n");
        LOG_INF("epaper_display: writing framebuffer");
    
        /* Reset write cursor to (0, 0) before streaming the framebuffer */
        epd_set_window_and_cursor();
    
        /*
         * Write the same framebuffer to BOTH RAM banks — required by V2.
         *   0x24 = BW RAM  (new / current frame)
         *   0x26 = RED RAM (previous frame — used by waveform engine for
         *                   differential e-ink drive; must not be garbage)
         * Waveshare V2 Python display() sends the image to both banks.
         */
        printk("[EPD] Writing BW RAM  (0x24): %u bytes...\n",
               (unsigned)sizeof(framebuf));
        epd_send_cmd(0x24);
        epd_send_buf(framebuf, sizeof(framebuf));
    
        printk("[EPD] Writing RED RAM (0x26): %u bytes (same data)...\n",
               (unsigned)sizeof(framebuf));
        epd_send_cmd(0x26);
        epd_send_buf(framebuf, sizeof(framebuf));
        printk("[EPD] Framebuffer write complete (both RAM banks)\n");
    
        /* Full update — LUT from OTP (temperature-compensated) */
        printk("[EPD] Sending DISPLAY_UPDATE_CONTROL_2 cmd 0x22, data=0xF7\n");
        epd_send_cmd(0x22);
        epd_send_byte(0xF7);
    
        printk("[EPD] Sending MASTER_ACTIVATION cmd 0x20  "
               "<-- e-ink waveform starts now\n");
        LOG_INF("epaper_display: triggering full refresh (may take ~2 s)");
        epd_send_cmd(0x20);
    
        /*
         * Monitor BUSY for up to 15 seconds after MASTER_ACTIVATION.
         *
         * A working SSD1683 drives BUSY HIGH within 1 ms of receiving 0x20 and
         * holds it HIGH for ~2-4 seconds during the full refresh waveform.
         * Polling every 50 ms is more than fine enough to catch it.
         *
         * If BUSY never goes HIGH in 15 seconds:
         *   (a) SPI data is not reaching the display controller.
         *   (b) The display is damaged.
         * The software SPI diagnostic earlier in epd_init_hw() distinguishes these.
         */
        printk("[EPD] Monitoring BUSY for up to 15 s after MASTER_ACTIVATION...\n");
        printk("[EPD]   (SSD1683 should drive BUSY HIGH within 1 ms of 0x20)\n");
    
        bool refresh_seen = false;
        int  last_print_s = 0;
    
        for (int t_ms = 0; t_ms <= 15000; t_ms += 50) {
            int b = gpio_pin_get_dt(&busy_gpio);
    
            /* Print a status line every 1000 ms */
            int s = t_ms / 1000;
            if (s > last_print_s || t_ms == 0) {
                last_print_s = s;
                printk("[EPD]   +%2d s : BUSY = %d\n", s, b);
            }
    
            if (b == 1) {
                refresh_seen = true;
                printk("[EPD] BUSY HIGH at +%d ms — display is refreshing!\n", t_ms);
                printk("[EPD] Waiting for BUSY to go LOW (refresh complete)...\n");
                epd_wait_busy("display_refresh");
                break;
            }
    
            k_msleep(50);
        }
    
        if (!refresh_seen) {
            printk("[EPD] *** BUSY never went HIGH in 15 s ***\n");
            printk("[EPD]   SPI data (0x20 MASTER_ACTIVATION) did not reach the display.\n");
            printk("[EPD]   Check wiring: SCK P1.14, DIN P1.08, CS P1.09, DC P1.11.\n");
        }
    
        printk("[EPD] epaper_display(): refresh COMPLETE — image should be visible\n");
        LOG_INF("epaper_display: refresh complete");
    }
    
    void epaper_sleep(void)
    {
        printk("[EPD] epaper_sleep(): sending DEEP_SLEEP cmd 0x10, data=0x01\n");
        epd_send_cmd(0x10);
        epd_send_byte(0x01); /* Deep sleep mode 1 — RAM contents retained */
        printk("[EPD] ePaper in deep sleep (RST required to wake)\n");
        LOG_INF("ePaper: deep sleep entered");
    }
    


    THhs the uart output while using SW SPI (bit bangging approach)

    [MAIN] ----------------------------------------
    [MAIN] Starting Waveshare 4.2" ePaper init
    [MAIN] ----------------------------------------
    [EPD] *** epaper_init() called ***
    [EPD] Display: Waveshare 4.2" b/w, SSD1683, 400x300 px
    [EPD] Transport: software SPI (bit-bang)
    [EPD] RST GPIO (gpio@d8200 pin 12) ready
    [EPD] BUSY GPIO (gpio@d8200 pin 6) ready
    [EPD] RST GPIO configured OUTPUT HIGH
    [EPD] BUSY GPIO configured INPUT+PULL-DOWN (current=0)
    [EPD] ============================================
    [EPD] SSD1683 init (V2 sequence)
    [EPD] ============================================
    [EPD] Step 0: power-on delay 200 ms
    [EPD] Step 1: hardware reset
    [EPD] HW reset: RST HIGH (100 ms)
    [EPD] HW reset: RST LOW pulse (2 ms)
    [EPD] HW reset: RST released, 100 ms settle done
    [EPD] HW reset: monitoring BUSY for 300 ms after RST release...
    [EPD] HW reset: BUSY never went HIGH in 300 ms.
    [EPD] --> RST wire (P1.12 to display RST) may not be connected.
    [EPD] --> The SPI init sequence will still proceed.
    [EPD] --> If display stays blank: verify P1.12 <-> RST wire.
    [EPD] HW reset: done
    [EPD] Step 1b: wait BUSY LOW after HW reset
    [EPD] wait_busy [post_hw_reset]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 7 ms [post_hw_reset] <-- OK
    [EPD] Step 2: SW_RESET 0x12
    [EPD] Step 2b: wait BUSY LOW after SW_RESET
    [EPD] wait_busy [SW_RESET]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 6 ms [SW_RESET] <-- OK
    [EPD] Step 3: DISPLAY_UPDATE_CTRL 0x21, data=0x40,0x00
    [EPD] Step 4: BORDER_WAVEFORM 0x3C, data=0x05
    [EPD] Step 5: DATA_ENTRY_MODE 0x11, data=0x03
    [EPD] Step 6: RAM X window 0x44: 0x00..0x31
    [EPD] Step 7: RAM Y window 0x45: 0x0000..0x012B
    [EPD] Step 8: RAM cursor 0x4E=0x00, 0x4F=0x0000
    [EPD] Step 10: final wait BUSY LOW
    [EPD] wait_busy [init_final]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 7 ms [init_final] <-- OK
    [EPD] ============================================
    [EPD] SSD1683 init COMPLETE — display ready
    [EPD] ============================================
    [EPD] epaper_init() SUCCESS — 400x300 display ready
    [MAIN] epaper_init OK — clearing framebuffer (white)
    [MAIN] Drawing 'Hello, Nugo!' at (92,120) scale=3
    [MAIN] Drawing 'nRF54L15DK' at (140,165) scale=2
    [MAIN] Calling epaper_display() — full refresh (~2 s)
    [EPD] epaper_display(): resetting cursor to (0,0)
    [EPD] Writing BW RAM (0x24): 15000 bytes...
    [EPD] Writing RED RAM (0x26): 15000 bytes (same data)...
    [EPD] Framebuffer write complete (both RAM banks)
    [EPD] Sending DISPLAY_UPDATE_CONTROL_2 cmd 0x22, data=0xF7
    [EPD] Sending MASTER_ACTIVATION cmd 0x20 <-- e-ink waveform starts now
    [EPD] Monitoring BUSY for up to 15 s after MASTER_ACTIVATION...
    [EPD] (SSD1683 should drive BUSY HIGH within 1 ms of 0x20)
    [EPD] + 0 s : BUSY = 1
    [EPD] BUSY HIGH at +0 ms — display is refreshing!
    [EPD] Waiting for BUSY to go LOW (refresh complete)...
    [EPD] wait_busy [display_refresh]: BUSY pin = 1 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] ... still waiting [display_refresh], elapsed 501 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 1001 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 1510 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 2010 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 2510 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 3020 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 3529 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 4029 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 4529 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 5039 ms, BUSY=1
    [EPD] ... still waiting [display_refresh], elapsed 5548 ms, BUSY=1
    [EPD] *** BUSY TIMEOUT *** [display_refresh] after 6000 ms
    --> Is display powered? Is P1.06 connected to BUSY?
    --> Continuing anyway — subsequent commands may fail.
    [EPD] epaper_display(): refresh COMPLETE — image should be visible
    [MAIN] epaper_display() returned — putting display to sleep
    [EPD] epaper_sleep(): sending DEEP_SLEEP cmd 0x10, data=0x01
    [EPD] ePaper in deep sleep (RST required to wake)
    [MAIN] ePaper sequence complete

    THis is my uart output for HW use case

    MAIN] ----------------------------------------
    [MAIN] Starting Waveshare 4.2" ePaper init
    [MAIN] ----------------------------------------
    [EPD] *** epaper_init() called ***
    [EPD] Display: Waveshare 4.2" b/w, SSD1683, 400x300 px
    [EPD] Transport: hardware SPIM22 + DMA (nrfx)
    [EPD-HW] SPIM22 ready via Zephyr SPI API (SCK=P1.14, DIN=P1.08, CS=P1.09 auto, DC=P1.11)
    [EPD] RST GPIO (gpio@d8200 pin 12) ready
    [EPD] BUSY GPIO (gpio@d8200 pin 6) ready
    [EPD] RST GPIO configured OUTPUT HIGH
    [EPD] BUSY GPIO configured INPUT+PULL-DOWN (current=0)
    [EPD] ============================================
    [EPD] SSD1683 init (V2 sequence)
    [EPD] ============================================
    [EPD] Step 0: power-on delay 200 ms
    [EPD] Step 1: hardware reset
    [EPD] HW reset: RST HIGH (100 ms)
    [EPD] HW reset: RST LOW pulse (2 ms)
    [EPD] HW reset: RST released, 100 ms settle done
    [EPD] HW reset: monitoring BUSY for 300 ms after RST release...
    [EPD] HW reset: BUSY never went HIGH in 300 ms.
    [EPD] --> RST wire (P1.12 to display RST) may not be connected.
    [EPD] --> The SPI init sequence will still proceed.
    [EPD] --> If display stays blank: verify P1.12 <-> RST wire.
    [EPD] HW reset: done
    [EPD] Step 1b: wait BUSY LOW after HW reset
    [EPD] wait_busy [post_hw_reset]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 7 ms [post_hw_reset] <-- OK
    [EPD] Step 2: SW_RESET 0x12
    [EPD] Step 2b: wait BUSY LOW after SW_RESET
    [EPD] wait_busy [SW_RESET]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 6 ms [SW_RESET] <-- OK
    [EPD] Step 3: DISPLAY_UPDATE_CTRL 0x21, data=0x40,0x00
    [EPD] Step 4: BORDER_WAVEFORM 0x3C, data=0x05
    [EPD] Step 5: DATA_ENTRY_MODE 0x11, data=0x03
    [EPD] Step 6: RAM X window 0x44: 0x00..0x31
    [EPD] Step 7: RAM Y window 0x45: 0x0000..0x012B
    [EPD] Step 8: RAM cursor 0x4E=0x00, 0x4F=0x0000
    [EPD] Step 10: final wait BUSY LOW
    [EPD] wait_busy [init_final]: BUSY pin = 0 at entry (1=busy HIGH, 0=idle LOW)
    [EPD] BUSY went LOW after 7 ms [init_final] <-- OK
    [EPD] ============================================
    [EPD] SSD1683 init COMPLETE — display ready
    [EPD] ============================================
    [EPD] epaper_init() SUCCESS — 400x300 display ready
    [MAIN] epaper_init OK — clearing framebuffer (white)
    [MAIN] Drawing 'Hello, Nugo!' at (92,120) scale=3
    [MAIN] Drawing 'nRF54L15DK' at (140,165) scale=2
    [MAIN] Calling epaper_display() — full refresh (~2 s)
    [EPD] epaper_display(): resetting cursor to (0,0)
    [EPD] Writing BW RAM (0x24): 15000 bytes...
    [EPD] Writing RED RAM (0x26): 15000 bytes (same data)...
    [EPD] Framebuffer write complete (both RAM banks)
    [EPD] Sending DISPLAY_UPDATE_CONTROL_2 cmd 0x22, data=0xF7
    [EPD] Sending MASTER_ACTIVATION cmd 0x20 <-- e-ink waveform starts now
    [EPD] Monitoring BUSY for up to 15 s after MASTER_ACTIVATION...
    [EPD] (SSD1683 should drive BUSY HIGH within 1 ms of 0x20)
    [EPD] + 0 s : BUSY = 0
    [EPD] + 1 s : BUSY = 0
    [EPD] + 2 s : BUSY = 0
    [EPD] + 3 s : BUSY = 0
    [EPD] + 4 s : BUSY = 0
    [EPD] + 5 s : BUSY = 0
    [EPD] + 6 s : BUSY = 0
    [EPD] + 7 s : BUSY = 0
    [EPD] + 8 s : BUSY = 0
    [EPD] + 9 s : BUSY = 0
    [EPD] +10 s : BUSY = 0
    [EPD] +11 s : BUSY = 0
    [EPD] +12 s : BUSY = 0
    [EPD] +13 s : BUSY = 0
    [EPD] +14 s : BUSY = 0
    [EPD] +15 s : BUSY = 0
    [EPD] *** BUSY never went HIGH in 15 s ***
    [EPD] SPI data (0x20 MASTER_ACTIVATION) did not reach the display.
    [EPD] Check wiring: SCK P1.14, DIN P1.08, CS P1.09, DC P1.11.
    [EPD] epaper_display(): refresh COMPLETE — image should be visible
    [MAIN] epaper_display() returned — putting display to sleep
    [EPD] epaper_sleep(): sending DEEP_SLEEP cmd 0x10, data=0x01
    [EPD] ePaper in deep sleep (RST required to wake)
    [MAIN] ePaper sequence complete
    [MAIN] ----------------------------------------



Children
Related