STM32HAL库滴答定时器(SysTick)实现1ms中断的机制详解
文章目录
1. SysTick定时器基础
- 功能定位:SysTick是Cortex-M内核自带的24位倒计时定时器,专为操作系统或实时任务设计。
- 核心寄存器:
- LOAD:设置重载值,决定中断周期。
- VAL:当前计数值,写入0可复位计数器。
- CTRL:控制寄存器,配置时钟源、中断使能等。
2. HAL库的SysTick初始化流程
HAL库通过HAL_Init()
函数自动初始化SysTick,但是SystemClock_Config()
修改系统时钟时,HAL库会自动更新SysTick,关键步骤如下:
2.1 调用HAL_InitTick()
传入参数为中断优先级,可在cubeMX里的NVIC配置:
// HAL库初始化函数
HAL_StatusTypeDef HAL_Init(void) {
// ...其他初始化...
HAL_InitTick(TICK_INT_PRIORITY); // 配置SysTick
return HAL_OK;
}
函数定义如下,主要是设置重装值和中断优先级:
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR;
}
/* Configure the SysTick IRQ priority */
if (TickPriority < (1UL << __NVIC_PRIO_BITS))
{
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
主要调用了HAL_SYSTICK_Config()
函数进行配置,参数为自动重装值,该函数定义如下:
/**
* @brief Initializes the System Timer and its interrupt, and starts the System Tick Timer.
* Counter is in free running mode to generate periodic interrupts.
* @param TicksNumb: Specifies the ticks Number of ticks between two interrupts.
* @retval status: - 0 Function succeeded.
* - 1 Function failed.
*/
uint32_t HAL_SYSTICK_Config(uint32_t TicksNumb)
{
return SysTick_Config(TicksNumb);
}
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL); /* Reload value impossible */
}
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
return (0UL); /* Function successful */
}
2.2 计算重载值
uint32_t ticks = SystemCoreClock / 1000; // 根据系统时钟计算1ms所需的计数值
SysTick->LOAD = (ticks - 1UL); // 设置重载值(必须减1)
- 示例:若系统时钟为72MHz,则
ticks = 72000
,重载值设为71999。
2.3 配置控制寄存器
SysTick->VAL = 0UL; // 清空计数器
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | // 选择CPU时钟(HCLK)
SysTick_CTRL_TICKINT_Msk | // 使能中断
SysTick_CTRL_ENABLE_Msk; // 启动定时器
2.4 关键参数定义
1、SystemCoreClock
:系统时钟频率
初始值是16M,但是SystemClock_Config()
修改系统时钟时会自动更新这个值,上面的说明文字说明了这件事,
/** @addtogroup STM32F1xx_System_Private_Variables
* @{
*/
/* This variable is updated in three ways:
1) by calling CMSIS function SystemCoreClockUpdate()
2) by calling HAL API function HAL_RCC_GetHCLKFreq()
3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency
Note: If you use this function to configure the system clock; then there
is no need to call the 2 first functions listed above, since SystemCoreClock
variable is updated automatically.
*/
/* 此变量通过以下三种方式更新:
1) 调用CMSIS函数 SystemCoreClockUpdate()
2) 调用HAL API函数 HAL_RCC_GetHCLKFreq()
3) 每次调用 HAL_RCC_ClockConfig() 配置系统时钟频率时
注意:若使用此函数配置系统时钟,则无需调用前两种方式,
因为 SystemCoreClock 变量会自动更新。
*/
uint32_t SystemCoreClock = 16000000;
具体实现可以在SystemClock_Config()
函数最后看到:
2、uwTickFreq
中断频率
因此 LOAD值 =系统时钟/中断频率 - 1= 72,000,000 / 1000 - 1
3. 时钟配置后的自动调整
当用户调用SystemClock_Config()
修改系统时钟时,HAL库会自动更新SysTick,该函数会在时钟配置最后调用:
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
3.1 HAL_RCC_ClockConfig()
的内部操作
HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency) {
// ...配置系统时钟...
SystemCoreClockUpdate(); // 更新全局时钟变量
HAL_InitTick(); // 重新初始化SysTick
return HAL_OK;
}
具体实现:
3.2 动态调整示例
- 原始时钟:HSI 8MHz → SysTick重载值为7999(1ms中断)。
- 切换后时钟:HSE+PLL 72MHz → 重载值更新为71999,维持1ms中断周期。
4. 中断服务函数与时间管理
4.1 默认中断处理
HAL库通过这个中断实现ms级延时:
// 在stm32f1xx_it.c中定义
void SysTick_Handler(void) {
HAL_IncTick(); // 递增全局计数器HAL_Tick
}
__weak void HAL_IncTick(void)
{
uwTick += uwTickFreq;//1ms计数加1
}
__weak uint32_t HAL_GetTick(void)
{
return uwTick;
}
- uwTick:32位变量,每1ms加1,用于
HAL_Delay()
和超时判断。
4.2 延时函数实现
记录起始计数值,获取当前计数值,当差值大于延时时间时说明延迟时间到了,退出死循环。
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = Delay;
/* Add a freq to guarantee minimum wait */
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
while ((HAL_GetTick() - tickstart) < wait)
{
}
}
5. 关键配置参数详解
参数 | 说明 | 典型值 |
---|---|---|
SystemCoreClock | 系统时钟频率(HAL库通过SystemCoreClockUpdate() 更新) | 72,000,000 Hz (72MHz) |
ticks | 每毫秒所需的时钟周期数(= SystemCoreClock / 1000) | 72,000 |
LOAD 寄存器 | 实际写入的重载值(ticks - 1) | 71,999 |
CTRL 寄存器配置 | 0x7:启用中断、选择CPU时钟、启动定时器 | 0x00000007 |
6. 验证与调试方法
6.1 GPIO翻转测试
void SysTick_Handler(void) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // PA5每1ms翻转一次
HAL_IncTick();
}
- 示波器测量:PA5输出方波,周期应为2ms(每个中断翻转一次)。
6.2 代码调试
-
检查
SystemCoreClock
值:printf("SystemCoreClock = %lu Hz\n", SystemCoreClock); // 确认与预期时钟一致
-
查看
HAL_Tick
递增:
在调试器中观察HAL_Tick
变量是否每1ms增加1。
7. 常见问题与解决
问题现象 | 可能原因 | 解决方案 |
---|---|---|
延时函数不准确 | 系统时钟配置错误 | 检查SystemClock_Config() 是否正确配置时钟 |
SysTick中断未触发 | 全局中断未使能 | 在main() 中调用__enable_irq() |
HAL_Tick 不更新 | SysTick中断服务函数未执行 | 检查是否重写了SysTick_Handler() 未调用HAL_IncTick() |
8. 自定义中断周期
若要修改SysTick中断周期(如改为2ms):
// 在main()中重新配置
uint32_t new_ticks = SystemCoreClock / 500; // 500Hz → 2ms
SysTick->LOAD = new_ticks - 1;
SysTick->VAL = 0;
// 注意:需同步修改HAL_Delay()逻辑或避免使用HAL库延时函数
9. 总结
- 自动化管理:HAL库通过
HAL_Init()
和HAL_RCC_ClockConfig()
自动完成SysTick的初始化和动态调整,确保1ms中断周期的准确性。 - 依赖系统时钟:SysTick的配置直接关联
SystemCoreClock
,时钟配置错误将导致定时异常。 - 调试建议:通过GPIO翻转和调试器监控,可快速定位时钟或中断配置问题。