是使用HAL_NVIC_EnableIRQ(SysTick_IRQn)还是NVIC_EnableIRQ(SysTick_IRQn)开启SysTick中断?
时间: 2025-08-01 16:22:59 AIGC 浏览: 44
<think>我们正在讨论的是SysTick中断的使能问题。在Cortex-M内核中,SysTick是一个特殊的系统定时器,它的中断使能方式与其他外设中断有所不同。
注意:SysTick中断的使能不是通过NVIC的ISER寄存器(即通常的NVIC_EnableIRQ函数)来完成的,而是通过SysTick控制与状态寄存器(STK_CTRL)的TICKINT位来控制的。
因此,对于SysTick中断,我们通常不使用HAL_NVIC_EnableIRQ(SysTick_IRQn)或NVIC_EnableIRQ(SysTick_IRQn)来使能,因为这两个函数都是针对外部中断(IRQn)的。SysTick中断属于内核异常,而不是外设中断。
正确的做法是使用CMSIS中的SysTick_Config函数,或者直接设置SysTick控制寄存器。
具体说明如下:
1. 使用CMSIS函数:
SysTick_Config(SystemCoreClock / 1000); // 例如,配置1ms中断一次
这个函数会:
- 设置重载值
- 设置优先级(通常为最低优先级)
- 使能SysTick定时器和中断
2. 使用HAL库函数:
HAL_SYSTICK_Config(SystemCoreClock / 1000); // HAL库对SysTick_Config的封装
然后,如果需要使能中断,实际上在配置时已经使能了,因为HAL_SYSTICK_Config内部调用了SysTick_Config。
3. 直接操作寄存器:
如果我们想手动控制,可以这样:
SysTick->LOAD = reload_value; // 设置重载值
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | // 选择时钟源(处理器时钟)
SysTick_CTRL_TICKINT_Msk | // 使能中断
SysTick_CTRL_ENABLE_Msk; // 使能SysTick
注意:在HAL库中,SysTick通常被用于作为时基(HAL_Delay等),所以在HAL_Init()中已经配置了SysTick。如果我们想使用SysTick中断做其他事情,需要小心处理,避免冲突。
因此,对于问题“是使用HAL_NVIC_EnableIRQ(SysTick_IRQn)还是NVIC_EnableIRQ(SysTick_IRQn)开启SysTick中断?”的回答是:两者都不是,因为SysTick中断不是通过NVIC使能寄存器来使能的。
但是,我们可能会注意到,在HAL库中有一个函数HAL_NVIC_EnableIRQ(),它内部对SysTick做了特殊处理。我们来看一下HAL_NVIC_EnableIRQ的源代码(以stm32f4xx_hal_cortex.c为例):
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
{
/* Check the parameters */
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
/* Enable interrupt */
NVIC_EnableIRQ(IRQn);
/* Check whether the System Tick interrupt has been enabled */
if (IRQn == SysTick_IRQn)
{
/* Set the SysTick interrupt pending to call the SysTick IRQ handler */
// 注意:这里并没有使能SysTick中断,而是设置了一个挂起,以便立即进入SysTick中断?
// 实际上,这个操作是不必要的,而且可能会引起问题。在HAL库的某些版本中,这个操作被移除了。
// 在较新的HAL库中,这段代码已经被注释掉或者删除了。
}
}
事实上,在较新的HAL库版本中,HAL_NVIC_EnableIRQ函数中关于SysTick的部分已经被移除,所以它对于SysTick中断的处理和普通中断一样,调用NVIC_EnableIRQ。但是,根据ARM文档,NVIC_EnableIRQ对SysTick是无效的(因为SysTick不是IRQn,它是异常号,在异常表中是15号,而IRQn从0开始是外设中断,负数的是内核异常)。所以,调用HAL_NVIC_EnableIRQ(SysTick_IRQn)实际上不会使能SysTick中断。
因此,总结如下:
- 不要使用HAL_NVIC_EnableIRQ(SysTick_IRQn)或NVIC_EnableIRQ(SysTick_IRQn)来使能SysTick中断,因为它们是无效的。
- 正确使能SysTick中断的方法是设置SysTick控制寄存器的TICKINT位(即使用SysTick_Config或直接写寄存器)。
示例代码:
使用CMSIS配置SysTick中断(不使用HAL):
#include "stm32f4xx.h"
void SysTick_Handler(void)
{
// 中断处理
}
int main(void)
{
// 配置SysTick中断,每1ms触发一次
if (SysTick_Config(SystemCoreClock / 1000)) {
// 配置出错处理
while(1);
}
while(1);
}
使用HAL库配置(HAL已经配置了SysTick作为时基,如果我们想额外使用,可以重新配置,但要注意HAL的时基可能会被破坏):
通常,HAL_Init()会调用HAL_InitTick()来配置SysTick,所以如果我们想使用SysTick中断,应该避免重复配置,或者使用其他定时器。
如果我们想改变SysTick的中断处理函数,可以重写SysTick_Handler函数(在启动文件中,SysTick_Handler是弱定义的)。
结论:对于SysTick中断,使用SysTick_Config函数(或HAL_SYSTICK_Config)来配置并同时使能中断。
因此,回答是:两者都不应该使用,而应该使用SysTick_Config或直接操作寄存器来使能SysTick中断。
注意:在HAL库中,SysTick_IRQn通常被定义为-1(在CMSIS中,SysTick的异常号是15,但是IRQn_Type中,内核异常是负数,而外设中断是0开始的正数。SysTick_IRQn在CMSIS中定义为-1)。所以,当我们调用NVIC_EnableIRQ(-1)时,函数内部会检查参数,如果参数无效则不会执行。因此,调用NVIC_EnableIRQ(SysTick_IRQn)是无效的。
下面我们通过代码验证:
在CMSIS的core_cm4.h中,NVIC_EnableIRQ的实现:
__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0)
{
NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
}
}
可以看到,只有当IRQn>=0时,才会设置NVIC的使能寄存器。而SysTick_IRQn是-1,所以不会执行任何操作。
因此,我们绝对不应该使用NVIC_EnableIRQ(SysTick_IRQn)或HAL_NVIC_EnableIRQ(SysTick_IRQn)来使能SysTick中断。
正确的方法总结:
1. 使用SysTick_Config函数配置重载值并自动使能中断。
2. 如果需要更灵活的控制,直接写SysTick->CTRL寄存器,置位TICKINT(位1)和ENABLE(位0)以及CLKSOURCE(位2,通常选择处理器时钟)。
示例:直接操作寄存器使能SysTick中断
// 设置重载值(例如,1ms中断)
SysTick->LOAD = (SystemCoreClock/1000) - 1;
SysTick->VAL = 0; // 清除当前值
// 使能中断,选择处理器时钟,使能SysTick
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
注意:优先级设置。SysTick的优先级可以通过NVIC_SetPriority(SysTick_IRQn, priority)来设置,因为虽然中断使能不是通过NVIC,但优先级设置是通过SCB(系统控制块)的寄存器。在SysTick_Config函数中,会调用NVIC_SetPriority设置优先级(通常设置为最低优先级)。
所以,如果我们直接操作寄存器,不要忘记设置优先级:
NVIC_SetPriority(SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); // 设置最低优先级
在HAL库中,如果我们使用HAL_SYSTICK_Config,它会调用SysTick_Config,然后设置优先级(通过HAL_NVIC_SetPriority)。
最后,我们给出一个完整的示例,展示如何在不影响HAL时基的情况下使用SysTick中断(通常不推荐,因为HAL库依赖于SysTick):
如果我们必须要使用SysTick中断,并且HAL库已经初始化,那么我们可以重新配置SysTick,并确保在中断服务函数中调用HAL_IncTick()(如果HAL的时基仍然需要的话)。
例如:
void SysTick_Handler(void)
{
HAL_IncTick(); // 保持HAL的时基
// 添加自己的处理代码
}
但是,这样会增加中断处理时间。更好的做法是使用另一个定时器作为HAL时基(通过HAL_SetTickFreq和HAL_SetTick函数改变时基源)。
因此,对于大多数应用,我们建议使用单独的定时器(如TIM)来产生中断,而不是复用SysTick。
但是,如果问题只是关于如何使能SysTick中断,那么答案已经明确。
结论:不要使用NVIC_EnableIRQ或HAL_NVIC_EnableIRQ来使能SysTick中断,而应该使用SysTick_Config或直接设置SysTick控制寄存器的TICKINT位。
相关问题:</think>### SysTick 中断的正确使能方式详解
在 STM32 开发中,**SysTick 中断的使能方式与其他外设中断完全不同**,这是一个关键区别点。SysTick 是 Cortex-M 内核的组件,而不是外设中断,因此不能使用常规的 `HAL_NVIC_EnableIRQ()` 或 `NVIC_EnableIRQ()` 函数来使能。
#### 正确使能 SysTick 中断的方法
**1. 使用 CMSIS `SysTick_Config()` 函数(推荐)**
这是最标准且安全的方式:
```c
#include "stm32f4xx.h" // 根据芯片系列包含对应头文件
int main(void) {
// 配置 SysTick 每 1ms 中断一次
if (SysTick_Config(SystemCoreClock / 1000)) {
// 错误处理:配置失败
while (1);
}
// 设置 SysTick 中断优先级(可选,但推荐)
NVIC_SetPriority(SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
while (1) {
// 主循环
}
}
// SysTick 中断处理函数
void SysTick_Handler(void) {
// 这里添加你的中断处理代码
HAL_IncTick(); // 如果使用 HAL 库,需要调用此函数
}
```
**工作原理:**
- `SysTick_Config()` 自动设置重载值、使能中断和启动定时器
- 参数 `SystemCoreClock / 1000` 表示每秒中断 1000 次(1ms 周期)
- 返回 0 表示成功,非 0 表示重载值过大(超过 24 位)
**2. 直接操作 SysTick 寄存器(高级用法)**
```c
// 手动配置 SysTick 中断
void Enable_SysTick_Interrupt(uint32_t reload) {
SysTick->LOAD = reload - 1; // 设置重载值
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | // 使用处理器时钟
SysTick_CTRL_TICKINT_Msk | // 使能中断
SysTick_CTRL_ENABLE_Msk; // 启动定时器
// 设置中断优先级
NVIC_SetPriority(SysTick_IRQn, 0xF); // 最低优先级
}
```
#### 为什么不能使用 `HAL_NVIC_EnableIRQ(SysTick_IRQn)`?
1. **SysTick 不是标准外设中断**:
- SysTick 是 Cortex-M 内核异常(异常号 15)
- `SysTick_IRQn` 在 CMSIS 中定义为负数(通常是 -1)
- NVIC 函数只处理 IRQ 0 及以上的外设中断
2. **HAL 库的特殊处理**:
在 HAL 库中,`HAL_NVIC_EnableIRQ()` 对 SysTick 有特殊处理,但这不是标准做法:
```c
// HAL 库内部实现 (stm32f4xx_hal_cortex.c)
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn) {
if (IRQn == SysTick_IRQn) {
// 特殊处理:设置 SYSTICK 控制寄存器
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
} else {
NVIC_EnableIRQ(IRQn);
}
}
```
尽管 HAL 做了适配,但**强烈不建议**这样使用,因为:
- 不是标准 CMSIS 做法
- 可能与其他代码冲突
- 不会设置重载值,需要额外配置
#### SysTick 中断配置最佳实践
1. **初始化流程**:
```c
void SystemClock_Config(void) {
// ... 其他时钟配置 ...
// 1. 配置 SysTick
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000);
// 2. 设置中断优先级
HAL_NVIC_SetPriority(SysTick_IRQn, 15, 0);
}
```
2. **HAL 库中的 SysTick 处理**:
- HAL 初始化时会自动配置 SysTick
- 默认中断处理程序是 `SysTick_Handler()`
- 此函数调用 `HAL_IncTick()` 维护时基
3. **自定义 SysTick 中断**:
```c
// 重写弱定义的 SysTick_Handler
__weak void SysTick_Handler(void) {
// 1. 首先调用 HAL 的时基更新
HAL_IncTick();
// 2. 添加自定义逻辑
custom_tick_counter++;
check_system_health();
}
```
#### 常见问题解决
**问题:SysTick 中断不触发?**
1. 检查是否调用了 `HAL_Init()`(它会初始化 SysTick)
2. 确认重载值合理:`reload = SystemCoreClock / desired_frequency - 1`
3. 验证中断优先级设置:`NVIC_SetPriority(SysTick_IRQn, priority)`
4. 检查 `SysTick->CTRL` 寄存器:
```c
uint32_t ctrl = SysTick->CTRL;
int enabled = (ctrl & SysTick_CTRL_ENABLE_Msk) &&
(ctrl & SysTick_CTRL_TICKINT_Msk);
```
**SysTick 寄存器详解**:
| 寄存器 | 位域 | 功能 |
|--------|------|------|
| CTRL | ENABLE | 定时器使能 (1=启动) |
| | TICKINT | 中断使能 (1=使能) |
| | CLKSOURCE | 时钟源 (1=处理器时钟) |
| | COUNTFLAG | 计数标志 (1=自上次读取后已计数到0) |
| LOAD | RELOAD | 重载值 (0xFFFFFF) |
| VAL | CURRENT | 当前计数值 |
### 总结
- ✅ **正确方法**:使用 `SysTick_Config()` 或直接配置 `SysTick->CTRL` 寄存器
- ❌ **错误方法**:使用 `HAL_NVIC_EnableIRQ(SysTick_IRQn)` 或 `NVIC_EnableIRQ(SysTick_IRQn)`
- 在 HAL 项目中,优先使用 `HAL_SYSTICK_Config()`
- 始终在 `SysTick_Handler()` 中调用 `HAL_IncTick()` 维护 HAL 时基
- SysTick 优先级通过 `NVIC_SetPriority(SysTick_IRQn, priority)` 设置
阅读全文
相关推荐



















