SENT protocol based sensor configuration with nrf boards

hey there, i have a project which uses  nrf9151 with a pressures sensor from BOSCH which uses SENT protocol to transfer data, my question is

1. Is it possible to configure the SENT protocol on the nrf9151
2. if yes, how am i gonna configure what should i use?

Parents Reply Children
  • I thought of an improved algorithm which uses no cpu time or interrupts until the entire SENT message has been captured in Timer registers. Two counters and two Timers are required, since there are 10 edges and capture events, and data is captured as distinct messages, which is of course how they are sent in practice. The message can actually be read out while the optional Pause field is being transmitted, and there is more time until the next message starts.

    Timer/Counters 0, 1 & 2 have 4 CC registers each; Timer/Counters 3 & 4 have 6 CC registers each:
     - COUNTER3 counts 1st 6 edges: set CC0=1, CC1=2, CC2=3, CC3=4, CC4=5, CC5=6
     - COUNTER2 counts next 4 edges: set CC0=7, CC1=8, CC2=9, CC3=10
     - TIMER4 saves the elapsed message time at the first 6 edges
     - TIMER1 saves the elapsed message time at the next 4 edges

    Both of these counters use PPI to count the negative-going edges

    // SENT protocol
    // SENT tick ranges from 3us to about 90us, with clock-rate tolerances as high as +-25%. Each
    // nibble begins with a 5us logic low followed by a variable-width logic-high pulse
    //
    // A SENT message frame includes a sync signal followed by eight nibbles and an optional pause,
    // the latter  to make up fixed-length messages
    // A nibble ranges from 12 to 27 ticks (representing 0x0 to 0xF), optional Pause is 12-768 ticks
    //
    //             |--------Fast Channel Data--------|       Optional
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========
    // T4[0] T4[1] T4[2] T4[3] T4[4] T4[5] T1[0] T1[1] T1[2]    T1[3]  <== Timer Capture CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Counter compare triggers Timer capture
    //     |     |     |     |     |     |     |     |     |        |
    // C3[0] C3[1] C3[2] C3[3] C3[4] C3[5] C2[0] C2[1] C2[2]    C2[3]  <== Counter Compare CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Falling edge triggers counter increment
    //     |     |     |     |     |     |     |     |     |        |
    // ----+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+    +---+
    //     | ||||| ||||| ||||| ||||| ||||| ||||| ||||| |||||    |||||  <== Nibble Framing
    //     +-+   +-+   +-+   +-+   +-+   +-+   +-+   +-+   +----+   +-
    //     |     |     |     |     |     |     |     |     |        |
    //  56   12-27 12-27 12-27 12-27 12-27 12-27 12-27 12-27 (12-768)  <== SENT numer of Ticks range
    //   0       1     2     3     4     5     6     7     8      (9)  <== SENT Falling Edge Number
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE
    //             |--------Fast Channel Data--------|       Optional

    This is the algorithm:

    //  COUNTER3 counts 1st 6 edges:  set CC0=1, CC1=2, CC2=3, CC3=4, CC4=5, CC5=6
    //  COUNTER2 counts next 4 edges: set CC0=7, CC1=8, CC2=9, CC3=10
    //  TIMER1 saves the elapsed message time at the next 4 edges
    //  START TIMER4 and TIMER5 together (use PPI event)
    //  START TIMER4 and TIMER5 together (use PPI event)
    //  START COUNTER3 and COUNTER2 together (use PPI event)
    //  Single Message
    //  {
    //    Counter EVENT      Timer Mode TASK     Nibble  Read registers
    //    =============      =================   ======  =================================
    //    Counter3-CC0  ==> capture Timer4-CC0   Sync
    //    Counter3-CC1  ==> capture Timer4-CC1   Status
    //    Counter3-CC2  ==> capture Timer4-CC2   DN1
    //    Counter3-CC3  ==> capture Timer4-CC3   DN2
    //    Counter3-CC4  ==> capture Timer4-CC4   DN3
    //    Counter3-CC5  ==> capture Timer4-CC5   DN4
    //    Counter2-CC0  ==> capture Timer1-CC0   DN5
    //    Counter2-CC1  ==> capture Timer1-CC1   DN6
    //    Counter2-CC2  ==> capture Timer1-CC2   CRC    Can start reading in interrupt here
    //    Counter2-CC3  ==> capture Timer1-CC3   PAUSE  read Timer4 CC0-CC5 and Timer1 CC0-CC3
    //  }

  • I wrote some code to implement the algorithm I described, and it seems to work ok. I don't have a SENT sensor to hand, so I spoofed a test using just a Uart with 10 negative edges: {0xFF,0xB7,0xEF,0xBB, 0x7F} which decodes as SENT data A44FF4A4

    Algorithm:

    // Version using 4 peripheral Timers (most efficient) for nRF52 and nRF54:
    //
    //             |--------Fast Channel Data--------|       Optional
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE       Description
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========       ===========================
    //     +--------------------------------------------------------------  Edge  2: Sync
    //     |     +--------------------------------------------------------  Edge  3: Status
    //     |     |     +--------------------------------------------------  Edge  4: DN1
    //     |     |     |     +--------------------------------------------  Edge  5: DN2
    //     |     |     |     |     +--------------------------------------  Edge  6: DN3
    //     |     |     |     |     |     +--------------------------------  Edge  7: DN4
    //     |     |     |     |     |     |     +--------------------------  Edge  8: DN5
    //     |     |     |     |     |     |     |     +--------------------  Edge  9: DN6
    //     |     |     |     |     |     |     |     |     +--------------  Edge 10: CRC      Interrupt
    //     |     |     |     |     |     |     |     |     |        +-----  Edge 11: PAUSE (Optional)
    //     |     |     |     |     |     |     |     |     |        |
    // T4[0] T4[1] T4[2] T4[3] T4[4] T4[5] T1[0] T1[1] T1[2]    T1[3]  <== Timer Capture CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Counter compare triggers Timer capture
    //     |     |     |     |     |     |     |     |     |        |
    // C3[0] C3[1] C3[2] C3[3] C3[4] C3[5] C2[0] C2[1] C2[2]    C2[3]  <== Counter Compare CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Falling edge triggers counter increment
    //     |     |     |     |     |     |     |     |     |        |
    // ----+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+    +---+
    //     | ||||| ||||| ||||| ||||| ||||| ||||| ||||| |||||    |||||  <== Nibble Framing
    //     +-+   +-+   +-+   +-+   +-+   +-+   +-+   +-+   +----+   +-
    //     |     |     |     |     |     |     |     |     |        |
    //    56 12-27 12-27 12-27 12-27 12-27 12-27 12-27 12-27 (12-768)  <== SENT numer of Ticks range
    //     2     3     4     5     6     7     8     9    10     (11)  <== SENT Falling Edge Number
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE
    //             |--------Fast Channel Data--------|       Optional
    
    //  Single Message
    //  {
    //    Counter EVENT      Timer Mode TASK     Nibble  Read registers
    //    =============      =================   ======  =================================
    //    Counter3-CC0  ==> capture Timer4-CC0   Sync
    //    Counter3-CC1  ==> capture Timer4-CC1   Status
    //    Counter3-CC2  ==> capture Timer4-CC2   DN1
    //    Counter3-CC3  ==> capture Timer4-CC3   DN2
    //    Counter3-CC4  ==> capture Timer4-CC4   DN3
    //    Counter3-CC5  ==> capture Timer4-CC5   DN4
    //    Counter2-CC0  ==> capture Timer1-CC0   DN5
    //    Counter2-CC1  ==> capture Timer1-CC1   DN6
    //    Counter2-CC2  ==> capture Timer1-CC2   CRC    Can start reading in interrupt here
    //    Counter2-CC3  ==> capture Timer1-CC3   PAUSE  read Timer4 CC0-CC5 and Timer1 CC0-CC3
    //  }

    Code - Proof of Concept; I used a few magic numbers as time is limited. Bare-metal but easy to translate to nRFx or (dare I say) nRFConnect:

    // PPI channels 0-19 are programmable, channels 20-31 are fixed
    #define PPI_SENT_SYNC      0 // Sync
    #define PPI_SENT_STATUS    1 // Status
    #define PPI_SENT_DN1       2 // DN1
    #define PPI_SENT_DN2       3 // DN2
    #define PPI_SENT_DN3       4 // DN3
    #define PPI_SENT_DN4       5 // DN4
    #define PPI_SENT_DN5       6 // DN5
    #define PPI_SENT_DN6       7 // DN6
    #define PPI_SENT_CRC       8 // CRC
    #define PPI_SENT_PAUSE     9 // PAUSE
    #define PPI_SENT_INPUT    10 // Input SENT signal
    #define PPI_SENT_TEST_HI  11 // Test output SENT signal high level
    #define PPI_SENT_TEST_LO  12 // Test output SENT signal low level
    #define PPI_SENT_START    13 // Use Input SENT signal to start timers
    
    // 8 GPIOTE channels available
    #define GPIOTE_SENT_INPUT 0 // Input SENT signal
    #define GPIOTE_SENT_TEST  1 // Test output SENT signal
    
    // Port pins to test SENT using PWM on output pin connected to SENT input pin
    #define PIN_SENT_INPUT    3 // Input SENT signal
    #define PIN_SENT_TEST     4 // Test output SENT signal, connect to Input SENT signal
    
    // TIMER0 is used by the RADIO, so don't use for now
    static NRF_TIMER_Type *pTimerSent_0to5   = NRF_TIMER4; // 6 x CC
    static NRF_TIMER_Type *pTimerSent_6to9   = NRF_TIMER1; // 4 x CC
    static NRF_TIMER_Type *pCounterSent_0to5 = NRF_TIMER3; // 6 x CC
    static NRF_TIMER_Type *pCounterSent_6to9 = NRF_TIMER2; // 4 x CC
    
    static void SentRxProtocol(void)
    {
       NRF_P0->OUTSET = (1 << PIN_SENT_INPUT);
       NRF_P0->OUTSET = (1 << PIN_SENT_TEST);
       // Configuration                       Direction    Input            Pullup         Drive Level      Sense Level
       // ================================    ==========   ==============   ============   ==============   =============
       NRF_P0->PIN_CNF[PIN_SENT_TEST]      = (PIN_OUTPUT | PIN_CONNECT    | PIN_PULLNONE | PIN_DRIVE_S0S1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[PIN_SENT_INPUT]     = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLUP   | PIN_DRIVE_S0S1 | PIN_SENSE_LOW);
    
       // 32-bit timers
       pTimerSent_0to5->MODE    = TIMER_MODE_MODE_Timer;
       pTimerSent_0to5->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
       pTimerSent_6to9->MODE    = TIMER_MODE_MODE_Timer;
       pTimerSent_6to9->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
       // 1us timer period
       pTimerSent_0to5->PRESCALER = 0; // 16/2^4 -> 1MHz, 16/2^8->62.5KHz or 16uSec tick
       pTimerSent_6to9->PRESCALER = 0; // 16/2^4 -> 1MHz, 16/2^8->62.5KHz or 16uSec tick
       // Now clear timer registers
       pTimerSent_0to5->TASKS_CLEAR = 1;
       pTimerSent_6to9->TASKS_CLEAR = 1;
    
       // 32-bit counters
       pCounterSent_0to5->MODE    = TIMER_MODE_MODE_Counter;
       pCounterSent_0to5->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
       pCounterSent_6to9->MODE    = TIMER_MODE_MODE_Counter;
       pCounterSent_6to9->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
       // Prescaler not used in counter mode
       pCounterSent_0to5->PRESCALER = 0;
       pCounterSent_6to9->PRESCALER = 0;
    
       // Configure input event for count on falling edge
       NRF_GPIOTE->CONFIG[GPIOTE_SENT_INPUT] = GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos     |
                                               GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos |
                                               PIN_SENT_INPUT                << GPIOTE_CONFIG_PSEL_Pos     |
                                               GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos;
       // Configure test output task pin for use with PWM
       NRF_GPIOTE->CONFIG[GPIOTE_SENT_TEST]  = GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos     |
                                               GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos |
                                               PIN_SENT_TEST                 << GPIOTE_CONFIG_PSEL_Pos     |
                                               GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos;
    
       // Count on falling edge of pulse via PPI
       NRF_PPI->CH[PPI_SENT_INPUT].EEP   = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_SENT_INPUT];
       NRF_PPI->CH[PPI_SENT_INPUT].TEP   = (uint32_t)&pCounterSent_0to5->TASKS_COUNT;
       NRF_PPI->FORK[PPI_SENT_INPUT].TEP = (uint32_t)&pCounterSent_6to9->TASKS_COUNT;
       // Start timers on falling edge of pulse via PPI
       NRF_PPI->CH[PPI_SENT_START].EEP   = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_SENT_INPUT];
       NRF_PPI->CH[PPI_SENT_START].TEP   = (uint32_t)&pTimerSent_0to5->TASKS_START;
       NRF_PPI->FORK[PPI_SENT_START].TEP = (uint32_t)&pTimerSent_6to9->TASKS_START;
    
       // Capture elapsed time on falling edge of compare count match via PPI
       for (uint32_t CC_Index=0, PPI_Index=PPI_SENT_SYNC; CC_Index<6; CC_Index++, PPI_Index++)
       {
          // Clear edge time compare events
          pTimerSent_0to5->CC[CC_Index] = 0;
          // Set compare edge count events
          pCounterSent_0to5->CC[CC_Index] = CC_Index+1;
          // Link edge event to time capture task
          NRF_PPI->CH[PPI_Index].EEP = (uint32_t)&pCounterSent_0to5->EVENTS_COMPARE[CC_Index];  // Event
          NRF_PPI->CH[PPI_Index].TEP = (uint32_t)&pTimerSent_0to5->TASKS_CAPTURE[CC_Index]; // Task
       }
    
       // Capture elapsed time on falling edge of compare count match via PPI
       for (uint32_t CC_Index=0, PPI_Index=PPI_SENT_SYNC; CC_Index<4; CC_Index++, PPI_Index++)
       {
          // Clear edge time compare events
          pTimerSent_6to9->CC[CC_Index] = 0;
          // Set compare edge count events
          pCounterSent_6to9->CC[CC_Index] = CC_Index+1+6;
          // Link edge event to time capture task
          NRF_PPI->CH[PPI_Index+6].EEP = (uint32_t)&pCounterSent_6to9->EVENTS_COMPARE[CC_Index];  // Event
          NRF_PPI->CH[PPI_Index+6].TEP = (uint32_t)&pTimerSent_6to9->TASKS_CAPTURE[CC_Index]; // Task
      }
    
       // Enable PPI channels
       NRF_GPIOTE->EVENTS_PORT = 0;
       NRF_PPI->CHENSET = (1UL << PPI_SENT_SYNC    ) |
                          (1UL << PPI_SENT_STATUS  ) |
                          (1UL << PPI_SENT_DN1     ) |
                          (1UL << PPI_SENT_DN2     ) |
                          (1UL << PPI_SENT_DN3     ) |
                          (1UL << PPI_SENT_DN4     ) |
                          (1UL << PPI_SENT_DN5     ) |
                          (1UL << PPI_SENT_DN6     ) |
                          (1UL << PPI_SENT_CRC     ) |
                          (1UL << PPI_SENT_PAUSE   ) |
                          (1UL << PPI_SENT_INPUT   ) |
                          (1UL << PPI_SENT_TEST_HI ) |
                          (1UL << PPI_SENT_TEST_LO ) |
                          (1UL << PPI_SENT_START);;
    
       // Now clear counter registers and start counters
       pCounterSent_0to5->TASKS_CLEAR = 1;
       pCounterSent_6to9->TASKS_CLEAR = 1;
       pCounterSent_0to5->TASKS_START = 1;
       pCounterSent_6to9->TASKS_START = 1;
       // Uart test data at 230400 baud
       uint8_t SentTestPacket[] = {0xFF,0xB7,0xEF,0xBB, 0x7F};
       //
       // 1              2    3  4      5     6        7   8   9     10       11     <== Falling edge number
       // !              !    !  |      !     !        !   !   !      !        !     <== Negative-going edges
       // |-----0x00-||  |-----0x24-||  |-----0x88-||  |-----0x23-||  |-----0x40-|   <== uart 8-bit data
       // 0 11111111 11  0 11101101 11  0 11110111 11  0 11011101 11  0 11111110 1   <== uart 10-bit data with start & stop, LSB first
       //               11    4  3      3     5        5   3   4      3        8     <== uart bit count between edges with start & stop
       //              691  276 207   207   345      345 207 276    207      552     <== Received counts between edges
       //               56   22  16    16    27       27  16  22     16       44     <== Ticks Synch=56, data-12 to 27
       //                     A   4     4     F        F   4   A      4              <== SENT decoded 8-bit data 0x0-0xF
       //                     |   |     |     |        |   |   |      |
       //                     |   |     |     |        |   |   |      +------------  CRC
       //                     |   |     |     |        |   |   +-------------------  DN5
       //                     |   |     |     |        |   +-----------------------  DN5
       //                     |   |     |     |        +---------------------------  DN4
       //                     |   |     |     +------------------------------------  DN3
       //                     |   |     +------------------------------------------  DN2
       //                     |   +------------------------------------------------  DN1
       //                     +----------------------------------------------------  STATUS
       // Decodes as SENT data A44FF4A4
       //
       uartSend(SentTestPacket, sizeof(SentTestPacket));
    
       // Sync is 56 Ticks, nibbles are between 12-27 Ticks
       const uint32_t SyncTicks = 56;
       uint32_t PulseWidth[10];
       uint32_t SyncWidth = pTimerSent_0to5->CC[0];
       PulseWidth[0] = pTimerSent_0to5->CC[0];
       PulseWidth[1] = pTimerSent_0to5->CC[1] - pTimerSent_0to5->CC[0];
       PulseWidth[2] = pTimerSent_0to5->CC[2] - pTimerSent_0to5->CC[1];
       PulseWidth[3] = pTimerSent_0to5->CC[3] - pTimerSent_0to5->CC[2];
       PulseWidth[4] = pTimerSent_0to5->CC[4] - pTimerSent_0to5->CC[3];
       PulseWidth[5] = pTimerSent_0to5->CC[5] - pTimerSent_0to5->CC[4];
       PulseWidth[6] = pTimerSent_6to9->CC[0] - pTimerSent_0to5->CC[5];
       PulseWidth[7] = pTimerSent_6to9->CC[1] - pTimerSent_6to9->CC[0];
       PulseWidth[8] = pTimerSent_6to9->CC[2] - pTimerSent_6to9->CC[1];
       PulseWidth[9] = pTimerSent_6to9->CC[3] - pTimerSent_6to9->CC[2];
    
       uint8_t Nibbles[9];
       Nibbles[0] = (uint8_t)(((PulseWidth[1] * SyncTicks)) / SyncWidth) - 12;
       Nibbles[1] = (uint8_t)(((PulseWidth[2] * SyncTicks)) / SyncWidth) - 12;
       Nibbles[2] = (uint8_t)(((PulseWidth[3] * SyncTicks)) / SyncWidth) - 12;
       Nibbles[3] = (uint8_t)(((PulseWidth[4] * SyncTicks)) / SyncWidth) - 12;
       Nibbles[4] = (uint8_t)(((PulseWidth[5] * SyncTicks)) / SyncWidth) - 12;
       Nibbles[5] = (uint8_t)(((PulseWidth[6] * SyncTicks)) / SyncWidth) - 12;
       Nibbles[6] = (uint8_t)(((PulseWidth[7] * SyncTicks)) / SyncWidth) - 12;
       Nibbles[7] = (uint8_t)(((PulseWidth[8] * SyncTicks)) / SyncWidth) - 12;
       Nibbles[8] = (uint8_t)(((PulseWidth[9] * SyncTicks)) / SyncWidth) - 12;
       // 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
       pCounterSent_0to5->TASKS_SHUTDOWN = 1;
       pCounterSent_6to9->TASKS_SHUTDOWN = 1;
       pTimerSent_0to5->TASKS_SHUTDOWN = 1;
       pTimerSent_6to9->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
       __DSB();
       while(1)
       {
       }
    }

  • Hi,

    Thank you, thank you so much its really helpful, i am gonna test it,  also one more thing to add the output digital SENT signal is of 5v, but the default nrf gpio sense is 1.8v, so to make it compatible i am gonna use a level shifter to make the output signal of 1.8v.

  • I forgot the nRF9151 has only 3 Timers, so here's the algorithm for both the 4-Timer and 2-Timer versions. I'll update the code I posted  also to use edges 2-11 rather than 1-10; the first edge can then be used to start the timers and counters via PPI which gives much better results.

    Edit: I updated the code to start the timers on the first falling edge; this is the test data result using 8-bit (10-bit with start and stop bits) uart data at 230400 baud:

    // UART test data at 230400 baud
    {0xFF,0xB7,0xEF,0xBB, 0x7F} Decodes as SENT data A44FF4A4

       // Uart test data at 230400 baud
       uint8_t SentTestPacket[] = {0xFF,0xB7,0xEF,0xBB, 0x7F};
       //
       // 1              2    3  4      5     6        7   8   9     10       11     <== Falling edge number
       // !              !    !  |      !     !        !   !   !      !        !     <== Negative-going edges
       // |-----0x00-||  |-----0x24-||  |-----0x88-||  |-----0x23-||  |-----0x40-|   <== uart 8-bit data
       // 0 11111111 11  0 11101101 11  0 11110111 11  0 11011101 11  0 11111110 1   <== uart 10-bit data with start & stop, LSB first
       //               11    4  3      3     5        5   3   4      3        8     <== uart bit count between edges with start & stop
       //              691  276 207   207   345      345 207 276    207      552     <== Received counts between edges
       //               56   22  16    16    27       27  16  22     16       44     <== Ticks Synch=56, data-12 to 27
       //                     A   4     4     F        F   4   A      4              <== SENT decoded 8-bit data 0x0-0xF
       //                     |   |     |     |        |   |   |      |
       //                     |   |     |     |        |   |   |      +------------  CRC
       //                     |   |     |     |        |   |   +-------------------  DN5
       //                     |   |     |     |        |   +-----------------------  DN5
       //                     |   |     |     |        +---------------------------  DN4
       //                     |   |     |     +------------------------------------  DN3
       //                     |   |     +------------------------------------------  DN2
       //                     |   +------------------------------------------------  DN1
       //                     +----------------------------------------------------  STATUS
       // Decodes as SENT data A44FF4A4


    Version using just 2 peripheral Timers for nRF9151:

    // Version using just 2 peripheral Timers for nRF9151
    //
    //             |--------Fast Channel Data--------|       Optional
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE       Description
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========       ===========================
    //     +--------------------------------------------------------------  Edge  2: Sync
    //     |     +--------------------------------------------------------  Edge  3: Status
    //     |     |     +--------------------------------------------------  Edge  4: DN1   Interrupt
    //     |     |     |     +--------------------------------------------  Edge  5: DN2
    //     |     |     |     |     +--------------------------------------  Edge  6: DN3
    //     |     |     |     |     |     +--------------------------------  Edge  7: DN4   Interrupt,change CC
    //     |     |     |     |     |     |     +--------------------------  Edge  8: DN5
    //     |     |     |     |     |     |     |     +--------------------  Edge  9: DN6
    //     |     |     |     |     |     |     |     |     +--------------  Edge 10: CRC   Interrupt
    //     |     |     |     |     |     |     |     |     |        +-----  Edge 11: PAUSE (Optional)
    //     |     |     |     |     |     |     |     |     |        |
    // T2[0] T2[1] T2[2] T2[3] T2[4] T2[5] T2[0] T2[1] T2[2]    T2[3]  <== Timer Capture CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Counter compare triggers Timer capture
    //     |     |     |     |     |     |     |     |     |        |
    // C3[0] C3[1] C3[2] C3[3] C3[4] C3[5] C3[0] C3[1] C3[2]    C3[3]  <== Counter Compare CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Falling edge triggers counter increment
    //     |     |     |     |     |     |     |     |     |        |
    // ----+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+    +---+
    //     | ||||| ||||| ||||| ||||| ||||| ||||| ||||| |||||    |||||  <== Nibble Framing
    //     +-+   +-+   +-+   +-+   +-+   +-+   +-+   +-+   +----+   +-
    //     |     |     |     |     |     |     |     |     |        |
    //    56 12-27 12-27 12-27 12-27 12-27 12-27 12-27 12-27 (12-768)  <== SENT numer of Ticks range
    //     2     3     4     5     6     7     8     9    10     (11)  <== SENT Falling Edge Number
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE
    //             |--------Fast Channel Data--------|       Optional
    

    Version using 4 peripheral Timers (most efficient) for nRF52 and nRF54:

    // Version using 4 peripheral Timers (most efficient) for nRF52 and nRF54:
    //
    //             |--------Fast Channel Data--------|       Optional
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE       Description
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========       ===========================
    //     +--------------------------------------------------------------  Edge  2: Sync
    //     |     +--------------------------------------------------------  Edge  3: Status
    //     |     |     +--------------------------------------------------  Edge  4: DN1
    //     |     |     |     +--------------------------------------------  Edge  5: DN2
    //     |     |     |     |     +--------------------------------------  Edge  6: DN3
    //     |     |     |     |     |     +--------------------------------  Edge  7: DN4
    //     |     |     |     |     |     |     +--------------------------  Edge  8: DN5
    //     |     |     |     |     |     |     |     +--------------------  Edge  9: DN6
    //     |     |     |     |     |     |     |     |     +--------------  Edge 10: CRC      Interrupt
    //     |     |     |     |     |     |     |     |     |        +-----  Edge 11: PAUSE (Optional)
    //     |     |     |     |     |     |     |     |     |        |
    // T4[0] T4[1] T4[2] T4[3] T4[4] T4[5] T1[0] T1[1] T1[2]    T1[3]  <== Timer Capture CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Counter compare triggers Timer capture
    //     |     |     |     |     |     |     |     |     |        |
    // C3[0] C3[1] C3[2] C3[3] C3[4] C3[5] C2[0] C2[1] C2[2]    C2[3]  <== Counter Compare CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Falling edge triggers counter increment
    //     |     |     |     |     |     |     |     |     |        |
    // ----+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+    +---+
    //     | ||||| ||||| ||||| ||||| ||||| ||||| ||||| |||||    |||||  <== Nibble Framing
    //     +-+   +-+   +-+   +-+   +-+   +-+   +-+   +-+   +----+   +-
    //     |     |     |     |     |     |     |     |     |        |
    //    56 12-27 12-27 12-27 12-27 12-27 12-27 12-27 12-27 (12-768)  <== SENT numer of Ticks range
    //     2     3     4     5     6     7     8     9    10     (11)  <== SENT Falling Edge Number
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE
    //             |--------Fast Channel Data--------|       Optional
    

  • I wrote a 2-timer version, one timer counts SENT falling edges and the other counts time between the edges measured in 16MHz clock cycles. It works as well as the 4-timer version, tested with a UART to spoof the Bosch sensor.

    Algorithm:

    // Version using just 2 peripheral Timers for nRF52, nRF54 and nRF9151
    //
    //             |--------Fast Channel Data--------|       Optional
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE  CC Description     Read Ts Set Cs
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========  == =============== ======= ======
    //     +----------------------------------------------------------- 0 Edge  2: Sync
    //     |     +----------------------------------------------------- 1 Edge  3: Status
    //     |     |     +----------------------------------------------- 2 Edge  4: DN1
    //     |     |     |     +----------------------------------------- 3 Edge  5: DN2    0,1,2,3 0,1,2,3
    //     |     |     |     |     +----------------------------------- 4 Edge  6: DN3
    //     |     |     |     |     |     +----------------------------- 5 Edge  7: DN4    4,5
    //     |     |     |     |     |     |     +----------------------- 0 Edge  8: DN5
    //     |     |     |     |     |     |     |     +----------------- 1 Edge  9: DN6
    //     |     |     |     |     |     |     |     |     +----------- 2 Edge 10: CRC
    //     |     |     |     |     |     |     |     |     |        +-- 3 Edge 11: PAUSE  6,7,8,9
    //     |     |     |     |     |     |     |     |     |        |
    // T2[0] T2[1] T2[2] T2[3] T2[4] T2[5] T2[0] T2[1] T2[2]    T2[3]  <== Timer Capture CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Counter compare triggers Timer capture
    //     |     |     |     |     |     |     |     |     |        |
    // C3[0] C3[1] C3[2] C3[3] C3[4] C3[5] C3[0] C3[1] C3[2]    C3[3]  <== Counter Compare CC register Id
    //     ^     ^     ^     ^     ^     ^     ^     ^     ^        ^
    //     |     |     |     |     |     |     |     |     |        |  <== Falling edge triggers counter increment
    //     |     |     |     |     |     |     |     |     |        |
    // ----+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+    +---+
    //     | ||||| ||||| ||||| ||||| ||||| ||||| ||||| |||||    |||||  <== Nibble Framing
    //     +-+   +-+   +-+   +-+   +-+   +-+   +-+   +-+   +----+   +-
    //     |     |     |     |     |     |     |     |     |        |
    //    56 12-27 12-27 12-27 12-27 12-27 12-27 12-27 12-27 (12-768)  <== SENT numer of Ticks range
    //     2     3     4     5     6     7     8     9    10     (11)  <== SENT Falling Edge Number
    // ====  ===== ===== ===== ===== ===== ===== ===== ===== ========
    // Sync  Stat    DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE
    //             |--------Fast Channel Data--------|       Optional
    

    Code:

    // SENT protocol
    // SENT tick ranges from 3us to about 90us, with clock-rate tolerances as high as +-25%. Each
    // nibble begins with a 5us logic low followed by a variable-width logic-high pulse
    //
    // A SENT message frame includes a sync signal followed by eight nibbles and an optional pause,
    // the latter to make up fixed-length messages
    // A nibble ranges from 12 to 27 ticks (representing 0x0 to 0xF), optional Pause is 12-768 ticks
    
    // PPI channels 0-19 are programmable, channels 20-31 are fixed
    #define PPI_SENT_SYNC      0 // Sync
    #define PPI_SENT_STATUS    1 // Status
    #define PPI_SENT_DN1       2 // DN1
    #define PPI_SENT_DN2       3 // DN2
    #define PPI_SENT_DN3       4 // DN3
    #define PPI_SENT_DN4       5 // DN4
    #define PPI_SENT_DN5       6 // DN5
    #define PPI_SENT_DN6       7 // DN6
    #define PPI_SENT_CRC       8 // CRC
    #define PPI_SENT_PAUSE     9 // PAUSE
    #define PPI_SENT_INPUT    10 // Input SENT signal
    #define PPI_SENT_TEST_HI  11 // Test output SENT signal high level
    #define PPI_SENT_TEST_LO  12 // Test output SENT signal low level
    #define PPI_SENT_START    13 // Use Input SENT signal to start timers
    
    // 8 GPIOTE channels available
    #define GPIOTE_SENT_INPUT 0 // Input SENT signal
    #define GPIOTE_SENT_TEST  1 // Test output SENT signal
    
    // Port pins to test SENT using PWM on output pin connected to SENT input pin
    #define PIN_SENT_INPUT    3 // Input SENT signal
    #define PIN_SENT_TEST     4 // Test output SENT signal, connect to Input SENT signal
    
    // TIMER0 is used by the RADIO, so don't use for now
    #define SENT_TIMER_IRQn TIMER3_IRQn
    static NRF_TIMER_Type * const pTimerSent   = NRF_TIMER4; // 6 x CC
    static NRF_TIMER_Type * const pCounterSent = NRF_TIMER3; // 6 x CC
    // Sync is 56 Ticks, nibbles are between 12-27 Ticks
    static const uint32_t SyncTicks  = 56;
    static const uint32_t Data0Ticks = 12;
    static uint32_t PulseWidth[10];
    static volatile uint32_t PulseTimes[10];
    
    static void SentRxProtocolT2(void)
    {
       NRF_P0->OUTSET = (1 << PIN_SENT_INPUT);
       NRF_P0->OUTSET = (1 << PIN_SENT_TEST);
       // Configuration                       Direction    Input            Pullup         Drive Level      Sense Level
       // ================================    ==========   ==============   ============   ==============   =============
       NRF_P0->PIN_CNF[PIN_SENT_TEST]      = (PIN_OUTPUT | PIN_CONNECT    | PIN_PULLNONE | PIN_DRIVE_S0S1 | PIN_SENSE_OFF);
       NRF_P0->PIN_CNF[PIN_SENT_INPUT]     = (PIN_INPUT  | PIN_CONNECT    | PIN_PULLUP   | PIN_DRIVE_S0S1 | PIN_SENSE_LOW);
    
       // 32-bit timers
       pTimerSent->MODE    = TIMER_MODE_MODE_Timer;
       pTimerSent->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
       // 1us timer period
       pTimerSent->PRESCALER = 0; // 16MHz timer clock
       // Now clear timer registers
       pTimerSent->TASKS_CLEAR = 1;
    
       // 32-bit counters
       pCounterSent->MODE    = TIMER_MODE_MODE_Counter;
       pCounterSent->BITMODE = TIMER_BITMODE_BITMODE_32Bit << TIMER_BITMODE_BITMODE_Pos;
       // Prescaler not used in counter mode
       pCounterSent->PRESCALER = 0;
    
       // Enable IRQ on EVENTS_COMPARE[] 3 and 5
       pCounterSent->INTENSET = (TIMER_INTENSET_COMPARE3_Enabled << TIMER_INTENSET_COMPARE3_Pos) |
                                (TIMER_INTENSET_COMPARE5_Enabled << TIMER_INTENSET_COMPARE5_Pos);
    
       // Clear the counter when COMPARE5 event is triggered - doesn't work, so clear in interrupt
       //pCounterSent->SHORTS = (TIMER_SHORTS_COMPARE5_CLEAR_Enabled << TIMER_SHORTS_COMPARE5_CLEAR_Pos);
    
       // Set interrupt priority and enable interrupt
       NVIC_SetPriority(SENT_TIMER_IRQn, 6);
       NVIC_ClearPendingIRQ(SENT_TIMER_IRQn);
       //nrf_delay_us(100);
       NVIC_EnableIRQ(SENT_TIMER_IRQn);
    
       // Configure input event for count on falling edge
       NRF_GPIOTE->CONFIG[GPIOTE_SENT_INPUT] = GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos     |
                                               GPIOTE_CONFIG_POLARITY_HiToLo << GPIOTE_CONFIG_POLARITY_Pos |
                                               PIN_SENT_INPUT                << GPIOTE_CONFIG_PSEL_Pos     |
                                               GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos;
       // Configure test output task pin for use with PWM
       NRF_GPIOTE->CONFIG[GPIOTE_SENT_TEST]  = GPIOTE_CONFIG_MODE_Task       << GPIOTE_CONFIG_MODE_Pos     |
                                               GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos |
                                               PIN_SENT_TEST                 << GPIOTE_CONFIG_PSEL_Pos     |
                                               GPIOTE_CONFIG_OUTINIT_High    << GPIOTE_CONFIG_OUTINIT_Pos;
    
       // Count on falling edge of pulse via PPI
       NRF_PPI->CH[PPI_SENT_INPUT].EEP   = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_SENT_INPUT];
       NRF_PPI->CH[PPI_SENT_INPUT].TEP   = (uint32_t)&pCounterSent->TASKS_COUNT;
       // Start timers on falling edge of pulse via PPI
       NRF_PPI->CH[PPI_SENT_START].EEP   = (uint32_t)&NRF_GPIOTE->EVENTS_IN[GPIOTE_SENT_INPUT];
       NRF_PPI->CH[PPI_SENT_START].TEP   = (uint32_t)&pTimerSent->TASKS_START;
    
       // Capture elapsed time on falling edge of compare count match via PPI
       for (uint32_t CC_Index=0, PPI_Index=PPI_SENT_SYNC; CC_Index<6; CC_Index++, PPI_Index++)
       {
          // Clear edge time compare events
          pTimerSent->CC[CC_Index] = 0;
          // Set compare edge count events from 2 to 7
          pCounterSent->CC[CC_Index] = CC_Index+2;
          // Link edge event to time capture task
          NRF_PPI->CH[PPI_Index].EEP = (uint32_t)&pCounterSent->EVENTS_COMPARE[CC_Index]; // Event
          NRF_PPI->CH[PPI_Index].TEP = (uint32_t)&pTimerSent->TASKS_CAPTURE[CC_Index];    // Task
       }
    
       // Enable PPI channels
       NRF_GPIOTE->EVENTS_PORT = 0;
       NRF_PPI->CHENSET = (1UL << PPI_SENT_SYNC    ) | //  0
                          (1UL << PPI_SENT_STATUS  ) | //  1
                          (1UL << PPI_SENT_DN1     ) | //  2
                          (1UL << PPI_SENT_DN2     ) | //  3
                          (1UL << PPI_SENT_DN3     ) | //  4
                          (1UL << PPI_SENT_DN4     ) | //  5
                          (1UL << PPI_SENT_DN5     ) | //  6
                          (1UL << PPI_SENT_DN6     ) | //  7
                          (1UL << PPI_SENT_CRC     ) | //  8
                          (1UL << PPI_SENT_PAUSE   ) | //  9
                          (1UL << PPI_SENT_INPUT   ) | // 10
                          (1UL << PPI_SENT_TEST_HI ) | // 11
                          (1UL << PPI_SENT_TEST_LO ) | // 12
                          (1UL << PPI_SENT_START);;    // 13
    
       // Now clear counter registers and start counters
       pCounterSent->TASKS_CLEAR = 1;
       pCounterSent->TASKS_START = 1;
       // Uart test data at 230400 baud
       uint8_t SentTestPacket[] = {0xFF,0xB7,0xEF,0xBB, 0x7F};
       //
       // If single message, manually start counter before reception starts:
       //
       // 1              2    3  4      5     6        7   8   9     10       11     <== Falling edge number
       // 1              2    3  4      5     6        7   8   9     10       11     <== Falling edge count in pCounterSent
       // !              !    !  |      !     !        !   !   !      !        !     <== Negative-going edges
       // |-----0xFF-|   |-----0xB7-|   |-----0xEF-|   |-----0xBB-|   |-----0x7F-|   <== uart 8-bit data
       // 0 11111111 11  0 11101101 11  0 11110111 11  0 11011101 11  0 11111110 11  <== uart 10-bit data with start & stop, LSB first
       // |             11    4  3      3     5        5   3   4      3        8     <== uart bit count between edges with start & stop
       // |            691  276 207   207   345      345 207 276    207      552     <== Received 16MHz counts between edges
       // |             56   22  16    16    27       27  16  22     16       44     <== Ticks Synch=56, data-12 to 27
       // |            (2C)   A  4      4     F        F   4   A      4      (20)    <== SENT decoded 8-bit data 0x0-0xF
       // |              |    |  |      |     |        |   |   |      |        |
       // |              |    |  |      |     |        |   |   |      |        +---  PAUSE  Edge 11   Counter 11
       // |              |    |  |      |     |        |   |   |      +------------  CRC    Edge 10   Counter 10
       // |              |    |  |      |     |        |   |   +-------------------  DN5    Edge  9   Counter  9
       // |              |    |  |      |     |        |   +-----------------------  DN5    Edge  8   Counter  8
       // |              |    |  |      |     |        +---------------------------  DN4    Edge  7   Counter  7
       // |              |    |  |      |     +------------------------------------  DN3    Edge  6   Counter  6
       // |              |    |  |      +------------------------------------------  DN2    Edge  5   Counter  5
       // |              |    |  +-------------------------------------------------  DN1    Edge  4   Counter  4
       // |              |    +----------------------------------------------------  STATUS Edge  3   Counter  3
       // |              +---------------------------------------------------------  SYNC   Edge  2   Counter  2
       // +------------------------------------------------------------------------  START  Edge  1   Counter  1
       // Decodes as SENT data A44FF4A4
       //
       uartSend(SentTestPacket, sizeof(SentTestPacket));
    
       // Sync is 56 Ticks, nibbles are between 12-27 Ticks
       uint32_t SyncWidth = PulseTimes[0];
       PulseWidth[0] = PulseTimes[0];
       for (uint32_t i=1; i<sizeof(PulseTimes)/sizeof(PulseTimes[0]); i++)
       {
          PulseWidth[i] = PulseTimes[i] - PulseTimes[i-1];
       }
    
       // Calculate decoded SENT 4-bit nibbles
       uint8_t Nibbles[9];
       for (uint32_t i=0; i<sizeof(Nibbles)/sizeof(Nibbles[0]); i++)
       {
          Nibbles[i] = (uint8_t)(((PulseWidth[i+1] * SyncTicks)) / SyncWidth) - Data0Ticks;
       }
    
       // Stop before next repeat
       pCounterSent->TASKS_STOP = 1;
       pTimerSent->TASKS_STOP   = 1;
       __DSB();
    
       // 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
       pCounterSent->TASKS_SHUTDOWN = 1;
       pTimerSent->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
       __DSB();
       while(1)
       {
       }
    }
    
    // Counter CC0-3 will be used twice
    static volatile bool RepeatEdges = false;
    
    // This IRQ handler will trigger every 3rd and 5th SENT falling edge
    void TIMER3_IRQHandler(void)
    {
       // Every 3rd SENT negative-going edge
       if (pCounterSent->EVENTS_COMPARE[3] == 1)
       {
          pCounterSent->EVENTS_COMPARE[0] = 0;
          pCounterSent->EVENTS_COMPARE[1] = 0;
          pCounterSent->EVENTS_COMPARE[2] = 0;
          pCounterSent->EVENTS_COMPARE[3] = 0;
          if (!RepeatEdges)
          {
             RepeatEdges = true;
             PulseTimes[0] = pTimerSent->CC[0];
             PulseTimes[1] = pTimerSent->CC[1];
             PulseTimes[2] = pTimerSent->CC[2];
             PulseTimes[3] = pTimerSent->CC[3];
             // Set compare edge count events from 8 to 11
             pCounterSent->CC[0] =  8;
             pCounterSent->CC[1] =  9;
             pCounterSent->CC[2] = 10;
             pCounterSent->CC[3] = 11;
          }
          else
          {
             PulseTimes[6] = pTimerSent->CC[0];
             PulseTimes[7] = pTimerSent->CC[1];
             PulseTimes[8] = pTimerSent->CC[2];
             PulseTimes[9] = pTimerSent->CC[3];
          }
       }
       // Every 5th SENT negative-going edge
       if (pCounterSent->EVENTS_COMPARE[5] == 1)
       {
          pCounterSent->EVENTS_COMPARE[4] = 0;
          pCounterSent->EVENTS_COMPARE[5] = 0;
          PulseTimes[4] = pTimerSent->CC[4];
          PulseTimes[5] = pTimerSent->CC[5];
          // This works whereas SHORTS clear doesn't ..
          pCounterSent->TASKS_CLEAR;
       }
       __DSB();
    }

Related