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
  • #include <zephyr/kernel.h>
    #include <zephyr/device.h>
    #include <nrfx_gpiote.h>
    #include <nrfx_timer.h>
    #include <nrfx_ppi.h>
    #include <zephyr/sys/printk.h>

    #define SENT_GPIO_PIN 11 // Replace with your actual GPIO pin number

    nrfx_timer_t timer = NRFX_TIMER_INSTANCE(0);
    static uint32_t pulse_buffer[64];
    static uint8_t pulse_index = 0;
    static uint32_t last_capture = 0;
    static uint8_t last_printed_index = 0;

    void gpio_handler(nrfx_gpiote_pin_t pin, nrfx_gpiote_trigger_t trigger, void *p_context)
    {
    uint32_t capture_value = nrf_timer_cc_get(timer.p_reg, NRF_TIMER_CC_CHANNEL0);
    uint32_t pulse_width = capture_value - last_capture;
    last_capture = capture_value;
    pulse_buffer[pulse_index] = pulse_width;
    pulse_index = (pulse_index + 1) % 64;
    }

    void timer_handler(nrf_timer_event_t event_type, void *p_context)
    {
    }

    int main()
    {
    printk("Starting pressure sensor example\n\r");
    int err_code;
    if (!nrfx_gpiote_is_init())
    {
    err_code = nrfx_gpiote_init(0);
    }
    uint8_t gpiote_channel_index;
    err_code = nrfx_gpiote_channel_alloc(&gpiote_channel_index);
    if (err_code != NRFX_SUCCESS)
    {
    printk("Failed to allocate GPIOTE channel. Error code: %d\n", err_code);
    return -1;
    }

    nrfx_gpiote_input_config_t input_config = {
    .pull = NRF_GPIO_PIN_PULLUP};

    nrfx_gpiote_trigger_config_t trigger_config = {
    .trigger = NRFX_GPIOTE_TRIGGER_HITOLO,
    .p_in_channel = &gpiote_channel_index};

    nrfx_gpiote_handler_config_t handler_config = {
    .handler = gpio_handler,
    .p_context = NULL};

    err_code = nrfx_gpiote_input_configure(SENT_GPIO_PIN, &input_config, &trigger_config, &handler_config);
    if (err_code != NRFX_SUCCESS)
    {
    printk("GPIOTE input configuration failed with error code: %d\n", err_code);
    return -1;
    }
    printk("GPIOTE configured for pin %d, channel %d\n", SENT_GPIO_PIN, gpiote_channel_index);
    uint32_t base_frequency = NRF_TIMER_BASE_FREQUENCY_GET(timer.p_reg);
    printk("Frequency is %d\n\r", base_frequency);
    nrfx_timer_config_t config = NRFX_TIMER_DEFAULT_CONFIG(base_frequency);
    config.mode = NRF_TIMER_MODE_TIMER;
    config.bit_width = NRF_TIMER_BIT_WIDTH_32;
    config.p_context = &timer;
    err_code = nrfx_timer_init(&timer, &config, timer_handler);
    if (err_code != NRFX_SUCCESS)
    {
    printk("Timer initialization failed. Error code: %d\n", err_code);
    return -1;
    }

    // Configure timer to clear on every 3µs (assuming 16MHz timer frequency)
    // nrf_timer_cc_set(timer.p_reg, NRF_TIMER_CC_CHANNEL1, 16 * 3);
    // nrf_timer_shorts_enable(timer.p_reg, NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK);

    // Enable Timer
    nrfx_timer_enable(&timer);

    // nrf_timer_cc_set(timer.p_reg, NRF_TIMER_CC_CHANNEL0, 16000000); // 1 second interval
    // nrf_timer_shorts_enable(timer.p_reg, NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK);
    nrf_timer_int_enable(timer.p_reg, NRF_TIMER_INT_COMPARE0_MASK);
    nrfx_gpiote_trigger_enable(SENT_GPIO_PIN, true);

    // Configure PPI
    nrf_ppi_channel_t ppi_channel;
    err_code = nrfx_ppi_channel_alloc(&ppi_channel);
    if (err_code != NRFX_SUCCESS)
    {
    printk("PPI channel allocation failed with error code: %d\n", err_code);
    return -1;
    }

    printk("PPI channel allocated\n\r");

    // Set up the event endpoint
    nrf_gpiote_event_t gpiote_event = nrf_gpiote_event_pin_get(NRF_GPIOTE, gpiote_channel_index);
    uint32_t gpiote_event_addr = nrf_gpiote_event_address_get(NRF_GPIOTE, gpiote_event);

    // Set up the task endpoint
    nrf_timer_task_t timer_task = NRF_TIMER_TASK_CAPTURE0;
    uint32_t timer_task_addr = nrf_timer_task_address_get(timer.p_reg, timer_task);

    // Connect the event and task using PPI
    err_code = nrfx_ppi_channel_assign(ppi_channel, gpiote_event_addr, timer_task_addr);
    if (err_code != NRFX_SUCCESS)
    {
    printk("PPI channel assignment failed with error code: %d\n", err_code);
    return -1;
    }

    printk("PPI channel assigned\n\r");

    // Enable the PPI channel
    err_code = nrfx_ppi_channel_enable(ppi_channel);
    if (err_code != NRFX_SUCCESS)
    {
    printk("PPI channel enable failed with error code: %d\n", err_code);
    return -1;
    }

    printk("PPI channel enabled\n\r");

    // Enable GPIOTE interrupt

    printk("GPIOTE Event Address: 0x%08X\n\r", gpiote_event_addr);
    printk("Timer Task Address: 0x%08X\n\r", timer_task_addr);

    while (1)
    {
    printk("Pulse %d: %u ticks\n\r", last_printed_index, pulse_buffer[last_printed_index]);
    last_printed_index = (last_printed_index + 1) % 64;
    k_sleep(K_MSEC(1000));
    }
    }
  • Something like this may work. What will happen is that it will automatically capture the TIMER value when the GPIO event triggers, that you can then read out in the gpio_handler, as you do. The issue is that you are dependent of the CPU to store away the buffer using the gpio_handler(). If the CPU is busy handling something else, only for a short time, that is fine, because the gpio_handler() will be called when that initial task is done. But if the CPU is busy for a bit longer, or two of the pulses are really close to eachother, then you risk not being able to handle the first interrupt before the second one occurs, and you risk that your application interprets two pulses as one that is the sum of the two (because the PPI task will trigger everytime. It doesn't know whether you have had time to update the last_capture parameter yet or not).

    I don't know at what rates these pulses come. What is the shortest possible pulse? If you want to try this, I suggest that you set the gpio_handler priority very high. 

    Best regards,

    Edvin

Reply
  • Something like this may work. What will happen is that it will automatically capture the TIMER value when the GPIO event triggers, that you can then read out in the gpio_handler, as you do. The issue is that you are dependent of the CPU to store away the buffer using the gpio_handler(). If the CPU is busy handling something else, only for a short time, that is fine, because the gpio_handler() will be called when that initial task is done. But if the CPU is busy for a bit longer, or two of the pulses are really close to eachother, then you risk not being able to handle the first interrupt before the second one occurs, and you risk that your application interprets two pulses as one that is the sum of the two (because the PPI task will trigger everytime. It doesn't know whether you have had time to update the last_capture parameter yet or not).

    I don't know at what rates these pulses come. What is the shortest possible pulse? If you want to try this, I suggest that you set the gpio_handler priority very high. 

    Best regards,

    Edvin

Children
  • Hi,

    thank you for clearing it out,

    regarding your questions, the measuring unit is Tick and 1 tick Minimum is 3us and the Minimum pulse time can be 12 Ticks which is 36us.

  • That sounds very fast, so not sure this would work. It would be difficult to guarantee that you have access to the CPU every 3µs. What if you get any other interrupts, e.g. from the modem. Even without any other interrupts, I don't know if your handler will be able to finish within the 3µs (in case you get two 0x0 in a row).

    Best regards,

    Edvin

  • One way of slowing down the required interrupt time is to use Counter3 on the falling edge of the signal with CC registers set at 1, 2, 3, 4, 5, 6 counts such that each CC trip will trigger a 32-bit 16MHz timer capture. A single Timer4 could be used, but also using a second Timer5 provides more time to read out data. If using 2 timers the Counter3-CC0 TASK will TRIGGER both Timer4 and Timer5, only one of which will be running at any one time.

    A single timer might be adequate, since only one interrupt every 3 falling edges. Timer4 (and Timer5 if used) could be cleared automatically or not, might be useful to not. Assuming if the timer is stopped the CC capture is disabled, if not as well as modifying the SHORTS the PPI would need updating in the interrupt if using 2 timers.

    // 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 Status   DN1   DN2   DN3   DN4   DN5   DN6   CRC    PAUSE
    // ==== ====== ===== ===== ===== ===== ===== ===== ===== ========
    //  56  12-27  12-27 12-27 12-27 12-27 12-27 12-27 12-27 (12-768)  <== Tick range
    //   0      1      2     3     4     5     6     7     8      (9)  <== Falling Edge No.
    //
    // Using single timer - repeat capture 6 falling edges, read out every 3
    //  Counter3-CC0=1, CC1=2, CC2=3, CC3=4, CC4=5, CC5=6
    //  START Timer4
    //  START COUNTER3
    //  Single Message
    //  {
    //      Counter3-CC0 Event ==> capture Task Timer4-CC0 Sync    
    //      Counter3-CC1 Event ==> capture Task Timer4-CC1 Status  
    //      Counter3-CC2 Event ==> capture Task Timer4-CC2 DN1    Interrupt read out Timer4 CC0-CC2
    //      Counter3-CC3 Event ==> capture Task Timer4-CC3 DN2     
    //      Counter3-CC4 Event ==> capture Task Timer4-CC4 DN3
    //      Counter3-CC5 Event ==> capture Task Timer4-CC5 DN4    Interrupt read out Timer4 CC3-CC5
    //                   Event ==> SHORT CLEAR Task Counter3
    //      Counter3-CC0 Event ==> capture Task Timer4-CC0 DN5
    //      Counter3-CC1 Event ==> capture Task Timer4-CC1 DN6
    //      Counter3-CC2 Event ==> capture Task Timer4-CC2 CRC   
    //      Counter3-CC3 Event ==> capture Task Timer4-CC3 PAUSE  Interrupt read out Timer4 CC0-CC3
    //                   Event ==> SHORT CLEAR Task Counter3
    //  }
    //
    // Using two timers - repeat capture 6 falling edges, read out every 6
    //  Counter3-CC0=1, CC1=2, CC2=3, CC3=4, CC4=5, CC5=6
    //  (Counter3-CC5 Event SHORT CLEAR Task Counter3)
    //  START Timer4 and Timer5 together (use PPI event)
    //  START COUNTER3
    //  Single Message
    //  {
    //      Counter3-CC0 Event ==> capture Task Timer4-CC0                Sync
    //      Counter3-CC1 Event ==> capture Task Timer4-CC1                Status
    //      Counter3-CC2 Event ==> capture Task Timer4-CC2                DN1
    //      Counter3-CC3 Event ==> capture Task Timer4-CC3                DN2
    //      Counter3-CC4 Event ==> capture Task Timer4-CC4                DN3
    //      Counter3-CC5 Event ==> capture Task Timer4-CC5, STOP Timer4   DN4
    //                   Event ==> START Task Timer5
    //                   Event ==> Interrupt (Counter3-CC3 Event SHORT CLEAR Task Counter3)
    //      Counter3-CC0 Event ==> capture Task Timer5-CC0                DN5
    //      Counter3-CC1 Event ==> capture Task Timer5-CC1                DN6
    //      Counter3-CC2 Event ==> capture Task Timer5-CC2                CRC
    //      Counter3-CC3 Event ==> capture Task Timer5-CC3, STOP Timer5  PAUSE
    //                   Event ==> Interrupt
    //                             ==> read out Timer4 CC0-CC5 and Timer5 CC0-CC3
    //  }
    

  • Hello,

    thank you replying, sorry to say but i didnt understand what you want me to do. can you please explain it a little bit more.

    thanks and regards 

    Akbar shah

  • I updated the bare-metal algorithm, but not sure if I can get time to write the code. Maybe edvin might have a spare moment ..

Related