Zephyr SPI works in timer handler but not main function

I'm having a problem using SPI within Zephyr. My custom nRF52840 board has 74HC959 shift registers to drive LEDs. I'm using the SPI bus to transfer LED on/off states and can successfully do so when spi_write_dt is only called from a kernel timer handler. If I try running the exact same function from the main function, the write doesn't work and the LEDs are set randomly.

Below is my minimal code that reproduces the issue. After setting output and reset pins, I wait 1 second before writing data in the main function via the update_leds function, which results in random LED results and return code 0.

After I start a 1 second timer which calls the same update_leds function, it successfully sets the LEDs, though it returns error code -116 (-ETIMEDOUT).

main.c

#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

#include <stdio.h>
#include <stdlib.h>

// Control GPIOs
static const struct gpio_dt_spec m_led_out_en = GPIO_DT_SPEC_GET(DT_NODELABEL(led_output_en), gpios); // NOLINT(cppcoreguidelines-interfaces-global-init)
static const struct gpio_dt_spec m_led_rst = GPIO_DT_SPEC_GET(DT_NODELABEL(led_rst), gpios);          // NOLINT(cppcoreguidelines-interfaces-global-init)

// Shift registers bus
static const struct spi_dt_spec m_led_ctrl = SPI_DT_SPEC_GET(DT_NODELABEL(led74hc959), (SPI_OP_MODE_MASTER | SPI_WORD_SET(8)), 0); // NOLINT(cppcoreguidelines-interfaces-global-init)

// LED data
static uint16_t led_data = 0;

// LED timer
static void led_timer_handler(struct k_timer *timer_id);
K_TIMER_DEFINE(led_timer, led_timer_handler, NULL);

static void update_leds()
{
    uint8_t tx_buffer[2];
    memcpy(tx_buffer, &led_data, sizeof(tx_buffer));

    struct spi_buf tx_spi_buf = {.buf = (void *)&tx_buffer, .len = 2};
    struct spi_buf_set tx_spi_buf_set = {.buffers = &tx_spi_buf, .count = 2};

    int ret = spi_write_dt(&m_led_ctrl, &tx_spi_buf_set);
    printk("SPI ret: %i\n", ret);
}

static void led_timer_handler(struct k_timer *timer_id)
{
    ARG_UNUSED(timer_id);

    update_leds();
}

int main(void)
{
    if (!device_is_ready(m_led_out_en.port))
    {
        printk("LED output enable not ready\n\r");
        return 1;
    }

    // Enable output pin
    gpio_pin_configure_dt(&m_led_out_en, GPIO_OUTPUT);
    gpio_pin_set(m_led_out_en.port, m_led_out_en.pin, 1);

    if (!device_is_ready(m_led_rst.port))
    {
        printk("LED reset not ready\n\r");
        return 1;
    }

    // Disable reset pin
    gpio_pin_configure_dt(&m_led_rst, GPIO_OUTPUT);
    gpio_pin_set(m_led_rst.port, m_led_rst.pin, 0);

    if (!spi_is_ready_dt(&m_led_ctrl))
    {
        printk("SPI is not ready!\n\r");
        return 1;
    }

    k_msleep(1000);

    // Turn on all LEDs (active low)
    led_data = 0;
    update_leds();

    // Trigger same function 1 seconds later from timer
    k_timer_start(&led_timer, K_MSEC(1000), K_NO_WAIT);
}

DTS

// Copyright (c) 2024 Nordic Semiconductor ASA
// SPDX-License-Identifier: Apache-2.0

/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>

/ {
	model = "NA";
	compatible = "mnfctr,NA";

	chosen {
		zephyr,sram = &sram0;
		zephyr,flash = &flash0;
		zephyr,code-partition = &slot0_partition;
	};

	led_ctrl {
		compatible = "gpio-keys";
		led_output_en: led_output_en {
			gpios = <&gpio0 14 (GPIO_ACTIVE_LOW)>;
		};

		led_rst: led_rst {
			gpios = <&gpio0 17 (GPIO_ACTIVE_LOW)>;
		};
	};
};

&flash0 {
	partitions {
		compatible = "fixed-partitions";
		#address-cells = <1>;
		#size-cells = <1>;

		boot_partition: partition@0 {
			label = "mcuboot";
			reg = <0x0 0xc000>;
		};
		slot0_partition: partition@c000 {
			label = "image-0";
			reg = <0xc000 0x72000>;
		};
		slot1_partition: partition@7e000 {
			label = "image-1";
			reg = <0x7e000 0x72000>;
		};
		scratch_partition: partition@f0000 {
			label = "image-scratch";
			reg = <0xf0000 0xa000>;
		};
		storage_partition: partition@fa000 {
			label = "storage";
			reg = <0xfa000 0x6000>;
		};
	};
};

&gpio0 {
	status = "okay";
};

&spi0 {
	status = "okay";
	cs-gpios = <&gpio0 13 (GPIO_ACTIVE_LOW)>;
	pinctrl-0 = <&spi0_default>;
	pinctrl-names = "default";
	max-frequency = <DT_FREQ_M(1)>;
	led74HC959: led74HC959@0 {
		compatible = "vnd,spi-device";
		reg = <0>;
		spi-max-frequency = <DT_FREQ_M(1)>;
		label = "LED74HC959";
	};
};

&pinctrl {
	spi0_default: spi0_default {
		group1 {
			psels = <NRF_PSEL(SPIM_MOSI, 0, 12)>,
			        <NRF_PSEL(SPIM_MISO, 0, 22)>,
			        <NRF_PSEL(SPIM_SCK, 0, 15)>;
		};
	};
};

prj.conf

# Enable RTT
CONFIG_RTT_CONSOLE=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_CBPRINTF_FP_SUPPORT=y

# Enable SPI
CONFIG_SPI=y

# Console
CONFIG_SERIAL=y
CONFIG_CONSOLE=y

Any idea what's happening here? I'd ideally like to run SPI writes outside of the timer handler. My initial thoughts are that running SPI functions within the context of a timer handler is changing the behavior of the SPI bus, either elevating its priority or something entirely different.

Parents Reply Children
No Data
Related