MCU universal microsecond timing function framework design

Publisher:BlissfulWhisperLatest update time:2023-02-01 Source: zhihuKeywords:MCU Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

In embedded software development, timing can be said to be a very basic functional module, and its applications are also very wide. For example, it can assist in calculating signal pulse width time, and can also be directly used for conventional delay, etc. I believe that many people's first experience with the magic of MCU started with small programs related to the timing function.


To achieve precise timing in an MCU, its internal hardware timer is often used. The timer design and use of MCUs from different manufacturers are different. Even within the same MCU, there are usually several different types of timers coexisting.


Based on this, today I share a very simple and practical universal timing function framework. The purpose of this framework is to unify the timing function interface and separate the common parts from the hardware-related parts in implementation. In this way, when your embedded project uses this framework, you can switch the underlying timer seamlessly and quickly.


Note: This framework is mainly suitable for MCUs whose timer clock source is not less than 1MHz, because the minimum delay unit in the function interface is 1us. For some MCUs with timer clock sources lower than 1MHz, this framework can be simply changed to milliseconds (milliseconds) timing function. For the project address, see "Read the original text" at the end of the article.


1. Design of microseconds timing function library

1. Function interface definition

The first is to design the general timing function framework header file: microseconds.h. This header file directly defines the following 7 interface function prototypes. Covers the necessary initialization processes init() and shutdown(), the core timing functions get_ticks() and convert_to_microseconds(), and the commonly used delay functions delay(), set_delay(), and is_timeout().


//! @brief Initialization timing

void microseconds_init(void);

//! @brief Turn off timing

void microseconds_shutdown(void);

//! @brief Get the system cumulative count value

uint64_t microseconds_get_ticks(void);

//! @brief Convert count value to time value (microseconds)

uint32_t microseconds_convert_to_microseconds(uint64_t ticks);

//! @brief blocking delay (microsecond level)

void microseconds_delay(uint32_t us);

//! @brief Set timeout (for non-blocking delay)

void microseconds_set_delay(uint32_t us);

//! @brief Determine whether it times out (for non-blocking delay)

bool microseconds_is_timeout(void);

2. Universal function implementation

Then there is the common source file for designing the universal timing function framework: microseconds_common.c. This file involves three static global variable definitions, four private function declarations, and 6 interface function implementations except get_ticks().


The s_tickPerMicrosecond variable stores the count value corresponding to each microsecond. In fact, this variable does not have to be defined and can be calculated in real time when the function needs it. However, in order to slightly improve the performance of the framework, this value is calculated first in init(). , so that other functions can be used directly.


The s_highCounter variable stores the number of timer interrupts, that is, the high counter, because the framework get_ticks() interface returns a 64-bit count value. For some timers with a width less than 32 bits, we often need to enable timer interrupts, otherwise the system cannot guarantee long-term operation. The accuracy of time running linear timing (for example, a 32-bit timer with a 100MHz clock source will clear and flip once in about 43 seconds at most, and the s_highCounter variable needs to record the number of flips).


Of course, if a 64-bit timer is connected to the energy level in the MCU, there is no need to enable interrupts (the clearing and flipping time is particularly long and can be approximately considered permanent), and s_highCounter is not needed at this time.


Regarding the delay function interface, delay() is used for blocking delay, that is, after calling this function, you must wait for the specified time before exiting, and the system will be forced to suspend; set_delay()/is_timeout() is used for non-blocking delay Delay, the system can continue to do other tasks, just check whether the timeout time has expired when needed. Both delays have their own uses.


//!< Equivalent count value per microsecond

static uint32_t s_tickPerMicrosecond;

//!< The timeout time point corresponds to the system count value (for non-blocking delay)

static uint64_t s_timeoutTicks;

//!< High-bit counter, only valid when the timer timeout interrupt is enabled, used to record the cumulative number of interrupts

volatile uint32_t s_highCounter;


//! @brief Turn on the hardware timer

extern void microseconds_timer_init(void);

//! @brief Turn off the hardware timer

extern void microseconds_timer_deinit(void);

//! @brief Get the timer clock source value

extern uint32_t microseconds_get_clock(void);

//! @brief Convert time value (microseconds) to count value

static uint64_t microseconds_convert_to_ticks(uint32_t microseconds);


void microseconds_init(void)

{

    //Clear the high counter

    s_highCounter = 0;

    // Turn on hardware timer

    microseconds_timer_init();

    // Calculate the equivalent count value per microsecond

    s_tickPerMicrosecond = microseconds_get_clock() / 1000000UL;

    // Assume that the timer clock source is not less than 1MHz

    assert(s_tickPerMicrosecond);

}


void microseconds_shutdown(void)

{

    //Close hardware timer

    microseconds_timer_deinit();

}


uint32_t microseconds_convert_to_microseconds(uint64_t ticks)

{

    return (ticks / s_tickPerMicrosecond);

}


uint64_t microseconds_convert_to_ticks(uint32_t microseconds)

{

    return ((uint64_t)microseconds * s_tickPerMicrosecond);

}


void microseconds_delay(uint32_t us)

{

    // Get the current count value of the system

    uint64_t currentTicks = microseconds_get_ticks();

    // Calculate the system count value at the timeout time

    uint64_t ticksNeeded = ((uint64_t)us * s_tickPerMicrosecond) + currentTicks;

    // Wait for the system count value to reach the timeout time point system count value

    while (microseconds_get_ticks() < ticksNeeded);

}


void microseconds_set_delay(uint32_t us)

{

    // Calculate the equivalent count value of the timeout time

    uint64_t ticks = microseconds_convert_to_ticks(us);

    //Set the system count value at the timeout time point

    s_timeoutTicks = microseconds_get_ticks() + ticks;

}


bool microseconds_is_timeout(void)

{

    // Get the current count value of the system

    uint64_t currentTicks = microseconds_get_ticks();

    // Determine whether the system count value is greater than the system count value at the timeout time point

    return (currentTicks < s_timeoutTicks) ? false : true;

}

2. Implementation of microseconds timing function library

1. Timer related implementation (SysTick based on Cortex-M core)

Finally, there is the general timing function framework source file related to the design of MCU: microseconds_xxTimer.c. Here we take the core timer SysTick of the Cortex-M series MCU as an example.


SysTick is a 24-bit decrement timer. There are two clock source configurations: one is the core frequency, and the other is an external clock (depending on the manufacturer's implementation). The most commonly used clock source configuration is the same frequency as the core.


We said before that when using a timer with a width less than 32 bits such as SysTick, you need to enable the timer interrupt, so s_highCounter will take effect. get_ticks() is the most basic and core functional interface in the entire timing function framework. One thing that needs special attention in its implementation is that there may be a risk of numerical regression when taking the current count value of the system. You need to use do in the code. {} while(); method to ensure correctness.


//!< High-bit counter, only valid when the timer timeout interrupt is enabled, used to record the cumulative number of interrupts

extern volatile uint32_t s_highCounter;


void microseconds_timer_init(void)

{

    //Call the initialization function in the core_cmx.h header file

    //The SysTick clock source is the kernel clock, enable interrupts, and the reload value is 0xFFFFFF

    SysTick_Config(SysTick_LOAD_RELOAD_Msk + 1);

}


void microseconds_timer_deinit(void)

{

    SysTick->CTRL &= ~(SysTick_CTRL_CLKSOURCE_Msk |

                       SysTick_CTRL_TICKINT_Msk |

                       SysTick_CTRL_ENABLE_Msk);

    SysTick->VAL = 0;

}


uint32_t microseconds_get_clock(void)

{

    return SystemCoreClock;

}


uint64_t microseconds_get_ticks(void)

{

    uint32_t high;

    uint32_t low;

    // The implementation here should pay attention to ensure the correctness of obtaining the system cumulative count value when an interrupt occurs.

    do

    {

        //Cache the high counter first

        high = s_highCounter;

        // Read the actual count value of the timer again

        low = ~SysTick->VAL & SysTick_LOAD_RELOAD_Msk;

    } while (high != s_highCounter); // Ensure that no interruption occurs between the cached high value and the read actual low value


    return ((uint64_t)high << 24) + low;

}


void SysTick_Handler(void)

{

    s_highCounter++;

}

Of course, there are many timer implementations for specific MCU platforms, so this project will be continuously updated, and everyone is welcome to contribute.


At this point, the design and implementation of the universal microseconds timing function framework in embedded systems has been introduced.


Keywords:MCU Reference address:MCU universal microsecond timing function framework design

Previous article:A brief discussion on several self-refresh methods of MCU Boot
Next article:What impact does CPU branch prediction have on your code?

Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号