This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Create accurate microsecond timer without interrupts

Hey guys,

I want to create an accurate timer without interrupts. My timer has to be very accurate because I have to send HIGH and LOW signals for 1us. I have read this post to know that I need high-frequency clock source (HFCLK, 16 MHz), which means better resolution (62.5 ns) and higher power consumption (typ. 5 or 70 uA depending on HFCLK source). But I don't know how to program my code so I followed this post and this post. But I have still some problems with it. I post my code below.

My timer should only replace the unaccurate nrf_delay_us.

#include <stdint.h>

#include "nrf_delay.h"
#include "app_error.h"
#include "nrf_gpio.h"
#include "nrf_drv_timer.h"

#define OUTPUT_PIN  18


/** @brief Function for Timer 1 initialization.
 */
void TIMEOUT_TIMER1_Init(void)
{
    NRF_TIMER1->TASKS_STOP = 1;           /**< Stop Timer 1 */
    NRF_TIMER1->TASKS_CLEAR = 1;          /**< Clear Timer 1 */

    NRF_TIMER1->MODE      = 0;            /**< Mode 0: Timer */
    NRF_TIMER1->BITMODE   = 3;            /**< Bitmode 3: 32 bit (for what?, What happens if I choose 16 bit?) */
    NRF_TIMER1->PRESCALER = 4;            /**< Prescaler 4: 1us Timer -> f(TIMER) = 16MHz / (2^PRESCALER) = 1MHz -> t(TIMER) = 1us */
    NRF_TIMER1->INTENCLR = TIMER_INTENCLR_COMPARE0_Disabled
                            | TIMER_INTENCLR_COMPARE1_Disabled
                            | TIMER_INTENCLR_COMPARE2_Disabled
                            | TIMER_INTENCLR_COMPARE3_Disabled
                            | TIMER_INTENCLR_COMPARE4_Disabled
                            | TIMER_INTENCLR_COMPARE5_Disabled; /**< Disable all interrupts */
}


void TIMEROUT_TIMER1_Start(uint32_t timeout_us)
{
    NRF_TIMER1->TASKS_STOP = 1;
    NRF_TIMER1->TASKS_CLEAR = 1;

    // this line: set TIMER1 to 0 (start value)
    // this line: hand over the time "timeout_us" to TIMER1 (end time)
    // this line: stop TIMER1 if end time ("timeout_us") is reached

    NRF_TIMER1->TASKS_START = 1;
}


void Write_Byte(uint8_t byte)
{
    int walk;

    for (walk = 0; walk < 8; ++walk)
    {
        nrf_gpio_pin_clear(OUTPUT_PIN);    
        if (byte & 0x01)
        {
            TIMEROUT_TIMER1_Start(1);       //instead of nrf_delay_us()                      
            nrf_gpio_pin_set(OUTPUT_PIN);                
            TIMEROUT_TIMER1_Start(60);      //instead of nrf_delay_us()                   
        }
        else
        {
            TIMEROUT_TIMER1_Start(60);       //instead of nrf_delay_us()                      
            nrf_gpio_pin_set(OUTPUT_PIN);                 
            TIMEROUT_TIMER1_Start(1);        //instead of nrf_delay_us()                     
        }
        byte >>= 1;                                       
    }
}


/**
 * @brief Function for application main entry.
 */
int main(void)
{
    uint32_t err_code;
    TIMEOUT_TIMER1_Init;
    
    uint8_t AA = 0xAA;
    nrf_gpio_cfg_output(OUTPUT_PIN);

    while (true)
    {
        Write_Byte(AA);
        nrf_delay_us(20);
    }
}

In line 19: (Init) Why should I use 32 bits and not 16 bits? where is the difference?

In lines 35 - 37: (time function) I hope you can help me here. I don't know how I can hand over my timeout value to my function and to my timer, so that the timer can work with it...

I hope, you can help me.

Thanks in advance,

Christoph

  • Hi,

     

    Since the prescaler is already setup with 4 (1 us resolution), you can set the specific CC directly with the input timeout_us.

    Something like this maybe?

    #define CC_USED 0
    
    if (timeout_us == 0) return;
    
    NRF_TIMERx->TASKS_STOP = 1;
    NRF_TIMERx->TASKS_CLEAR = 1;
    
    NRF_TIMERx->CC[CC_USED] = timeout_us;
    NRF_TIMERx->TASKS_START = 1;
    while(NRF_TIMERx->EVENTS_COMPARE[CC_USED] == 0) 
    {
      /* Call __WFE() if loop is long */
    }
    NRF_TIMERx->EVENTS_COMPARE[CC_USED] = 0;

     

    In line 19: (Init) Why should I use 32 bits and not 16 bits? where is the difference?

    16 bit = 0 to 65535 us

    32 bit = 0 to 4 294 seconds

     

    Depends on the range you'd like.

     

    Kind regards,

    Håkon

  • Hey Håkon,

    thank you for your fast reply. I have some questions for my understanding (1,2,3,4) and another question (5) for my time issue.

    1) In your Code:

    while(NRF_TIMERx->EVENTS_COMPARE[CC_USED] == 0)    //e.g. timeout_us = 40us

    the events_compare[CC_USED] is as long as zero until the 40us are reached, right? And when the 40us are reached, the counter is incremented which causes the compare event (EVENTS_COMPARE[CC_USED]) to be generated and the EVENTS_COMPARE becomes equal to the value specified in CC_USED (40us), right?  This leads to question 2.

    2) In your Code:

    NRF_TIMERx->EVENTS_COMPARE[CC_USED] = 0;

    I have to delete the value because of the EVENTS_COMPARE[CC_USED] have the value "40us" now?

    3) After NRF_TIMERx->EVENTS_COMPARE[CC_USED] = 0; why I don't set at the end of the function the stop and clear command (NRF_TIMERx->TASKS_STOP = 1; and NRF_TIMERx->TASKS_CLEAR = 1;)? I ask because if I don't set the stop command at the end of the function the timer will expire the whole time, not? Why it's better to set the stop and clear commands at the beginning of the function?

    4)

    32 bit = 0 to 4 294 seconds

    For example I set timeout_us = 4400 seconds. This timeout_us is never reached because after 4294 seconds there will happen an overflow and the timer will restart at 0seconds, right? This will be an endless loop, right?

    5) I tried our code (inclusive your idea) and I get the same problem like nrf_delay_us. I messure with an oscilloscope the high and low signals of my output pin and the time is as accurate as nrf_delay_us ...

    void Write_Byte(uint8_t byte)
    {
        int walk;
    
        for (walk = 0; walk < 8; ++walk)
        {
            nrf_gpio_pin_clear(OUTPUT_PIN);    
            if (byte & 0x01)
            {
                TIMEROUT_TIMER1_Start(1);       //instead of nrf_delay_us()                      
                nrf_gpio_pin_set(OUTPUT_PIN);                
                TIMEROUT_TIMER1_Start(60);      //instead of nrf_delay_us()                   
            }
            ....

    In line 10 I want a timeout of 1us and in oscilloscope the timout is nearly 3us.. and in line 12 i want a timeout of 60us and get a timeout between 66 and 67us... Do you know what it can be? Maybe it's my board which causes my issue? I'm using the DVK-BL652 from Laird.

    Kind regards,

    Christoph

  • Hi,

     

    I see what you mean. I get a delay of ~2.3 us when using the timer, and approx. 1.2 us when using nrf_delay_us().

    When dealing with such low delays, the overhead with handling the actual timer tasks and events will give a significant error in your GPIO toggling.

     

    For the 1 us specific delay, I think you have to use a busy-loop, and not the timer.

    I have played a bit around and tried to create a function that can give a more reliable delay.

    1 us input:

    2 us input:

    10 us input:

     

    Could you try this and see if it works similarly on your end?

    /**
     * Copyright (c) 2014 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, must reproduce the above copyright notice, this list of
     *    conditions and the following disclaimer in the documentation and/or other
     *    materials provided with the distribution.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
     * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     */
    /** @file
     *
     * @defgroup blinky_example_main main.c
     * @{
     * @ingroup blinky_example
     * @brief Blinky Example Application main file.
     *
     * This file contains the source code for a sample application to blink LEDs.
     *
     */
    
    #include <stdbool.h>
    #include <stdint.h>
    #include "nrf_delay.h"
    #include "boards.h"
    
    #define CC_USED 0
    
    void timer_setup(void)
    {
        NRF_TIMER1->BITMODE = TIMER_BITMODE_BITMODE_32Bit;
        NRF_TIMER1->SHORTS = TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos| TIMER_SHORTS_COMPARE0_STOP_Enabled << TIMER_SHORTS_COMPARE0_STOP_Pos;
        NRF_TIMER1->PRESCALER = 0;
    }
    
    /* Just a test function*/
    static inline void sleep_us(uint32_t timeout_us)
    {
        for (register uint32_t i = 0; i < timeout_us; i++)
        {
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
        }
    }
    
    #pragma push
    #pragma O3
    void timer_sleep(uint32_t timeout_us)
    {
    
        if (timeout_us == 0) 
        {
            return;
        }
        if (timeout_us == 1)
        {
            /* Adjust the amount of NOPs here */
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop(); __nop(); __nop(); 
            __nop(); __nop();
            return;
        }
    
        NRF_TIMER1->CC[CC_USED] = (timeout_us << 4) - 18;
        NRF_TIMER1->TASKS_START = 1;
        while(NRF_TIMER1->EVENTS_COMPARE[CC_USED] == 0) 
        {
          /* Call __WFE() if loop is long */
        }
        NRF_TIMER1->EVENTS_COMPARE[CC_USED] = 0;
    }
    #pragma pop
    
    /**
     * @brief Function for application main entry.
     */
    int main(void)
    {
        /* Configure board. */
        bsp_board_init(BSP_INIT_LEDS);
        NRF_CLOCK->TASKS_HFCLKSTART = 1;
        while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0);
        NRF_NVMC->ICACHECNF = NVMC_ICACHECNF_CACHEEN_Msk;
        timer_setup();
        /* Toggle LEDs. */
        while (true)
        {
            timer_sleep(2);
            NRF_P0->OUT ^= (1 << LED_1);
    
        }
    }
    
    /**
     *@}
     **/
    

     

    Christoph_I said:
    For example I set timeout_us = 4400 seconds. This timeout_us is never reached because after 4294 seconds there will happen an overflow and the timer will restart at 0seconds, right? This will be an endless loop, right?

     a uint32_t will overflow in this scenario, resulting in a lower timeout. If you set the timer to 0x 12 0000 1000, the timer will count to 0x1000, and give an event.

     

    Kind regards,

    Håkon

  • Hey Håkon,

    thank you, the times have become much more accurate. This is a big step forward!

    With your function timer_sleep without adjust the amount of NOPs I'm getting 2us instead of 1us on my end. But deleting all NOPs in this function I'm getting 1.340us (LOW signal, HIGH: 1.370) instead of 1us and that's very close to what I want.

    void timer_sleep(...)
    {
        ...
        if (timeout_us == 1)
            {
                /* NO NOPs here */
                return;
            }
        ...
    }

    Do you know if there are any other options to reduce the inaccuracy more and more?

    Kind regards,

    Christoph

    ______________________________________

    Edit:

    P.S.:

    Without NOPs I'm getting this times:

    2us -> HIGH: 3.08us; LOW: 2.840

    10us -> HIGH: 11.10us; LOW: 10.70us

    60us --> LOW: 60.80us

  • Hi Christoph,

     

    Did you run the example as-is to see if you are able replicate my measurements? Note the ICACHE is enabled, and external HFCLK is running, and the function itself is with O3 optimization (Keil syntax). Which compiler are you using?

     

    Kind regards,

    Håkon

Related