FreeRTOS知识铺垫——读Cortex-M3 权威指南

目录

  • 前言
  • 指令集
  • 中断与异常
  • 操作模式和特权极别
  • 寄存器组
  • 中断屏蔽
  • 双堆栈机制
  • 非对齐数据传送
  • Fault 类异常
      • 总线 Faults
      • 存储器管理 faults
      • 用法 faults
      • 硬fault
  • SVC与PendSV

前言

····写本文的目的是项目中使用了FreeRTOS,带来了太多的HardFault问题,定位这些问题需要内核基础知识,本文是我看《Cortex-M3 权威指南》侧重于操作系统的一些总结。后面会跟随一些解决HardFault问题的记录。

指令集

  • ARM指令长度:32位
  • Thumb指令长度:16位
  • Thumb-2指令长度:32位

····ARM指令和Thumb指令不能共存,Thumb-2指令和Thumb指令可以共存。CM3中放弃了ARM指令,使用的是Thumb-2指令集

中断与异常

····凡是打断程序顺序执行的事件,都被称为异常(exception),除了外部中断外,当有指令
执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的 fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。程序代码也可以主动请求进入异常状态,用于系统调用。

操作模式和特权极别

操作模式:处理者模式和线程模式,handler mode 和 thread mode。
特权分级:特权级和用户级

我的理解:
····普通应用程序代码,如main函数刚进入执行的代码,是处于thread mode下执行的。中断服务函数的代码是处于handler mode模式下执行的。需要注意:准确的来说,handler mode执行的是异常下的代码,中断如定时中断只是异常的一种
····CM3 运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;但是异常服
务例程必须在特权级下执行。复位后,处理器默认进入线程模式,特权极访问。在特权级下,程序
可以访问所有范围的存储器(如果有 MPU,还要 在 MPU 规定的禁地之外),并且可以执行所有指令。
两个分类的组合:
FreeRTOS知识铺垫——读Cortex-M3 权威指南_第1张图片
····组合切换
FreeRTOS知识铺垫——读Cortex-M3 权威指南_第2张图片

  • CONTROL[0]=0:特权级的线程模式
  • CONTROL[0]=1:用户级的线程模式
    FreeRTOS知识铺垫——读Cortex-M3 权威指南_第3张图片
    FreeRTOS知识铺垫——读Cortex-M3 权威指南_第4张图片

寄存器组

FreeRTOS知识铺垫——读Cortex-M3 权威指南_第5张图片
16位Thumb指令集可以访问低位寄存器
32位Thumb-2指令集可以访问所有通用寄存器

  • R13:一个物理地址放入两个寄存器,通过不同的处理模式区分。
    • 主堆栈指针(MSP):复位后缺省使用的堆栈指针
    • 进程堆栈指针(PSP):由用户的应用程序代码使用
  • R14:LR连接寄存器,当呼叫一个子程序时,由 R14 存储返回地址
  • R15:程序计数寄存器PC,保存下一条要执行的指令

以下为特殊功能寄存器

  • 程序状态寄存器PSRs,PSR,或者xPSR。可以通过下述名称单独访问,也可以通过PSR,xPSR一起访问。
    • 应用程序 PSR(APSR)
    • 中断号 PSR(IPSR)
    • 执行 PSR(EPSR)
  • PRIMASK:除能所有的可屏蔽中断,配合BASEPRI使用,不可屏蔽中断(NMI)不受影响
  • BASEPRI:除能所有优先级不高于某个具体数值的中断
  • FAULTMASK:除能所有的 fault,不可屏蔽中断(NMI)不受影响
  • CONTROL:定义特权状态,决定使用MSP or PSP

中断屏蔽

只有在特权级下,才允许访问PRIMASK, FAULTMASK 以及 BASEPRI,3 个寄存器
baserpi可以关闭Fault吗?
FreeRTOS知识铺垫——读Cortex-M3 权威指南_第6张图片

双堆栈机制

  • CONTROL[1]=0:只使用 MSP
  • CONTROL[1]=1:线程模式将不再使用 MSP,而改用 PSP
    FreeRTOS知识铺垫——读Cortex-M3 权威指南_第7张图片
    FreeRTOS知识铺垫——读Cortex-M3 权威指南_第8张图片

为什么要使用双堆栈?在OS环境下, OS内核仅在handler模式下运行,为了不破坏OS Kernel 运行环境,故将内核堆栈和线程堆栈分离。

非对齐数据传送

对齐访问字,地址要对齐4字节。
对齐访问半字,地址要对齐2字节。
以下是图例:
在这里插入图片描述
CM3中,常规的数据传送指令如 LDR/LDRH/LDRSH 支持非对齐,以下仍然不支持

  • 多个数据的加载/存储(LDM/STM)
  • 堆栈操作 PUSH/POP
  • 互斥访问(LDREX/STREX)。如果非对齐会导致一个用法 fault
  • 位带操作。因为只有 LSB 有效,非对齐的访问会导致不可预料的结果

CM3内部会把非对齐访问转换为对齐访问,这个操作对编程人员是透明的。编程用户不需要考虑,但是这确实影响效率,需要更多的总线周期。
|

Fault 类异常

异常

编号 类型 优先级 简介
0 NA NA 没有异常
1 复位 -3(最高) 复位
2 NMI -2 不可屏蔽中断(来自外部 NMI 输入脚)
3 硬(hard)fault -1 所有被除能的 fault,都将“上访”(escalation)成硬 fault。只要
FAULTMASK 没有置位,硬 fault 服务例程就被强制执行。Fault
被除能的原因包括被禁用,或者被 PRIMASK/BASEPRI 被掩蔽。
若 FAULTMASK 也置位,则硬 fault 也被除能,此时彻底“关中
4 MemManage
fault
可编程 存储器管理 fault,MPU 访问违例以及访问非法位置均可引发。
企图在“非执行区”取指也会引发此 fault
5 总线 fault 可编程 从总线系统收到了错误响应,原因可以是预取流产(Abort)或
数据流产,企图访问协处理器也会引发此 fault
6 用法(usage)
fault
可编程 由于程序错误导致的异常。通常是使用了一条无效指令,或者是
非法的状态转换,例如尝试切换到 ARM 状态
7-10 保留
11 SVCall 可编程 执行系统服务调用指令(SVC)引发的异常
12 调试监视器 可编程 调试监视器(断点,数据观察点,或者是外部调试请求)
13 保留
14 PendSV 可编程 为系统设备而设的“可悬挂请求”(pendable request)
15 SysTick 可编程 系统滴答定时器
Fault 类异常:
  • 总线 faults
  • 存储器管理 faults
  • 用法 faults
  • 硬 fault

总线 Faults

AHB接口正在传输数据的时候吗,如果回复了一个错误信号,则触发Faults

  • 取指的时候出现错误信号,称为预取流产,prefetch abort
  • 数据读写的时候出现错误信号,称为数据流产,data abort
  • 中断处理起始阶段的堆栈 PUSH 动作,出现错误信号,则称为“入栈错误”
  • 中断处理收尾阶段的堆栈 POP动作,出现错误信号,则称为“出栈错误”
  • 在处理器启动中断服务序列(sequence)后读取向量时,少见

什么情况下会在APB传输数据的时候回复错误信号

  • 企图访问无效的存储器 region。常见于访问的地址没有相对应的存储器
  • 设备还没有作好传送数据的准备。比如,在尚未初始化 SDRAM 控制器的时候试图访问 SDRAM
  • 在企图启动一次数据传送时,传送的尺寸不能为目标设备所支持
  • 因为某些原因,设备不能接受数据传送。例如,某些设备只有在特权级下才允许访问,可当前却是用户级
    FreeRTOS知识铺垫——读Cortex-M3 权威指南_第9张图片
    如何定位
    BFARVALID有效的时候,表示发生的是精确的总线访问错误。触发该错误的指令存放到了BFAR寄存器中。否则发生的是不精确的,距离指令发生已经过去了若干个周期,这种情况下无法定位。

存储器管理 faults

MemManage fault,多与MPU有关。如果没有MPU但是在不可执行的存储器区域试图取指,也会触发MemManage fault

  • 访问了所有 MPU regions 覆盖范围之外的地址
  • 访问了没有存储器与之对应的空地址
  • 往只读 region 写数据
  • 用户级下访问了只允许在特权级下访问的地址
    FreeRTOS知识铺垫——读Cortex-M3 权威指南_第10张图片
    如何定位
    MMARVALID=1,触发错误的指令存在于MMAR
    DACCVIOL,IACCVIOL,则违例指令的地址已经被压入栈中
    MSTKERR,MUNSTKERR???????

用法 faults

最常见原因就是试图切入 ARM 状态,只要在加载 PC 时使用了 LSB 为零的数(也就是偶数),就被视作试图切入 ARM 状态

  • 执行了协处理器指令
  • 执行了未定义的指令
  • 尝试进入 ARM 状态
  • 无效的中断返回(LR 中包含了无效/错误的值)
  • 使用多重加载/存储指令时,地址没有对齐。
    FreeRTOS知识铺垫——读Cortex-M3 权威指南_第11张图片

硬fault

FreeRTOS知识铺垫——读Cortex-M3 权威指南_第12张图片

SVC与PendSV

····先说PendSV,PendSV称为可悬起系统调用。悬起状态是已经设置了中断或者异常标志,但是不可以立即执行的状态。可能的原因是当前系统中有更高优先级的中断或者异常。PendSV会等待所有的中断都执行完成之后,才会执行。为什么我们需要这个特性呢?如果没有PendSV,利用定时中断执行上下文切换就会出现如下问题
FreeRTOS知识铺垫——读Cortex-M3 权威指南_第13张图片
在PendSV中执行中断切换,就不会延误中断
FreeRTOS知识铺垫——读Cortex-M3 权威指南_第14张图片
····SVC 用于产生系统函数的调用请求。例如,操作系统通常不让用户程序直接访问硬件,而是通过提供一些系统服务函数,让用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就要产生一个SVC 异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。
····FreeRTOS只在启动第一个进程的过程中使用SVC,之后进程切换都使用PendSV。原因如下,以下是FreeRTOS论坛里的一个问答

Why SVC is used in FreeRTOS

Posted by niekiran on January 21, 2017

Hello All, 
I went through the xTaskStartSechudler() function, which ends up triggering 
the SVC instruction. and i came to know that it used in only in this function.
If i am not wrong , is SVC used to launch the first Task ? 
So, in my understanding SVC is used only one time, and subsequent task
switching in and out is carried out in the PendSV handler. am i right ?
Why can cant we use pendsv itself instead of SVC ? Thanks

Posted by rtel on January 21, 2017
Yes you are correct, unless you are using the version that supports the 
MPU the SVC instruction is only used once.
Old versions don’t use the SVC, and instead use the PendSV, and there 
was a really good reason for changing that I can’t recall the exact 
details of but it was something to do with having to add extra code into 
the PendSV instruction so it knew whether it had to save a context or 
not, and also loosing a bit of stack space.  If you use SVC to start the 
first task then the SVC handler resets the stack to the start of the 
stack and just has to restore the context of the first task to run.  If 
you are using the PendSV handler then you can’t reset the stack (because 
you are using it?), and you have to test whether or not a task was 
running when the PendSV handler was called (so the context is not saved 
if it is the first call because there is no task context to save).

大致意思:启动第一个进程只存在恢复现场的动作,进程切换的时候存在保存现场和回复现场两套动作。老版本FreeRTOS就是全部使用PendSV,但这样的PendSV代码很多,因为需要判断是不是在启动第一个进程进而判断需不需要保存现场。新版本SVC负责启动第一个进程,PendSV负责进程切换。这样的话PendSV代码量少一点,切换进程更快。

你可能感兴趣的:(stm32,单片机)