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?

  • Couple of issue; first this test for messages does not work as it assumes the message ends on edge 7 instead of edge 9:

            // Wait for a complete SENT message
            while (ReceivingEdges_2to7) {
                __WFE();
            }
    

    Try replacing with:

            // Wait for a complete SENT message
            while (ReceivingEdges) {
                __WFE();
            }
    

    and add a new bool:

    // Counter CC0-5 will be used twice; edge 1 starts counter & timer, first pass uses edges 2 to 7, 2nd pass edges 8 to 10
    static volatile bool ReceivingEdges_2to7 = true;
    
    // Message is complete including CRC when starting next message search for START falling edge
    static volatile bool ReceivingEdges = true;
    
    use in 3 places:
    1: TIMER3_IRQHandler
       // Every 2nd SENT negative-going edge on 2nd pass, starts next message search for START falling edge
       if (pCounterSent->EVENTS_COMPARE[2] == 1)
       {
          // Events 0,1,2 triggered by SENT edge counter have now occurred
          pCounterSent->EVENTS_COMPARE[2] = 0;
          if (!ReceivingEdges_2to7)
          {
             ReceivingEdges_2to7 = true;
             // Message is complete including CRC when starting next message search for START falling edge
             ReceivingEdges = false;
    
    2: TIMER4_IRQHandler
          if (pTimerSent->CC[5] == mTimeoutTrip)
          {
             // Message is complete including CRC when starting next message search for START falling edge
             ReceivingEdges = false;
             // Timeout: Stop timer, clear counter
    
    3: main
            // Wait for a complete SENT message
            while (ReceivingEdges) {         <<==!
                __WFE();
            }
    
            // Process the received data
            process_sent_data();
    
            // Reset for next message
            ReceivingEdges = true;          <<==!

    Correct this typo:

    Typo: 2,3,5 not 2,5,5
       // Enable edge counter IRQ on EVENTS_COMPARE[] 3 and 5
       pCounterSent->INTENSET = (TIMER_INTENSET_COMPARE2_Enabled << TIMER_INTENSET_COMPARE2_Pos) | // 2nd pass 2 registers
                                (TIMER_INTENSET_COMPARE3_Enabled << TIMER_INTENSET_COMPARE3_Pos) | // 1st pass 6 registers
                                (TIMER_INTENSET_COMPARE5_Enabled << TIMER_INTENSET_COMPARE5_Pos);  // 1st pass 2 registers
    

    Do you have a copy of the full SENT standard? The Bosch sensor data refers to that without mentioning polarity:

    SENT standard (SENT Standard J2716 Jan 2010 [1]; SENT = Single Edge Nibble Transmission for Automotive Applications).

    Edit: I still think the Bosch data is inverted. You might find this study interesting: diva2:1162587.pdf

  • unfortunately not i don't have the SAEj2716 2010 standard they didn't provide us that. but i have the SAEj2716 2016 standard.

    here it is

    sent-single-edge-nibble-transmission-for-automotive-applications-j2716-2016-04_compress.pdf


    About the signal you are right its inverted and we are trying to communicate this to bosch, will let you know once we get a response.



  • #include <stdbool.h>
    #include <stdint.h>
    
    #include "nrf.h"
    #include "nordic_common.h"
    #include "boards.h"
    #include "nrf_delay.h"
    #include "nrfx_gpiote.h"
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    #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
    // Set end-of-message timeout to stop timer & edge counter
    #define SENT_MESSAGE_TIMEOUT    400 // 16MHz cycles after edge 9
    
    // TIMER0 is used by the RADIO, so don't use for now
    #define SENT_COUNTER_IRQn TIMER3_IRQn
    #define SENT_TIMER_IRQn   TIMER4_IRQn
    static NRF_TIMER_Type * const pCounterSent = NRF_TIMER3; // 6 x CC
    static NRF_TIMER_Type * const pTimerSent   = NRF_TIMER4; // 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];
    
    // Define two SENT test messages
    static uint8_t CorrectNibblesA[] = {0x0A,0x04,0x04,0x0F,0x0F,0x04,0x0A,0x04};
    static uint8_t CorrectNibblesB[] = {0x0A,0x04,0x04,0x0F,0x0F,0x04,0x04,0x0A};
    static uint8_t *pCorrectNibbles = CorrectNibblesA;
    
    // Calculate decoded SENT 4-bit nibbles
    static uint8_t Nibbles[sizeof(CorrectNibblesA)];
    static uint32_t MessageErrorCount = 0;
    
    
    static void SentRxInit(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);
    
       // Configuration for PIN_SENT_TEST (output)
        NRF_P0->PIN_CNF[PIN_SENT_TEST] = (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
                                     (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
                                     (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) |
                                     (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
                                     (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
    
        // Configuration for PIN_SENT_INPUT (input with pull-up)
        NRF_P0->PIN_CNF[PIN_SENT_INPUT] = (GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) |
                                      (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) |
                                      (GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) |
                                      (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) |
                                      (GPIO_PIN_CNF_SENSE_Low << GPIO_PIN_CNF_SENSE_Pos);
    
       // 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
    
       // 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 edge counter IRQ on EVENTS_COMPARE[] 3 and 5
       pCounterSent->INTENSET = (TIMER_INTENSET_COMPARE2_Enabled << TIMER_INTENSET_COMPARE2_Pos) | // 2nd pass 2 registers
                                (TIMER_INTENSET_COMPARE3_Enabled << TIMER_INTENSET_COMPARE3_Pos) | // 1st pass 6 registers
                                (TIMER_INTENSET_COMPARE5_Enabled << TIMER_INTENSET_COMPARE5_Pos);  // 1st pass 2 registers
    
       // Enable edge timer IRQ on EVENTS_COMPARE[5] to act as timeout value
       pTimerSent->INTENSET = (TIMER_INTENSET_COMPARE5_Enabled << TIMER_INTENSET_COMPARE5_Pos);
    
       // Clear the counter when COMPARE5 event is triggered - doesn't work, loses capture due to 1x16MHz
       //   clock delay for PPI, so clear in interrupt
       //pCounterSent->SHORTS = (TIMER_SHORTS_COMPARE5_CLEAR_Enabled << TIMER_SHORTS_COMPARE5_CLEAR_Pos);
    
       // Set interrupt priority and enable interrupt for SENT edge counter
       NVIC_SetPriority(SENT_COUNTER_IRQn, 6);
       NVIC_ClearPendingIRQ(SENT_COUNTER_IRQn);
       __DSB();
       NVIC_EnableIRQ(SENT_COUNTER_IRQn);
    
       // Set interrupt priority and enable interrupt for SENT edge timer
       NVIC_SetPriority(SENT_TIMER_IRQn, 6);
       NVIC_ClearPendingIRQ(SENT_TIMER_IRQn);
       __DSB();
       NVIC_EnableIRQ(SENT_TIMER_IRQn);
    
       // Configure input event for count on rising edge
       NRF_GPIOTE->CONFIG[GPIOTE_SENT_INPUT] = GPIOTE_CONFIG_MODE_Event      << GPIOTE_CONFIG_MODE_Pos     |
                                               GPIOTE_CONFIG_POLARITY_LoToHi << GPIOTE_CONFIG_POLARITY_Pos |
                                               PIN_SENT_INPUT                << GPIOTE_CONFIG_PSEL_Pos     |
                                               GPIOTE_CONFIG_OUTINIT_Low     << GPIOTE_CONFIG_OUTINIT_Pos;
       // Configure test output task pin for use with PWM or UART
       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; repeated TASKS_START command is benign
       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;
       NRF_PPI->FORK[PPI_SENT_START].TEP = (uint32_t)&pCounterSent->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 1 to 6, repeated edge counts should also be 1 to 6
          pCounterSent->CC[CC_Index] = CC_Index+1;
          // 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 and timer registers and start counters
       pCounterSent->TASKS_CLEAR = 1;
       pTimerSent->TASKS_CLEAR   = 1;
       //pCounterSent->TASKS_START = 1;
    }
    
    // Counter CC0-5 will be used twice; edge 1 starts counter & timer, first pass uses edges 2 to 7, 2nd pass edges 8 to 10
    static volatile bool ReceivingEdges_2to7 = true;
    
    // Message is complete including CRC when starting next message search for START falling edge
    static volatile bool ReceivingEdges = true;
    
    // Set up timer CC[5] for end-of-message timeout to stop timer & edge counter, also counter
    static volatile uint32_t mTimeoutTrip = 400;
    
    void TIMER3_IRQHandler(void)
    {
       // Every 3rd SENT negative-going edge on first pass
       if (pCounterSent->EVENTS_COMPARE[3] == 1)
       {
          // Events 0,1,2,3 triggered by SENT edge counter have now occurred
          pCounterSent->EVENTS_COMPARE[0] = 0;
          pCounterSent->EVENTS_COMPARE[1] = 0;
          pCounterSent->EVENTS_COMPARE[2] = 0;
          pCounterSent->EVENTS_COMPARE[3] = 0;
          // CC[3] is only reached for edges 2 to 7, as edge 10 is actually edge 1 in the next message
          if (ReceivingEdges_2to7)
          {
             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] =  7;
             pCounterSent->CC[1] =  8;
             pCounterSent->CC[2] =  9;
             pCounterSent->CC[3] = 10;
             pTimerSent->CC[0] = 0;
             pTimerSent->CC[1] = 0;
             pTimerSent->CC[2] = 0;
             pTimerSent->CC[3] = 0;
          }
       }
       // Every 5th SENT negative-going edge, both 4,5 have occurred
       if (pCounterSent->EVENTS_COMPARE[5] == 1)
       {
          ReceivingEdges_2to7 = false;
          pCounterSent->EVENTS_COMPARE[4] = 0;
          pCounterSent->EVENTS_COMPARE[5] = 0;
          PulseTimes[4] = pTimerSent->CC[4];
          PulseTimes[5] = pTimerSent->CC[5];
          // Set up next timer CC[5] for end-of-message timeout to stop timer & edge counter, also counter
          mTimeoutTrip = pTimerSent->CC[2] + SENT_MESSAGE_TIMEOUT;
          pTimerSent->CC[5] = mTimeoutTrip;
       }
       // Every 2nd SENT negative-going edge on 2nd pass, starts next message search for START falling edge
       if (pCounterSent->EVENTS_COMPARE[2] == 1)
       {
          // Events 0,1,2 triggered by SENT edge counter have now occurred
          pCounterSent->EVENTS_COMPARE[2] = 0;
          if (!ReceivingEdges_2to7)
          {
             ReceivingEdges_2to7 = true;
             // Message is complete including CRC when starting next message search for START falling edge
             ReceivingEdges = false;
             PulseTimes[6] = pTimerSent->CC[0];
             PulseTimes[7] = pTimerSent->CC[1];
             PulseTimes[8] = pTimerSent->CC[2];
             // CC[3] is only reached for edges 2 to 7, as edge 10 is actually edge 1 in the next message
             //PulseTimes[9] = pTimerSent->CC[3];
             pCounterSent->CC[0] =  1;
             pCounterSent->CC[1] =  2;
             pCounterSent->CC[2] =  3;
             pCounterSent->CC[3] =  4;
             pCounterSent->CC[4] =  5;
             pCounterSent->CC[5] =  6;
             //pTimerSent->TASKS_CLEAR = 1;
             pTimerSent->CC[0] = 0;
             pTimerSent->CC[1] = 0;
             pTimerSent->CC[2] = 0;
             pTimerSent->CC[3] = 0;
             pTimerSent->CC[4] = 0;
             // Edge 10 is actually edge 1 in the next message, start search for next edge which is message START
             pCounterSent->TASKS_STOP = 1;
             __DSB();
             pCounterSent->TASKS_CLEAR = 1;
             // Also stop timer as we don't require timeout protection after entire message received
             pTimerSent->TASKS_STOP = 1;
             __DSB();
             pTimerSent->TASKS_CLEAR = 1;
         }
       }
       __DSB();
    }
    
    void TIMER4_IRQHandler(void)
    {
       // Use CC[5] as a fall-back timeout for stop and wait for next message
       if (pTimerSent->EVENTS_COMPARE[5] == 1)
       {
          pTimerSent->EVENTS_COMPARE[5] = 0;
          if (pTimerSent->CC[5] == mTimeoutTrip)
          {
             // Message is complete including CRC when starting next message search for START falling edge
             ReceivingEdges = false;
             // Timeout: Stop timer, clear counter
             pTimerSent->TASKS_STOP    = 1;
             pTimerSent->TASKS_CLEAR   = 1;
             pCounterSent->TASKS_STOP  = 1;
             pCounterSent->TASKS_CLEAR = 1;
             // Clear the timeout register ready for next (5+1)th edge capture
             pTimerSent->CC[5] = 0;
          }
       }
    }
    
    static void log_init(void)
    {
        ret_code_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    }
    
    static void process_sent_data(void)
    {
        // Calculate pulse widths
        for (int i = 0; i < 9; i++) {
            PulseWidth[i] = PulseTimes[i+1] - PulseTimes[i];
        }
    
        // Log the pulse widths
        NRF_LOG_INFO("Pulse Widths:");
        for (int i = 0; i < 9; i++) {
            NRF_LOG_INFO("Pulse %d: %d ticks", i, PulseWidth[i]);
        }
    
        NRF_LOG_FLUSH();
    }
    
    int main(void)
    {
        log_init();
        SentRxInit();
    
        NRF_LOG_INFO("SENT Signal Measurement Started");
        NRF_LOG_FLUSH();
    
        while (true)
        {
            // Wait for a complete SENT message
            while (ReceivingEdges) {
                __WFE();
            }
    
            // Process the received data
            process_sent_data();
    
            // Reset for next message
            ReceivingEdges = true; 
    
            nrf_delay_ms(10);
        }
    }



    is this code okay for rising to rising edge detection and counting?

    the results are some how i think okay it all depends on now the tick time use by the sensor module which i am not sure if its really 3us, as in the SENT standard this tick can be from 3us to 90us so the reason i am getting all these values high could be that the tick time is greater then 3us.


    but then on the other hand the signal receive through logic analyzer is somehow okay with just the issue of its inverted so its really confusing whats happening.....

  • The code looks ok, and the auto-scale of the clock using the SYNC value will allow the use of any tick time. Try adding some code to convert edge times to actual data values:

          // Sync is 56 Ticks, nibbles are between 12-27 Ticks
          uint32_t SyncWidth = PulseTimes[0];
          PulseWidth[0] = PulseTimes[0];
          // CC[3] is only reached for edges 2 to 7, as edge 10 is actually edge 1 in the
          // next message; ie PulseTimes[9], PulseWidth[9] and Nibbles[9] PAUSE width are not reported
          for (uint32_t i=1; i<sizeof(PulseTimes)/sizeof(PulseTimes[0])-1; i++)
          {
             PulseWidth[i] = PulseTimes[i] - PulseTimes[i-1];
          }
    
          // Calculate decoded SENT 4-bit nibbles
          for (uint32_t i=0; i<sizeof(Nibbles)/sizeof(Nibbles[0]); i++)
          {
             Nibbles[i] = (uint8_t)(((PulseWidth[i+1] * SyncTicks)) / SyncWidth) - Data0Ticks;
             if (Nibbles[i] != pCorrectNibbles[i])
             {
                MessageErrorCount++;
             }
          }
          // Can use a signal to indicate when data is ready for each message
          ReceivingEdges = true;
    

    Running test code over 10,000 messages spoofing with a uart I get no errors. One difference with a real sensor is the sensor starts asynchronously and some method of aligning reception is required, either a timeout after a period of no signal or simple search for the correct-width sync pulse.

    I found some arduino code here, haven't had time to look at it BlinxFox/sent

Related