STM32裸机开发(11) — STM32的异常(基于Keil-MDK)

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来说,即初始化为08字节对齐
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 BlockSystem 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

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值