|
本帖最后由 在水一方 于 2018-11-12 16:44 编辑
STM32的 堆栈分析,
STM32 xPSR影响的条件指令,
STM32寄存器说明:
R0-R12:通用寄存器
R0‐R12 都是 32 位通用寄存器,用于数据操作。但是注意:绝大多数 16 位 Thumb 指令只能访
问 R0‐R7,而 32 位 Thumb‐2 指令可以访问所有寄存器。
Banked R13: 两个堆栈指针
Cortex‐M3 拥有两个堆栈指针,然而它们是 banked,因此任一时刻只能使用其中的一个。
主堆栈指针(MSP),这个地址就是我们进入main 函数时使用的堆栈 查看工程的map文件 __initial_sp:
进程堆栈指针(PSP),由用户的应用程序代码使用。
R14:连接寄存器, 存储返回地址
/*在中断前未使用FPU*/
0xFFFF_FFF1 返回handler模式0xFFFF_FFF9 返回线程模式,并使用主堆栈(SP=MSP)
0xFFFF_FFFD 返回线程模式,并使用线程堆栈(SP=PSP)
/*在中断前使用FPU*/
0xFFFF_FFE1 返回handler模式 0xFFFF_FFE9 返回线程模式,并使用主堆栈(SP=MSP)
0xFFFF_FFED 返回线程模式,并使用线程堆栈(SP=PSP)
R15:程序计数寄存器指向当前的程序地址。如果修改它的值,就能改变程序的执行流特殊功能寄存器
程序状态字寄存器组(PSRs)
中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)
控制寄存器(CONTROL)
请在 《Cortex M3权威指南》中查看 26页寄存器的意义 ,在121页查看 SVC 和 PendSV 异常
1.堆栈的初始化
①. 堆栈的定义:uint32_t TEST_TASK_STK[128],这里存放着 临时变量压栈信息,以及R0~R15,xPSR 寄存器 。在任务切换的时候使用
②. 堆栈的位置: STM32为 堆栈向下生长的方式,所以我们的堆栈定义位置则是 TEST_TASK_STK[128 - 1] 这个数组的最后一位地址。
③. 堆栈的初始化:
堆栈在内存的的排列
;xPSR 状态寄存器 栈顶
;R15 PC
;R14 LR
;R12
;R3
;R2
;R1
;R0 任务形参
;手动保存寄存器 其他形参都在压栈中
;R14
;R11
;R10
;R9
;R8
;R7
;R6
;R5
;R4
uint32_t *stk = (uint32_t *)&TEST_TASK_STK[128 - 1]; /* 这里定义一个指针指向堆栈顶部 */
/* 这里的赋值顺序根据 寄存器在堆栈中的排列 */
*(stk) = (uint32_t)0x01000000L; /* xPSR 程序状态寄存器 这一部分值PSP自动保存 */
*(--stk) = (uint32_t)task; /* 加载任务函数 这里是我们线程函数 */
*(--stk) = (uint32_t)SX_TaskError; /* R14 (LR) 加载退出任务 错误函数 任务不能return */
stk -= 4; /* 这里存放 R12, R3, R2 and R1. 四个寄存器 */
*(--stk) = (uint32_t)p_arg; /* R0 : 形参 任务的入口形参 */
*(--stk) = (uint32_t)0xfffffffdL; /* R14默认返回线程模式,并使用线程堆栈(SP=PSP)*/
stk -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
此时堆栈的指针位置在 stk ,在任务切换的时候 加载的就是此时的stk值。
④.通过链表 把 任务的 控制块 连接到一起,实现了任务的讯轮
2.任务的启动
①.调用启动函数
SXTCBCur:当前运行任务 初始化 为0
SXTCBReady :为当前就绪任务 初始化为 第一个需要运行的任务
SXTCBStart ; 任务启动函数
LDR R0, =NVIC_SYSPRI14 ; 设置 PendSV 优先级
LDR R1, =NVIC_PENDSV_PRI
STR R1, [R0]
CPSIE I ; 使能中断
SVC 0 ;触发SVC中断
NOP
SVC_Handler ; SVC_Handler异常中断处理
LDR R0, =SXRunning ; SSRunning = TRUE 全局变量 系统启动标志
MOV R1, #1
STRB R1, [R0]
LDR R0, =SXTCBCur ; 把 SXTCBReady 任务 加载到 SXTCBCur
LDR R1, =SXTCBReady
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] ; 我们将新的栈地址送到R0
LDMIA R0!, {R4-R11,R14} ; 将R4-R11,R14从栈弹出加回偏移地址(回到R0 的地址),在执行BX跳转指令时,硬件能从正确的栈地址自动弹出;R0-R3,R12,LR,PSR,PC的内容
MSR PSP, R0 ; 给加载 堆栈指针到 PSP中
BX R14 ;跳转到第一个任务
3.任务的切换
①.任务的切换思想
最简单的任务切换时轮训 没有优先级的,每次滴答定时器中断时从当前任务指向的下一个任务轮训查找就绪任务;有优先级的 通过优先级组,从高优先级轮训到低优先级查询人物状态。
②.任务的切换触发
LDR R0, =NVIC_INT_CTRL ; 触发PendSV_Handler 异常 中断(causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
③.任务的切换
PendSV_Handler
CPSID I ; 关闭中断 避免 任务切换被打断
MRS R0, PSP ; 保存 PSP 堆栈 (PSP 堆栈为线程堆栈,MSP 堆栈为 主堆栈 main)
TST R14, #0x10 ; 堆栈是否使用 FPU ?
IT EQ ; 如果使用
VSTMDBEQ R0!,{S16-S31} ; 要将高 16个vfp 寄存器压栈
STMDB R0!, {R4-R11,R14} ; 保存当前任务的 R4-R11, R14的寄存器值;进入中断时 R0-R3,R12,LR,PSR,PC会自动存储到当前任务堆栈 R0地址-32
LDR R1, =SXTCBCur ; 将当前的 TCB 赋值给R1 寄存器
LDR R1, [R1] ;
STR R0, [R1] ; 将 R0 保存的堆栈信息 保存到 R1 中 到此 堆栈保存已经结束 下面开始任务的切换
LDR R0, =SXTCBCur ; 把 SXTCBCur 赋值给 R0
LDR R1, =SXTCBReady ; 将 SXTCBReady 赋值给 R1
LDR R2, [R1] ; 加载 SXTCBReady 到R2寄存器
STR R2, [R0] ; 存储 SXTCBReady 到 R0寄存器
LDR R0, [R2] ; 我们将新的栈地址送到R0
LDMIA R0!, {R4-R11,R14} ; 将R4-R11,R14从栈弹出加回偏移地址(回到R0 的地址),在执行BX跳转指令时,硬件能从正确的栈地址自动弹出;R0-R3,R12,LR,PSR,PC的内容
TST R14, #0x10 ; 堆栈是否使用 FPU ? 如果使用 要将高 16个vfp 寄存器弹出
IT EQ
VLDMIAEQ R0!, {S16-S31}
MSR PSP, R0 ; 将R0中的栈地址送到PSP线程栈
ORR LR, LR, #0x04 ; 这句是确保后面执行BX命令时使用的是线程栈 SP,而不是主堆栈MSP
CPSIE I ; 开总中断
BX LR ; 执行中断返回,此时R0-R3,R12,LR,PSR,PC自动 ;出栈 在这里任务 任务切换完毕
由于不能上传文件,放网盘了
链接:https://pan.baidu.com/s/1Z731y7d_sf3RGUWc4FzBZQ
提取码:17r6
|
|