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.
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?
- Popular Resources
- Popular amplifiers
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- LED chemical incompatibility test to see which chemicals LEDs can be used with
- Application of ARM9 hardware coprocessor on WinCE embedded motherboard
- What are the key points for selecting rotor flowmeter?
- LM317 high power charger circuit
- A brief analysis of Embest's application and development of embedded medical devices
- Single-phase RC protection circuit
- stm32 PVD programmable voltage monitor
- Introduction and measurement of edge trigger and level trigger of 51 single chip microcomputer
- Improved design of Linux system software shell protection technology
- What to do if the ABB robot protection device stops
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications
- 【TOPWAY 5-inch smart TFT module】Smart curtains
- The difference between the heating of the resistor circuit and the constant current IC circuit
- [National Technology N32G457 Review] V. Porting the small embedded multi-button library MultiButton
- Pi Cast celebrates Raspberry Pi's 10th anniversary
- Verilog task call
- [RVB2601 Creative Application Development] + 2 DEMO Trials
- EEWORLD University ---- RT-Thread Studio
- Invite you to make an appointment for TI live broadcast: Analysis of the classic and innovative TI ultrasonic gas flow meter solution + the latest SimpleLink platform wireless products
- 2017 MG6 smart key disassembly (NXP solution, with keyless entry, one-button start)
- [Sipeed LicheeRV 86 Panel Review] Review Project Summary Report - Desktop Calendar Weather Assistant