Single-wire UART on Nrf9160

  • I’m working with a device that communicates over single-wire UART (half-duplex), where TX and RX share the same line (because the peer device is designed that way). Now I need the nRF9160 to communicate with it on the same single wire.

I know that implementing a software UART using GPIO is an option, but it seems too power-hungry for my use case. I’d like to ask:

  • Is there any way to utilize the hardware peripheral and DMA of the nRF9160, but still achieve single-wire communication (TX and RX on the same wire)?

Thanks in advance!

Parents
  • The uart can be used in 1-wire mode but requires attention to timing. Using the same pin for Tx and Rx effectively allows a loopback where all transmitted data is received without an external device connected, and this provides the clue for a 1-wire uart. On transmission the Tx pin is configured to the TxRx pin and remains so until the last bit has physically left the uart (see RS485 implementations). At this event the Tx pin is set to disconnect and the external device is now able to drive the Rx input. A short but finite time is required for the Tx disconnect which depends a little on the external device; lower baud rates help with this. The Rx pin can be left enabled during transmission and reception, which means both transmitted data and received data appear in the Rx buffer, or the Rx pin can be disconnected during transmission to avoid also receiving transmitted data.

    Low-level or nrfx code is pretty simple; probably not so in nRFConnect.

  • Ah, interesting, seems like it is possible to make this work by using PPI and ENDTX connect to STOPTX/STARTRX and ENDRX connect to STOPRX/STARTTX.

    I have not tested this and do not know how you set the pin configuration in dts and uarte_init but it seems like this can be possible with something like this

    #define PPI_CH_STOP_TX    0
    #define PPI_CH_START_RX   1
    #define PPI_CH_STOP_RX    2
    #define PPI_CH_START_TX   3
    
    static nrfx_ppi_channel_t ch_stop_tx, ch_start_rx;
    static nrfx_ppi_channel_t ch_stop_rx, ch_start_tx;
    
    void setup_onewire_ppi(void)
    {
        // Allocate all four channels
        nrfx_ppi_channel_alloc(&ch_stop_tx);
        nrfx_ppi_channel_alloc(&ch_start_rx);
        nrfx_ppi_channel_alloc(&ch_stop_rx);
        nrfx_ppi_channel_alloc(&ch_start_tx);
    
        // TX ends → stop driving & start RX
        nrfx_ppi_channel_assign(ch_stop_tx,
            nrfx_uarte_evt_address_get(&uart, NRF_UARTE_EVENT_ENDTX),
            &uart.p_reg->TASKS_STOPTX);
        nrfx_ppi_channel_assign(ch_start_rx,
            nrfx_uarte_evt_address_get(&uart, NRF_UARTE_EVENT_ENDTX),
            &uart.p_reg->TASKS_STARTRX);
        nrfx_ppi_channel_enable(ch_stop_tx);
        nrfx_ppi_channel_enable(ch_start_rx);
    
        // RX ends → stop listening & start TX
        nrfx_ppi_channel_assign(ch_stop_rx,
            nrfx_uarte_evt_address_get(&uart, NRF_UARTE_EVENT_ENDRX),
            &uart.p_reg->TASKS_STOPRX);
        nrfx_ppi_channel_assign(ch_start_tx,
            nrfx_uarte_evt_address_get(&uart, NRF_UARTE_EVENT_ENDRX),
            &uart.p_reg->TASKS_STARTTX);
        nrfx_ppi_channel_enable(ch_stop_rx);
        nrfx_ppi_channel_enable(ch_start_tx);
    }
    

Reply
  • Ah, interesting, seems like it is possible to make this work by using PPI and ENDTX connect to STOPTX/STARTRX and ENDRX connect to STOPRX/STARTTX.

    I have not tested this and do not know how you set the pin configuration in dts and uarte_init but it seems like this can be possible with something like this

    #define PPI_CH_STOP_TX    0
    #define PPI_CH_START_RX   1
    #define PPI_CH_STOP_RX    2
    #define PPI_CH_START_TX   3
    
    static nrfx_ppi_channel_t ch_stop_tx, ch_start_rx;
    static nrfx_ppi_channel_t ch_stop_rx, ch_start_tx;
    
    void setup_onewire_ppi(void)
    {
        // Allocate all four channels
        nrfx_ppi_channel_alloc(&ch_stop_tx);
        nrfx_ppi_channel_alloc(&ch_start_rx);
        nrfx_ppi_channel_alloc(&ch_stop_rx);
        nrfx_ppi_channel_alloc(&ch_start_tx);
    
        // TX ends → stop driving & start RX
        nrfx_ppi_channel_assign(ch_stop_tx,
            nrfx_uarte_evt_address_get(&uart, NRF_UARTE_EVENT_ENDTX),
            &uart.p_reg->TASKS_STOPTX);
        nrfx_ppi_channel_assign(ch_start_rx,
            nrfx_uarte_evt_address_get(&uart, NRF_UARTE_EVENT_ENDTX),
            &uart.p_reg->TASKS_STARTRX);
        nrfx_ppi_channel_enable(ch_stop_tx);
        nrfx_ppi_channel_enable(ch_start_rx);
    
        // RX ends → stop listening & start TX
        nrfx_ppi_channel_assign(ch_stop_rx,
            nrfx_uarte_evt_address_get(&uart, NRF_UARTE_EVENT_ENDRX),
            &uart.p_reg->TASKS_STOPRX);
        nrfx_ppi_channel_assign(ch_start_tx,
            nrfx_uarte_evt_address_get(&uart, NRF_UARTE_EVENT_ENDRX),
            &uart.p_reg->TASKS_STARTTX);
        nrfx_ppi_channel_enable(ch_stop_rx);
        nrfx_ppi_channel_enable(ch_start_tx);
    }
    

Children
  • Looks good; to avoid changing the Tx pin setting to "disconnected" via an interrupt when in Rx receive mode the Tx pin can be set permanently to H0D1 (instead of S0S1 or H0H1) with Pullup enabled; that does somewhat limit the transmission speed unless an external pullup is added though I would suggest activating the pullup on both Rx and Tx pins which would give better performance and not require the external pullup.

Related