How to get more than 2 UART's

From the nRF52840 specification it seems that the CPU only has 2 UART peripherals. For my particular project, I need to have atleast 4 UART peripherals:

1. UART0 - for logging and debugging

2. UART1 - For communicating with LTE modem

3. UART2 - For communicating with the GNSS

4. UART3 - For communicating with the CO2 sensor

I would like to know what are the most optimal solutions if we need to use more than 2 UART on the nRF52840?

Parents Reply Children
  • Thanks for the reply. I understand that there are many workarrounds when it comes to this particular example.

    But I want to know if there is a possibility to create more UART's on the nRF52840 in software? Would there be any limitations or drawbacks? 

    Lets assume that the custom PCB has already been designed and there is no option to choose any other method but to configure the UART on spare GPIO's

  • I do believe bit banging UART is possible, but it will only be possible for slow UART communication and increase current consumption since the CPU will be running most of the time. Not recommended and not something I have looked into in specific.

    Kenneth

  • As Kenneth says, bit-banging is an option but not very reliable for faster baud rates. You can also use PWM for output Tx, and hardware timers for input Rx.

    // PWM Half-Duplex UART - nrfx drivers
    // ===================================
    //
    // Requirements
    //  10 bit bytes (Start - 8xData - Stop)
    //  single char or burst (packet) transmission
    //  Differential output using 2 pins
    //  No level change on pins outside transmitted data to avoid spurious character detection
    
    // Standard Uart character 0x21
    //
    //        |     |     |     |     |     |     |     |     |     |     |
    //  Idle   Start   0     1     2     3     4     5     6     7   Stop    Idle
    // -------+     +-----+                       +-----+           +-----+-------
    //        |     |     |                       |     |           |
    //        |     |     |                       |     |           |       Normal
    //        +-----+     +-----+-----+-----+-----+     +-----+-----+
    //
    //        +-----+     +-----+-----+-----+-----+     +-----+-----+
    //        |     |     |                       |     |           |       Invert
    //        |     |     |                       |     |           |
    // -------+     +-----+                       +-----+           +-----+-------
    

    There was some discussion here pwm-waveform which might be useful. PWM output and timer input works well, but it's quite a bit of code. I can share some PWM code for you to try, Tx allows a very high baud rate.

    This is a build message function:

    #define FEATURE_NRFX_DRIVERS               // Comment out this line for low-level drivers
    #define LL_BAUDRATE      1200              // Tested ok up to 9600 baud with voting inside interrupt (30uSec); faster if emcoding outside
    #define F_CLK        16000000              // nRF52832/nRF52840 fixed at 16MHz
    #define F_PRESCALER  (PWM_PRESCALER_PRESCALER_DIV_1)  // Divide by 1, 16MHz clock
    #define COUNTER_TOP  ((F_CLK+(LL_BAUDRATE/2)) / LL_BAUDRATE)
    STATIC_ASSERT(COUNTER_TOP < 32768, "COUNTER_TOP value too large for 15-bit register");
    STATIC_ASSERT(COUNTER_TOP >= 3, "COUNTER_TOP value too small for correct operation");
    #define BIT_LOW               0           // Normal encoding
    #define BIT_HIGH    (COUNTER_TOP)         // Normal encoding
    //#define BIT_LOW   ((2*COUNTER_TOP)/3)   // Manchester encoding 2/3 bit
    //#define BIT_HIGH  (COUNTER_TOP/3)       // Manchester encoding 1/3 bit
    #define PWM_NEG (PIN_FEATHER_D6)          // P0.07 0x00000080 Low level outside transmission
    #define PWM_POS (PIN_FEATHER_D11)          // P0.26 0x04000000 High level outside transmission
    #define DEBUG_PIN (PIN_FEATHER_D9)       // P0.06 0x00000080 Low level outside transmission
    #define STX_BYTE 0x02 // Example start character: STX (^B)
    #define ETX_BYTE 0x03 // Example start character: ETX (^C)
    
    typedef struct TX_BYTE_T {
        nrf_pwm_values_grouped_t StartBit;
        nrf_pwm_values_grouped_t DataBits[8];
        nrf_pwm_values_grouped_t StopBit;
    } TxByte_t;
    
    TxByte_t TxPacket[4];
    const uint16_t TxPacketSize = (sizeof(TxPacket)/sizeof(uint16_t));
    
    void encodeByte(TxByte_t* pBuffer, const uint8_t ch)
    {
        // Encode byte in little-endian format with start and stop bits
        pBuffer->StartBit.group_0 = pBuffer->StartBit.group_1 = (BIT_LOW);  pBuffer->StartBit.group_0 |= 0x8000; // 0 Start bit
        pBuffer->StopBit.group_0  = pBuffer->StopBit.group_1  = (BIT_HIGH); pBuffer->StopBit.group_0  |= 0x8000; // 0 Stop bit
        for(uint32_t i=0; i<8; i++)
        {
            pBuffer->DataBits[i].group_0 = pBuffer->DataBits[i].group_1 = (((ch>>i) & 0x01) ? BIT_HIGH : BIT_LOW);
            pBuffer->DataBits[i].group_0 |= 0x8000; // invert
        }
    }
    
    void buildMessage(void)
    {
        TxByte_t *pTx = TxPacket;
        uint8_t STX         = 0x82;
        uint8_t signal_id   = 0x64 | 0x80;
        uint8_t data_byte_0 = 0x03 | 0x80;
        uint8_t checksum    = (signal_id) + (data_byte_0);
        checksum &= 0x7F;
    #if 0
        encodeByte(pTx++, STX);
        encodeByte(pTx++, signal_id);
        encodeByte(pTx++, data_byte_0);
        encodeByte(pTx++, checksum);
    #else
        encodeByte(pTx++, 'A');
        encodeByte(pTx++, 'B');
        encodeByte(pTx++, 'C');
        encodeByte(pTx++, 'D');
    #endif
    }

    This is the PWM function to transmit the message built above:

    // Timer for this module, not all timers have 6 CC registers
    static NRF_TIMER_Type *pTimer = NRF_TIMER3;
    #define PWM_TIMER_IRQn TIMER3_IRQn
    void RxTimerInit(void);
    
    void TestPWM_Uart(void)
    {
        // Start accurate HFCLK (XOSC)
        NRF_CLOCK->TASKS_HFCLKSTART = 1;
        while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
            ;
        NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
        static nrfx_pwm_t m_pwm0 = NRFX_PWM_INSTANCE(0);
        uint32_t err_code;
        // Declare a configuration structure and use a macro to instantiate it with default parameters.
        nrfx_pwm_config_t pwm_config = NRFX_PWM_DEFAULT_CONFIG;
        //    .irq_priority = NRFX_PWM_DEFAULT_CONFIG_IRQ_PRIORITY,                  \
        //    .base_clock   = (nrf_pwm_clk_t)NRFX_PWM_DEFAULT_CONFIG_BASE_CLOCK,     \
        //    .count_mode   = (nrf_pwm_mode_t)NRFX_PWM_DEFAULT_CONFIG_COUNT_MODE,    \
        //    .top_value    = NRFX_PWM_DEFAULT_CONFIG_TOP_VALUE,                     \
        //    .load_mode    = (nrf_pwm_dec_load_t)NRFX_PWM_DEFAULT_CONFIG_LOAD_MODE, \
        //    .step_mode    = (nrf_pwm_dec_step_t)NRFX_PWM_DEFAULT_CONFIG_STEP_MODE
        static nrf_pwm_sequence_t pwm_sequence;
    
        // Override some of the default parameters:
        pwm_config.output_pins[0] = PWM_NEG | NRFX_PWM_PIN_INVERTED;
        pwm_config.output_pins[1] = NRFX_PWM_PIN_NOT_USED;
        pwm_config.output_pins[2] = PWM_POS;
        pwm_config.output_pins[3] = NRFX_PWM_PIN_NOT_USED;
    
        pwm_config.load_mode      = NRF_PWM_LOAD_GROUPED;   // 1 == 1st half word (16-bit) used in channels 0 and 1; 2nd word in channels 2 and 3
        pwm_config.base_clock     = F_PRESCALER;            // 0 == divide by 1 for 16MHz clock
        pwm_config.step_mode      = NRFX_PWM_DEFAULT_CONFIG_STEP_MODE; // 0 == Count Up
        pwm_config.top_value      = COUNTER_TOP;
    
        // Pass config structure into driver init() function
        err_code = nrfx_pwm_init(&m_pwm0, &pwm_config, NULL);
        APP_ERROR_CHECK(err_code);
        // Boost differential pwm driver pins to high drive - this is optional
        nrf_gpio_cfg(PWM_NEG, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
        nrf_gpio_cfg(PWM_POS, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
    
        buildMessage();
        pwm_sequence.values.p_grouped = (uint32_t)TxPacket;
        pwm_sequence.length           = TxPacketSize;
        pwm_sequence.repeats          = 0;
        // Test Rx using hardware timer
        RxTimerInit();
        pTimer->TASKS_START = 1;
        nrfx_pwm_simple_playback(&m_pwm0, &pwm_sequence, 1, NRFX_PWM_FLAG_STOP); // LOOP or STOP
        while (1)
        {
            __WFE();
        }
    }

    This all works, hopefully not too hard to follow. This technique allows stuff like Manchester Encoding and non-standard character lengths.

    Edit: Here's a post using the SAADC for Rx: capturing-gpio-transitions-of-a-4khz-signal-with-ble-enabled

Related