一、STM32的异常处理机制
# L! m9 t, V e4 i) ]对于cortex M3/M4来说,CPU每执行完一条指令都会检查有无异常产生,当CPU发现有异常产生时,它就会进行如下处理:
( u- M2 d2 X, N1 t保存现场
% M$ C% j1 z% n, ?$ p; ]& c: ]分辨异常/中断,调用对应的异常/中断处理函数
9 x+ u0 d" E: z& G ^, ?2 J2 r% D恢复现场
: o, u5 I$ _1 z$ k每个异常/中断对应着一个异常向量,所有的异常向量组成一个异常向量表,对于cortex M3/M4来说,这个异常向量表中放置的就是具体异常/中断的处理函数的地址,当发生异常时,CPU就会从向量表里找到对应的项,从而得到处理函数的地址,跳转去执行。另外,对于cortex M3/M4来说,保存/恢复现场都是是硬件实现的。
8 C: w8 u0 G% N& O3 o. \我们可以打开一个STM32库的汇编启动文件,例如startup_stm32f10x_hd.s,我们可以看到,前面的时异常,后面的就是中断了(其实中断也是一种异常)。
~$ a. z/ N6 p2 m0 P3 F8 _% u8 I# Q, Q+ x
7 p9 Z2 G% B3 Z
" x: V3 ^% K9 O. K: Y& q7 u二、未定义指令异常
& K6 R; n+ r8 y' Y( R未定义指令,即使"还没有定义的指令",也就是CPU不认识的指令。
) s: ^. T" |9 @% H) g) f修改汇编文件,如下所示,添加各种异常的向量表项,另外在调用mymain()函数前调用串口初始化函数,并添加一个未定义的指令异常。
# O# D3 W2 `4 |+ o! J4 i, S5 q0 H+ x3 g
- Stack_Size EQU 0x00000500 ;定义堆栈大小为1024byte/ i. W6 D' O: B8 ^! w5 t) [
- AREA STACK, NOINIT, READWRITE, ALIGN=3 ;定义一个数据段,标记为STACK,即栈,不写入初始值初,对RAM来说,即初始化为0,8字节对齐* b' I" b0 t4 B+ R
- Stack_Mem SPACE Stack_Size ;保留Stack_Size大小的栈空间8 r! H( V* H' i
- __initial_sp ;标号,代表堆栈顶部地址,后面有用8 I8 G! f- ~1 r
- # ]9 ], Z$ v, Z& L! ]. T& Z* V
- PRESERVE8 ;指示编译器8字节对齐# X0 b, u; J) r: g# R2 Z5 f6 S
- THUMB ;指示编译器以后的指令为THUMB指令 ) C& ^. C- _' t
) ~( z1 M4 A: H
, o* Z+ M, v ?0 V9 d( n+ J& p. L- ; Vector Table Mapped to Address 0 at Reset
' l0 E$ l: J/ A7 M% D# ]/ { - AREA RESET, CODE, READONLY ;定义只读数据段,标记为RESET,其实放在CODE区,位于0地址; d% b; |# G. ]& o8 v8 R
- EXPORT __Vectors ;在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用# N3 X6 y. x& A" n
- IMPORT NMI_Handler ; NMI Handler7 v ?* k) E0 J+ k
- IMPORT HardFault_Handler ; Hard Fault Handler1 y4 H- r2 Z, U/ d9 R6 P$ _
- IMPORT MemManage_Handler ; MPU Fault Handler) F* |2 G; A. s) a! C* \! R
- IMPORT BusFault_Handler ; Bus Fault Handler' x, q. X+ I* Q6 _, f9 D' U
- IMPORT UsageFault_Handler ; Usage Fault Handler
2 l6 G* A* ]6 Z6 U2 J/ C - IMPORT SVC_Handler ; SVCall Handler) P& X2 O/ N& Q/ _: x9 {* o
- IMPORT DebugMon_Handler ; Debug Monitor Handler6 j/ g4 ]: e0 q/ J! e/ Y, ]& _: j
- IMPORT PendSV_Handler ; PendSV Handler
, M7 n5 U1 g! H6 y$ V# E- j - IMPORT SysTick_Handler ; SysTick Handler : g& w \' T0 X+ n& m5 w. ~
- : p0 h' I" v+ r5 m& ~8 L, }/ D) w, u
- __Vectors DCD __initial_sp ;当前地址写入一个字(32bit)数据,值应该为栈顶地址5 ^# H6 K1 c7 M! N+ f: k
- DCD Reset_Handler ;当前地址写入一个字(32bit)数据,值为Reset_Handler指向的地址值,即程序入口地址5 H0 |' g/ S9 o1 ? h$ T
- DCD NMI_Handler ; NMI Handler. Y0 M2 F: k" ^( C: u' N
- DCD HardFault_Handler ; Hard Fault Handler6 a& n0 N+ q- M
- DCD MemManage_Handler ; MPU Fault Handler
( ] t# D* U, F* p$ e2 m5 I$ ] - DCD BusFault_Handler ; Bus Fault Handler! o4 `" Q0 f/ B. K P5 @
- DCD UsageFault_Handler ; Usage Fault Handler
* W' U4 T9 }* F, W - DCD 0 ; Reserved
3 ~7 t- u9 F* b7 K7 v2 E1 W0 b0 i4 h - DCD 0 ; Reserved
! q: s. M6 H- }5 z8 _% J- f - DCD 0 ; Reserved% w% f0 H8 T9 ?2 b9 \& b
- DCD 0 ; Reserved3 b9 d) e" a6 j/ x6 {/ b; g
- DCD SVC_Handler ; SVCall Handler- h' ]/ K# `. _# W W+ L# b
- DCD DebugMon_Handler ; Debug Monitor Handler0 R# K4 r. p4 u
- DCD 0 ; Reserved, z/ |4 G7 d9 M) k8 U/ g/ c
- DCD PendSV_Handler ; PendSV Handler0 a/ A; O% a! S& d+ F$ R9 I3 a
- DCD SysTick_Handler ; SysTick Handler" }9 r" Y+ U! \# \
- ' l# [1 i) K' u/ V, Y6 ]
- AREA |.text|, CODE, READONLY ;定义代码段,标记为.text
2 M' b# @) W" C$ t - " \- {0 o5 [. o, E
- ; Reset handler ;利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰
& m# \3 z! u+ s - Reset_Handler PROC ;过程的开始
U# Y: g' M6 q/ h* J- ]8 x, Z - EXPORT Reset_Handler [WEAK] ;[WEAK] 弱定义,意思是如果在别处也定义该标号(函数),在链接时用别处的地址。! M5 }5 F0 U2 i: Q |
- ) z1 Y# y( @# G% j7 k2 }
- IMPORT |Image$RW_IRAM1$Base| ;从别处导入data段的链接地址
! E j6 i g/ ]' m6 V" B$ V* t' K) l- @ - IMPORT |Image$RW_IRAM1$Length| ;从别处导入data段的长度) J8 G( Z6 I3 d2 S$ m; F9 e
- IMPORT |Load$RW_IRAM1$Base| ;从别处导入data段的加载地址
3 o# L# d' j% d* g& ^5 t - IMPORT |Image$RW_IRAM1$ZI$Base| ;从别处导入ZI段的链接地址, w* s* o5 T- X# |5 I* E
- IMPORT |Image$RW_IRAM1$ZI$Length|;从别处导入ZI段的长度 ]6 l; g5 i6 S0 R, F; V+ m! X
- : t0 f. C6 Z1 R+ w/ L6 i& ~
- ; 复制数据段4 y- j7 H) X6 w' r+ z R
- LDR R0, = |Load$RW_IRAM1$Base| ;将data段的加载地址存入R0寄存器
/ j* A4 G9 Q& G8 c5 ^$ R - LDR R1, = |Image$RW_IRAM1$Base| ;将data段的链接地址存入R1寄存器$ W& R2 A. |3 A3 d& K, T( J* b
- LDR R2, = |Image$RW_IRAM1$Length| ;将data段的长度存入R2寄存器
1 N( o$ J4 y. o# I& b; _ - CopyData * H6 @% P4 V' P. ]' {! {
- SUB R2, R2, #4 ;每次复制4个字节的data段数据9 f( I! O. a4 s& W
- LDR R3, [R0, R2] ;把加载地址处的值取出到R3寄存器: ^5 t2 I1 S1 E [
- STR R3, [R1, R2] ;把取出的值从R3寄存器存入到链接地址
9 C& G6 R" a# ~ - CMP R2, #0 ;将计数和0相比较" v6 p5 G: C( y1 f* A' k
- BNE CopyData ;如果不相等,跳转到CopyData标签处,相等则往下执行
3 b; O {0 }; o# v6 A5 Y8 j
. D0 O* ^. W) z% D# ~+ K- ; 清除BSS段
/ X+ o5 L& G$ _1 c' P' y - LDR R0, = |Image$RW_IRAM1$ZI$Base| ;将bss段的链接地址存入R1寄存器
7 m0 ]9 }% u7 \, R1 c7 n - LDR R1, = |Image$RW_IRAM1$ZI$Length| ;将bss段的长度存入R2寄存器: g) {! H/ E4 R6 Z" u- Q0 L
- CleanBss
+ b+ { l |5 s. X- Q9 H0 x6 D - SUB R1, R1, #4 ;每次清除4个字节的bss段数据
' R+ q, g+ U, y) u( P; W7 Z. w - MOV R3, #0 ;将0存入r3寄存器% ~- B8 l- ~' W/ S
- STR R3, [R0, R1] ;把R3寄存器存入到链接地址 ! U0 T7 }# e3 {9 }: ]; ~4 w
- CMP R1, #0 ;将计数和0相比较0 z$ s) L' Q3 \* i6 n* o) c8 y* n$ [
- BNE CleanBss ;如果不相等,跳转到CleanBss标签处,相等则往下执行
& e- h# \4 |6 B d -
2 D( C6 H2 w$ y - * |4 @- Q1 o1 V( K
- IMPORT mymain ;通知编译器要使用的标号在其他文件' A1 `, R9 h' n
- IMPORT uart_init0 p5 Z4 L# G# C/ W' ~7 G
- 2 N& P( M& e% t% ~# ?# r1 j( D
- BL uart_init ;跳转去执行uart_init函数
+ T3 p% Y% [8 b0 d* A - # {; q, u% q; \, A$ r! z# B4 M2 C
- DCD 0XFFFFFFFF ;一个未定义的指令
; v4 g# T- Q# N# E - 9 M' W4 s6 U* X! f. V/ }) C
- BL mymain ;跳转去执行main函数
) H1 ^3 S1 V) L- I& L- q2 D - B . ;原地跳转,即处于循环状态
$ D& u* b! v, u! q/ ~ - ENDP
% `2 E ]$ H% z/ S - " |1 I. s H* T; ?+ F! R4 g
- ALIGN ;填充字节使地址对齐$ i# ~" G& C6 W2 \
- END ;整个汇编文件结束
复制代码
+ g- I1 [7 c5 x4 t; I然后新建exception.c文件,并添加各种异常处理函数
$ c& B$ n. i0 {/ g1 I+ q9 `
- p+ W4 G' P. x. k3 Q |- #include "uart.h"
! `; o8 U$ ^. e7 g
5 [* I/ {" r" r- void NMI_Handler(void)1 V6 p$ h W9 W% y a$ {
- {, \9 v* B3 n( A4 @
- putstring("Exception: NMI.\r\n");6 Y) w- M1 \ e0 W; A) U' E
- }! U' s# P; c h, P9 ]8 R3 I
- void HardFault_Handler(void)# K& ~8 ?; `) y0 E4 n! Z, ^ `0 {1 ?# f
- {
6 v$ J7 r) E8 W& i - putstring("Exception: Hard Fault.\r\n");
Q% _8 J) `: A; g6 b - }3 M2 I! Q: {5 S% S1 ^7 G4 z
- void MemManage_Handler(void)
% p n2 F+ n( u: R - {1 y4 N' z. e7 B7 ]' a i/ K
- putstring("Exception: Mem Manage Fault.\r\n");
0 D; A+ v k, b" Q0 x5 m1 ?+ r% o! Z$ [ - }
0 U8 \& O" ?# d3 P( Z - void BusFault_Handler(void)
% b* f" |! |: S - {. {& w" j( @: R* D
- putstring("Exception: Bus Fault.\r\n");
% g, A# _) F6 C- R+ U - }' K" t- U6 u0 Z8 Y1 Q) I9 a
- void UsageFault_Handler(void)& S" M3 x, N R" C
- {* c( w, M# L+ \. G
- putstring("Exception: Usage Fault.\r\n");
) I; C7 C; S- l7 H, ~ w1 w9 u - }. u5 p! d% e8 p/ U& o
- void SVC_Handler(void)
2 A4 o8 y6 J+ k! a - {% K0 v7 M) z: c8 g
- putstring("Exception: SVCall.\r\n");, E' t' N7 h7 G7 h2 g
- }
. M$ Y' O* E* F* T/ F# q - void DebugMon_Handler(void)% r" k/ X$ }5 S6 _$ V1 U6 l
- {, a, m, s+ b$ p& v9 A9 Q! ^
- putstring("Exception: Debug Monitor.\r\n");+ s. n: c* i9 Q$ y" B/ y/ Z- A$ p
- }
. }% g0 w9 M# o& ~9 a: U/ X( i& C! Z - void PendSV_Handler(void)
7 O7 w4 s' q) r( s - {
3 ~# M; Y7 p/ @( P: i) m2 H. G - putstring("Exception: PendSV.\r\n");/ l) R) S8 C; }
- }. s3 O+ C; h d% u& t! Z8 }) l' y" j
- void SysTick_Handler(void)$ r3 V6 g4 J8 ~, z
- {9 k* g% Z. P7 u% f* h
- putstring("Exception: SysTick.\r\n");
) }6 Z$ `/ D* l - }
复制代码 1 L- T# j2 R/ l" j" X0 ]
然后编译烧录运行,可以看到,串口一种打印硬件错误异常,这是因为没有将异常标志位清除的结果8 v2 H% _ Q3 T8 {0 t3 D
$ v2 z" X \0 }
9 N! p9 f$ c2 Q8 {% Y3 e6 X9 g( I! x, v# t( T* H+ V+ Q
另外,进入了硬件错误异常而不是未定义指令异常,是因为cortex M3/M4的如下机制,因为未定义指令属于"处理器操作相关的错误",如果没有使能"Usage Fault",发就会触发"Hard Fault"。
$ o. G4 C, @* ?7 B# k& [0 y3 U, ^ {7 ?
$ Y! ^$ Y; c) S( w( b
2 Z8 k" W6 P8 L2 Y" V那么要怎么使能"Usage Fault",我们要对SCB->SHCSR寄存器的第十八位写1来使能。即System Control Block的System Handler Control and State Register。4 A R% |7 y! n0 @; U- G
我们找到STM32库里的core_cm3.h文件可以看到这个寄存器的结构体定义,将这个文件加入我们的工程中
- m: P1 N( x8 ~; x5 e/ N9 e( ~
8 Q) N; D4 w& w9 [6 j3 k- W
# K% S# k$ W2 F/ A
$ u7 A" h- q% Y4 B然后在exception.c文件中添加exception_init()函数使能"Usage Fault",
' b7 b: M9 G- T; w% z$ n$ j
' c* V3 n! \4 `* N3 X* E- |- void exception_init(void)
8 I9 p5 N/ U; Q q - {
. q+ o* J7 z1 W* B: [ - SCB->SHCSR |= (SCB_SHCSR_USGFAULTENA_Msk);
" v @/ ]. b9 e1 X+ d! U- w U+ r" s - }
复制代码
0 P$ f3 Q; D5 n# ^' y2 T然后在汇编文件中调用
$ G1 o+ I- O0 K/ J' s. }8 u% F) [& s
( G0 \; b% i9 n. Q5 s+ x) K+ ~ w; V3 S3 Y
编译发现core_cm3.h有许多未定义的错误,
: H2 \! x% Z1 w# V0 ` h" D, q% F, [" s
9 A! J; T# x" h" g$ V7 N0 q. \, @5 w* l- y/ w7 Y
我们不使用这些将其屏蔽掉即可,然后重新编译链接烧录运行,可以看到进入了使用错误异常
4 D( G' R$ q8 d9 C9 n# c
5 X3 v& j8 m) O f$ ~* |3 e3 e
" h9 G# _! W* R4 O0 i/ v5 |9 f2 @& Q* ^& S* K. m5 v7 H: |
————————————————) w% r8 s1 M4 _2 W: o
转载:Willliam_william
5 J! A1 O4 H1 ~8 X) s; B! V
# n1 W6 ]4 H4 x/ t/ s# A9 p3 }6 K5 w
|