How to wake UART from System-OFF without the use of RDY and REQ pins (or any sort of Hardware Flow Control), for an nRF54L15?

Issue

How to properly wake an nRF54L15 from System-Off mode when the device receives a UART transmission, without the use of the LPUART Ready and Request lines or any sort of HWFC.

I understand that System-Off fully powers down the UART so we can't rely on the UART and DMA hardware to capture the input for us. Additionally, the UART transmission is not using any form of flow control futher complicating the process.

We are looking for any recomendations and/or pointers to help resolve this particular problem. It seems as though if the hardware solution turns out to be a bust, the only thing we can do to replace the 3rd party chip upstream of us with our own but we would like to avoid that as much as possible.

Exact Constraints

- Device cannot consume more than 1 \[mA] while waiting for a transmission - our power profiler readings suggest system ON idle consumes around 7mA.
- Device needs to maintain System-Off as often as possible to save power.
- Upstream UART producer is a 3rd party device we would like to avoid replacing.
- UART Transmission:
- 9600 Baudrate
- 1 Stop bit
- No parity
- Constant pre-amble of ~8 bytes that we do not care about.
- Length of ~32 bytes

What have we attempted/explored

Maintaing System-On in idle

This leaves the UART and DMA fully intact and allows it to immediately capture the data burst without needed to wake the CPU.

The core issue with this solution is that the power consumption from leaving the device in system-on idle is way too high.

System-Off with RX shorted to a GPIO

In this solution, we shorted the RX pin of the device's UART to another GPIO set to ACTIVE LOW with SENSE enabled. Since UART always holds the RX pin high until it is ready to transmit, this works perfectly for signaling the device to wake up.

The issue we run into here is that waking from System-Off is essentially the same as rebooting the device. This means that the UART interface and any configuration for it is lost and does not exist at the moment the system starts to reboot. By the time the Zephyr kernel is loaded the data burst is already over.

System-Off with RX shorted to a GPIO (PRE-KERNEL INIT)

Building off the last solution, we placed an init function for the UART before the Kernel starts to load at the ``PRE_KERNEL_1`` entry point. This successfully initializes the UART early enough to begin capturing the data burst however it starts ~6 bytes into the transmission.

The issue with this approach is that the UART protocol does not define a way to start capturing in the middle of a data burst (transmission). If the first edge (hi->lo) the hardware sees is not the start bit of that byte, that byte will be corrupted and will either cause the following bytes to become corrupted or trigger a corrupted frame error stopping capture all together.

This essentially means that even though we managed to wake up fast enough to "see" the transmission, we have no way of reliably understanding what is actually being sent.

Potential Hardware Solution

We haven't fully explored this yet but the basic idea is to place our own UART periphereal with a FIFO right infront of the device. The RX line is again shorted to a GPIO on the device to enable wakeup on a transmission. The external UART and FIFO would always be powered allowing it to capture the transmission and store it in the FIFO for the device to retrieve once it is ready.

In the initial numbers we ran for this, it seems to be plausible but completely depends on if we can find a UART and FIFO chip that doesn't violate our power budget (1 [mA]).

GPIO Sampling

As of writing this, we just thought of another potential solution of using the ``PRE_KERNEL_1`` entry point to setup a GPIO pin that samples the data at the 9600 baudrate dumping what it sees into a bitstream. Since we know the UART configuration, we could in theory work backwards from the last stop bit seen and mark out each byte correctly. This completely circumnavigates the UART hardware issues entirely but relies on us being able to reasonably sample the GPIO at as close to 9600 baud as possible.

Device Setup

Currently we are testing with a nRF54L15 that is housed on a custom PCB with a PMIC (NPM2100) using its boost converter and a coin-cell battery to power the device.

SDK Version: ncs-V3.2.1



All advice, support, and feedback is greatly appreciated! 

Thank you Devzone team :)

Parents
  • Preambles are usually known and could therefore be used to correlate (slide against) an otherwise unknown bit stream. However a timer-based Rx might be worth investigating as that would require less power than a UART methinks. I tested this approach a while back; it works well.

    Single bit sample, dual timers (T3 and T4), no reload:

    // Standard Uart character 0x21 - Single sample, dual timers (T3 and T4), no reload
    //
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |
    // Idle 'Start'Bit 0'Bit 1'Bit 2'Bit 3'Bit 4'Bit 5'Bit 6'Bit 7'Stop '  Idle
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |                       |     |           |
    //      |     |     |                       |     |           |       Normal
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //      |     |     |                       |     |           |       Invert
    //      |     |     |                       |     |           |
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //         |     |     |     |     |     |     |     |     |     |                                          Clear
    //         |     |     |     |     |     |     |     |     |     +--- Stop bit  pTimer4->CC[5] = BT/2+(9*BT)  *
    //         |     |     |     |     |     |     |     |     +--------- Bit 7     pTimer4->CC[4] = BT/2+(8*BT)  -
    //         |     |     |     |     |     |     |     +--------------- Bit 6     pTimer4->CC[3] = BT/2+(7*BT)  -
    //         |     |     |     |     |     |     +--------------------- Bit 5     pTimer4->CC[2] = BT/2+(6*BT)  -
    //         |     |     |     |     |     +--------------------------- Bit 4     pTimer4->CC[1] = BT/2+(5*BT)  -
    //         |     |     |     |     +--------------------------------- Bit 3     pTimer4->CC[0] = BT/2+(4*BT)  -
    //         |     |     |     +--------------------------------------- Bit 2     pTimer3->CC[3] = BT/2+(3*BT)  -
    //         |     |     +--------------------------------------------- Bit 1     pTimer3->CC[2] = BT/2+(2*BT)  -
    //         |     +--------------------------------------------------- Bit 0     pTimer3->CC[1] = BT/2+(1*BT)  -
    //         +--------------------------------------------------------- Start Bit pTimer3->CC[0] = BT/2         -
    

    Single bit sample, single timer (T4), requires reload:

    // Standard Uart character 0x21 - Single sample, single timer (T4), requires reload
    //
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |
    // Idle 'Start'Bit 0'Bit 1'Bit 2'Bit 3'Bit 4'Bit 5'Bit 6'Bit 7'Stop '  Idle
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |                       |     |           |
    //      |     |     |                       |     |           |       Normal
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //      |     |     |                       |     |           |       Invert
    //      |     |     |                       |     |           |
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |     |     |     |     |     |     |     |     |
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |
    //         |     |     |     |     |     |     |     |     |     |                                          Clear Reload
    //         |     |     |     |     |     |     |     |     |     +--- Stop bit  pTimer4->CC[3] = BT/2+(9*BT) *      2,3,0
    //         |     |     |     |     |     |     |     |     +--------- Bit 7     pTimer4->CC[2] = BT/2+(8*BT) -      1
    //         |     |     |     |     |     |     |     +--------------- Bit 6     pTimer4->CC[1] = BT/2+(7*BT) -      0
    //         |     |     |     |     |     |     +--------------------- Bit 5     pTimer4->CC[0] = BT/2+(6*BT) -      -
    //         |     |     |     |     |     +--------------------------- Bit 4     pTimer4->CC[5] = BT/2+(5*BT) *      -
    //         |     |     |     |     +--------------------------------- Bit 3     pTimer4->CC[4] = BT/2+(4*BT) -      -
    //         |     |     |     +--------------------------------------- Bit 2     pTimer4->CC[3] = BT/2+(3*BT) -      2
    //         |     |     +--------------------------------------------- Bit 1     pTimer4->CC[2] = BT/2+(2*BT) -      1
    //         |     +--------------------------------------------------- Bit 0     pTimer4->CC[1] = BT/2+(1*BT) -      0
    //         +--------------------------------------------------------- Start Bit pTimer4->CC[0] = BT/2        -      -
    

    More robust (better noise immunity) 3-vote bit samples, single timer (T4), SBT=BT/3, requires reload:

    // Standard Uart character 0x21 - 3-vote samples, single timer (T4), SBT=BT/3, requires reload
    //
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |
    // Idle 'Start'Bit 0'Bit 1'Bit 2'Bit 3'Bit 4'Bit 5'Bit 6'Bit 7'Stop '  Idle
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |                       |     |           |
    //      |     |     |                       |     |           |       Normal
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //      |     |     |                       |     |           |       Invert
    //      |     |     |                       |     |           |
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |     |     |     |     |     |     |     |     |
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |                                                  Clear Reload
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +- Stop Bit  after 1/2 Stop bit wait for next Start  -     -
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | | +--- Stop Bit  Check  pTimer4->CC[4] = SBT/2+(28*SBT)  *     3,4,0
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | +----- Stop Bit  Check  pTimer4->CC[3] = SBT/2+(27*SBT)  -     2
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | +------- Bit 7     Vote 3 pTimer4->CC[2] = SBT/2+(26*SBT)  -     1
    //       | | | | | | | | | | | | | | | | | | | | | | | | | +--------- Bit 7     Vote 2 pTimer4->CC[1] = SBT/2+(25*SBT)  -     0
    //       | | | | | | | | | | | | | | | | | | | | | | | | +----------- Bit 7     Vote 1 pTimer4->CC[0] = SBT/2+(24*SBT)  -     5
    //       | | | | | | | | |                                              etc for pairs of bits
    //       | | | | | | | | +------------------------------------------- Bit 1     Vote 3 pTimer4->CC[2] = SBT/2+(8*SBT)   -     1
    //       | | | | | | | +--------------------------------------------- Bit 1     Vote 2 pTimer4->CC[1] = SBT/2+(7*SBT)   -     0
    //       | | | | | | +----------------------------------------------- Bit 1     Vote 1 pTimer4->CC[0] = SBT/2+(6*SBT)   -     5
    //       | | | | | +------------------------------------------------- Bit 0     Vote 3 pTimer4->CC[5] = SBT/2+(5*SBT)   *     4
    //       | | | | +--------------------------------------------------- Bit 0     Vote 2 pTimer4->CC[4] = SBT/2+(4*SBT)   -     3
    //       | | | +----------------------------------------------------- Bit 0     Vote 1 pTimer4->CC[3] = SBT/2+(3*SBT)   -     2
    //       | | +------------------------------------------------------- Start Bit Vote 3 pTimer4->CC[2] = SBT/2+(2*SBT)   -     1
    //       | +--------------------------------------------------------- Start Bit Vote 2 pTimer4->CC[1] = SBT/2+(1*SBT)   -     0
    //       +----------------------------------------------------------- Start Bit Vote 1 pTimer4->CC[0] = SBT/2           -     -
    

  • Exactly! This is where my mind was going when we thought up just sampling the GPIO at the baudrate. We can by-pass the UART hardware issues and just decode the data ourselves to get the transmission back. Then we can orient the data by finding which bytes correspond to the preamble. Since I know we can get a UART up early enough in System-Off to capture part of the preamble, a GPIO pins is even more trivial.

    Interesting idea to sample even more frequently than needed to help reduce noise. I think this is the most ideal solution as it offers the most control over the physical data and prevents a poorly timed UART init from corrupting the data.

    Thanks for the reply and the diagrams explaining it in more detail. I'll make sure to read into this a bit more too.

  • Here's my code for the nRF52840 which might save some time. I used Timer 3 to capture the 3 samples per bit and EGU4 with a lower interrupt priority to process the received character. I was using differential RS485 so simply ignore the unwanted signal for a normal UART.

    Any feedback for improvements welcomed.

    #define NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE  3
    #define NUMBER_OF_RX_INPUT_BITS                 10    // Start - 8 x Data - Stop
    #define NUMBER_OF_INPUT_PIN_SAMPLES (NUMBER_OF_RX_INPUT_BITS * NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE)
    #define COUNT_IS_STOP_BIT (NUMBER_OF_INPUT_PIN_SAMPLES-2)
    #define TIMER_PRESCALER 0
    #define LL_SAMPLE_RATE (LL_BAUDRATE * NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE)
    #define RX_BUFFER_SIZE 4
    #define LL_SAMPLE_TIME ((F_CLK + (LL_SAMPLE_RATE/2))/ LL_SAMPLE_RATE)
    #define RX_NEG_INPUT_PIN_MASK (1 << PWM_NEG)
    #define RX_POS_INPUT_PIN_MASK (1 << PWM_POS)
    
    // Rx pins sample history
    volatile uint32_t mRxInputPinSamples[NUMBER_OF_INPUT_PIN_SAMPLES] = {0};
    volatile uint32_t mRxInputPinSampleCount = 0;
    volatile uint8_t mRxByteP[RX_BUFFER_SIZE] = {0};
    volatile uint8_t mRxByteN[RX_BUFFER_SIZE] = {0};
    volatile uint8_t mRxIndex = 0;
    // Event Generator Unit (EGUn)
    static NRF_EGU_Type* pNRF_EGU = NRF_EGU4;
    #define SWI_EGU_IRQn SWI4_EGU4_IRQn
    
    // Standard Uart character 0x21 - 3-vote samples, single timer (T4), SBT=BT/3, requires reload
    //
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |
    // Idle 'Start'Bit 0'Bit 1'Bit 2'Bit 3'Bit 4'Bit 5'Bit 6'Bit 7'Stop '  Idle
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |                       |     |           |
    //      |     |     |                       |     |           |       Normal
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //      |     |     |                       |     |           |       Invert
    //      |     |     |                       |     |           |
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |     |     |     |     |     |     |     |     |
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |                                                  Clear Reload
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +- Stop Bit  after 1/2 Stop bit wait for next Start  -     -
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | | +--- Stop Bit  Check  pTimer4->CC[4] = SBT/2+(28*SBT)  *     3,4,0
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | +----- Stop Bit  Check  pTimer4->CC[3] = SBT/2+(27*SBT)  -     2
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | +------- Bit 7     Vote 3 pTimer4->CC[2] = SBT/2+(26*SBT)  -     1
    //       | | | | | | | | | | | | | | | | | | | | | | | | | +--------- Bit 7     Vote 2 pTimer4->CC[1] = SBT/2+(25*SBT)  -     0
    //       | | | | | | | | | | | | | | | | | | | | | | | | +----------- Bit 7     Vote 1 pTimer4->CC[0] = SBT/2+(24*SBT)  -     5
    //       | | | | | | | | |                                              etc for pairs of bits
    //       | | | | | | | | +------------------------------------------- Bit 1     Vote 3 pTimer4->CC[2] = SBT/2+(8*SBT)   -     1
    //       | | | | | | | +--------------------------------------------- Bit 1     Vote 2 pTimer4->CC[1] = SBT/2+(7*SBT)   -     0
    //       | | | | | | +----------------------------------------------- Bit 1     Vote 1 pTimer4->CC[0] = SBT/2+(6*SBT)   -     5
    //       | | | | | +------------------------------------------------- Bit 0     Vote 3 pTimer4->CC[5] = SBT/2+(5*SBT)   *     4
    //       | | | | +--------------------------------------------------- Bit 0     Vote 2 pTimer4->CC[4] = SBT/2+(4*SBT)   -     3
    //       | | | +----------------------------------------------------- Bit 0     Vote 1 pTimer4->CC[3] = SBT/2+(3*SBT)   -     2
    //       | | +------------------------------------------------------- Start Bit Vote 3 pTimer4->CC[2] = SBT/2+(2*SBT)   -     1
    //       | +--------------------------------------------------------- Start Bit Vote 2 pTimer4->CC[1] = SBT/2+(1*SBT)   -     0
    //       +----------------------------------------------------------- Start Bit Vote 1 pTimer4->CC[0] = SBT/2           -     -
    
    void RxTimerInit(void)
    {
        // Errata: [78] TIMER: High current consumption when using timer STOP task only
        //  - Workaround: Use the SHUTDOWN task after the STOP task or instead of the STOP task
        //  - this also stops the Timer from hanging after a time change
        //  - affects nRF52832, nRF52840
        pTimer->TASKS_SHUTDOWN = 1;
        // There is no STOPPED Event, and this register is read-only, so add a delay to ensure it is stopped before changes
        // pTimer->TASKS_STOP = 1;
        nrf_gpio_cfg(DEBUG_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
        nrf_delay_us(200);
        // 32-bit timer
        pTimer->MODE    = TIMER_MODE_MODE_Timer;
        pTimer->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
        // 1us timer period
        pTimer->PRESCALER = TIMER_PRESCALER << TIMER_PRESCALER_PRESCALER_Pos; // 16/2^4 -> 1MHz, 16/2^8->62.5KHz or 16uSec tick
        // Now clear timer register
        pTimer->TASKS_CLEAR = 1;
    
        // Sample compare value, generates EVENTS_COMPARE[n]
        pTimer->CC[0] = LL_SAMPLE_TIME / 2;
        pTimer->CC[1] = LL_SAMPLE_TIME * 1 + LL_SAMPLE_TIME / 2;
        pTimer->CC[2] = LL_SAMPLE_TIME * 2 + LL_SAMPLE_TIME / 2;
        pTimer->CC[3] = LL_SAMPLE_TIME * 3 + LL_SAMPLE_TIME / 2;
        pTimer->CC[4] = LL_SAMPLE_TIME * 4 + LL_SAMPLE_TIME / 2;
        pTimer->CC[5] = LL_SAMPLE_TIME * 5 + LL_SAMPLE_TIME / 2;
    
        // Enable IRQ on EVENTS_COMPARE[n]
        pTimer->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE1_Enabled << TIMER_INTENSET_COMPARE1_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE2_Enabled << TIMER_INTENSET_COMPARE2_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE3_Enabled << TIMER_INTENSET_COMPARE3_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE4_Enabled << TIMER_INTENSET_COMPARE4_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE5_Enabled << TIMER_INTENSET_COMPARE5_Pos;
    
        // Clear the timer when COMPARE5 event is triggered
        pTimer->SHORTS = (TIMER_SHORTS_COMPARE5_CLEAR_Enabled << TIMER_SHORTS_COMPARE5_CLEAR_Pos);
    
        // Clear all events
        pTimer->EVENTS_COMPARE[0] = 0;
        pTimer->EVENTS_COMPARE[1] = 0;
        pTimer->EVENTS_COMPARE[2] = 0;
        pTimer->EVENTS_COMPARE[3] = 0;
        pTimer->EVENTS_COMPARE[4] = 0;
        pTimer->EVENTS_COMPARE[5] = 0;
    
        mRxIndex = 0;
    
        // Set interrupt priority and enable interrupt
        NVIC_SetPriority(PWM_TIMER_IRQn, 6);
        NVIC_ClearPendingIRQ(PWM_TIMER_IRQn);
    
        // Clear the character ready to process event
        pNRF_EGU->EVENTS_TRIGGERED[0] = 0;
        pNRF_EGU->INTENSET = 1;
       // Set interrupt priority lower priority than timer and enable interrupt
        NVIC_SetPriority(SWI_EGU_IRQn, 6+1);
        NVIC_ClearPendingIRQ(SWI_EGU_IRQn);
        NVIC_EnableIRQ(SWI_EGU_IRQn);
    
        nrf_delay_us(100);
        NVIC_EnableIRQ(PWM_TIMER_IRQn);
        pTimer->TASKS_START = 1;
    }
    
    // Event Generator Unit (EGUn)
    void SWI4_EGU4_IRQHandler(void)
    {
        // Handle the character ready to process event
        if (pNRF_EGU->EVENTS_TRIGGERED[0])
        {
            // Clear the character ready to process event
            pNRF_EGU->EVENTS_TRIGGERED[0] = 0;
    
            // copy data to release buffer or use dual buffare!!
    
            // stop character frame here, ready for next, process voting
            // Rx pins sample history, later copy this buffer and process outside ISR
            volatile uint32_t * PinSample = &mRxInputPinSamples[NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE];
            uint8_t RxByteP = 0, RxByteN = 0, RxBitMask = 0x01;
    
            for (uint32_t BitIndex = 1; BitIndex < NUMBER_OF_RX_INPUT_BITS - 1; BitIndex++, RxBitMask <<= 1)
            {
                uint32_t VoteP = 0, VoteN = 0;
                for (uint32_t VoteIndex = 0; VoteIndex < NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE; VoteIndex++, PinSample++)
                {
                    VoteP += ((*PinSample & RX_NEG_INPUT_PIN_MASK) == 0 ? 1 : 0);
                    VoteN += ((*PinSample & RX_POS_INPUT_PIN_MASK) == 0 ? 1 : 0);
                }
                if (VoteP >= (NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE + 1) / 2)
                {
                    RxByteP |= RxBitMask;
                }
                if (VoteN >= (NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE + 1) / 2)
                {
                    RxByteN |= RxBitMask;
                }
            }
            mRxInputPinSampleCount = 0;
            mRxByteN[mRxIndex] = RxByteN;
            mRxByteP[mRxIndex] = RxByteP;
            mRxIndex++;
        }
    }
    
    // This IRQ handler will trigger 3 times every Rx character bit
    void TIMER3_IRQHandler(void)
    {
        bool TerminateReception = false;
    
        NRF_GPIO->OUTSET = (1 << DEBUG_PIN);
    
    
        if (pTimer->EVENTS_COMPARE[0] == 1)
        {
            // SCOPE_PROBE_2_HIGH;
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[0] = 0;
        }
        if (pTimer->EVENTS_COMPARE[1] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[1] = 0;
            // Adjust initial Start Bit sample time back to same as other bits
            pTimer->CC[0] = LL_SAMPLE_TIME;
        }
        if (pTimer->EVENTS_COMPARE[2] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[2] = 0;
        }
        if (pTimer->EVENTS_COMPARE[3] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[3] = 0;
        }
        if (pTimer->EVENTS_COMPARE[4] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[4] = 0;
            if (mRxInputPinSampleCount >= COUNT_IS_STOP_BIT)
            {
                // Trigger the character ready to process event
                pNRF_EGU->TASKS_TRIGGER[0] = 1;
                if (mRxIndex >= RX_BUFFER_SIZE)
                {
                    TerminateReception = true;
                }
                // SCOPE_PROBE_2_LOW;
            }
        }
        if (pTimer->EVENTS_COMPARE[5] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[5] = 0;
            //if (mRxInputPinSampleCount <= COUNT_IS_STOP_BIT)
            {
                // repeat here  for next 2 bits, use SHORTS
            }
            // Update sample compare value, generates EVENTS_COMPARE[n]
            pTimer->CC[0] = LL_SAMPLE_TIME * 1;
            pTimer->CC[1] = LL_SAMPLE_TIME * 2;
            pTimer->CC[2] = LL_SAMPLE_TIME * 3;
            pTimer->CC[3] = LL_SAMPLE_TIME * 4;
            pTimer->CC[4] = LL_SAMPLE_TIME * 5;
            pTimer->CC[5] = LL_SAMPLE_TIME * 6;
        }
        if (mRxInputPinSampleCount >= NUMBER_OF_INPUT_PIN_SAMPLES)
        {
            TerminateReception = true;
        }
        if (TerminateReception)
        {
            //pTimer->TASKS_SHUTDOWN = 1;
            pTimer->TASKS_STOP = 1;
            //pTimer->SHORTS = (TIMER_SHORTS_COMPARE5_STOP_Enabled << TIMER_SHORTS_COMPARE5_STOP_Pos);
        }
        // Clear pending hardware register bus operations
        __DSB();
        //nrf_delay_us(30);
        NRF_GPIO->OUTCLR = (1 << DEBUG_PIN);
    }
    

Reply
  • Here's my code for the nRF52840 which might save some time. I used Timer 3 to capture the 3 samples per bit and EGU4 with a lower interrupt priority to process the received character. I was using differential RS485 so simply ignore the unwanted signal for a normal UART.

    Any feedback for improvements welcomed.

    #define NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE  3
    #define NUMBER_OF_RX_INPUT_BITS                 10    // Start - 8 x Data - Stop
    #define NUMBER_OF_INPUT_PIN_SAMPLES (NUMBER_OF_RX_INPUT_BITS * NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE)
    #define COUNT_IS_STOP_BIT (NUMBER_OF_INPUT_PIN_SAMPLES-2)
    #define TIMER_PRESCALER 0
    #define LL_SAMPLE_RATE (LL_BAUDRATE * NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE)
    #define RX_BUFFER_SIZE 4
    #define LL_SAMPLE_TIME ((F_CLK + (LL_SAMPLE_RATE/2))/ LL_SAMPLE_RATE)
    #define RX_NEG_INPUT_PIN_MASK (1 << PWM_NEG)
    #define RX_POS_INPUT_PIN_MASK (1 << PWM_POS)
    
    // Rx pins sample history
    volatile uint32_t mRxInputPinSamples[NUMBER_OF_INPUT_PIN_SAMPLES] = {0};
    volatile uint32_t mRxInputPinSampleCount = 0;
    volatile uint8_t mRxByteP[RX_BUFFER_SIZE] = {0};
    volatile uint8_t mRxByteN[RX_BUFFER_SIZE] = {0};
    volatile uint8_t mRxIndex = 0;
    // Event Generator Unit (EGUn)
    static NRF_EGU_Type* pNRF_EGU = NRF_EGU4;
    #define SWI_EGU_IRQn SWI4_EGU4_IRQn
    
    // Standard Uart character 0x21 - 3-vote samples, single timer (T4), SBT=BT/3, requires reload
    //
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |
    // Idle 'Start'Bit 0'Bit 1'Bit 2'Bit 3'Bit 4'Bit 5'Bit 6'Bit 7'Stop '  Idle
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |                       |     |           |
    //      |     |     |                       |     |           |       Normal
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //      |     |     |                       |     |           |       Invert
    //      |     |     |                       |     |           |
    // -----+     +-----+                       +-----+           +-----+-------
    //      |     |     |     |     |     |     |     |     |     |     |
    //      |     |     |     |     |     |     |     |     |     |     | Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |                                                  Clear Reload
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +- Stop Bit  after 1/2 Stop bit wait for next Start  -     -
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | | +--- Stop Bit  Check  pTimer4->CC[4] = SBT/2+(28*SBT)  *     3,4,0
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | | +----- Stop Bit  Check  pTimer4->CC[3] = SBT/2+(27*SBT)  -     2
    //       | | | | | | | | | | | | | | | | | | | | | | | | | | +------- Bit 7     Vote 3 pTimer4->CC[2] = SBT/2+(26*SBT)  -     1
    //       | | | | | | | | | | | | | | | | | | | | | | | | | +--------- Bit 7     Vote 2 pTimer4->CC[1] = SBT/2+(25*SBT)  -     0
    //       | | | | | | | | | | | | | | | | | | | | | | | | +----------- Bit 7     Vote 1 pTimer4->CC[0] = SBT/2+(24*SBT)  -     5
    //       | | | | | | | | |                                              etc for pairs of bits
    //       | | | | | | | | +------------------------------------------- Bit 1     Vote 3 pTimer4->CC[2] = SBT/2+(8*SBT)   -     1
    //       | | | | | | | +--------------------------------------------- Bit 1     Vote 2 pTimer4->CC[1] = SBT/2+(7*SBT)   -     0
    //       | | | | | | +----------------------------------------------- Bit 1     Vote 1 pTimer4->CC[0] = SBT/2+(6*SBT)   -     5
    //       | | | | | +------------------------------------------------- Bit 0     Vote 3 pTimer4->CC[5] = SBT/2+(5*SBT)   *     4
    //       | | | | +--------------------------------------------------- Bit 0     Vote 2 pTimer4->CC[4] = SBT/2+(4*SBT)   -     3
    //       | | | +----------------------------------------------------- Bit 0     Vote 1 pTimer4->CC[3] = SBT/2+(3*SBT)   -     2
    //       | | +------------------------------------------------------- Start Bit Vote 3 pTimer4->CC[2] = SBT/2+(2*SBT)   -     1
    //       | +--------------------------------------------------------- Start Bit Vote 2 pTimer4->CC[1] = SBT/2+(1*SBT)   -     0
    //       +----------------------------------------------------------- Start Bit Vote 1 pTimer4->CC[0] = SBT/2           -     -
    
    void RxTimerInit(void)
    {
        // Errata: [78] TIMER: High current consumption when using timer STOP task only
        //  - Workaround: Use the SHUTDOWN task after the STOP task or instead of the STOP task
        //  - this also stops the Timer from hanging after a time change
        //  - affects nRF52832, nRF52840
        pTimer->TASKS_SHUTDOWN = 1;
        // There is no STOPPED Event, and this register is read-only, so add a delay to ensure it is stopped before changes
        // pTimer->TASKS_STOP = 1;
        nrf_gpio_cfg(DEBUG_PIN, NRF_GPIO_PIN_DIR_OUTPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_H0H1, NRF_GPIO_PIN_NOSENSE);
        nrf_delay_us(200);
        // 32-bit timer
        pTimer->MODE    = TIMER_MODE_MODE_Timer;
        pTimer->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
        // 1us timer period
        pTimer->PRESCALER = TIMER_PRESCALER << TIMER_PRESCALER_PRESCALER_Pos; // 16/2^4 -> 1MHz, 16/2^8->62.5KHz or 16uSec tick
        // Now clear timer register
        pTimer->TASKS_CLEAR = 1;
    
        // Sample compare value, generates EVENTS_COMPARE[n]
        pTimer->CC[0] = LL_SAMPLE_TIME / 2;
        pTimer->CC[1] = LL_SAMPLE_TIME * 1 + LL_SAMPLE_TIME / 2;
        pTimer->CC[2] = LL_SAMPLE_TIME * 2 + LL_SAMPLE_TIME / 2;
        pTimer->CC[3] = LL_SAMPLE_TIME * 3 + LL_SAMPLE_TIME / 2;
        pTimer->CC[4] = LL_SAMPLE_TIME * 4 + LL_SAMPLE_TIME / 2;
        pTimer->CC[5] = LL_SAMPLE_TIME * 5 + LL_SAMPLE_TIME / 2;
    
        // Enable IRQ on EVENTS_COMPARE[n]
        pTimer->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE1_Enabled << TIMER_INTENSET_COMPARE1_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE2_Enabled << TIMER_INTENSET_COMPARE2_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE3_Enabled << TIMER_INTENSET_COMPARE3_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE4_Enabled << TIMER_INTENSET_COMPARE4_Pos;
        pTimer->INTENSET = TIMER_INTENSET_COMPARE5_Enabled << TIMER_INTENSET_COMPARE5_Pos;
    
        // Clear the timer when COMPARE5 event is triggered
        pTimer->SHORTS = (TIMER_SHORTS_COMPARE5_CLEAR_Enabled << TIMER_SHORTS_COMPARE5_CLEAR_Pos);
    
        // Clear all events
        pTimer->EVENTS_COMPARE[0] = 0;
        pTimer->EVENTS_COMPARE[1] = 0;
        pTimer->EVENTS_COMPARE[2] = 0;
        pTimer->EVENTS_COMPARE[3] = 0;
        pTimer->EVENTS_COMPARE[4] = 0;
        pTimer->EVENTS_COMPARE[5] = 0;
    
        mRxIndex = 0;
    
        // Set interrupt priority and enable interrupt
        NVIC_SetPriority(PWM_TIMER_IRQn, 6);
        NVIC_ClearPendingIRQ(PWM_TIMER_IRQn);
    
        // Clear the character ready to process event
        pNRF_EGU->EVENTS_TRIGGERED[0] = 0;
        pNRF_EGU->INTENSET = 1;
       // Set interrupt priority lower priority than timer and enable interrupt
        NVIC_SetPriority(SWI_EGU_IRQn, 6+1);
        NVIC_ClearPendingIRQ(SWI_EGU_IRQn);
        NVIC_EnableIRQ(SWI_EGU_IRQn);
    
        nrf_delay_us(100);
        NVIC_EnableIRQ(PWM_TIMER_IRQn);
        pTimer->TASKS_START = 1;
    }
    
    // Event Generator Unit (EGUn)
    void SWI4_EGU4_IRQHandler(void)
    {
        // Handle the character ready to process event
        if (pNRF_EGU->EVENTS_TRIGGERED[0])
        {
            // Clear the character ready to process event
            pNRF_EGU->EVENTS_TRIGGERED[0] = 0;
    
            // copy data to release buffer or use dual buffare!!
    
            // stop character frame here, ready for next, process voting
            // Rx pins sample history, later copy this buffer and process outside ISR
            volatile uint32_t * PinSample = &mRxInputPinSamples[NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE];
            uint8_t RxByteP = 0, RxByteN = 0, RxBitMask = 0x01;
    
            for (uint32_t BitIndex = 1; BitIndex < NUMBER_OF_RX_INPUT_BITS - 1; BitIndex++, RxBitMask <<= 1)
            {
                uint32_t VoteP = 0, VoteN = 0;
                for (uint32_t VoteIndex = 0; VoteIndex < NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE; VoteIndex++, PinSample++)
                {
                    VoteP += ((*PinSample & RX_NEG_INPUT_PIN_MASK) == 0 ? 1 : 0);
                    VoteN += ((*PinSample & RX_POS_INPUT_PIN_MASK) == 0 ? 1 : 0);
                }
                if (VoteP >= (NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE + 1) / 2)
                {
                    RxByteP |= RxBitMask;
                }
                if (VoteN >= (NUMBER_OF_RX_INPUT_PIN_SAMPLES_PER_VOTE + 1) / 2)
                {
                    RxByteN |= RxBitMask;
                }
            }
            mRxInputPinSampleCount = 0;
            mRxByteN[mRxIndex] = RxByteN;
            mRxByteP[mRxIndex] = RxByteP;
            mRxIndex++;
        }
    }
    
    // This IRQ handler will trigger 3 times every Rx character bit
    void TIMER3_IRQHandler(void)
    {
        bool TerminateReception = false;
    
        NRF_GPIO->OUTSET = (1 << DEBUG_PIN);
    
    
        if (pTimer->EVENTS_COMPARE[0] == 1)
        {
            // SCOPE_PROBE_2_HIGH;
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[0] = 0;
        }
        if (pTimer->EVENTS_COMPARE[1] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[1] = 0;
            // Adjust initial Start Bit sample time back to same as other bits
            pTimer->CC[0] = LL_SAMPLE_TIME;
        }
        if (pTimer->EVENTS_COMPARE[2] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[2] = 0;
        }
        if (pTimer->EVENTS_COMPARE[3] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[3] = 0;
        }
        if (pTimer->EVENTS_COMPARE[4] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[4] = 0;
            if (mRxInputPinSampleCount >= COUNT_IS_STOP_BIT)
            {
                // Trigger the character ready to process event
                pNRF_EGU->TASKS_TRIGGER[0] = 1;
                if (mRxIndex >= RX_BUFFER_SIZE)
                {
                    TerminateReception = true;
                }
                // SCOPE_PROBE_2_LOW;
            }
        }
        if (pTimer->EVENTS_COMPARE[5] == 1)
        {
            mRxInputPinSamples[mRxInputPinSampleCount++] = NRF_GPIO->IN;
            pTimer->EVENTS_COMPARE[5] = 0;
            //if (mRxInputPinSampleCount <= COUNT_IS_STOP_BIT)
            {
                // repeat here  for next 2 bits, use SHORTS
            }
            // Update sample compare value, generates EVENTS_COMPARE[n]
            pTimer->CC[0] = LL_SAMPLE_TIME * 1;
            pTimer->CC[1] = LL_SAMPLE_TIME * 2;
            pTimer->CC[2] = LL_SAMPLE_TIME * 3;
            pTimer->CC[3] = LL_SAMPLE_TIME * 4;
            pTimer->CC[4] = LL_SAMPLE_TIME * 5;
            pTimer->CC[5] = LL_SAMPLE_TIME * 6;
        }
        if (mRxInputPinSampleCount >= NUMBER_OF_INPUT_PIN_SAMPLES)
        {
            TerminateReception = true;
        }
        if (TerminateReception)
        {
            //pTimer->TASKS_SHUTDOWN = 1;
            pTimer->TASKS_STOP = 1;
            //pTimer->SHORTS = (TIMER_SHORTS_COMPARE5_STOP_Enabled << TIMER_SHORTS_COMPARE5_STOP_Pos);
        }
        // Clear pending hardware register bus operations
        __DSB();
        //nrf_delay_us(30);
        NRF_GPIO->OUTCLR = (1 << DEBUG_PIN);
    }
    

Children
No Data
Related