nRF54 SPI Asynchronous Slave - Done never raised

Hello !
I'm currently working on a SPI slave implementation for the nRF54L15.

I already read the spi-master-slave example.


The idea atm is basic and as follow : 

My slave needs to listen if the master sent anything. If not, nothing happens.
If he received something, I'll do some operations on the received message.

On the other side, if my slave wants to send something, he triggers a WAKE GPIO that tells the master to clock.
He then send his message with spi_transceive_signal() and wait for the tx completion.

I have a thread running called "listening" that calls spi_read_signal().
In the main, I check either spi_read_signal() changed the poll signal.
If it changed it, I print the message and reset the signal_poll value.

It's pretty much the same thing as the example except that I want to read periodically.

The code for now is the following : 

main.c :

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/logging/log.h>
#include "svc_spi_nrf54.h"
#include "drv_spi_nrf54.h"
LOG_MODULE_REGISTER(spi_slave_log);
#define SLEEP_TIME_MS 500
#define STACKSIZE 1024
#define THREADSPI_PRIORITY 7
#define SLEEPTIME 1000

void thread_spi_listening(void){
    while(1){
		LOG_INF("SPI THREAD!");
		svc_nrf_listening_rx_v();
		k_msleep(SLEEPTIME);		
    }
}

K_THREAD_DEFINE(thread_spi_id, STACKSIZE, thread_spi_listening, NULL, NULL, NULL, THREADSPI_PRIORITY, 0, 0);

int main(void){
	svc_nrf_spi_init_v();
	LOG_INF("SPI SLAVE STARTED !");

	while(1){
		LOG_INF("MAIN LOOP !");
		svc_nrf_spi_hdlr_v();
		k_sleep(K_MSEC(SLEEP_TIME_MS));
	}
}

svc calls the following : 

void svc_nrf_spi_hdlr_v(){
    rx_raised = drv_nrf_msg_check_u8();
    if(rx_raised != 0){
    //We received a msg.
    LOG_HEXDUMP_INF(spi_buffer_u8, sizeof(spi_buffer_u8),"MSG RECEIVED !");
    drv_nrf_reset_rx_v();
    }
}

void svc_nrf_listening_rx_v(){
    if(rx_raised == 0){
        drv_spi_slave_rx_v(spi_buffer_u8, spi_buffer_len_u8);
    }
}

rx function : 

 

//GPIOs declarations
static const struct gpio_dt_spec wake_pin = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(wake_pin),gpios,{0});
static const struct gpio_dt_spec cts_pin = GPIO_DT_SPEC_GET_OR(DT_NODELABEL(cts_pin),gpios,{0});

#define SPI_SLAVE DT_NODELABEL(spi_slave)
const struct device *nrf_spi_slave;
static struct k_poll_signal spi_rx_done_sig = K_POLL_SIGNAL_INITIALIZER(spi_rx_done_sig);

uint8_t rx_raised;
//SPI config
static const struct spi_config spi_slave_cfg = {
	.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB |
				 SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_OP_MODE_SLAVE,
	.frequency = 4000000,
	.slave = 0,
};

//Transfer buffers
static uint8_t spi_rx_buffer[C_SPI_MSG_SIZE];

void drv_spi_slave_rx_v(uint8_t* rx_buffer_pt, uint8_t rx_buffer_size_u8){
    if(NULL != rx_buffer_pt){
	    struct spi_buf rx_buf = {
		    .buf = rx_buffer_pt,
		    .len = rx_buffer_size_u8
	    };
	    struct spi_buf_set s_rx = {
		    .buffers = &rx_buf,
		    .count = 1
	    };
		LOG_INF("LISTENING RX...");
        int8_t error = spi_read_signal(nrf_spi_slave, &spi_slave_cfg,&s_rx,&spi_rx_done_sig);
            if(error < 0){
                LOG_ERR("TRANSCEIVING ERROR !");
            }
    }else{
        LOG_ERR("POINTER ISSUE");
    }
}

msg check function & reset signal function : 

uint8_t drv_nrf_msg_check_u8(){
    uint8_t result;
    k_poll_signal_check(&spi_rx_done_sig, &rx_raised, &result);
    return(rx_raised);
} 

void drv_nrf_reset_rx_v(){
	k_poll_signal_reset(&spi_rx_done_sig);
}

The issue is that the done signal is never raised and that the thread is paused indefinitely after 2 executions :

It seems to be a problem with a semaphore never released.
I may be lacking of understanding of the transceive_signal functions. I read the documentation about it but I'm not sure that I'm using it the right way to achieve what I want here.
If you could give me some help, it would be great !
Regards,
Hugo

Parents
  • It seems to be a problem with a semaphore never released.

    You have come very far in debugging this. Can you point to the exact semaphore that is not getting released? It seems to be a deadlock issue. Probably you are transmitting and polling for something in the same context? I need to understand more after you tell me which semaphore is not getting released.

  • Hello !
    Sorry for the late answer, it was a public holiday in France.

    I modified a bit my code but the problem remains the same.
    My SPI thread is the following : 

    void thread_spi_listening(void){
        while(1){
    		LOG_INF("SPI THREAD!");
    		svc_nrf_listening_rx_v();
    		k_msleep(SLEEPTIME);		
        }
    }

    The listening function basically calls the RX function if this is the first occurrence, and then wait for a done signal to be raised.

    void svc_nrf_listening_rx_v(){
        if(true == first){
            LOG_INF("FIRST ITERATION, POLLING RX");
            first = false;
            drv_spi_slave_rx_v(spi_buffer_u8, spi_buffer_len_u8);
        }else{
            rx_raised = drv_nrf_msg_check_u8();
            if(rx_raised != 0){
                //We received a msg.
                //TODO handling msg decryption
                LOG_HEXDUMP_INF(spi_buffer_u8, sizeof(spi_buffer_u8),"MSG RECEIVED !");
                drv_nrf_reset_rx_v();
                drv_spi_slave_rx_v(spi_buffer_u8, spi_buffer_len_u8);
            }
        }
    }

    If we received something, I want to print it. Then I reset the signal and try another RX.

    Here is the signal initialisation : 

    static struct k_poll_signal spi_rx_done_sig = K_POLL_SIGNAL_INITIALIZER(spi_rx_done_sig);
    

    And the RX function : 

    void drv_spi_slave_rx_v(uint8_t* rx_buffer_pt, uint8_t rx_buffer_size_u8){
        if(NULL != rx_buffer_pt){
    	    struct spi_buf rx_buf = {
    		    .buf = rx_buffer_pt,
    		    .len = rx_buffer_size_u8
    	    };
    	    struct spi_buf_set s_rx = {
    		    .buffers = &rx_buf,
    		    .count = 1
    	    };
    		LOG_INF("READING RX...");
            int8_t error = spi_read_signal(nrf_spi_slave, &spi_slave_cfg,&s_rx,&spi_rx_done_sig);
                if(error < 0){
                    LOG_ERR("TRANSCEIVING ERROR !");
                }
        }else{
            LOG_ERR("POINTER ISSUE");
        }
    }

    When I execute this code, I have the following logs : 

    So my problem here isn't a paused thread but the k_poll_signal_check that never returns true.

    The documentation about spi_read_signal is not really detailed. But I assume that the k_poll_signal should be raised whenever a transaction is done ?
    PS : I have a master connected that pushes messages periodically.

  • I think there seems to be some kind of deadlock in the way you are using signal, not very obvious though. 

    The common way to use this signal is to correlate it with a K_POLL_EVENT_INITIALIZER and do a k_poll on that event rather done checking that signal from some other thread.

    There are samples for this provided in zephyr for example in zephyr\tests\drivers\spi\spi_controller_peripheral\src\main.c

    As you can see, The async_evt that is associated with the signal here is more reliable poll candidate and you can reset the signal right after the polling is complete.

    Can you give it a try and see if this helps else we need to dive into the whole signal usage of the spi driver to understand why you are seeing that deadlock when using only signals and not the poll_evts.

Reply Children
  • I introduced the EVENT as follow : 

    static struct k_poll_event async_rx_evt = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &spi_rx_done_sig);
    

    And modified the periodical function with : 

    int8_t drv_nrf_msg_check_u8(){
    	LOG_INF("CHECKING RECEPTION");
    	rx_raised = k_poll(&async_rx_evt, 1, K_MSEC(200));
    	if(rx_raised == -EAGAIN){
    		LOG_INF("Waiting period timed out.");
    	}
    	/* Reinitializing for next call */
    	async_rx_evt.signal->signaled = 0U;
    	async_rx_evt.state = K_POLL_STATE_NOT_READY;
        return(rx_raised);
    } 

    The log here is : 

    So yeah basically it seems that the event never happens. I may be doing something wrong with the usage of spi_read_signal. 

Related