完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
手上没有使用Cortex-M3内核的芯片,只有一个STM32F407VG的Discovery开发版,芯片芯片使用的是Cortex-M4F的内核,这个内核是基于ARMV7E-M架构的与Cortex-M3非常类似,只能说一个FPU(浮点运算单元)以及浮点操作需要的相关接口。也因为这个FPU导致进入中断的时候保存的夹子司机,不过这些都是像素影响自动保存的,对写任务切换代码没有,在文章的最后来简单分析下浮点寄存器如何入栈。
一,IAR代码工程 使用IAR和MDK建立的工程在系统初始化研究时是不一样的,MDK在启动文件的开头就初始化了场景的大小,而IAR如何在工程的大小定义是在文件中,具体的AR工程中修改初始信息可以参考这篇文章《STM32》编程:是时候深入理解了》,作者写的很清楚了。从icf文件中了解到MSP端口的大小为0x200,下面具体文件代码,os.asm文件是代码,用于实现任务的具体切换,这我们不再为MSP单独指定源c,火焰使用系统上电初始化时创建的火花。工程里一共包含两个文件main.c和_os.asm。main 代码如下,代码里拦截了RTOS创建主的代码,如果像Ucos一样在中断中使用RTOS自己的设置主,可以打开截断代码。 #include "stm32f4xx.h" /******************变量类型定义*******************************/ typedef unsigned int uint32; typedef struct TaskTCB { uint32 *StackPtr; }sTaskTCB,*pTaskTCB; typedef void( *pFun )( void ); /********************定义任务控制块************************************/ sTaskTCB StartTaskTCB; sTaskTCB SecondTaskTCB; pTaskTCB OSTCBCurPtr; pTaskTCB OSTCBHighRdyPtr; /***********************定义进程堆栈**********************************/ #define START_STACK_SIZE 48 #define SECOND_STACK_SIZE 48 uint32 StartTaskStack[START_STACK_SIZE]; uint32 SecondTaskStack[SECOND_STACK_SIZE]; /**********************定义主堆栈*****************************************/ //#define OS_CPU_STACK_SIZE 128 未使用 //uint32 OS_CPU_ExceptStk[OS_CPU_STACK_SIZE]; 未使用 //uint32 *g_OS_CPU_ExceptStkBase; 未使用 /*************************全局变量****************************************/ static int StartTaskFlag ,SecondTaskFlag,EndFlag; /*************************函数定义*****************************************/ extern void OSCtxSw(void); extern void OSStartHighRdy(void); void TaskSwitch(void) { if(OSTCBCurPtr == &StartTaskTCB) OSTCBHighRdyPtr=&SecondTaskTCB; else OSTCBHighRdyPtr=&StartTaskTCB; OSCtxSw(); } void StartTask(void) { StartTaskFlag = 1; TaskSwitch(); } void SecondTask(void) { SecondTaskFlag = 1; StartTaskFlag = 0; TaskSwitch(); } void EndTask(void) { EndFlag = 1; SecondTaskFlag = 0; while(1) { ; } } //任务创建函数 void Task_Create(sTaskTCB *pTcb,pFun task,uint32 *stk,uint32 stacksize) { uint32 *pStack;/*grows from high memory to low*/ pStack = &stk[stacksize];/*Top of stack*/ pStack = (uint32 *)((uint32)(pStack) & 0xFFFFFFF8u);//AAPCS(ARM application procedure all standard) Align the stack to 8bytes /*Registers stacked as if auto-saved on exception*/ *(--pStack) = (uint32)0x01000000uL; //xPSR bit24 thumb state bit *(--pStack) = (uint32)task; // Entry Point(PC) *(--pStack) = (uint32)EndTask; // R14 (LR) *(--pStack) = (uint32)0x12121212uL; // R12 *(--pStack) = (uint32)0x03030303uL; // R3 *(--pStack) = (uint32)0x02020202uL; // R2 *(--pStack) = (uint32)0x01010101uL; // R1 *(--pStack) = (uint32)0x00000000u; // R0 /*Remaining registers saved on process stack*/ *(--pStack) = (uint32)0x11111111uL; // R11 *(--pStack) = (uint32)0x10101010uL; // R10 *(--pStack) = (uint32)0x09090909uL; // R9 *(--pStack) = (uint32)0x08080808uL; // R8 *(--pStack) = (uint32)0x07070707uL; // R7 *(--pStack) = (uint32)0x06060606uL; // R6 *(--pStack) = (uint32)0x05050505uL; // R5 *(--pStack) = (uint32)0x04040404uL; // R4 pTcb->StackPtr=pStack; } int main() { //g_OS_CPU_ExceptStkBase =(tTaskStack*) ((tTaskStack)(OS_CPU_ExceptStk + 64)&0xFFFFFFF8); Task_Create(&StartTaskTCB,StartTask,StartTaskStack,START_STACK_SIZE); Task_Create(&SecondTaskTCB,SecondTask,SecondTaskStack,SECOND_STACK_SIZE); OSTCBHighRdyPtr=&StartTaskTCB; OSStartHighRdy(); return 0; } os_port.asm代码如下,代码中屏蔽了初始化MSP的代码,使用的是系统初始化时建立的堆栈。如果像Ucos一样在中断中使用RTOS自己设置的主堆栈,可以打开这段代码。 NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register. NVIC_SYSPRI14 EQU 0xE000ED22 ; System priority register (priority 14). NVIC_PENDSV_PRI EQU 0xFF ; PendSV priority value (lowest). NVIC_PENDSVSET EQU 0x10000000 ; Value to trigger PendSV exception. RSEG CODE:CODE:NOROOT(2) ;Define code segment align 4 bytes THUMB EXTERN g_OS_CPU_ExceptStkBase EXTERN OSTCBCurPtr EXTERN OSTCBHighRdyPtr PUBLIC OSStartHighRdy PUBLIC PendSV_Handler PUBLIC OSCtxSw ;LR(R14),用于在调用子程序时存储返回地址 ;BX 转移到由寄存器给出的地址 OSCtxSw LDR R0, =NVIC_INT_CTRL LDR R1, =NVIC_PENDSVSET STR R1, [R0] BX LR ; Enable interrupts at processor level OSStartHighRdy LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority LDR R1, =NVIC_PENDSV_PRI STRB R1, [R0] ;PSP 由用户的应用程序代码使用 MOVS R0, #0 ; Set the PSP to 0 for initial context switch call MSR PSP, R0 ;MSP 用于操作系统内核及异常处理例程 ;LDR R0, =g_OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase ;LDR R1, [R0] ;MSR MSP, R1 LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch) LDR R1, =NVIC_PENDSVSET STR R1, [R0] CPSIE I ; Enable interrupts at processor level OSStartHang B OSStartHang ; Should never get here PendSV_Handler CPSID I ; Prevent interruption during context switch MRS R0, PSP ; PSP is process stack pointer CBZ R0, OS_CPU_PendSVHandler_nosave ; if psp == 0 Skip register save the first time MRS R11,MSP ;监视代码可以删除 MRS R10,PSP ;监视代码可以删除 STMDB R0!, {R4-R11} ; Save remaining regs r4-11 on process stack LDR R1, =OSTCBCurPtr ; OSTCBCur->OSTCBStkPtr = SP; Update top of stack LDR R1, [R1] STR R0, [R1] ; R0 is SP of process being switched out ; At this point, entire context of process has been saved OS_CPU_PendSVHandler_nosave LDR R0, =OSTCBCurPtr ;R0 = &gOSCurrentTCB (Double Pointer)OSTCBCur = OSTCBHighRdy; LDR R1, =OSTCBHighRdyPtr ;R1 = &gOSHiRdyTCB (Double Pointer) LDR R2, [R1] ;R2 = gOSHiRdyTCB STR R2, [R0] ;ggOSCurrentTCB = gOSHiRdyTCB; LDR R0, [R2] ; Struct first address is stack ptr, R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr; LDMIA R0!, {R4-R11} ; Restore r4-11 from new process stack MSR PSP, R0 ; Load PSP with new process SP ORR LR, LR, #0xF4 ; Ensure exception return uses process stack CPSIE I BX LR ; Exception return will restore remaining context END 二、仿真分析 仿真分析主(MSP)使用启动文件定义的0x2000(8K)的使用系统上,不再单独定义。 SP的值 1、 初始化任务初始化函数的执行完成StartTaskStack内容如下: 其中0x08000030F是EndTask函数的地址,0x800_02ED是StartTask函数的地址,此时StartTaskStack目标为0X2000_0080,图如下 SecondTask任务的进程过程SecondTaskStack内容如下: 其中0x0800_030FEndTask函数的地址,0x0800_02FB是SecondTask函数的地址,此时SecondTaskStack追踪执行为0x2000_0140,如下图 2、启动第一个任务是 OSStartHighRdy函数将PSP零后触发PendSV中断,进入PendSVHandler函数,因为运行这第一次启动任务,因此跳过保存R4-R11的过程,将进程触发切换到StartTaskStack并从它里面出栈8个数据到R4-R11,出栈后的R0- R11寄存器内的值如下图所示,和StartTaskStack初始化时放入的数据一致。 出栈8个数据完成后堆栈指针由0X2000_0080变为0x2000_00A0,如下图所示的R0的值,则此时PSP的值为0X2000_00A0。 执行ORR LR,LR,#0xF4后,LR的值由0XFFFF_FFE9更改为0xFFFF_FFFD,LR的值如下图所示,确保执行BX LR退出中断后进入线程模式,并使用PSP,从退出中断再到下次进入中断中断后下中断SP值表示的都是PSP的值。 退出中断后PSP,硬件自动从08个启动进程使用StartTaskStack的0X0000_0_A0处出栈数据到调用XPSR R0,出栈动作完成后进程任务PSP由0X2000_00A0变为0x2000_00C0, 因为如下图所示SP的值。StartTaskStack中对应PC位置存储在StartTask函数路径的值0x0800_02ED,因为一条指令占两个字节,此程序会跳转到0x0800_02EC取指开始运行StartTask函数。 3,任务切换 StartTask运行后调用TaskSwtich触发PendSVHandler主动进行任务切换;进入中断前CM3硬件自动压栈寄存器xPSR寄存器-R0值到StartTaskStack堆栈当中,此时进程堆栈指针PSP变更为0x2000_0090; StartTaskStack堆栈中的数据如下图所示。 在代码中我们预埋了R10和R11来确认进入中断后PSP和MSP的值,如下图所示PSP的值为0x2000_0090,MSP的值为0x1000_1F90,进入中断函数后SP表示是MSP的值。 进入PendSVHandler操作处理函数后判断到PSP值不零,因此会执行保留值判断R4-R11的,把控制R4-R11的压栈到StarTaskStack中,压栈完成后StarTaskStack的值如下图所示。 过程中描边轨迹PSP由0x2000_0090变更为:0x2000_0070并更新到任务控制块当中,如下图所示。 执行完标PendSV_NO_SAVE的代码后,过程由StarTaskStack切换为SecondTaskStack;从SecondTaskStack出栈8 R11,SecondTaskStack的堆栈指针由0x2000_0140变为0x2000_0160,如下图所示。 指令MSR PSP,R0表示把0x2000_0160赋值为PSP,执行完这条指令后PSP就指向了堆栈SecondTaskStack;退出中断后,CM3硬件继续自动从SecondTask栈8个数据到栈XPSR-R0;PSP更改为0x2000_0180,如图所示 。PC = 0X8000_02FA,程序跳转到SecondTask运行。 |
|
|
|
4、函数返回
SecondTask运行后会再次触发PendSV中断任务后会触发继续运行StartTask,现在有一个疑问从PendSV的中断返回后代码会从StartTask的哪个位置开始运行以及StartTask运行结束后代码会转向哪里? 现在来分析一下此时StartTaskStack堆栈里的内容,从0x2000_0070 - 0X2000_00AC这16一个寄存器的数据是先切换到SecondTask运行之前的,R10和R11的值被我们保护的PSP和MSP的值所以会和聚合初始化的值不一样,那么剩下的0x2000_00B0 – 0x2000_00BC这4数据是从哪里来的呢? 从任务栈后进先出的特性分析这4个值入栈是早于xPSR – R0由3中断时由硬件自动压压的,进入中断时间StartTask调用了程序,前面准备知识的文章中讲过在一个子程序时,要做的第一件事就是把相关的进入感应先PUSH入栈,下面来看StartTask和TaskSwitch的反寄代码。 进入StartTask函数后,执行PUSH {R7,LR}指令将R7和LR压入StartTaskStack堆栈中,R7和LR的值在进行任务创建初始化时被分别赋值为了0x07070707和TaskEnd函数的指针,因此0x2000_00B8和0x2000_00BC存储的是他们两个的值。 在StartTask函数中执行BL TaskSwith时LR摄像头的值将被更新为下一条指令的地址即0x8000_02F8,一条指令占两个字节,所以实际LR的值为0x8000_02F9,进TsakSwitch入函数后首先将R7和LR入栈, 0x2000_00B4和0x2000_000B0分别存储LR和R7的值。 SecondTask调用TaskSwtich触发PendSVHandler主动进行任务切换后,在PendSVHandler中断函数里进程堆栈由SecondTaskStack切换到StartTaskStack;首先出栈8数据到寄存器R4-R11,堆栈指针由0x2000_0070退出变为0x2000_0090,退出后,CM3硬件继续自动出栈8个数据到XPSR – R0,此时PSP变为0x2000_00B0,PC = 0x8000_02EA;执行程序跳转到TaskSwitchPOP{R0,PC}指令,PSP = 0X2000_00B8 ,PC = 0X80002F9,因为一个指令占两个字节,PC从0x8000_02F8处取指运行,如下图所示 程序继续跳转到StartTask执行POP{R0,PC},则PSP = 0x2000_00C0,PC = 0x8000_030F,因为一条指令占两个字节,所以跳转到0x8000_030E处取指开始执行EndTask 三、Cortex-M4F的Lazy堆栈 系统上电初始化完成后MSP的值为0x1000_2000,进入主函数后首先PUSH{R,LR}后运行0x1000_1FF8,进入次PendSV重启时自动入栈7XPSR – R0的值,为什么会变成0x1000_1F90左右入栈的26个 判断呢?CM4F内核LazyStack特性每次上电都是打开的,会在中断的时候自动保存浮点计算相关的控制器S0 – S15以及FPSCR(剩的S16 – S16 – S16 – S 需要用户手动保存),同时如果你没有在中断的使用FPU,核心的兴趣在中为这几个用户界面而不是他们真正入栈,同时如果在中断返回时将 EXC_RET 的第 4 位置1,那么再中断触发时系统都不会保存这 几个寄存器。为了防止 XPSR – R0 这些在入栈时浮点对再浮点操作产生干扰,浮点因为 RTOS中第一次触发结束SV中断时自动入栈了XPSR – R0以及S0 – 15和FPSR共25个磁盘,因为浮点位置入栈格式特殊需要一个,所以数据地址共减少了26个的值。由于中断退出返回时LR的值是0XFFFF_FD即EXC_RETURN的第41条,中断中断再触发时硬件就不用自动保存浮点了,保存XPSR-R0分类。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1618 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1545 浏览 1 评论
979 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
683 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1597 浏览 2 评论
1865浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
647浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
517浏览 3评论
534浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
506浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-23 03:07 , Processed in 0.968337 second(s), Total 49, Slave 43 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号