Wait time accuracy in k_busy_wait() function in Matter application

Hello,

I created a code that switches the PWM duty every 562us or 1.69ms. The implementation uses the pwm_set_dt() function to switch the duty and waits using the k_busy_wait() function until the next pwm_set_dt() function. When I implemented this in an empty application, it worked correctly as intended.

However, Matter applications created from Matter: Template are exhibiting strange behavior. Specifically, the PWM output is controlled according to the ClusterId and AttributeId received by the MatterPostAttributeCallback() function, but the wait time in the k_busy_wait() function during PWM control is extended.

When I checked the PWM waveform with an oscilloscope, it was as shown in the graph. The correct time is 562us for the short wait and 1.69ms for the long wait, but as shown in red, there is a wait that extends for several tens of microseconds. There is no regularity to this extended wait time, and it appears to occur randomly.

Why does this phenomenon occur?


  • Development PC
    • OS: Windows 10
    • SDK: nRF Connect SDK 2.5.0
    • ZAP tool: v2024.01.20
  • Microcontroller
    • nRF52840
Parents
  • Hi,

    When you are using k_busy_wait, other higher priority threads or interrupts, like radio activity in Matter samples will be allowed to run and preemt the thread where you are busy-waiting. It is normally not possible to get exact timing in the application when you are running radio protocols together with other tasks, since there is only one CPU that must be shared between all tasks.

    You can try to increase the priority of the thread that does the PWM duty cycle updates, but most likely, radio protocols will have even higher priority. 

    I would also recommend you to not use k_busy_wait, as this is not optimal when it comes to power consumption and application flow. k_busy_wait will block other lower priority tasks from executing, and it will keep the CPU on and busy, burning power for no reason. You should rather use k_sleep to put the thread to sleep for the desired time, or use a timer to change the PWM duty cycle.

    Best regards,
    Jørgen

Reply
  • Hi,

    When you are using k_busy_wait, other higher priority threads or interrupts, like radio activity in Matter samples will be allowed to run and preemt the thread where you are busy-waiting. It is normally not possible to get exact timing in the application when you are running radio protocols together with other tasks, since there is only one CPU that must be shared between all tasks.

    You can try to increase the priority of the thread that does the PWM duty cycle updates, but most likely, radio protocols will have even higher priority. 

    I would also recommend you to not use k_busy_wait, as this is not optimal when it comes to power consumption and application flow. k_busy_wait will block other lower priority tasks from executing, and it will keep the CPU on and busy, burning power for no reason. You should rather use k_sleep to put the thread to sleep for the desired time, or use a timer to change the PWM duty cycle.

    Best regards,
    Jørgen

Children
  • Jørgen-san,

    Thank you for your answering. I understand that when using the k_busy_wait() function, other threads, such as wireless protocols, may be interrupted, making it difficult to maintain accurate timing.

    Since using the k_busy_wait() function is not desirable in terms of power consumption and task processing, I tried other methods to change the PWM duty. First, I tried using the k_sleep() function. However, even with this method, I encountered a phenomenon where the waiting time increased from the desired 562us or 1.69ms, similar to when using the k_busy_wait() function.

    Next, I looked into how to use a timer. I found that using the K_TIMER_DEFINE() macro and k_timer_start() function provided by Zephyr is not suitable because the resolution is in milliseconds, as answered in Timer accuracy for micro seconds. Therefore, I decided to use a hardware timer with the nrfx API by referring to the nrfx_timer sample. Below is an excerpt of that code.

    static void timer_event_handler(nrf_timer_event_t event_type, void* context)
    {
        if (event_type == NRF_TIMER_EVENT_COMPARE1)
        {
            // write timer handler
        }
    }
    
    static const nrfx_timer_t timer = NRFX_TIMER_INSTANCE(1);
    
    void timer_init()
    {
        uint32_t base_frequency = NRF_TIMER_BASE_FREQUENCY_GET(timer.p_reg);
        nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG(base_frequency);
    
        nrfx_err_t err = nrfx_timer_init(&timer, &timer_config, timer_event_handler);
        NRFX_ASSERT(err == NRFX_SUCCESS);
    
        nrfx_timer_clear(&timer);
    
        IRQ_CONNECT(TIMER1_IRQn, IRQ_PRIO_LOWEST, nrfx_timer_1_irq_handler, NULL, 0);
        uint32_t time_ticks = nrfx_timer_us_to_ticks(&timer, 562);
        nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL1, time_ticks, NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK, true);
    }
    
    void timer_enable()
    {
        nrfx_timer_enable(&ir_timer);
    }
     
    I also tried adding the following line, but it causes a hard fault at runtime, so I removed it:
    timer_config.bit_width = NRF_TIMER_BIT_WIDTH_32;
    conf file add the following line:
    CONFIG_NRFX_TIMER1=y
    When I built the above, wrote it to the nRF52840, and executed timer_init() and timer_enable() functions, I was able to control the PWM duty, but when I looked at the waveform with an oscilloscope, the period was output about 8 times longer than the desired 562us or 1.69ms. The timer_event_handler() function is called in that period.
    Is there something wrong with this setting?
  • This problem was self resolved. I make a note for anyone who encounters the same problem.

    After controlling the PWM duty, I used the nrfx_timer_disable() function to stop the timer every time, but the second and subsequent nrfx_timer_enable() functions caused a long-cycle waveform like the one above. When I wrote the code to run the nrfx_timer_extended_compare() function every time before running the nrfx_timer_enable() function, the waveform was output with the correct period.

    The code is below.

    static void timer_event_handler(nrf_timer_event_t event_type, void* context)
    {
        if (event_type == NRF_TIMER_EVENT_COMPARE1)
        {
            // write timer handler
        }
    }
    
    static const nrfx_timer_t timer = NRFX_TIMER_INSTANCE(1);
    
    void timer_init()
    {
        uint32_t base_frequency = NRF_TIMER_BASE_FREQUENCY_GET(timer.p_reg);
        nrfx_timer_config_t timer_config = NRFX_TIMER_DEFAULT_CONFIG(base_frequency);
    
        nrfx_err_t err = nrfx_timer_init(&timer, &timer_config, timer_event_handler);
        NRFX_ASSERT(err == NRFX_SUCCESS);
    
        nrfx_timer_clear(&timer);
    
        IRQ_CONNECT(TIMER1_IRQn, IRQ_PRIO_LOWEST, nrfx_timer_1_irq_handler, NULL, 0);
    }
    
    void timer_enable()
    {
        uint32_t time_ticks = nrfx_timer_us_to_ticks(&timer, 562);
        nrfx_timer_extended_compare(&timer, NRF_TIMER_CC_CHANNEL1, time_ticks, NRF_TIMER_SHORT_COMPARE1_CLEAR_MASK, true);
    
        nrfx_timer_enable(&ir_timer);
    }

Related