移植μC/OS-II + FPU到stm32f407方法主要参考了正点原子和 FPU堆栈修改.文章。在不使用浮点运算的情况下,我们无需开启STM32F407的FPU;而频繁地进行浮点运算时,则建议开启硬件PFU,运算的效率将大幅提升,尤其在实时操作系统中,运算的快慢,关系到整个系统的实时性。
在多任务运行时,且在没有针对PFU进行修改ucos源码的情况下,我们调用sprintf进行浮点数格式转换,可能使系统进入HardFault_Handler函数,意味着,程序发生堆栈溢出,程序跑飞。
为此,我们需要针对正点原子提供的移植方法基础上,再进一步针对FPU进行内核的源码修改。
我们针对FPU,主要修改 os_cpu_c.c 的OSTaskStkInit,以及os_cpu_a.asm的 PendSV_Handler和PendSV_Handler_Nosave。
μC/OS-II源码可以在官网上下载,但需要登录。为了节省时间,可以直接使用正点原子资料包里的
μC/OS-II源码。
拿到源码后,我们在工程目录需要建立三个文件夹,用来存放μC/OS-II源码文件。
三个文件夹分别为:CONFIG、CORE、PORT。
在CONFIG文件夹中,添加includes.h 和 os_cfg.h。includes.h包含了相关的头文件,os_cfg.h用于配置和裁剪μC/OS-II。
在CORE文件夹中,添加4个文件。
在CORE文件夹中,将内核源码,服务函数源码都添加进来。
首先,将源文件添加到工程上。
然后,添加相应的头文件路径。这一步很重要,否则编译不通过。
首先,编译一下,你会发现很多错误。接下来,我们需要整理一下源码。
在ucos_ii.h中,我们将#include
将OS_CPU_PendSVHandler函数名改为 PendSV_Handler。接着修改内部源码。
OS_CPU_PendSVHandler
CPSID I ; Prevent interruption during context switch
MRS R0, PSP ; PSP is process stack pointer
CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
STM R0, {R4-R11}
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SUB R0, R0, #0x40
VSTM R0, {D8-D15}
#endif
LDR R1, =OSTCBCur ; OSTCBCur->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out
将OS_CPU_PendSVHandler_nosave函数名修改为 PendSV_Handler_Nosave。接着修改内部源码。
PendSV_Handler_Nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14}
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCur ; OSTCBCur = OSTCBHighRdy;
LDR R1, =OSTCBHighRdy
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->StkPtr;
;#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
VLDM R0, {D8-D15}
ADD R0, R0, #0x40
;#endif
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20
;#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
BIC.W LR, LR, #0x10
;#endif
MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; Ensure exception return uses process stack
CPSIE I
BX LR ; Exception return will restore remaining context
end
修改函数名字的目的是:为了匹配stm32f407的启动文件里面中断函数名字,避免去修改启动文件。但我们需要将原来的中断服务函数屏蔽掉,不然编译发生错误。
1)将void PendSV_Handler(void)函数屏蔽,不然重复定义。
2)修改SysTick_Handler函数
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
OSIntEnter(); //进入中断
OSTimeTick(); //调用ucos的服务函数
OSIntExit(); //触发任务切换中断
/* USER CODE END SysTick_IRQn 1 */
}
提示:每次生成stm32cubemx工程时,记得将void PendSV_Handler(void)函数屏蔽。
1)修改OSTaskStkInit函数
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
int i = 0;
OS_STK *stk;
(void)opt; /* 'opt' is not used, prevent warning */
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
stk = ptos; /* Load stack pointer */
/* Registers stacked as if auto-saved on exception */
*(stk) = (INT32U)0x00000000uL;
*(--stk) = (INT32U)0x00000000uL;
for (i = 0; i < 16; ++i)
{
*(--stk) = (INT32U)(0x00000000uL);
}
*(--stk) = (INT32U)0x01000000uL; // xPSR
*(--stk) = (INT32U)task; // Entry Point
*(--stk) = (INT32U)OS_TaskReturn; // R14 (LR)
*(--stk) = (INT32U)0x12121212uL; // R12
*(--stk) = (INT32U)0x03030303uL; // R3
*(--stk) = (INT32U)0x02020202uL; // R2
*(--stk) = (INT32U)0x01010101uL; // R1
*(--stk) = (INT32U)p_arg; // R0 : argument
// Remaining registers saved on process stack
*(--stk) = (INT32U)0x11111111uL; // R11
*(--stk) = (INT32U)0x10101010uL; // R10
*(--stk) = (INT32U)0x09090909uL; // R9
*(--stk) = (INT32U)0x08080808uL; // R8
*(--stk) = (INT32U)0x07070707uL; // R7
*(--stk) = (INT32U)0x06060606uL; // R6
*(--stk) = (INT32U)0x05050505uL; // R5
*(--stk) = (INT32U)0x04040404uL; // R4
for (i = 0; i < 16; ++i)
{
*(--stk) = (INT32U)(0x00000000uL);
}
#else
stk = ptos; /* Load stack pointer */
/* Registers stacked as if auto-saved on exception */
*(stk) = (INT32U)0x01000000uL; // xPSR
*(--stk) = (INT32U)task; // Entry Point
*(--stk) = (INT32U)OS_TaskReturn; // R14 (LR)
*(--stk) = (INT32U)0x12121212uL; // R12
*(--stk) = (INT32U)0x03030303uL; // R3
*(--stk) = (INT32U)0x02020202uL; // R2
*(--stk) = (INT32U)0x01010101uL; // R1
*(--stk) = (INT32U)p_arg; // R0 : argument
// Remaining registers saved on process stack
*(--stk) = (INT32U)0x11111111uL; // R11
*(--stk) = (INT32U)0x10101010uL; // R10
*(--stk) = (INT32U)0x09090909uL; // R9
*(--stk) = (INT32U)0x08080808uL; // R8
*(--stk) = (INT32U)0x07070707uL; // R7
*(--stk) = (INT32U)0x06060606uL; // R6
*(--stk) = (INT32U)0x05050505uL; // R5
*(--stk) = (INT32U)0x04040404uL; // R4
#endif
return (stk);
}
第一种是在stm32f407xx.h 添加 #define __FPU_USED 1U
第二种是 设置Option for target–>Target–>floating Point Hardware:Single Precision
还需添加宏,STM32F407xx__FPU_PRESENT=1,__TARGET_FPU_VFP,ARM_MATH_CM4,__CC_ARM,STM32F407xx 在Option for Target
4.1 首先,在stm32f4xx_hal.h中,滴答定时器默认频率为1K,HAL_TICK_FREQ_DEFAULT = HAL_TICK_FREQ_1KHZ (读者可以自行查找,找到它的位置)
所以,1ms进入一次中断 SysTick_Handler()
4.2 在OSTimeDlyHMSM函数中,它是这样设置时间的:
ticks = ((INT32U)hours * 3600uL + (INT32U)minutes * 60uL + (INT32U)seconds) * OS_TICKS_PER_SEC
+ OS_TICKS_PER_SEC * ((INT32U)ms + 500uL / OS_TICKS_PER_SEC) / 1000uL;
意味着,我们要设置OS_TICKS_PER_SEC,才能实现准确的延时。
OS_TICKS_PER_SEC 在os_cfg.h,它的意思是 设置1秒的ticks值。在这里,我们设置为1000,即得到1s。
所以,延时的机制如下:
OSTimeDlyHMSM用来设置一个时间--->
这个时间的设置与我们要设置OS_TICKS_PER_SEC有关,系统默认1ms进入一次中断,每次进入中断,都会将计数器减一。
假如,我们延时1秒,在OSTimeDlyHMSM内部,ticks = 1000,意味着,进入滴答定时器中断1000次,才能延时1秒