This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

SPI transfer in timer handler retrieves wrong values, hangs and crashes

I was having an issue where reading from SPI inside a timer handler always yielded 0xFF instead of the actual values. I tried to reproduce the issue with a simple test program but it shows a different behavior than my actual application - it simply hangs during the second transfer. When I step through it with the debugger it frequently just crashes (reboots).

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

const struct device * spi_dev = DEVICE_DT_GET(DT_NODELABEL(spi1));

void read_byte_from_spi() {
	struct spi_config spi_cfg = {};
    spi_cfg.frequency = 1000000;
    spi_cfg.operation = SPI_WORD_SET(8);
	char buf[1] = {};
    struct spi_buf bufs[] = {
        {
            .buf = buf,
            .len = 1
        }
	};
	struct spi_buf_set bufset = {
		.buffers = bufs,
        .count = 1
	};
    spi_transceive(spi_dev, &spi_cfg, &bufset, &bufset);
	printk("Read: %02x\n", buf[0]);
}

void spi_timer_expired() {
	printk("Reading in spi_timer_expired() - ");
	read_byte_from_spi();
}
K_TIMER_DEFINE(spi_timer, spi_timer_expired, NULL);

void main(void)
{
	printk("Start\n");
	k_timer_start(&spi_timer, K_MSEC(3000), K_MSEC(3000));
	while(1) {
		printk("Reading in main() - ");
		read_byte_from_spi();
		k_msleep(5000);
	}
}

prj.conf:

CONFIG_SPI=y
CONFIG_DEBUG_THREAD_INFO=y
CONFIG_DEBUG_OPTIMIZATIONS=y

I'm running this on an nRF52-DK with P0.29 connected to GND.

  • Hello!

    It looks like you're calling read_byte_from_spi() both in your main loop and in your timer handler, is this intentional?

    Also, I would advise against reading spi inside your timer interrupt, this could be why you're seeing unexpected behavior. Instead I would use a semaphore to signal from the interrupt that the timer has expired, and do the reading in your main function or another thread.

    Best regards,

    Einar

  • Well, in the demo program above that is intentional, yes, to demonstrate that it works from the main function but not from the timer expiry function.

    For now I used a workaround similar to the semaphore, I used a message queue with the main loop blocking on k_msgq_get() and the timer expiry function publishing to the message queue. But since I couldn't find anything about (not) doing SPI transfers in a timer handler I assumed I was doing something wrong.

    Does the timer expiry function actually run as part of an ISR? I assumed Zephyr internally handles the actual timer interrupt and then runs the expiry function on a normal thread.

  • So what you're doing in your code is similar to this example?

    Yes, the timer expiry function is executed at interrupt level.

    -Einar

  • OMG, I had actually read that exact paragraph (in the Zephyr docs) but I hadn't looked properly at the code and I assumed that when I use an expiry function it will always run it in the system workqueue thread.

    But no, the timer handler is part of the ISR and if I want to defer the work to a "userspace" thread I have to do that myself.

    I'm not really surprised that doing SPI I/O is not possible in an ISR, I just didn't know the timer handler is executed while still handling the interrupt.

Related