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

Parents
  • 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 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

  • Ok Håkon,

    Did you run the example as-is to see if you are able replicate my measurements?

    that is a good objection. I run your example and reduced the amount of __NOP() a little bit.

    #include <stdbool.h>
    #include <stdint.h>
    #include "nrf_delay.h"
    #include "boards.h"
    
    #define OUTPUT_PIN 18
    #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;
    }
    
    
    #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);
        bsp_board_leds_init();
        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 << OUTPUT_PIN);
    
        }
    }
    
    /**
     *@}
     **/
    

    Now I'm getting nearly the 1us! :)

    But why is there a delay when I'm using:

    nrf_gpio_pin_set() / clear() ?

    Can you please check if my adoptions are true:

    To set the output I'm using: NRF_P0->OUT |= (1 << OUTPUT_PIN);

    To clear the output: NRF_P0->OUT &= ~(1 << OUTPUT_PIN);

    To read from input: NRF_P0->IN = (1 << INPUT_PIN);

    I'm using SES and SDK 14.2.0

    Kind regards,

    Christoph

  • Hi Christoph,

    Christoph_I said:
    Now I'm getting nearly the 1us! :)

    That is good news!

    Christoph_I said:
    To read from input: NRF_P0->IN = (1 << INPUT_PIN);

    Try this: uint32_t pin =  (NRF_P0->IN & (1 << INPUT_PIN));

    Others look OK.

    Christoph_I said:
    nrf_gpio_pin_set() / clear() ?

    Because they are function calls, although static inlined, which use stacked memory to access the register. If you look at the assembly output, you'll likely see more instructions than just directly accessing the register. The output depends on the compiler used as well.

     

    For GCC, you need to force the optimization like this:

    #pragma GCC push_options
    #pragma GCC optimize ("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 GCC pop_options

    Your overall settings for optimization will be overridden for this specific function only.

     

    *edit*

    Tested with "O3" optimization on SES v4.22, and got:

    1 us input = 1.05 to 1.1 us

    2 us input = 1.9 us

    10 us input = 9.9 us

     

    Please check with these pragma's for GCC optimization and see if you also see similar behavior.

      

    Kind regards,

    Håkon

  • Hey Håkon,

    it's the last day before weekend. Slight smile

    uint32_t pin =  (NRF_P0->IN & (1 << INPUT_PIN));

    As note: when I'm trying this I had to set the INPUT_PIN as input as follows:

    nrf_gpio_cfg_input(INPUT_PIN, NRF_GPIO_PIN_NOPULL);

    Because they are function calls,

    Thanks for your explanation.

    What is doing the ICACHE?

    Your overall settings for optimization will be overridden for this specific function only.

    Does the pragma set the priority for this function at the highest level or which settings for optimization will be overritten by pragma?

    Please check with these pragma's for GCC optimization and see if you also see similar behavior.

    I've check the input with the new GCC optimization and your amount of __NOP() (50 i think) and I'm getting the same values as you.

    1 us input = 1.08 us

    2 us input = 1.88 us

    10 us input = 9.9 us

    60 us input = 60 us

    But first I have to thank you for your patience and devotiation. Without your help I would be very helpless. Thanks Håkon, you are a very nice and smart one! I really enjoyed working with you.

    Kind regards,

    Christoph

  • Hi Christoph

     

    Christoph_I said:
    As note: when I'm trying this I had to set the INPUT_PIN as input as follows

    Ah, yes, that is a pre-requisite. Same goes for GPIO outputs, which needs to be configured (nrf_gpio_cfg_output(my_pin_number); for instance), but those you set/clear via NRF_GPIO->OUTSET, OUTCLR, or OUT (or any other "helper" functions, like nrf_gpio_pin_toggle()).

     

    Christoph_I said:
    What is doing the ICACHE?

     This is the instruction cache. This cache reads instructions from flash, assuming that the program execution is incremental, thus speeding up your program. Its sped up as flash is actually a bit slower than the frequency the CPU run on. The implementation is explained more in detail here: https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.ps.v1.1/nvmc.html?cp=4_2_0_10_5#concept_dh3_cyy_vr

    But, for generic cache explanations, wiki would help you out as well.

     

    Christoph_I said:
    Does the pragma set the priority for this function at the highest level or which settings for optimization will be overritten by pragma?

     The pragma sets the optimization level to the compiler. Let's say you want to optimize your firmware for size (-Os flag to the compiler), but you have certain functions that you want to run as fast as possible, you can wrap that option around those functions. You can even optimize a full .c file individually if you'd like.

     

    Christoph_I said:
    I've check the input with the new GCC optimization and your amount of __NOP() (50 i think) and I'm getting the same values as you

    This is good news! You can ofcourse add or remove amount of NOPs to see if you're able to fine-tune it even further.

     

    Christoph_I said:
    But first I have to thank you for your patience and devotiation. Without your help I would be very helpless. Thanks Håkon, you are a very nice and smart one! I really enjoyed working with you.

     Thank you for the kind words! I am very glad to hear that I was of good use! If you run into issues or questions in the future, you know where to ask Slight smile

    Hope you have a nice weekend, Christoph!

     

    Kind regards,

    Håkon

      

Reply
  • Hi Christoph

     

    Christoph_I said:
    As note: when I'm trying this I had to set the INPUT_PIN as input as follows

    Ah, yes, that is a pre-requisite. Same goes for GPIO outputs, which needs to be configured (nrf_gpio_cfg_output(my_pin_number); for instance), but those you set/clear via NRF_GPIO->OUTSET, OUTCLR, or OUT (or any other "helper" functions, like nrf_gpio_pin_toggle()).

     

    Christoph_I said:
    What is doing the ICACHE?

     This is the instruction cache. This cache reads instructions from flash, assuming that the program execution is incremental, thus speeding up your program. Its sped up as flash is actually a bit slower than the frequency the CPU run on. The implementation is explained more in detail here: https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.ps.v1.1/nvmc.html?cp=4_2_0_10_5#concept_dh3_cyy_vr

    But, for generic cache explanations, wiki would help you out as well.

     

    Christoph_I said:
    Does the pragma set the priority for this function at the highest level or which settings for optimization will be overritten by pragma?

     The pragma sets the optimization level to the compiler. Let's say you want to optimize your firmware for size (-Os flag to the compiler), but you have certain functions that you want to run as fast as possible, you can wrap that option around those functions. You can even optimize a full .c file individually if you'd like.

     

    Christoph_I said:
    I've check the input with the new GCC optimization and your amount of __NOP() (50 i think) and I'm getting the same values as you

    This is good news! You can ofcourse add or remove amount of NOPs to see if you're able to fine-tune it even further.

     

    Christoph_I said:
    But first I have to thank you for your patience and devotiation. Without your help I would be very helpless. Thanks Håkon, you are a very nice and smart one! I really enjoyed working with you.

     Thank you for the kind words! I am very glad to hear that I was of good use! If you run into issues or questions in the future, you know where to ask Slight smile

    Hope you have a nice weekend, Christoph!

     

    Kind regards,

    Håkon

      

Children
Related