完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
|
|
相关推荐
1个回答
|
|
写下这篇文章的主要目的是对自己学习RTOS的历程做一个记录和总结,方便以后回忆翻看。以下内容主要来自宋岩先生翻译的《Cortex-M3权威指南》。
一、Cortex-M3寄存器简介 Cortex‐M3 处理器拥有 R0‐R15 的寄存器组。R0‐R12都是32位通用寄存器,用于数据操作。R13-R15是专用寄存器。 R0-R3:用作传入函数参数,传出函数的返回值。在子程序运行时,可将R0-R3用于任何用途。被调用函数在返回之前不必恢复R0-R3,如果调用函数需要再次使用 r0-r3 的内容,则它必须保留这些内容。 R4-R11:被用来存放函数的局部变量。如果调用函数使用了这些寄存器,它在返回之前必须恢复寄存器的值。 R13是堆栈指针(SP),Cortex‐M3 拥有两个堆栈指针,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。需要注意的是堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的。
main ;主程序 … BL Task ; 使用“分支并连接”指令呼叫 Task,PC= Task,并且 LR=main 的下一条指令地址 … Task … ; Task 的代码 BX LR ; 函数返回(如果 Task再次调用其他函数要使用 LR,必须在使用前 PUSH,否则返回时LR的值被改变程序就可能跑飞了) BX LR ; 函数返回(如果 Task再次调用其他函数要使用 LR,必须在使用前 PUSH,否则返回时LR的值被改变程序就可能跑飞了) R15是程序计数器(PC)指向当前的程序地址。如果修改它的值,就能改变程序的执行流程。 Cortex‐M3 还在内核水平上搭载了若干特殊功能寄存器,它们只能被专用的 MSR 和 MRS 指令访问,而且它们也没有存储器地址。这些寄存器包括:
PRIMASK, FAULTMASK 和 BASEPRI这三个寄存器用于控制异常(中断)的使能和除能。 控制寄存器(CONTROL)控制寄存器用于定义特权级别,还用于选择当前使用MSP还是PSP。 二、堆栈操作简介 堆栈操作就是对内存的读写操作,但是其地址由SP给出。寄存器的数据通过PUSH操作存入堆栈,以后用POP操作从堆栈中取回。在PUSH与POP的操作中,SP的值会按堆栈的使用法则自动调整,以保证后续的PUSH不会破坏先前 PUSH 进去的内容。堆栈的功能就是把寄存器的数据放入内存,以便将来能恢复之——当一个任务或者子程序执行完毕后恢复。正常情况下,PUSH与POP必须成对使用,而且参与的寄存器,不论是身份还是先后顺序都必须完全一致。当 PUSH/POP 指令执行时,SP指针的值也跟着自减/自加 通常在进入一个子程序后,第一件事就是把寄存器的值先PUSH入堆栈中,在子程序退出前再 POP 曾经 PUSH 的那些寄存器。如PUSH {R7,LR};寄存器的PUSH和POP 操作永远都是4字节对齐的——也就是说他们的地址必须是0x4,0x8,0xC,这样一来,R13 的最低两位被硬线连接到0,并且总是读出0(Read As Zero)。在分支时,无论是直接写PC的值还是使用分支指令,都必须保证加载到PC的数值是奇数(即 LSB=1),用以表明这是在Thumb状态下执行。倘若写了0,则视为企图转入ARM模式,CM3将产生一个fault异常。 在进入ISR(中断程序)时, Cortex-M3 会自动把一些寄存器压栈,这里使用的是进ISR之前使用的 SP指针( MSP 或者是 PSP)。离开 ISR 后,只要 ISR 没有更改过 CONTROL[1],就依然使用先前的 SP 指针来执行出栈操作。在RTOS中为了避免系统堆栈因应用程序的错误使用而毁坏,可以给应用程序专门配一个堆栈,不让它共享操作系统内核的堆栈。在这个管理制度下,运行在线程模式的用户代码使用 PSP,而异常服务例程则使用 MSP。这两个堆栈指针的切换是全自动的,就在出入异常服务例程时由硬件处理。如:进入异常时的自动压栈使用的是进程堆栈(PSP),进入异常 handler 后自动改为 MSP,退出异常时切换回 PSP,并且从进程堆栈上弹出数据。 Cortex-M3 使用的是向下生长的满栈,任意时刻SP都指向栈顶元素,所以空栈的MSP 的初始值必须是堆栈内存的末地址加1。举例来说,如果你的堆栈区域在 0x20007C00‐0x20007FFF 之间,当前的MSP是0x20007FC,此时出栈(POP)一个数据后栈中的数据就空了,由于一个32位数据占4个字节,MSP加4就变为0x20008000,那么 MSP 的初始值就必须是0x20008000。 void Task(void){ PUSH {R7,LR} . . . POP {R7,PC}} 以上例程 POP 的最后一个寄存器是 PC,并不是先前PUSH的LR。这其实是一个返回的小技巧。因为总要把先前LR的值弹出来,再使用此值返回,干脆绕过 LR,直接传给PC!那不怕LR的值没有被恢复吗?不怕,因为LR在子程序返回时的唯一用处就是提供返回地址,在返回后,先前保存的返回地址就没有利用价值了,所以只要PC得到了正确的值,不恢复也没关系。 三、汇编指令简介 下面仅对RTOS分析中遇到的汇编指令进行简介,如果需要了解更多请参考《Cortex-M3权威指南》或者《 ARMv7‐M应用程序级架构参考手册》( ARMv7‐M Application Level Architecture Reference Manual) LDR和STR指令 “加载(Load)”和“存储(Store)”是用于访问存储器的基础指令。加载指令 LDR 把存储器中的内容加载到寄存器中,存储指令 STR 则把寄存器的内容存储至存储器中。 LDR R0, [R1] ;将存储器地址为R1的内容读入寄存器R0。LDR R0, [R1, #8] ;将存储器地址为R1+8的内容读入寄存器R0。LDR R0, [R1], #8 ;将存储器地址为R1的内容存到R0中,同时R1=R1+8STR R1, [R0] ;将R1寄存器的值,传送到地址值为R0的(存储器)内存中STR R1, [R0],#8 ;将R0中的内容写入以R1为地址的存储器中,并将新地址R0+8写入R0。STR R1, [R0,#8] ;将R0中的内容写入以R1+8为地址的存储器中。 ARM指令集中,LDR通常都是作加载指令的,但是它也可以作伪指令。LDR伪指令的形式是“LDR Rn,=expr”。将一个32位常数值或者一个地址数据载入寄存器。 NVIC_INT_CTRL EQU 0xE000ED04 ;Interrupt control state register.LDR R0, =NVIC_INT_CTRL ;加载0xE000ED04到寄存器R0LDR R0, =0XFFFFFFF0 ;加载0xFFFFFFF0到寄存器R0LDR R0,=Buf ;加载变量Buf的地址到寄存器R0,即R0 = &Buf STMDB和LDMIA指令 STMDB SP!, {R0-R3,LR};等效于PUSH SP!, {R0-R3,LR}LDMIA SP!, {RO-R3,PC};等效于POP SP!, {RO-R3,PC} SP后面的“!”是什么意思?它表示要自增(Increment)或自减(Decrement)基址寄存器 SP的值,时机是在每次访问前(Before)或访问后(After)。增/减单位:字(4字节)。 B、BX、 BL、 BLX指令 以上指令为无条件跳转指令,以X结尾的跳转到包含在寄存器Rm中的地址处,不带X的跳转到标号所表示的地址处, B Label ;转移到Label处对应的地址Bx Rm ;转移到由寄存器Rm给出的地址,根据Rm的LSB切换处理器状态,BL Label;转移到Label处对应的地址,并且把转移前的下条指令地址保存到 LRBLx Rm ;转移到由寄存器Rm给出的地址,根据Rm的LSB切换处理器状态, ;并且把转移前的下条指令地址保存到 LR 在 BX和BLX中,Rm的最低位指示出在转移后,将进入的状态是 ARM(LSB=0)还是 Thumb(LSB=1)。既然 CM3 只在 Thumb 中运行,就必须保证 Rm的 LSB=1,否则会引起fault异常 。 MRS 和 MSR指令 这两条是访问特殊寄存器的指令,特殊寄存器主要包括xPSR( APSR+EPSR+IPSR)、MSP、PSP、PRIMASK、BASEPRI等,具体见《Cortex-M3权威指南》。 MRS Rd,Sreg ;加载特殊寄存器Sreg的值到通用寄存器Rd中,Rd不能是R15(PC)MSR Sreg, Rd ;加载通用寄存器Rd的值到特殊寄存器Sreg中 四、中断简介 在 ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。除了外部中断外,当有指令执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的 fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入异常状态的(常用于系统调用)。 中断响应过程简介 由于RTOS中任务切换的具体操作是在PendSV中断中进行的因此我们先了解一下中断响应的具体过程,当Cortex-CM3开始响应一个中断时,会在硬件层面自动执行以下过程:
取向量:从向量表中找出正确的异常向量,然后在服务程序的入口处预取指。由此可以看到各自都有专用总线的好处:入栈与取指这两个工作能同时进行。 更新寄存器:在入栈和取向量的工作都完毕之后,执行服务例程之前,还要更新一系列的寄存器:
当异常服务例程执行完毕后,需要很正式地做一个“异常返回”动作序列,从而恢复先前的系统状态,才能使被中断的程序得以继续执行。从形式上看,有3种途径可以触发异常 返回,如下表所示; 不管使用哪一种方式返回,都需要用到先前储的LR的值。合法的EXC_RETURN值共三个,如下所示: 有些处理器使用特殊的返回指令来标示中断返回,例如8051就使用reti。但是在CM3中,是通过把EXC_RETURN往PC里写来识别返回动作的。因此,可以使用上述的常规返回指令,从而为使用C语言编写服务例程扫清了最后的障碍(无需特殊的编译器命令,如__interrupt)。 在启动了中断返回序列后,下述的处理就将进行: 1. 出栈:先前压入栈中的寄存器在这里恢复(xPSR, PC, LR, R12以及R3‐R0由硬件自动出栈)。内部的出栈顺序与入栈时的相对应,堆栈指针的值也改回去。 2. 更新NVIC寄存器:伴随着异常的返回,它的活动位也被硬件清除。对于外部中断,倘若中断输入再次被置为有效,悬起位也将再次置位,新一次的中断响应序列也可随之再次开始。 SVC和PensSV中断简介 在这里简单介绍一下SVC和PendSV中断的功能,如果需要更深入的了解这两个中断请参考《Coretex-M3权威指南》的第7章内容。 SVC(系统服务调用,亦简称系统调用)和 PendSV(可悬起系统调用),它们多用于在操作系统之上的软件开发中。 SVC 用于产生系统函数的调用请求。例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就会产生一个 SVC 异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。 PendSV(可悬起的系统调用),它和 SVC 协同使用。一方面, SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高, 或是其它原因使之无法立即响应, 将上访成硬 fault), 应用程序执行 SVC 时都是希望所需的请求立即得到响应。另一方面, PendSV 则不同,它是可以像普通的中断一样被悬起的(不像 SVC 那样会上访)。 OS 可以利用它“缓期执行” 一个异常——直到其它重要的任务完成后才执行动作。 悬起 PendSV 的方法是: 手工往 NVIC 的 PendSV 悬起寄存器中写 1。 悬起后, 如果优先级不够高,则将缓期等待执行。 PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。 例如, 一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:执行一个系统调用或者系统滴答定时器(SYSTICK)中断,(轮转调度中需要)。通常在RTOS中把 PendSV 编程为最低优先级的异常。如果 OS 检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个 PendSV 异常,以便缓期执行上下文切换。 软件中断 软件中断,包括手工产生的普通中断,能以多种方式产生。最简单的就是使用相应的SETPEND寄存器;而更专业更快捷的作法,则是通过使用软件触发中断寄存器STIR,软件触发中断寄存器STIR(地址:0xE000_EF00),需要注意的是系统异常(NMI, faults, PendSV等),不能用此法悬起。而且缺省时就不允许用户程序改动NVIC寄存器的值。 五、汇编基础 标号是可选的, 如果有, 它必须顶格写并且不能包含任何空白字符(如空格或制表符)。标号的作用是让汇编器来计算程序转移的地址。操作码是指令的助记符,它的前面必须有至少一个空白符, 通常使用一个“Tab”键来产生,操作码可以全部用大写字母或者全部小写字母编写,但是不能使用混合大小写来编写操作码。立即数必须以“#”开头,注释均以”;”开头, 它的有无不影响汇编操作, 只是给程序员看的, 能让程序更易理解;常数使用“EQU”指示字来定义。 STACK_TOP EQU 0x20002000 ; SP初始值,常数 AREA |Header Code|, CODE DCD STACK_TOP ; 栈顶(MSP的) DCD Start ; 复位向量 ENTRY ; 指示程序从这里开始执行 Start ; 标号,主程序开始 ; 初始化寄存器 MOV r0, #10 ; 加载循环变量的初值 MOV r1, #0 ; 初始化运算结果的值 ; 计算 10+9+8+...+1 loop ; 标号,Loop ADD r1, r0 ; R1 += R0 SUBS r0, #1 ; R0自减,并且根据结果更新标志(有”S”后缀) BNE loop ; if (R0!=0) goto loop; ; 现在,运算结果在R1中 deadloop B deadloop ; 工作完成后,进入无穷循环 END ; 标记文件结束 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1752 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1611 浏览 1 评论
1052 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
721 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1666 浏览 2 评论
1926浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
711浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
560浏览 3评论
584浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
544浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-18 21:34 , Processed in 0.739446 second(s), Total 76, Slave 59 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号