This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

UART SPI Issue nRF52840

USB Virtual COM

Until now, the development has been done by sending the commands via a USB virtual COM. The received buffer of int is processed and sent to the function `ExecuteCommandLine`, from where the appropiate functions are called depending on the content of the string.

UART communication

I have tried to replicate the same behavior with the UART. I have added 2 functions, the UART Initialization and the UART Event Handle.


//UART Initialization
void uart_init(void)
{
uint32_t err_code;
app_uart_comm_params_t const comm_params =
{
.rx_pin_no = RX_PIN_NUMBER,
.tx_pin_no= TX_PIN_NUMBER,
.rts_pin_no = NRF_UART_PSEL_DISCONNECTED,
.cts_pin_no = NRF_UART_PSEL_DISCONNECTED,
.flow_control = APP_UART_FLOW_CONTROL_DISABLED,
.use_parity = false,
.baud_rate = NRF_UARTE_BAUDRATE_115200
};

APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_event_handle,
APP_IRQ_PRIORITY_LOWEST,
err_code);
APP_ERROR_CHECK(err_code);
}

uart_init follows the Nordic example, and disconnects RTS and CTS pins because we do not use them.

The uart_event_handle is similar to the one used by the Nordic example, changing only the content inside case APP_UART_DATA_READY to process the buffer and pass it to ExecuteCommandLine


//UART Event Handle
static void uart_event_handle(app_uart_evt_t * p_event)
{
//Define Local variables

switch (p_event->evt_type)
{
case APP_UART_DATA_READY:
//Process buffer and pass it to ExecuteCommandLine. A couple of PrintString along the way
}
break;

case APP_UART_COMMUNICATION_ERROR:
APP_ERROR_HANDLER(p_event->data.error_communication);
break;

case APP_UART_FIFO_ERROR:
APP_ERROR_HANDLER(p_event->data.error_code);
break;

default:
break;
}
}

All the code uses a function called `PrintString` to return strings through the virtual USB COM and the UART COM.


//Print String
void PrintString(app_usbd_cdc_acm_t const * p_cdc_acm, const void * m_tx_buffer, size_t size)
{
ret = app_usbd_cdc_acm_write(&m_app_cdc_acm, m_tx_buffer, size);
nrf_delay_ms(2);
send_answer_over_UART((uint8_t *)m_tx_buffer, size);
nrf_delay_ms(2);
}

void send_answer_over_UART(uint8_t * data, uint8_t length)
{
uint8_t i;
uint32_t err_code;
for (i = 0; i < length; i++) {
while (app_uart_put(data[i]) != NRF_SUCCESS);;
}
}

To my understanding, all the code that follows from there, after going into `ExecuteCommandLine` with the same inputs, should behave exactly the same.

SPI Blocking

When the code arrives at a SPI transfer, it looks like this:


spi1_xfer_done = false;
APP_ERROR_CHECK(nrf_drv_spi_transfer(&spi1, spi1_tx_buf, spi1_length, &spi1_rx_buf, spi1_length));
while (!spi1_xfer_done)
{
__WFE();
}

When the command buffer has been sent via the USB Virtual COM, it works with no problem. But when I send it through the UART, it remains stuck in the while because the `spi1_xfer_done` is never updated to true. The SPI is declared with the 2 following functions:


//SPI1 event handler
void spi1_event_handler(nrf_drv_spi_evt_t const * p_event, void * p_context)
{
spi1_xfer_done = true;
}

// SPI1 init
void spi1_init(uint8_t SPI1_CS)
{
nrf_drv_spi_config_t spi1_config = NRF_DRV_SPI_DEFAULT_CONFIG;
spi1_config.ss_pin = SPI1_CS;
spi1_config.miso_pin = SPI1_MISO;
spi1_config.mosi_pin = SPI1_MOSI;
spi1_config.sck_pin = SPI1_CLK;
if (SPI1_CS == U1_nCS)
{
spi1_config.mode=NRF_DRV_SPI_MODE_3;
}else if (SPI1_CS == U2_nCS)
{
spi1_config.mode=NRF_DRV_SPI_MODE_3;
}

APP_ERROR_CHECK(nrf_drv_spi_init(&spi1, &spi1_config, spi1_event_handler, NULL));
}

As far as I can tell, when the command has been sent from the UART, `spi1_event_handler` is never called.

 First workaround

The first workaround I have tried is to ignore the flag, by modifying the while loop:

uint8_t wait_counter=0;
while ((!spi1_xfer_done)&&(wait_counter<10))
{
nrf_delay_ms(10);
wait_counter++;
}

It works for a single transaction, because the transaction is actually happening (I have checked the received buffer in memory in the debugger), and with this workaround the problem is partly solved. But for some commands I need several SPI transactions to happen, and the code is getting stuck in the second one.

Multiple SPI transactions problem

When a second SPI transaction happens after that one, the code remains stuck at `nrfx_spim.c`, line 557:


if (p_cb->transfer_in_progress)
{
err_code = NRFX_ERROR_BUSY;
NRFX_LOG_WARNING("Function: %s, error code: %s.",
__func__,
NRFX_LOG_ERROR_STRING_GET(err_code));
return err_code;
}

After a couple of iterations of this if, execution jumps to `app_error_weak.c`, line 99:


NRF_BREAKPOINT_COND;
// On assert, the system can only recover with a reset.

#ifndef DEBUG
NRF_LOG_WARNING("System reset");
NVIC_SystemReset();

This resets the chip, and the code starts again from the beginning of main().

I have tried to uninitialize and reinitialize the spi between transactions, but the chip keeps resetting there.

Ideas about what may be wrong

I am not managing to make sense of it, since the code is the same. But I do notice that the USB packets sent towards the PC are happening immediately, while the UART ones don't. The UART part of `PrintString` is printing the first character, then the execution of `ExecuteCommandLine` takes place, and then the accumulated strings are printed to the terminal. So maybe there is some conflict on the priority of the different events of SPI and UART, but I don't understand where the error may be. Thank you in advance for your help.

Related