3. MCU底层寄存器配置:SysTick、NVIC与FPU
好,咱们进入第三章。这一章讲的是移植FreeRTOS之前,MCU底层必须配好的三样东西:系统滴答定时器(SysTick)、中断优先级控制器(NVIC),还有浮点运算单元(FPU)。
这三样配不好,操作系统要么跑不起来,要么跑起来也是各种诡异问题。我在项目里见过太多次了——任务切换卡死、中断响应乱套、浮点运算结果莫名其妙……追根溯源,都是底层寄存器没配对。
3.1 系统滴答定时器(SysTick)配置
SysTick是Cortex-M内核自带的24位递减计数器。FreeRTOS靠它产生周期性中断,作为时间基准。说白了,它就是操作系统的“心跳”。
3.1.1 SysTick寄存器一览
| 寄存器 | 地址(Cortex-M3/M4) | 功能 |
|---|---|---|
| SYST_CSR | 0xE000E010 | 控制与状态寄存器:使能、中断使能、时钟源选择 |
| SYST_RVR | 0xE000E014 | 重装载值寄存器:设定计数初值 |
| SYST_CVR | 0xE000E018 | 当前值寄存器:读取当前计数值 |
| SYST_CALIB | 0xE000E01C | 校准寄存器(一般不用) |
3.1.2 配置步骤与代码
配置SysTick其实就三步:
- 设定重装载值:根据时钟源频率计算。比如时钟源是72MHz,想要1ms中断一次,重装载值就是 72000000 / 1000 - 1 = 71999。
- 清空当前值:写任意值到CVR寄存器,它会自动清零。
- 配置CSR寄存器:选择时钟源,使能中断,启动定时器。
/* SysTick配置函数示例(基于STM32F4,HCLK=168MHz) */
void SysTick_Config(uint32_t ticks)
{
/* 1. 设置重装载值,最大不能超过2^24-1 */
SYST_RVR = ticks & 0x00FFFFFF;
/* 2. 清空当前值 */
SYST_CVR = 0;
/* 3. 配置控制寄存器:
Bit 0: ENABLE - 使能定时器
Bit 1: TICKINT - 使能中断
Bit 2: CLKSOURCE - 选择时钟源(1=处理器时钟,0=外部参考时钟) */
SYST_CSR = 0x07; /* 使能 + 中断使能 + 处理器时钟 */
}
/* 在FreeRTOS移植中,通常这样调用 */
/* 假设系统时钟 SystemCoreClock = 168000000 */
SysTick_Config(SystemCoreClock / configTICK_RATE_HZ);
/* configTICK_RATE_HZ 在FreeRTOSConfig.h中定义,通常为1000 */
3.1.3 时钟源选择
SysTick有两个时钟源可选:
- 处理器时钟(AHB时钟):精度高,但频率也高,重装载值可能溢出。
- 外部参考时钟(AHB/8):频率低,适合低频场景,但精度稍差。
我个人建议:除非系统时钟特别高(超过200MHz),否则都用处理器时钟。精度优先。
3.2 NVIC中断优先级配置
NVIC是Cortex-M内核的中断控制器。FreeRTOS需要用它来管理中断优先级,特别是要确保SysTick中断和PendSV中断的优先级设置正确。
3.2.1 优先级分组
Cortex-M3/M4支持中断优先级分组。你可以把优先级分为抢占优先级和子优先级。FreeRTOS建议使用4位抢占优先级、0位子优先级(即分组方式为NVIC_PriorityGroup_4)。
/* 设置优先级分组:4位抢占优先级,0位子优先级 */
NVIC_SetPriorityGrouping(0x05); /* 对应 PRIGROUP = 7,即4位抢占优先级 */
/* 设置SysTick中断优先级为最低(15) */
NVIC_SetPriority(SysTick_IRQn, 15);
/* 设置PendSV中断优先级为最低(15) */
NVIC_SetPriority(PendSV_IRQn, 15);
为什么是15?因为Cortex-M用4位表示优先级,数值越小优先级越高。15就是最低优先级。这样任何其他中断都能抢占SysTick和PendSV,保证系统响应及时。
3.2.2 NVIC配置完整流程
- 调用
NVIC_SetPriorityGrouping()设置分组方式。 - 调用
NVIC_SetPriority()设置SysTick和PendSV优先级为最低。 - 如果有其他外设中断,根据需求设置优先级(建议高于15)。
- 使能全局中断(通常默认已使能)。
3.3 FPU单元使能
如果你的MCU支持硬件浮点运算(比如Cortex-M4F、M7),一定要把FPU打开。否则所有浮点运算都会走软件模拟,速度慢得离谱。
3.3.1 FPU使能寄存器
FPU的控制寄存器在CPACR(Coprocessor Access Control Register)里。需要设置第20-23位,使能CP10和CP11协处理器。
/* FPU使能代码(适用于Cortex-M4F/M7) */
void FPU_Enable(void)
{
/* 读取CPACR寄存器 */
uint32_t cpacr = SCB->CPACR;
/* 设置CP10和CP11为全权限(0b11) */
cpacr |= (0x0F << 20);
/* 写回CPACR寄存器 */
SCB->CPACR = cpacr;
/* 数据同步屏障,确保配置生效 */
__DSB();
/* 指令同步屏障,刷新流水线 */
__ISB();
}
3.3.2 FPU与FreeRTOS的关联
FreeRTOS在任务切换时需要保存和恢复FPU寄存器。如果FPU没使能,任务切换时访问FPU寄存器会导致异常。所以:
- FPU必须在启动FreeRTOS调度器之前使能。
- 在FreeRTOSConfig.h中,需要定义
configUSE_TASK_FPU_SUPPORT为1(如果使用FPU)。 - 每个使用浮点运算的任务,栈空间要适当加大(FPU寄存器有32个,每个32位,共128字节)。
3.4 本章知识体系
下面这张图把本章三个核心模块的关系画清楚了:
3.5 配置检查清单
最后,给你一份我平时用的检查清单。移植完记得逐条核对:
| 序号 | 检查项 | 正确状态 |
|---|---|---|
| 1 | SysTick重装载值 | 不超过0xFFFFFF,且计算正确 |
| 2 | SysTick中断使能 | CSR寄存器的TICKINT位为1 |
| 3 | SysTick时钟源 | 选择处理器时钟(CLKSOURCE=1) |
| 4 | NVIC优先级分组 | 设置为4位抢占优先级(PRIGROUP=7) |
| 5 | SysTick中断优先级 | 设为最低(15) |
| 6 | PendSV中断优先级 | 设为最低(15) |
| 7 | FPU使能 | CPACR寄存器CP10/CP11为全权限 |
| 8 | FPU使能时机 | 在启动调度器之前完成 |