第三讲:移植前的准备工作——理解英飞凌启动流程、中断向量表配置、系统时钟与定时器初始化

好,咱们进入正题。这一讲的内容,说白了就是移植 FreeRTOS 之前,你得先把英飞凌这颗芯片的“脾气”摸清楚。我见过不少朋友,拿到开发板就急着往上怼 FreeRTOS 源码,结果系统跑不起来,连 HardFault 都找不到原因。嗯,其实问题往往出在最底层——启动流程、中断向量表、时钟和定时器,这三样东西没搞对,后面全是白搭。

3.1 英飞凌 MCU 的启动流程——从复位到 main()

我个人习惯,拿到一款新芯片,第一件事就是看它的启动文件。英飞凌的启动流程,跟 ARM Cortex-M 标准启动流程大体一致,但有一些自家的小脾气。

芯片上电复位后,硬件会做这几件事:

  1. 从 0x00000000 地址读取栈顶指针,赋值给 MSP(主栈指针)。
  2. 从 0x00000004 地址读取复位向量,跳转到 Reset_Handler。
  3. Reset_Handler 里,会依次调用 SystemInit()、初始化 .bss 段、初始化 .data 段,最后跳转到 main()。

这里有个坑,我踩过。英飞凌某些系列(比如 XMC4000 系列)的启动文件里,SystemInit() 是在进入 main() 之前被调用的。但如果你用的是第三方 IDE 生成的工程,SystemInit() 可能被放在了别的位置。我曾经在一个项目里,发现时钟配置根本没执行,就是因为启动文件被改过,SystemInit() 被跳过了。嗯,所以移植前,务必确认你的启动文件里,SystemInit() 确实被调用了。

关键点:FreeRTOS 的移植,依赖一个稳定的系统时钟。而系统时钟的初始化,就在 SystemInit() 里。这一步错了,后面全错。

3.2 中断向量表配置——FreeRTOS 的“命门”

FreeRTOS 能实现任务切换,靠的就是 SysTick 中断和 PendSV 中断。这两个中断的向量,必须正确配置在中断向量表中。

英飞凌 MCU 的中断向量表,默认放在 Flash 起始地址(0x00000000)。但 FreeRTOS 运行起来后,有时需要把向量表重映射到 SRAM 里。为什么?因为有些场景下,你需要动态修改中断向量(比如实现 IAP 在线升级)。

配置方法很简单,就一行代码:

SCB->VTOR = (uint32_t) &__Vectors;

但要注意,__Vectors 这个符号,必须在链接脚本里正确导出。我见过有人直接写了个固定地址,结果程序跑飞了。你想想看,如果向量表地址不对,中断来了 CPU 都不知道该跳到哪里去,不跑飞才怪。

我的建议:移植 FreeRTOS 时,先把 SCB->VTOR 的配置放在 SystemInit() 里,或者放在 FreeRTOS 的 prvSetupHardware() 函数中。这样能保证在第一个中断到来之前,向量表已经就位。

另外,英飞凌的某些芯片,中断优先级分组默认是 4 位抢占优先级、0 位子优先级。FreeRTOS 要求使用 4 位抢占优先级(即 NVIC_PriorityGroup_4)。这个配置通常在 FreeRTOSConfig.h 里通过宏定义完成:

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY   0x0f
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

嗯,这里要注意,英飞凌的优先级数值越大,优先级越低。跟一些其他厂商的芯片是反的。我第一次用英飞凌时,就因为这个搞反了,导致高优先级中断一直抢不过低优先级中断,查了两天才发现。

3.3 系统时钟与定时器初始化——给 FreeRTOS 一个心跳

FreeRTOS 的心跳,默认由 SysTick 定时器提供。SysTick 是一个 24 位的递减计数器,挂在系统时钟上。配置 SysTick 的代码,通常长这样:

void vPortSetupTimerInterrupt(void)
{
    // 配置 SysTick 重装载值
    // 假设系统时钟为 120MHz,心跳周期为 1ms
    uint32_t ulReloadValue = (configCPU_CLOCK_HZ / configTICK_RATE_HZ) - 1;
    
    // 写入重装载寄存器
    SysTick->LOAD = ulReloadValue;
    
    // 清零当前值
    SysTick->VAL = 0;
    
    // 使能 SysTick,使用处理器时钟,使能中断
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | 
                    SysTick_CTRL_TICKINT_Msk   | 
                    SysTick_CTRL_ENABLE_Msk;
}

这里有个细节,我特别想强调:configCPU_CLOCK_HZ 这个宏,必须跟你实际配置的系统时钟一致。 英飞凌的芯片,系统时钟来源可以是内部振荡器、外部晶振、PLL 倍频等。如果你在 SystemInit() 里配置了 PLL 输出 120MHz,但 configCPU_CLOCK_HZ 写的是 80MHz,那 SysTick 的溢出时间就不准了。FreeRTOS 的心跳不准,任务调度就会乱套。

避坑指南:我曾经在一个项目里,因为修改了 PLL 配置,但忘了更新 configCPU_CLOCK_HZ,导致系统跑起来后,任务切换频率忽快忽慢。用逻辑分析仪抓了波形才发现,SysTick 的中断间隔根本不是 1ms,而是 1.5ms。嗯,从那以后,我每次改时钟配置,都会第一时间去检查这个宏。

除了 SysTick,英飞凌的芯片还有丰富的通用定时器(如 CCU4、CCU8、GPT12 等)。如果你不想用 SysTick 做心跳,也可以用这些外设定时器。但我不建议这么做,除非你有特殊需求(比如 SysTick 被其他高优先级中断占用了)。

用通用定时器做心跳,需要额外配置:

  • 使能定时器时钟
  • 配置定时器周期
  • 使能定时器中断
  • 在中断服务函数里调用 xPortSysTickHandler()

说白了,就是多绕了一圈。我个人习惯,能用 SysTick 就用 SysTick,简单可靠。

3.4 小结——移植前的“三查”

好,这一讲的内容,我帮你总结成“三查”:

检查项 检查内容 常见问题
一查启动流程 SystemInit() 是否被调用?.bss/.data 段是否初始化? SystemInit() 被跳过,时钟未配置
二查中断向量表 SCB->VTOR 是否正确?优先级分组是否为 Group4? 向量表地址错误,优先级数值搞反
三查系统时钟 configCPU_CLOCK_HZ 是否与实际时钟一致?SysTick 配置是否正确? 宏定义与实际时钟不匹配,心跳周期不准

这三样东西,你移植前花半小时确认一遍,后面能省下好几个小时的调试时间。嗯,我每次做新平台的移植,都会打印出系统时钟值和 SysTick 的溢出值,亲眼确认一遍才放心。

下一讲,我们会正式开始 FreeRTOS 的移植工作,把内核源码“塞”进英飞凌的工程里。到时候,你会发现,前面这些准备工作做得越扎实,移植过程就越顺利。