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

nRF51 UART Confusion

I am completely confused by the nRF51 UART. I'm trying to port code originally written for an STM32 (another cortex chip). There, I define an interrupt, enable the RXRDY or TXEMPTY interrupts, and if a character comes in, I get an interrupt, or if I send a character, I get an interrupt as soon as it leaves the TX register and enters the shift register. Simple, and it just works.

I'm trying to do the same here, but can't get the IRQ routine to fire at all. I'm using the PCA10028 dev kit, pins 9 and 11 as RX and TX, don't need flow control, and soft device 110 and SDK 7.

I assume my problem has something to do with the PPI interface, but can't wrap my head around things like STARTTX, STARTRX or events. Can you use the UART without these, and just use INTENSET and INTENCLR? Or are these required?

I looked at the uart examples, but none of these looked, to me, suited for the kind of serial I/O I need to perform. The uart example doesn't use IRQ, so it doesn't look suitable as a starting point. Neither does the experimental_ble_app_uart example, as it too uses simple, non interrupt uart code.

Plus, despite having "uart" in their project names, almost none of the examples actually send or receive anything over the UART, unless it is debug data via printf, which is certainly not what I'm looking to do.

Any hints on how to take control of the UART?

Thanks, Jeff.

  • Hi Jeff

    I agree with you that the uart example does not show how to use those events that you mention (data_ready and data_sent), but what SDK version are you using? I suspect that you are using older than SDK 8 at least since you mention experimental_ble_app_uart which is no longer experimental in SDK 8.

    I refer to SDK 8.1.0. The example \examples\peripheral\uart uses the app_uart library which uses two FIFO buffers in the background to queue data.

    • When data arrives on the UART it is put into the RX FIFO buffer and then the registered app_uart handler is called (the uart_error_handle in the example) with event APP_UART_DATA_READY. In the app_uart handler you could call app_uart_get to extract data from the RX FIFO buffer instead of polling it as done in the uart example.

    • When you want to send data to the UART, you call app_uart_put in order to put data into the TX FIFO, which will be sent over the UART if the UART is not currently busy. If the UART TX is currently busy sending data, an additional byte will be sent from the TX FIFO when the UART TX becomes available (when TXDRDY event is received, handled internally in app_uart library) or when CTS line goes low (indicating that the peer device is ready to receive data). When the TX FIFO is empty, i.e. all data has been sent, the app_uart event handler is called with the event type APP_UART_TX_EMPTY.

    So, in the \examples\peripheral\uart example, capture the APP_UART_TX_EMPTY and APP_UART_DATA_READY events (in uart_error_handle) instead of polling in the main loop. When you capture APP_UART_DATA_READY, call app_uart_get to extract data from the RX FIFO.

  • Thank you for that explanation.

    Currently, I'm on SDK 7, as 8 was not out when I started, and I didn't want to get sidetracked switching until I had something at least as a skeleton that worked.

    However, I'm porting a large amount of code from another processor, and already have buffering code that duplicates the fifo code, and would rather continue using my own, rather than either have duplicate functionality (and waste limited code space), or have to rewrite the rest of my libraries to use this fifo instead of my own buffering routines. In addition, do I have to bring in all of the event code just to use the UART? It seems like added complexity on an already complex project, and I'd rather avoid it if possible.

  • Hi Jeff

    Of course you can skip using the app_uart and use the UART directly without any FIFOs. The UART has 1 byte TXD register and 6 byte RXD registers. You have the RXDRDY and TXDRDY events to indicate you of data_received and data_sent respectively. You can get code reference for how to do that by looking into the app_uart_fifo.c. In that file you can see how the UART0_IRQHandler captures the NRF_UART0->EVENTS_RXDRDY and NRF_UART0->EVENTS_TXDRDY events and what action is taken in each case. Also take a look at the app_uart_init to see how the UART is manually initialized.

  • This is exactly what I did at first, and so far I have been completely unable to get the IRQ routine to execute. I assumed that if I sent a characer, the interrupt would fire the TXDRDY event as soon as the character was sent (probably as soon as it left the TX register and into the shift register). But nothing happens when I send the character, no interrupt. Could this be because I'm using the DEV KIT board? Do I need to pick different I/O lines for TX and RX than what the DEV KIT used?

  • I believe I've found the problem.

    In the code I am porting, to start transmitting, I would simply enable the TXEMPTY interrupt. Since there was nothing in the TX register at the time, the interrupt would immediately fire, and the handler would pull a character from my buffer and send it. When that character was sent, the interrupt would fire again, and the next character would be pulled, and so on until the last character was sent, at which time the handler would disable the TXEMPTY interrupt.

    I assumed that this UART would behave similarly, and that to trigger sending data all I would need to do would be to enable the equivalent of TXEMPTY, which looked to be TXRDY. So my code to enable the transmitter was simply this:

    void SendTxBuff(void)
    {
      // Start the UART.
      NRF_UART0->INTENSET = (UART_INTENSET_TXDRDY_Set << UART_INTENSET_TXDRDY_Pos);
    }
    

    However, this does not cause the TXRDY interrupt to fire.

    I found that in order to kick start the process, I had to actually send the first character. Once I did, then the handler was called, and the rest of the characters would be sent. Like this:

    void SendTxBuff(void)
    {
      // Start the UART.
      NRF_UART0->INTENSET = (UART_INTENSET_TXDRDY_Set << UART_INTENSET_TXDRDY_Pos);
      // Send the first byte to get things started
      NRF_UART0->TXD = bufGetByte(&TxBuff);
    }
    

    Is this correct? Must I send the first byte to kick start the process, or is there something I missed about being able to enable the TXRDY interrupt "dry"?

    Thanks, Jeff.

Related