STM32F401xx是意法半导体新推出的Cortex-M4内核的MCU,相较于已经非常流行的STM32F407xx和STM32F427xx等相同内核的MCU而言,其特点是功耗仅为128uA/MHz,且存在64pin封装的小封装器件。我设计一款新产品使用了STM32F401xx,想把自己熟悉的uC/OS-II实时操作系统移植到这款MCU上。懒得从底层开始从头移植,偷懒从原子的“探索者”开发板移植的uC/OS-II开始修改。完成后在板子上一跑,发现系统时钟总存在约5%的偏差。仔细搜索代码后才发现,问题在把原子的uC/OS-II移植直接用在STM32F401上时的一个小bug,把发现问题的过程和解决办法分享给大家。
以下原创内容欢迎网友转载,但请注明出处:http://cnblogs.com/helesheng
一、把探索者开发板移植的uC/OS-II用在STM32F401xx上
原子的探索者开发板使用了STM32F407ZG,其内核是和32F401xx 相同的Cortex-M4,外设也基本相同。简单地将原子在探索者开发板上移植的uC/OS-II工程中的Device修改为STM32F401RC,编译后发现大量的预定义找不到。想到可能是代表处理器的预定义的宏没有修改,在Option中的C/C++页中,将Preprocessor Symbols改为代表401的“STM32F401xx,USE_STDPERIPH_DRIVER”,直接编译通过,心中一阵窃喜。
图1 修改预定义
二、uC/OS-II系统时钟偏差的小问题
运行之前在其它款STM32上写的uC/OS-II程序,开始一切顺利,但随后用示波器仔细分析各个任务内部及相互间的时间关系时,发现总存在+5%左右的时间误差,即实际系统时钟的周期只有理论系统时钟的95%左右。以每秒系统节拍数OS_TICKS_PER_SEC设置为200为例,每个OSTimeDly(1);的任务延迟仅为4750us。
开始以为是原子编写的延迟函数void delay_ms(u16 nms);的问题,仔细阅读代码后发现,这个函数只是在调用系统延迟函数OSTimeDly();的基础之上,将无法由系统延迟实现的us级延迟改由硬延迟实现。虽然我个人非常不赞同这种做法,因为这会造成uC/OS-II时间调度的盲区,从而影响uC/OS-II系统的实时性。但这不至于造成系统时钟的偏差,继续查找问题的原因。
既然调用系统延迟函数OSTimeDly的过程没有问题,那么只可能是系统时钟本身出了问题。Cortex-M内核的uC/OS移植都会用了SysTick定时器的中断构建系统时钟,因此在SysTick上找原因。打开MDK,连接自制的STM32F401电路板和仿真器,进入Debug模式,运行程序,让uC/OS完成初始化配置,然后暂停程序;在外设(peripheral)菜单中找到SysTick定时器,界面如下图所示。
图2 System tick定时器状态监视器
其中重装寄存器中的值为0xC350,即50_000,仔细一想发现有问题——STM32F401的主频为84MHz,那要达到200Hz的系统时钟,无论如何都不可能把System Tick的初值配置为50_000啊!于是开始查找uC/OS移植代码中配置System Tick的部分,原来在探索者开发板移植代码中初始化延迟函数delay_init();中。看看原子的代码。
图3 delay_init();函数原来的代码
原子的代码简洁清晰,将System Tick的时钟配置为AHB时钟的1/8,在计算System Tick的初值。其中红圈中的一句是计算AHB时钟的八分之一,为后续计算定时器初始值做准备。但进一步仔细查看后发现变量SYSCLK中存放的AHB时钟数是以MHz作为单位的,对STM32F401xx而言,就是84。84无法整除8,而赋值语句左边的变量reload却是int型变量,从而导致了红圈中的一句计算误差。这样即使后一句将reload的单位切换回了Hz,也无法挽回前一句无法整除造成的计算误差。而这真是uC/OS系统时钟误差5%的真正原因!对这两句进行简单修改——现将SYSCLK折算为以Hz为单位,即可保证足够的计算精度。
图4 修改过的delay_init();函数代码
修改过后,uC/OS的系统时钟又恢复了原有的准确性。
究其原因并不是原子探索者开发板移植的uC/OS的问题,因为探索者采用的STM32F407ZG运行在168MHz,能够整除8,不存在这个问题。这里给大家展示这个过程,一是给大家在STM32F401上移植uC/OS做些探索,二是分享一下嵌入式调试和查找问题的点滴思路和心得。