I am using the nRF54L15 (PANB511 Module ) controller with an ST7789V-based display connected via SPI21 at 8 MHz. Image bin files are stored using LittleFS in external flash(W25Q128) at SPI00 at 32 MHz crystal.
The display shows images correctly if I include LOG_DBG() calls at 4 places inside the display draw function. However, if I remove those LOG_DBG() calls, the display becomes corrupted. Can you help with this, please?
.dtsi file
&pinctrl {
uart20_default: uart20_default {
group1 {
psels = <NRF_PSEL(UART_TX, 1, 2)>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 1, 3)>;
bias-pull-up;
};
};
uart20_sleep: uart20_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 1, 2)>,
<NRF_PSEL(UART_RX, 1, 3)>;
low-power-enable;
};
};
spi00_default: spi00_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 2, 1)>,
<NRF_PSEL(SPIM_MOSI, 2, 2)>,
<NRF_PSEL(SPIM_MISO, 2, 4)>;
};
};
spi00_sleep: spi00_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 2, 1)>,
<NRF_PSEL(SPIM_MOSI, 2, 2)>,
<NRF_PSEL(SPIM_MISO, 2, 4)>;
low-power-enable;
};
};
spi21_default: spi21_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 2, 6)>,
<NRF_PSEL(SPIM_MOSI, 2, 8)>,
<NRF_PSEL(SPIM_MISO, 2, 9)>;
};
};
spi21_sleep: spi21_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 2, 6)>,
<NRF_PSEL(SPIM_MOSI, 2, 8)>,
<NRF_PSEL(SPIM_MISO, 2, 9)>;
low-power-enable;
};
};
pwm20_default: pwm20_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 1, 4)>;
};
};
pwm20_sleep: pwm20_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 1, 4)>;
low-power-enable;
};
};
};
.................................................................
overlay file
/ {
fstab {
compatible = "zephyr,fstab";
lfs1: lfs1 {
compatible = "zephyr,fstab,littlefs";
mount-point = "/lfs1";
partition = <&filesystem>;
automount;
read-size = <16>;
prog-size = <16>;
cache-size = <64>;
lookahead-size = <32>;
block-cycles = <512>;
};
};
};
&spi21 {
easydma-maxcnt-bits = <16>;
compatible = "nordic,nrf-spim";
};
.......................................................
/dts-v1/;
#include <nordic/nrf54l15_cpuapp.dtsi>
#include "prj.dtsi"
/ {
model = "Display Board";
compatible = "Prj-cpuapp";
chosen {
zephyr,console = &uart20;
zephyr,shell-uart = &uart20;
zephyr,uart-mcumgr = &uart20;
zephyr,bt-mon-uart = &uart20;
zephyr,bt-c2h-uart = &uart20;
zephyr,code-partition = &slot0_partition;
zephyr,flash-controller = &rram_controller;
zephyr,flash = &cpuapp_rram;
zephyr,sram = &cpuapp_sram;
zephyr,ieee802154 = &ieee802154;
nordic,pm-ext-flash = &w25q128;
zephyr,display = &st7789v_display;
};
/** Peripherals */
/** Display Driver */
mipi-dbi-st7789v-display {
compatible = "zephyr,mipi-dbi-spi";
spi-dev = <&spi21>;
dc-gpios = <&gpio2 7 GPIO_ACTIVE_HIGH>; /* D9 */
reset-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>; /* D8 */
#address-cells = <1>;
#size-cells = <0>;
st7789v_display: st7789v@0 {
compatible = "sitronix,st7789v";
mipi-max-frequency = <8000000>;
reg = <0>;
width = <320>;
height = <240>;
x-offset = <0>;
y-offset = <0>;
vcom = <0x20>;
gctrl = <0x35>;
vrhs = <0x12>;
vdvs = <0x20>;
mdac = <0x60>;
gamma = <0x01>;
colmod = <0x55>;
lcm = <0x2c>;
porch-param = [ 0c 0c 00 33 33 ];
cmd2en-param = [ 5a 69 02 01 ];
pwctrl1-param = [ a4 a1 ];
pvgam-param = [ d0 00 02 07 0a 28 32 44 42 06 0e 12 14 17 ];
nvgam-param = [ d0 00 02 07 0a 28 31 54 47 0e 1c 17 1b 1e ];
ram-param = [ 00 F0 ];
rgb-param = [ cd 08 14 ];
mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";
};
};
/** Level Shifter Pins */
levelShifter: lvl_shifter{
compatible = "gpio-keys";
lvl_shift_en: lvl_shift_en{
gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
label = "Level Shifter Enable";
};
lvl_shift_dir: lvl_shift_dir{
gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
label = "Level Shifter Direction";
};
};
parallelPort: p_port{
compatible = "gpio-keys";
/** Controll pins */
/** Verify parallel port pins later */
pport_Enable: Enable{
gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
label = "Enable";
};
pport_read: read{
gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>;
label = "RDC1";
};
pport_write: write{
gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
label = "WRC0";
};
/** Data pins */
pport_d0: d0{
gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>;
label = "D0";
};
pport_d1: d1{
gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
label = "D1";
};
pport_d2: d2{
gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
label = "D2";
};
pport_d3: d3{
gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>;
label = "D3";
};
pport_d4: d4{
gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
label = "D4";
};
pport_d5: d5{
gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
label = "D5";
};
pport_d6: d6{
gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>;
label = "D6";
};
pport_d7: d7{
gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>;
label = "D7";
};
};
enable_pins: enable_pins{
compatible = "gpio-leds";
/** Display backlight enable */
display_en: display_en{
gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
label = "Display Backlight Enable";//blacklight will be enabled
};
};
detect_pins: detect_pins{
compatible = "gpio-keys";
display_detect: display_detect{
gpios = <&gpio2 3 GPIO_ACTIVE_HIGH>;
label = "Display Detect"; // HIGH - DISPLAY1, //LOW - DISPLAY2
};
};
pwm_devices: pwm_devices {
compatible = "pwm-leds";
/** PWM backlight */
backlight: backlight {
pwms = <&pwm20 0 PWM_MSEC(20) 0>;
label = "Backlight PWM";
};
};
/** Alias */
aliases {
level-shift-enable = &lvl_shift_en;
level-shift-direction = &lvl_shift_dir;
parallel-port-enable = &pport_Enable;
parallel-port-rd = &pport_read;
parallel-port-wr = &pport_write;
parallel-port-d0 = &pport_d0;
parallel-port-d1 = &pport_d1;
parallel-port-d2 = &pport_d2;
parallel-port-d3 = &pport_d3;
parallel-port-d4 = &pport_d4;
parallel-port-d5 = &pport_d5;
parallel-port-d6 = &pport_d6;
parallel-port-d7 = &pport_d7;
watchdog0 = &wdt31;
display-enable = &display_en;
display-detect = &display_detect;
display-brightness = &backlight;
};
};
&grtc {
owned-channels = <0 1 2 3 4 5 6 7 8 9 10 11>;
/* Channels 7-11 reserved for Zero Latency IRQs, 3-4 for FLPR */
child-owned-channels = <3 4 7 8 9 10 11>;
status = "okay";
};
&cpuapp_sram {
status = "okay";
};
&lfxo {
load-capacitors = "internal";
load-capacitance-femtofarad = <15500>;
};
&hfxo {
load-capacitors = "internal";
load-capacitance-femtofarad = <15000>;
};
®ulators {
status = "okay";
};
&vregmain {
status = "okay";
regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
};
&uart20 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart20_default>;
pinctrl-1 = <&uart20_sleep>;
pinctrl-names = "default", "sleep";
};
&uart30 {
status = "disabled";
};
&pwm20 {
status = "okay";
pinctrl-0 = <&pwm20_default>;
pinctrl-1 = <&pwm20_sleep>;
pinctrl-names = "default", "sleep";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&gpio2 {
status = "okay";
};
&gpiote20 {
status = "okay";
};
&gpiote30 {
status = "okay";
};
&radio {
status = "okay";
};
&ieee802154 {
status = "okay";
};
&temp {
status = "disabled";
};
&clock {
status = "okay";
};
&spi00 {
status = "okay";
compatible = "nordic,nrf-spim";
pinctrl-0 = <&spi00_default>;
pinctrl-1 = <&spi00_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; // controller CS = P2.5
w25q128: w25q128@0 {
status = "okay";
compatible = "jedec,spi-nor";
reg = <0>; /* CS 0 (matches controller cs-gpios) */
label = "W25Q128";
spi-max-frequency = <DT_FREQ_M(32)>; /* 32 MHz */
jedec-id = [ ef 40 18 ];/* explicit JEDEC for Winbond W25Q128 */
size = <0x8000000>;
/* has-dpd; */
/* t-enter-dpd = <10000>; */
/* t-exit-dpd = <30000>; */
partitions{
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
filesystem: partition@0 {
label = "filesystem";
reg = <0x0 DT_SIZE_M(16)>;
};
};
};
};
&spi21 {
status = "okay";
compatible = "nordic,nrf-spim";
cs-gpios = <&gpio2 10 GPIO_ACTIVE_LOW>; /* CS = P2.10 */
pinctrl-0 = <&spi21_default>;
pinctrl-1 = <&spi21_sleep>;
pinctrl-names = "default", "sleep";
st7789v_320x240: st7789v@0 {
compatible = "spi-device";
spi-max-frequency = <DT_FREQ_M(8)>;
reg = <0>;
};
};
&adc {
status = "disabled";
};
&uicr {
nfct-pins-as-gpios;
};
/* Enable watchdog - 2 second timeout */
&wdt31 {
status = "okay";
};
...............................................................
prj.conf
CONFIG_GPIO=y
CONFIG_SPI=y
CONFIG_LOG=y
CONFIG_LOG_MODE_IMMEDIATE=n
CONFIG_PWM=y
CONFIG_WATCHDOG=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_STDOUT_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_CLOCK_CONTROL_NRF_K32SRC_SYNTH=y
CONFIG_BOOT_BANNER=y
###
# Bluetooth Low Energy configurations
####
CONFIG_BT=y
CONFIG_BT_CTLR_TX_PWR_PLUS_4=y
CONFIG_BT_HCI=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_SMP=y
CONFIG_BT_BONDABLE=y
CONFIG_BT_CTLR_ADV_EXT=n
CONFIG_BT_DEVICE_APPEARANCE=1345
CONFIG_BT_DEVICE_NAME_DYNAMIC=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_BT_FILTER_ACCEPT_LIST=y
CONFIG_BT_SIGNING=y
# Bluetooth security and bonding settings
CONFIG_BT_KEYS_OVERWRITE_OLDEST=y
CONFIG_BT_SMP_ALLOW_UNAUTH_OVERWRITE=y
CONFIG_BT_ID_UNPAIR_MATCHING_BONDS=y
CONFIG_BT_PRIVACY=n
CONFIG_BT_CTLR_PHY_2M=y
# Bluetooth buffer and connection settings
CONFIG_BT_BUF_ACL_RX_SIZE=502
CONFIG_BT_ATT_PREPARE_COUNT=2
CONFIG_BT_BUF_ACL_TX_SIZE=502
CONFIG_BT_L2CAP_TX_MTU=247
CONFIG_BT_L2CAP_TX_BUF_COUNT=10
CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
CONFIG_BT_CONN_TX_MAX=10
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n
CONFIG_BT_GATT_DM=y
CONFIG_BT_GATT_DYNAMIC_DB=y
# Allow only LESC pairing
CONFIG_BT_SMP_SC_PAIR_ONLY=y
CONFIG_BT_RX_STACK_SIZE=4096
# Configure Bluetooth connection settings
CONFIG_BT_CTLR_TX_PWR_PLUS_4=y
CONFIG_BT_MAX_CONN=1
CONFIG_BT_MAX_PAIRED=1
# Enable PHY updates
CONFIG_BT_USER_PHY_UPDATE=y
CONFIG_BT_USER_DATA_LEN_UPDATE=y
CONFIG_BT_DEVICE_NAME_GATT_WRITABLE=n
CONFIG_BT_HCI_TX_STACK_SIZE=4096
CONFIG_MPSL_WORK_STACK_SIZE=4096
CONFIG_BT_HCI_TX_STACK_SIZE_WITH_PROMPT=y
# Enable FOTA/DFU over Bluetooth LE
CONFIG_NCS_SAMPLE_MCUMGR_BT_OTA_DFU=y
# Additional MCUmgr settings (if needed beyond what NCS_SAMPLE provides)
CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS=y
CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS=y
CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK=y
CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS=y
# MCUmgr callback support for OTA progress monitoring
CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK=y
CONFIG_MCUMGR_TRANSPORT_NETBUF_COUNT=4
CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE=2048
###
# Stack Config
###
###
# Stack Config
###
CONFIG_MAIN_STACK_SIZE=8192
CONFIG_MCUMGR_TRANSPORT_WORKQUEUE_STACK_SIZE=4608
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
CONFIG_ISR_STACK_SIZE=4096
CONFIG_HEAP_MEM_POOL_SIZE=16384
CONFIG_PRINTK=y
###
# External Flash
###
CONFIG_FLASH=y
CONFIG_SPI_NOR=y
CONFIG_FLASH_MAP=y
CONFIG_STREAM_FLASH=y
# Disable device power management to keep flash always active
CONFIG_PM_DEVICE=n
###
# File System
###
CONFIG_FILE_SYSTEM=y
CONFIG_FILE_SYSTEM_LITTLEFS=y
CONFIG_SPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096
CONFIG_NORDIC_QSPI_NOR=n
###
# Display
###
CONFIG_DISPLAY=y
CONFIG_ST7789V=y
CONFIG_DISPLAY_LOG_LEVEL_DBG=y
###
# Debugging
###
CONFIG_DEBUG_OPTIMIZATIONS=y
CONFIG_DEBUG_THREAD_INFO=y
CONFIG_THREAD_NAME=y
# Use internal RC oscillator instead of external 32.768 kHz crystal
CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=n
CONFIG_CLOCK_CONTROL_NRF_K32SRC_500PPM=n
CONFIG_CLOCK_CONTROL_LOG_LEVEL_INF=y
...........................................................
Display Draw fucntion
#define DISPLAY_CHUNK_MAX_BYTES (16*1024u)
static int32_t display_engine_draw_image(const display_engine_image_t* image)
{
if (image == NULL || image->file_name == NULL)
{
return -EINVAL;
}
display_state_service_set_graphics_mode(true);
if (!storage_service_is_ready())
{
return -ENODEV;
}
if (image->width == 0 || image->height == 0)
{
return -EINVAL;
}
uint32_t row_bytes = (uint32_t) image->width * BYTES_PER_PIXEL;
uint32_t total_bytes = row_bytes * image->height;
/* Declare double 8 KB buffers - strict queue: fill one while display reads the other */
static uint8_t
buffer_a[DISPLAY_CHUNK_MAX_BYTES]; /* 8 KB - fill buffer (data copied from Flash) */
static uint8_t buffer_b[DISPLAY_CHUNK_MAX_BYTES]; /* 8 KB - display buffer (read by DMA) - DO
NOT MODIFY UNTIL NEXT ITERATION */
uint8_t* fill_buffer = buffer_a; /* Buffer being filled with flash data */
uint8_t* display_buffer = buffer_b; /* Buffer being read by display DMA */
/* Clear both buffers at image start to remove stale data from previous images.
This is amortized cost (once per image call), not per iteration. */
memset(fill_buffer, 0, DISPLAY_CHUNK_MAX_BYTES);
memset(display_buffer, 0, DISPLAY_CHUNK_MAX_BYTES);
# if DISPLAY_ENGINE_DEBUG_LOGGING
/* LOG_DBG in production provides timing + synchronization for SPI stability */
LOG_DBG("Drawing image: %s, dimensions: %ux%u, total_bytes: %u", image->file_name, image->width,
image->height, total_bytes);
# endif
/* LOG_DBG calls throughout provide necessary synchronization without k_sleep() */
/* Now start reading actual image data (begins at byte 512 after dummy reads) */
uint32_t file_offset = 0;
/* Read and display image in 4 KB blocks, reading from flash in 512 B chunks */
while (file_offset < total_bytes)
{
uint32_t buffer_fill_level = 0; /* How much data collected in fill buffer for this block */
/* Inner loop: fill the 16 KB buffer with 1 KB flash reads */
while (buffer_fill_level < DISPLAY_CHUNK_MAX_BYTES
&& (file_offset + buffer_fill_level) < total_bytes)
{
uint32_t remaining_file_bytes = total_bytes - (file_offset + buffer_fill_level);
uint32_t space_in_buffer = DISPLAY_CHUNK_MAX_BYTES - buffer_fill_level;
/* Determine size for this read: 1 KB chunk or remaining data */
uint32_t to_read = (remaining_file_bytes < FLASH_READ_CHUNK_SIZE)
? remaining_file_bytes
: FLASH_READ_CHUNK_SIZE;
/* Cap at available buffer space */
if (to_read > space_in_buffer)
{
to_read = space_in_buffer;
}
# if DISPLAY_ENGINE_DEBUG_LOGGING
/* LOG_DBG provides timing + synchronization for SPI stability */
LOG_DBG("Reading: offset=%u, to_read=%u", file_offset + buffer_fill_level, to_read);
# endif
/* Critical section: disable interrupts for stable display
controller operation. ST7789V needs proper timing between SPI transactions. */
unsigned int key = irq_lock();
/* Read 1 KB chunk from external flash into fill buffer */
int32_t read_count =
storage_service_read_offset(image->file_name, &fill_buffer[buffer_fill_level],
file_offset + buffer_fill_level, to_read);
irq_unlock(key);
if (read_count < 0 || (uint32_t) read_count != to_read)
{
return -EIO;
}
buffer_fill_level += to_read;
/* Feed watchdog to prevent reset during long flash reads */
hal_watchdog_feed();
}
/* Calculate how many complete rows we can display from buffer */
uint32_t complete_rows = buffer_fill_level / row_bytes;
bool is_last_block = (file_offset + buffer_fill_level >= total_bytes);
# if DISPLAY_ENGINE_DEBUG_LOGGING
/* LOG_DBG provides timing + synchronization for SPI stability */
LOG_DBG("Buffer fill: level=%u, rows=%u", buffer_fill_level, complete_rows);
# endif
/* Display complete rows using display_buffer (fill_buffer is not touched by display) */
if (complete_rows > 0)
{
/* Copy data from fill_buffer to display_buffer before display operation */
uint32_t bytes_to_display = complete_rows * row_bytes;
memcpy(display_buffer, fill_buffer, bytes_to_display);
uint32_t display_row_offset = file_offset / row_bytes;
uint16_t num_rows = (uint16_t) complete_rows;
bool frame_incomplete = !is_last_block;
# if DISPLAY_ENGINE_DEBUG_LOGGING
/* LOG_DBG provides timing + synchronization for SPI stability */
LOG_DBG("Before display: rows=%u", complete_rows);
# endif
/* Measure execution time of display_service_draw_buffer_ex() */
uint64_t start_time_ms = k_uptime_get();
int32_t ret = display_service_draw_buffer_ex(display_buffer, (uint16_t) image->x,
(uint16_t) (image->y + display_row_offset),
image->width, num_rows, frame_incomplete);
# if DISPLAY_ENGINE_DEBUG_LOGGING
/* LOG_DBG provides timing + synchronization for SPI stability */
LOG_DBG("Display done: rows=%u, time=%llu ms", num_rows, elapsed_ms);
# endif
if (ret != 0)
{
return ret;
}
hal_watchdog_feed();
}
/* Advance file offset by complete rows displayed */
uint32_t bytes_consumed = complete_rows * row_bytes;
/* If last block, consume any remaining partial data */
if (is_last_block)
{
bytes_consumed = buffer_fill_level;
}
file_offset += bytes_consumed;
}
return 0;
}

