STM32裸机开发(11) — STM32的异常(基于Keil-MDK)
一、STM32的异常处理机制
对于cortex M3/M4来说,CPU每执行完一条指令都会检查有无异常产生,当CPU发现有异常产生时,它就会进行如下处理:
- 保存现场
- 分辨异常/中断,调用对应的异常/中断处理函数
- 恢复现场
每个异常/中断对应着一个异常向量,所有的异常向量组成一个异常向量表,对于cortex M3/M4来说,这个异常向量表中放置的就是具体异常/中断的处理函数的地址,当发生异常时,CPU就会从向量表里找到对应的项,从而得到处理函数的地址,跳转去执行。另外,对于cortex M3/M4来说,保存/恢复现场都是是硬件实现的。
我们可以打开一个STM32库的汇编启动文件,例如startup_stm32f10x_hd.s
,我们可以看到,前面的时异常,后面的就是中断了(其实中断也是一种异常)。
二、未定义指令异常
未定义指令,即使"还没有定义的指令",也就是CPU不认识的指令。
修改汇编文件,如下所示,添加各种异常的向量表项,另外在调用mymain()
函数前调用串口初始化函数,并添加一个未定义的指令异常。
Stack_Size EQU 0x00000500 ;定义堆栈大小为1024byte
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;定义一个数据段,标记为STACK,即栈,不写入初始值初,对RAM来说,即初始化为0,8字节对齐
Stack_Mem SPACE Stack_Size ;保留Stack_Size大小的栈空间
__initial_sp ;标号,代表堆栈顶部地址,后面有用
PRESERVE8 ;指示编译器8字节对齐
THUMB ;指示编译器以后的指令为THUMB指令
; Vector Table Mapped to Address 0 at Reset
AREA RESET, CODE, READONLY ;定义只读数据段,标记为RESET,其实放在CODE区,位于0地址
EXPORT __Vectors ;在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用
IMPORT NMI_Handler ; NMI Handler
IMPORT HardFault_Handler ; Hard Fault Handler
IMPORT MemManage_Handler ; MPU Fault Handler
IMPORT BusFault_Handler ; Bus Fault Handler
IMPORT UsageFault_Handler ; Usage Fault Handler
IMPORT SVC_Handler ; SVCall Handler
IMPORT DebugMon_Handler ; Debug Monitor Handler
IMPORT PendSV_Handler ; PendSV Handler
IMPORT SysTick_Handler ; SysTick Handler
__Vectors DCD __initial_sp ;当前地址写入一个字(32bit)数据,值应该为栈顶地址
DCD Reset_Handler ;当前地址写入一个字(32bit)数据,值为Reset_Handler指向的地址值,即程序入口地址
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
AREA |.text|, CODE, READONLY ;定义代码段,标记为.text
; Reset handler ;利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰
Reset_Handler PROC ;过程的开始
EXPORT Reset_Handler [WEAK] ;[WEAK] 弱定义,意思是如果在别处也定义该标号(函数),在链接时用别处的地址。
IMPORT |Image$$RW_IRAM1$$Base| ;从别处导入data段的链接地址
IMPORT |Image$$RW_IRAM1$$Length| ;从别处导入data段的长度
IMPORT |Load$$RW_IRAM1$$Base| ;从别处导入data段的加载地址
IMPORT |Image$$RW_IRAM1$$ZI$$Base| ;从别处导入ZI段的链接地址
IMPORT |Image$$RW_IRAM1$$ZI$$Length|;从别处导入ZI段的长度
; 复制数据段
LDR R0, = |Load$$RW_IRAM1$$Base| ;将data段的加载地址存入R0寄存器
LDR R1, = |Image$$RW_IRAM1$$Base| ;将data段的链接地址存入R1寄存器
LDR R2, = |Image$$RW_IRAM1$$Length| ;将data段的长度存入R2寄存器
CopyData
SUB R2, R2, #4 ;每次复制4个字节的data段数据
LDR R3, [R0, R2] ;把加载地址处的值取出到R3寄存器
STR R3, [R1, R2] ;把取出的值从R3寄存器存入到链接地址
CMP R2, #0 ;将计数和0相比较
BNE CopyData ;如果不相等,跳转到CopyData标签处,相等则往下执行
; 清除BSS段
LDR R0, = |Image$$RW_IRAM1$$ZI$$Base| ;将bss段的链接地址存入R1寄存器
LDR R1, = |Image$$RW_IRAM1$$ZI$$Length| ;将bss段的长度存入R2寄存器
CleanBss
SUB R1, R1, #4 ;每次清除4个字节的bss段数据
MOV R3, #0 ;将0存入r3寄存器
STR R3, [R0, R1] ;把R3寄存器存入到链接地址
CMP R1, #0 ;将计数和0相比较
BNE CleanBss ;如果不相等,跳转到CleanBss标签处,相等则往下执行
IMPORT mymain ;通知编译器要使用的标号在其他文件
IMPORT uart_init
BL uart_init ;跳转去执行uart_init函数
DCD 0XFFFFFFFF ;一个未定义的指令
BL mymain ;跳转去执行main函数
B . ;原地跳转,即处于循环状态
ENDP
ALIGN ;填充字节使地址对齐
END ;整个汇编文件结束
然后新建exception.c
文件,并添加各种异常处理函数
#include "uart.h"
void NMI_Handler(void)
{
putstring("Exception: NMI.\r\n");
}
void HardFault_Handler(void)
{
putstring("Exception: Hard Fault.\r\n");
}
void MemManage_Handler(void)
{
putstring("Exception: Mem Manage Fault.\r\n");
}
void BusFault_Handler(void)
{
putstring("Exception: Bus Fault.\r\n");
}
void UsageFault_Handler(void)
{
putstring("Exception: Usage Fault.\r\n");
}
void SVC_Handler(void)
{
putstring("Exception: SVCall.\r\n");
}
void DebugMon_Handler(void)
{
putstring("Exception: Debug Monitor.\r\n");
}
void PendSV_Handler(void)
{
putstring("Exception: PendSV.\r\n");
}
void SysTick_Handler(void)
{
putstring("Exception: SysTick.\r\n");
}
然后编译烧录运行,可以看到,串口一种打印硬件错误异常,这是因为没有将异常标志位清除的结果
另外,进入了硬件错误异常而不是未定义指令异常,是因为cortex M3/M4的如下机制,因为未定义指令属于"处理器操作相关的错误",如果没有使能"Usage Fault",发就会触发"Hard Fault"。
那么要怎么使能"Usage Fault",我们要对SCB->SHCSR
寄存器的第十八位写1来使能。即System Control Block
的System Handler Control and State Register
。
我们找到STM32库里的core_cm3.h
文件可以看到这个寄存器的结构体定义,将这个文件加入我们的工程中
然后在exception.c
文件中添加exception_init()
函数使能"Usage Fault",
void exception_init(void)
{
SCB->SHCSR |= (SCB_SHCSR_USGFAULTENA_Msk);
}
然后在汇编文件中调用
编译发现core_cm3.h
有许多未定义的错误,
我们不使用这些将其屏蔽掉即可,然后重新编译链接烧录运行,可以看到进入了使用错误异常
三、SVC异常
四、SysTick异常
五、附录
上一篇:STM32裸机开发(10) — 复制data段和清除BSS段
下一篇:
代码存放:https://gitee.com/william_william/stm32f103_noos/tree/master/keil-mdk/04_exception