网站公司设计,微信云开发小程序,地方门户网站开发,网站后台 英语#x1f431;作者#xff1a;一只大喵咪1201 #x1f431;专栏#xff1a;《理解ARM架构》 #x1f525;格言#xff1a;你只管努力#xff0c;剩下的交给时间#xff01; 目录 #x1f35c;中断#x1f368;GPIO中断代码实现 #x1f35c;CPU#x1f368;CONTROL… 作者一只大喵咪1201 专栏《理解ARM架构》 格言你只管努力剩下的交给时间 目录 中断GPIO中断代码实现 CPUCONTROL寄存器模式代码 提升访问等级EXC_RETURN 总结 中断 如上图在上篇文章中本喵主要介绍的是右侧框中的异常这里开始介绍一下左边框里的中断中断主要由三部分组成 中断源 中断源多种多样比如GPIO、定时器、UART、DMA等等。它们都有自己的寄存器可以进行相关设置使能中断、中断状态、中断类型等等。 中断控制器在STM32F103中被叫做NVICNested vectored interrupt controller(嵌套向量中断控制器) 各种中断源发出的中断信号汇聚到中断控制器。可以在中断控制器中设置各个中断的优先级。中断控制器会向CPU发出中断信号CPU可以读取中断控制器的寄存器判断当前处理的是哪个中断。 CPU CPU每执行完一条指令都会判断一下是否有中断发生了。CPU也有自己的寄存器可以设置它来使能/禁止中断这是中断处理的总开关。
GPIO中断
就以本喵用的STM32F103ZET6的GPIO中断为例来讲解配置PA0为中断输入引脚 如上图所示中断体系结构图对于GPIO中断STM32F103又引入了External interrupt/event controller (EXTI)用来设置GPIO的中断类型。
EXTI可以给NVIC提供16个中断信号EXTI0~EXTI15。某个EXTIx来自哪个GPIO需要设置GPIO控制器。 每一个中断源都有自己的寄存器来配置自己的中断类型。中断源将自己的中断信号给到中断控制器(NVIC)GPIO的中断信号先给到EXTI再由EXTI给到中断控制器。中断控制器再比较不同中断的优先级将某一个中断信号送给CPU。 当CPU检测到来自NVIC的中断信号时如果该中断没有被屏蔽就会跳转到向量表中执行相应的外部中断处理函数。 如上图所示是向量表下面的蓝色框就是中断的入口函数CPU的处理流程和异常一模一样也是先保护现场分辨中断源并处理最后恢复现场。 按照上面框图来配置一下GPIO中断也就是我们常用的按键中断。
GPIO控制器 如上图所示的寄存器STM32F103的GPIO控制器中有AFIO_EXTICR1~AFIO_EXTICR4一共4个寄存器名为External interrupt configuration register外部中断配置寄存器用来选择某个外部中断EXTIx的中断源。 如上图所示AFIO模块的基地址是0x40010000。 从上图可知EXTI0只能从PA0、……、PG0中选择一个这也意味着PA0、……、PG0中只有一个引脚可以用于中断。这跟其他芯片不一样很多芯片的任一GPIO引脚都可以同时用于中断。
EXTI 如EXTI框图所示这个用来设置中断的触发方式如高电平触发、低电平触发、上升沿触发、下降沿触发等等。 如上图该模块的基地址是0x40010400。
沿着上面框图中的红线我们要设置
Falling trigger selection register是否选择下降沿触发 如上图所示EXTI_FTSR寄存器bit0~bit19设置19个外部中断源的下降沿触发将对应EXTIx的比特位置一就是使能置为0就是不使能。
Rising trigger selection register是否选择上升沿触发 如上图所示EXTI_RTSR寄存器bit0~bit19设置19个外部中断源的上升沿触发将对应EXTIx的比特位置一就是使能置为0就是不使能。
Interrupt mask register是否屏蔽中断 如上图所示EXTI_IMR寄存器bit0~bit19设置19个外部中断源是否被屏蔽将对应EXTIx的比特位置一就是不屏蔽置为0就是屏蔽。
当发生中断时可以读取下面寄存器判断是否发生了中断、发生了哪个中断 如上图所示EXTI_PR寄存器bit0~bit19对应19个外部中断源的发生情况对应EXTIx的比特位为一就是发生中断为0就是没有发生中断。 在EXTI中配置完外部中断的触发方式以后要解除相应中断源的屏蔽也就是将中断开关打开方便中断信号到达NVIC。 NVIC
多个中断源汇聚到NVICNVIC的职责就是从多个中断源中取出优先级最高的中断向CPU发出中断信号。 处理中断时程序要写NVIC的寄存器清除中断标志。 如上图所示NVIC涉及到的寄存器我们暂时只需要关注ISER(中断设置使能寄存器)、ICPR(中断清除挂起寄存器)、中断优先级寄存器(设置中断优先级该寄存器是8位)。 如上图所示中断使能和清除寄存器这些寄存器有很多个比如ISER0、ISER1等等里面的每一位对应一个中断。 描述中的中断编号就是中断/异常处理函数在向量表中的位置如中断#0就等于异常#16因为中断也属于异常。中断处理入口有单独从0开始的一套编号也和异常一起的一套编号。 如上图所示向量表我们设置的PA0对应的中断编号是6所以设置ISER0和ICER0中的bit6即可。
对于中断优先级设置寄存器每一个中断在NVIC中都有一个8位的寄存器来设置它的优先级用这8位来表示中断的抢占优先级和子优先级具体规则本喵就不再介绍了。
CPU
cortex M3/M4处理器内部有这几个寄存器
PRIMASK 如上图所示PRIMASK寄存器 把bit0设置为1就可以屏蔽所有优先级可配置的中断。
可以使用这些指令来设置它
CPSIE I ; 清除PRIMASK使能中断
CPSID I ; 设置PRIMASK禁止中断或者
MOV R0, #1
MSR PRIMASK, R0 ; 将1写入PRIMASK禁止所有中断MOV R0, #0
MSR PRIMASK, R0 ; 将0写入PRIMASK使能所有中断因为这是CPU的寄存器属于特殊寄存器必须按照这几条指令以及规则修改寄存器。
FAULTMASK 如上图所示FAULTMASK和PRIMASK很像它更进一步除了一般的中断外把HardFault都禁止了只有NMI可以发生。该寄存器我们一般不会去设置它。
可以使用这些指令来设置它
CPSIE F ; 清除FAULTMASK
CPSID F ; 设置FAULTMASK或者
MOV R0, #1
MSR FAULTMASK R0 ; 将1写入FAULTMASK禁止中断MOV R0, #0
MSR FAULTMASK, R0 ; 将0写入FAULTMASK使能中断BASEPRI 如上图BASEPRI用来屏蔽这些中断它们的优先级的值大于或等于BASEPRI。比如设置将BASEPRI设置为0x60那么优先级低于0x60的所有中断CPU都屏蔽掉了不会去处理只有优先级为0x00~0x60的中断CPU才会去处理。
可以使用这些指令来设置它
MOVS R0, #0x60
MSR BASEPRI, R0 ; 禁止优先级在0x60~0xFF间的中断MRS R0, BASEPRI ; 读取BASEPRIMOVS R0, #0
MSR BASEPRI, R0 ; 取消BASEPRI屏蔽代码实现 如上图为了访问寄存器方便将AFIO定义成一个结构体成员包含该模块中的所有寄存器。 如上图代码所示定义key_init按键初始化函数函数中对于PA0引脚配置成输入在前面的文章中本喵讲解过这里直接使用重要的第二步中将PA0映射到EXTI0上让PA0成为中断源。 如上图代码所示将EXTI模块用结构体描述出来然后定义exti_init初始化函数设置为双边沿触发方式并且使能中断也就是不进行屏蔽让EXTI能够向NVIC发生中断信号。 如上图代码所示同样将NVIC模块用结构体描述出来并且定义nvic_init函数初始化NVIC这里仅使能了EXTI0中断。
还定义了清除中断标志位的函数nvic_clear_int在函数内将ICPR[0]的bit6置一。
所有的配置完毕以后就可以实现中断处理函数了当PA0产生中断以后会自动跳转到向量表中从EXTI0_IRQHandler处理入口处执行中断服务函数。 如上图所示在中断服务函数中读取PA0的IDR寄存器值当该值为0说明按键被按下否则按键没有按键并且打印不同状态下的字符串。在中断函数的最后清除EXTI和NVIC中的重点标志位方便下一次中断产生。 如上图使用软件仿真勾选PA0引脚模拟中断产生在串口中可以看到按键按下和松开的字符串说明我们的中断功能配置成功。 默认情况下CPU是会处理所有来自NVIC的中断的如果将所有中断在CPU这里都屏蔽掉呢 如上图所示在启动文件中调用mymain函数之前给PRIMASK特殊寄存器写1屏蔽掉所有中断此时程序运行起来后无论怎么点击PA0都不会有字符串显示因为CPU此时不处理任何一个中断。
虽然默认情况下CPU是不屏蔽任何一个中断的但是保险起见还是在启动文件中使用CPSIE I指令使能所有中断让CPU处理所有NVIC送过来的中断。
CPU
CPU有不同的工作模式状态以及可以使用不同的栈寄存器。
ARM芯片支持Thumb指令集、ARM指令集处理器运行Thumb指令时处于Thumb状态运行ARM指令时处于ARM状态。
CortexM3/M4只支持Thumb指令集所以处理器运行时只有Thumb状态。除此之外还有一个调试状态比如通过调试器或触发断点后处理器就会进入调试状态并停止指令执行。这里本喵不涉及调试状态所以处理器只处于Thumb状态。
CONTROL寄存器 如上图所示CONTROL寄存器这是一个特殊寄存器是属于CPU的影响着CPU的一些特定功能。不同类型的内核该寄存器使用的位数不同这里本喵仅讲解Cortex-M3/M4内核时的该寄存器。 如上图所示CONTROL不同位的作用。
SPSEL用来选择线程模式使用的是MSP还是PSP。nPRIV用来设置线程模式的访问等级(特权/非特权)。
模式 如上图所示CortexM3/M4处理器有两种模式
处理模式执行中断服务程序等异常处理在处理模式下处理器有最大权限(具有特权访问等级)。线程模式执行普通程序这时处理器可以处于特权访问等级也可以处于非特权访问等级。
不同模式下处理权限可能不同但是最大的不同就是栈寄存器可能也不同。 访问等级有两种
特权访问等级可以访问所有寄存器、所有存储器。非特权访问等级无法访问某些寄存器比如无法访问NVIC寄存器(嵌套向量中断控制器)。
在一般的单片机系统中RTOS和各类应用之间是无法隔离的某个应用程序崩溃了整个系统也就崩溃了。如果能让RTOS和各类应用程序彼此之间隔离开那么可以增强系统的健壮性。这需要硬件的支持比如需要有MPU(Memory Protection Unit)。 没有MPU时访问等级的用处不大只能用来限制应用程序无法访问某些寄存器。 ARM处理器的通用寄存器有R0、R1、……、R15其中的R13也被称为SP即栈寄存器。对于SP它有两个实体MSP(Main SP)、PSP(Process SP)。
栈寄存器的选择
① 启动时CONTROL寄存器的SPSEL等于0默认使用MSP。注意启动时是线程模式使用的仍然是MSP② 程序可以修改CONTROL寄存器让SPSEL等于1以使用PSP③ 发生异常时异常处理函数中使用的必定是MSP④ 异常返回时可以控制返回之后使用MSP还是PSP 在处理模式下使用MSP也就是说指令中使用SP时它对应的物理寄存器是MSP。在线程模式下根据CONTROL寄存器的设置处理器可能用的是MSP也可能用的是PSP。 处理模式下使用的栈必然是MSP。 代码
MRS r0, CONTROL ; 将CONTROL寄存器的值读入R0
MSR CONTROL, r0 ; 将R0写入CONTROL寄存器读写CONTROL特殊寄存器必须使用上面的指令。
模式 如上图将前面GPIO中断的代码拿来仿真运行查看寄存器时可以看到Mode Thread表示此时是线程模式Privilege Privileged表示此时是特权访问等级Stack MSP表示此时使用的是MSP栈。
这是时CONTROL寄存器中的值都是默认值本喵没有做任何改动所以说默认情况下使用的是特权访问等级和MSP栈。 如上图所示在按键中断处理函数中打一个断点让程序运行到中断函数中停下来此时Mode Handler表示此时处于处理模式优先级和栈类型都没有改变。 在处理异常或者中断时CPU必定处于处理模式其他情况处于线程模式包括上电时。 访问等级
下面本喵来改变一下CONTROL中的值 如上图将CONTROL中的bit0置一此时CPU处于线程模式所以访问等级就是非特权访问等级。 如上图在线程模式的非特权访问等级下调用mymain函数在该函数中会访问NVIC所以在访问的时候就会产生HardFault_Handler硬件异常更进一步说明非特权访问等级无法访问NVIC。 如上图将访问NVIC的代码放在将访问等级改为非特权访问等级之前此时就可以正常执行了。此时CPU处于线程模式和非特权访问等级。 如上图当执行中断服务函数的时候CPU就自动变为处理模式和特权访问等级了。 处理中断和异常时CPU必定处于特权访问等级也就是处理模式时CPU必定处于特权访问等级。只有线程模式时CPU才存在不同的访问等级。 PSP/MSP 如上图所示在CPU仍然是特权访问等级时将栈设置为PSP在将访问等级设置为非特权访问等级后CPU处于线程模式非特权访问等级使用的是PSP。 非特权访问等级无法访问CONTROL寄存器。 如上图当CPU在处理中断服务函数时自动恢复成了处理模式特权访问等级使用MSP不再使用PSP。 在处理中断异常时CPU必定使用MSP。 如上图从中断服务函数中退出以后就会恢复到进入之前的状态恢复成线程模式特权访问等级使用PSP。 如上图所示PSP和MSP是两个不同栈顶指针默认情况下SP寄存器就是指MSP。
给MSP赋值0x20010000给PSP赋值0x20008000让PSP挨着放在MSP的下面。 一般使用了RTOS时任务会使用PSP。裸机程序中仅使用MSP即可。在中断和异常中使用MSP是为了让程序更加高效使用独立的栈减少了硬件在线程保护和恢复时的工作量。 提升访问等级 如上图所示在非特权访问等级下无法访问CONTROL寄存器所以也就无法将访问等级提升为特权访问等级。
在前面我们看到CPU在处理中断或者异常时会变成处理模式特权访问等级以及使用MSP。 所以可以按照上图所示的流程在异常或者中断中将CONTROL寄存器的bit0设置为0让CPU从此变成特权访问模式。 如上图定义一个SetPrivileged函数在函数内使用汇编代码块asm {}产生SVC异常此时CPU就会执行SVC_Handler异常处理函数此时CPU就会处于特权访问状态。
在异常处理函数中将CONTROL寄存器的bit0清零让CPU变成特权访问等级之后在执行完异常处理函数以后CPU就一直都是特权访问等级了如此就成功的修改了CPU的访问等级。 如上图所示在执行SetPrivileged函数之前处于非特权访问等级在执行完毕后处于特权访问等级此时就成功的修改了访问等级。
EXC_RETURN
在线程模式中CPU是非特权访问等级使用的是PSP栈在处理模式时是特权访问等级使用的是MSP栈在处理完中断或异常后由处理模式变为线程模式时会恢复对应的访问等级和使用的栈。 这是怎么做到的呢在哪里记录着前一个模式的CONTROL寄存器中的值 在介绍异常和中断时本喵就讲解过调用异常和中断处理函数进行线程保护时LR寄存器会被赋予一个特殊的值该值就是EXC_RETIRN 如上图所示不考虑浮点单元使用当LR中的值是0xFFFFFFF1时CPU在处理完异常或者中断后返回的是处理模式并且使用主栈MSP。
当LR中的值是0xFFFFFFF9时返回的是线程模式并且使用的是主栈MSP。
当LR中的值是0xFFFFFFFD时返回的是线程模式并且使用的是进程栈PSP。 如上图所示使用前面的代码在线程模式中使用的是PSP在SVC_Handler异常处理函数中使用的是MSP可以看到LR寄存器中的值是0xFFFFFFFD。
对照EXC_RETURN的合法值表中可以知道在执行完SVC异常处理函数以后返回线程处理模式并使用PSP。
总结
中断的处理和异常处理非常的相似都是由硬件去向量表中找到对应的处理入口并执行处理函数。在调用处理函数之前由硬件进行现场保护中断源分辨在执行完处理函数返回时由软件触发现场恢复机制由硬件完成现场恢复工作。 所有中断都要经过中断控制器NVIC。 CPU有两种工作模式处理模式和现场模式处理模式对应的访问等级是特权访问等级使用的是主栈MSP在处理中断或者异常时必然处于处理模式。
在线程模式时访问等级和使用的栈都可以根据CONTROL去修改有四种组合