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

Understanding how bytes are sent through UART service over BLE

I'm looking at examples\ble_peripheral\ble_app_uart example and I have a couple of questions:

- You explicitly configure the UART inside the main so now once you have the HW connections made to RX/TX pins, you could start sending bytes from a minicom/serial session to the device over UART, but how is UART Service for sending bytes to the serial session "configured"? How are bytes actually sent from the RX characteristic of the BLE service in nRF52 Connect App to the device over UART? In other words, how is the IRQ handler invoked as soon as the data is written to the RX characteristic?

So you could use a minicom session to send to the UART service and receive from the UART service to the minicom session. How is the situation handled if the data is sent from both the COM listener and the nRF52 App around the same time? i.e meaning an interrupt is triggered by the COM port as well as the UART service? if so, it's feasible to send bytes from different sources to the same UART peripheral?

- What if we want to send a complete 'message' as opposed to just a byte from each producer provided how the IRQ is fired for each byte?

  • Hi

    how is the IRQ handler invoked as soon as the data is written to the RX characteristic?

    Any activity in the NUS service that the application might be interested in is forwarded to the nus_data_handler(..) callback function, on line 196 of main.c. 

    This callback can be triggered by different things, and when data is received on the RX characteristic you can identify it by the type BLE_NUS_EVT_RX_DATA. 

    As you can see in the implementation the default behavior for the example when this callback occurs is to loop through all the bytes and pass them to the app_uart driver so that they will be sent over the UART. 

    So you could use a minicom session to send to the UART service and receive from the UART service to the minicom session. How is the situation handled if the data is sent from both the COM listener and the nRF52 App around the same time? i.e meaning an interrupt is triggered by the COM port as well as the UART service? if so, it's feasible to send bytes from different sources to the same UART peripheral?

    The design of the nRF52 allows the UART and RADIO peripherals to run in parallel, to ensure that there is no problem receiving bytes over the Bluetooth connection and the UART interface at the same time. 

    The NVIC (Nested Vectored Interrupt Controller) in the ARM core ensures that the interrupt handlers are run in an orderly manner, even if two or more interrupts are triggered at the same time by two different peripherals. In this case the interrupt with the highest priority will run first, or the interrupt with the lowest peripheral ID in the case they both have the same priority. 

    - What if we want to send a complete 'message' as opposed to just a byte from each producer provided how the IRQ is fired for each byte?

    A bit unsure what you mean by this. The Bluetooth connection already operates on messages, and by default no UART data will be sent to the Bluetooth stack until you either type the newline character in the terminal, or until you have received as many bytes over the UART as you can fit in a single Bluetooth packet. 

    The physical UART itself does not have a concept of messages (as opposed to other interfaces like SPI or I2C), and the only way to pass messages over a UART interface is to add some kind of messaging protocol on top of the UART communication. 

    Best regards
    Torbjørn

  • Thanks for the detailed response.

    Okay basically the following is what happens:

    - Data sent from NUS -> nus_data_handler() -> puts the data into TXD -> invokes UART IRQ handler -> sees data in a COM PORT listener

    - Data sent from COM PORT listener -> data is put into RXD -> invokes UART IRQ handler for each byte -> uart_event_handle() -> sends data to NUS via ble_nus_data_send()

    so one common thing between these two processes is the triggering of the same UART IRQ handler. So in case the data is received from the COM port listener and sent from the NUS around the same time, one interrupt would have to wait for the currently executing one to finish before it starts executing? There's no need for mutex protection for the FIFO buffer because only interrupt is going to be running at a time, yes?

  • Hi

    Your flow is pretty correct, except that when using the app_uart_fifo.c driver you also have the intermediate step of storing incoming and outgoing data in two FIFO's, called m_rx_fifo and m_tx_fifo in the code. Whenever you send data by calling app_uart_put it will first be stored in the m_tx_fifo FIFO. Then the driver will check if the UART is busy, and send it out immediately if it is not. If the UART is busy the data will simply be left in the FIFO, and when the next TX complete interrupt occurs the driver will send the next byte from the FIFO (line 119 of app_uart_fifo.c). 

    When you receive data it will first be stored in the m_rx_fifo FIFO by the uart_event_handler in app_uart_fifo.c, before the application callback is triggered. Once you call app_uart_get(..) in the code it will read from m_rx_fifo and return the result. 

    In the case that both the RX and TX interrupts occur at exactly the same time the RX interrupt will be handled first, followed by the TX interrupt, since there is more risk if you delay the RX interrupt handling (the worst that can happen if TX interrupt handling is delayed is that you get a gap in the data transmission, but you don't drop any bytes). 

    The interrupt handler will only run once, and if you look inside the interrupt handler in nrfx_uart.c or nrfx_uarte.c you can see how every possible interrupt source is checked one after the other. 

    Essentially there is no need for mutex protection of the FIFO buffers because they only have a single consumer and producer of data.

    m_tx_fifo is only written by the app_uart_put(..) function, and is only read by the UART TX interrupt handler (except for line 237 of app_uart_fifo.c which is safe because you only run this when there is no TX activity, and hence no risk of TX interrupts). 

    m_rx_fifo is only written by the UART RX interrupt handler, and only read by the app_uart_get(..) functions. 

    Please do note that this assumes that you don't call app_uart_put or app_uart_get from different interrupt priorities, as these functions do not support reentrancy. It is up to the application to ensure that you don't call any of these functions in a reentrant manner. 

    Since UART is an asynchronous interface, and you don't have any control over when data is received, there is always a risk of dropping RX data if you don't react to the interrupts fast enough. 

    To mitigate this issue the UART peripheral has an internal 6 byte FIFO before the RXD register, so that you can receive up to 6 bytes before you have to read them out without losing data. In newer chips we have replaced the UART peripheral with the UARTE peripheral, which uses EasyDMA to automatically store incoming data in RAM, which means you are no longer limited by the 6 byte RX FIFO and can provide as large a RAM buffer as you see fit. Essentially you should scale the UART RX buffer by the worst case interrupt time and the UART baudrate. 

    Best regards
    Torbjørn

Related