一、STM32的异常处理机制
8 ^/ _0 M+ O' e对于cortex M3/M4来说,CPU每执行完一条指令都会检查有无异常产生,当CPU发现有异常产生时,它就会进行如下处理:
, O+ M/ X1 M7 Z保存现场
* N; I: c- N, L8 \8 ~, g分辨异常/中断,调用对应的异常/中断处理函数* A; E, L# w0 D2 b6 a
恢复现场: j4 |% j3 W( g& ^! ~' K# \* G# m- q
每个异常/中断对应着一个异常向量,所有的异常向量组成一个异常向量表,对于cortex M3/M4来说,这个异常向量表中放置的就是具体异常/中断的处理函数的地址,当发生异常时,CPU就会从向量表里找到对应的项,从而得到处理函数的地址,跳转去执行。另外,对于cortex M3/M4来说,保存/恢复现场都是是硬件实现的。
+ | E; y5 w& s3 ^我们可以打开一个STM32库的汇编启动文件,例如startup_stm32f10x_hd.s,我们可以看到,前面的时异常,后面的就是中断了(其实中断也是一种异常)。
% H+ [' b( D+ b# A4 e/ s5 s' B% j$ Q& l5 @7 t4 B; _
$ L$ k4 s: K0 C4 T5 P) K
. E. h' t: X3 {! ~二、未定义指令异常1 b& v. z+ C c# s$ s) F
未定义指令,即使"还没有定义的指令",也就是CPU不认识的指令。8 h) E: ?, N) R9 ^% h, B4 ?
修改汇编文件,如下所示,添加各种异常的向量表项,另外在调用mymain()函数前调用串口初始化函数,并添加一个未定义的指令异常。
% s) ]) C' Q- Q3 J; _
- G0 b4 O! |6 B' c8 y" \3 g- Stack_Size EQU 0x00000500 ;定义堆栈大小为1024byte* n& s& e8 m2 b$ A4 G$ n
- AREA STACK, NOINIT, READWRITE, ALIGN=3 ;定义一个数据段,标记为STACK,即栈,不写入初始值初,对RAM来说,即初始化为0,8字节对齐: h/ b6 S* g$ ]; H# X4 J: i* @. N% S
- Stack_Mem SPACE Stack_Size ;保留Stack_Size大小的栈空间
. K6 h4 e. u( {7 T - __initial_sp ;标号,代表堆栈顶部地址,后面有用3 W6 Q3 T, ~ C/ Q$ Z* j- {; V6 Y. @7 s
- / \, ~. D0 {# h' `: h& c! s
- PRESERVE8 ;指示编译器8字节对齐; O* o* E: q& z( c& h
- THUMB ;指示编译器以后的指令为THUMB指令 1 y4 Z# N6 y3 I. V7 F7 C, n6 m* d
- . E7 I( b$ `( N, ] |7 G: o
- 3 |/ x$ u( ?1 c. b I) V; I
- ; Vector Table Mapped to Address 0 at Reset5 x' J. F5 F8 v4 L" N$ U @2 P
- AREA RESET, CODE, READONLY ;定义只读数据段,标记为RESET,其实放在CODE区,位于0地址
, ~" e8 A6 G3 t - EXPORT __Vectors ;在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用2 b; Y( {! K, R0 a# V$ O9 ?
- IMPORT NMI_Handler ; NMI Handler
1 U$ P0 C# z' y: b6 L+ c - IMPORT HardFault_Handler ; Hard Fault Handler
0 \+ \4 Y" O' u6 t: b: X - IMPORT MemManage_Handler ; MPU Fault Handler
8 m& x6 y. f+ }7 H9 j+ D; H6 _ - IMPORT BusFault_Handler ; Bus Fault Handler
5 ~7 Y) C6 W( X! \" ^- w {0 a. [: C( Y - IMPORT UsageFault_Handler ; Usage Fault Handler' c! d) a6 r& {7 [7 S6 n
- IMPORT SVC_Handler ; SVCall Handler2 q1 ~/ H- X; X4 k1 D: m5 _0 S
- IMPORT DebugMon_Handler ; Debug Monitor Handler
% S( i1 f$ z) M- Y. {' o& s - IMPORT PendSV_Handler ; PendSV Handler2 f$ s5 d9 Q( F1 j7 G, l
- IMPORT SysTick_Handler ; SysTick Handler ! l* H. Y5 [. L' D' s2 |& a
- . N$ a- V+ V5 D% x
- __Vectors DCD __initial_sp ;当前地址写入一个字(32bit)数据,值应该为栈顶地址
$ e2 G' s: o+ Z0 {' F - DCD Reset_Handler ;当前地址写入一个字(32bit)数据,值为Reset_Handler指向的地址值,即程序入口地址
6 E8 b0 ` R7 I- U8 q - DCD NMI_Handler ; NMI Handler
0 n0 j- T6 Q4 U; r - DCD HardFault_Handler ; Hard Fault Handler5 K, z. g& W$ I# Y5 b$ j
- DCD MemManage_Handler ; MPU Fault Handler% B- g2 Y" a, x% l0 E/ J' H1 r. s
- DCD BusFault_Handler ; Bus Fault Handler" S5 ^' V7 y+ @4 F
- DCD UsageFault_Handler ; Usage Fault Handler7 z. b8 L; x& n
- DCD 0 ; Reserved
1 ^+ u! B5 ~. X M6 v; m3 q - DCD 0 ; Reserved
) u, C, R' G" i9 }: e - DCD 0 ; Reserved. y3 \+ Q6 F8 A+ F& e, P( i
- DCD 0 ; Reserved* t9 r7 ~; y e2 C. X9 C, J+ J
- DCD SVC_Handler ; SVCall Handler; U/ u0 @. S/ m2 ~, w2 a
- DCD DebugMon_Handler ; Debug Monitor Handler' p" U6 O" X) F$ p0 i8 R3 D
- DCD 0 ; Reserved) [( y+ o; g: P- ?1 E' G; P
- DCD PendSV_Handler ; PendSV Handler
& b1 {8 L3 g- W- u - DCD SysTick_Handler ; SysTick Handler
7 F) o6 z$ V4 d: ]5 Z' P: G - 0 ?1 ^) j1 Z) Q: g: \
- AREA |.text|, CODE, READONLY ;定义代码段,标记为.text
9 r- @& u9 I: Q7 h1 S# r - & H) F4 }" t9 n: j8 A1 T
- ; Reset handler ;利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰4 l A$ }' N8 P* O8 A. `( L! @+ j
- Reset_Handler PROC ;过程的开始
$ P( S/ J* Q; F& s, ]' k1 h - EXPORT Reset_Handler [WEAK] ;[WEAK] 弱定义,意思是如果在别处也定义该标号(函数),在链接时用别处的地址。! {; H m- P3 F$ ^& B- j/ l% `
- $ C( e, y l4 o
- IMPORT |Image$RW_IRAM1$Base| ;从别处导入data段的链接地址
5 y+ R6 ?: \. \4 y7 D - IMPORT |Image$RW_IRAM1$Length| ;从别处导入data段的长度
8 m5 J3 h5 f7 M - IMPORT |Load$RW_IRAM1$Base| ;从别处导入data段的加载地址
; L; f$ F; T: t5 K - IMPORT |Image$RW_IRAM1$ZI$Base| ;从别处导入ZI段的链接地址8 w0 _# v0 N* d
- IMPORT |Image$RW_IRAM1$ZI$Length|;从别处导入ZI段的长度+ G" E" h7 z9 j6 l n
; A' \6 B: u# y1 b- ; 复制数据段
; v0 \4 _ ~" m$ F$ o5 L2 A - LDR R0, = |Load$RW_IRAM1$Base| ;将data段的加载地址存入R0寄存器. i9 Q6 F- |" g9 B6 {
- LDR R1, = |Image$RW_IRAM1$Base| ;将data段的链接地址存入R1寄存器4 E9 w/ P J* l! L3 c; N9 s
- LDR R2, = |Image$RW_IRAM1$Length| ;将data段的长度存入R2寄存器
6 p0 V. w k. I9 j0 Q& }9 r - CopyData # q4 C: h' S, ^) x. z& @- {
- SUB R2, R2, #4 ;每次复制4个字节的data段数据7 V5 t- H/ o3 B# I0 ^6 u1 S
- LDR R3, [R0, R2] ;把加载地址处的值取出到R3寄存器# X. V! d7 }' C6 v0 C
- STR R3, [R1, R2] ;把取出的值从R3寄存器存入到链接地址
7 T2 _$ q2 t- O% o. u* q - CMP R2, #0 ;将计数和0相比较
& ^! M8 h6 n2 w* E - BNE CopyData ;如果不相等,跳转到CopyData标签处,相等则往下执行2 K/ H/ \ P- W. T4 U/ ~9 N
- # J }) f" E% A0 l/ G# Z, ~; i
- ; 清除BSS段, Z4 f/ {2 Y) a1 M
- LDR R0, = |Image$RW_IRAM1$ZI$Base| ;将bss段的链接地址存入R1寄存器
6 S6 y4 M, f, Z- E# |) X5 d% } - LDR R1, = |Image$RW_IRAM1$ZI$Length| ;将bss段的长度存入R2寄存器! H# w$ Z9 v( j* V. I$ z4 W
- CleanBss
7 p' l* m" Z* l7 T4 Q& v( _ - SUB R1, R1, #4 ;每次清除4个字节的bss段数据
+ t' s, P% k) { - MOV R3, #0 ;将0存入r3寄存器; h' {/ S0 y2 e2 h4 _1 [. z& m
- STR R3, [R0, R1] ;把R3寄存器存入到链接地址 $ g* l. v' ^( n: j- Q9 G& J
- CMP R1, #0 ;将计数和0相比较
, Y" W# V9 @& V# R - BNE CleanBss ;如果不相等,跳转到CleanBss标签处,相等则往下执行
S/ Y4 Z7 j* M1 ^ -
, R9 g# ]+ ]- L5 k1 [ - . _# U4 Y7 \+ _9 D2 l
- IMPORT mymain ;通知编译器要使用的标号在其他文件
$ T# @3 v' I% f% K+ v - IMPORT uart_init
: S0 c$ w2 I' r; [; U& p, Y -
3 s' n4 v( d' i+ o& A - BL uart_init ;跳转去执行uart_init函数
! ^+ `. ^9 B6 O" Z$ c( R. q - " ~1 B6 x; C4 A* |/ f
- DCD 0XFFFFFFFF ;一个未定义的指令* T$ j! e: L% o, j6 {3 a1 z- r! Y4 Q
- 1 M5 v9 w0 P6 J* k( m
- BL mymain ;跳转去执行main函数$ Y7 L: R, N0 S' W
- B . ;原地跳转,即处于循环状态
* Q$ Q8 |$ t/ R& \$ E* i1 A - ENDP; w" d$ M2 c' A7 C) W
d+ j; H) N9 u& p- ALIGN ;填充字节使地址对齐2 J: A+ R8 w) C9 |/ w9 ?
- END ;整个汇编文件结束
复制代码 8 o5 t% \# S5 @+ ~, d! e T
然后新建exception.c文件,并添加各种异常处理函数) K8 ?. a. n' g* ^+ y6 O2 B
4 e. n6 K% x$ Y- #include "uart.h"
1 F1 D; _! h& g0 f
; U5 a# t+ l$ O1 P8 {3 O- void NMI_Handler(void)8 w9 i! p( v2 O% E3 A
- {' U% O: X+ ]9 x$ j* F( |
- putstring("Exception: NMI.\r\n");: Z$ G G% S# I7 J" d+ w5 r
- }1 {6 s+ E N) C: J' J9 m2 g0 D
- void HardFault_Handler(void)! q2 F F4 P% W0 `
- {
- f9 v: K$ m5 D1 } - putstring("Exception: Hard Fault.\r\n");
( a6 j2 }7 }1 \4 q k3 | - }, D' g" }9 v3 p+ ^2 J
- void MemManage_Handler(void); }4 g4 E$ H; y6 t2 |6 b( e' e+ q
- {' x+ c& l; I1 ?: R' N. f
- putstring("Exception: Mem Manage Fault.\r\n");
4 b" H2 U5 w( B' U z- B k - }/ {& E3 f8 s) W D8 T0 p% ?) o
- void BusFault_Handler(void)
0 c; i( R1 [+ H/ u - {
4 G# g) K# d- ^6 x9 ?4 l8 X3 @ - putstring("Exception: Bus Fault.\r\n");
, z; ?+ c6 g/ ~1 ^1 P* d4 e5 X/ u - }. b+ b# i( P, y
- void UsageFault_Handler(void)) Q) \8 n5 `7 F5 Y, V8 G3 B' s
- {
M, }2 u# A& J3 ^" g - putstring("Exception: Usage Fault.\r\n");2 H* t4 y8 s, m Q& ?# k C
- }' Z8 X s, q* O* K' I
- void SVC_Handler(void)
6 @" X: i! P" b7 E6 j+ N# f - {
7 \6 H" @' j+ _" k1 h' n- ], x - putstring("Exception: SVCall.\r\n");: r0 S0 c% T- W& o2 l
- }% j: F+ E* `& |& A
- void DebugMon_Handler(void)
) z2 L# B0 D4 C - {
9 k+ f- U$ |5 F9 x$ z - putstring("Exception: Debug Monitor.\r\n");
% v3 p/ \: U) y! w) r/ n: G7 m. b" A - }' S7 s- i9 p" R
- void PendSV_Handler(void)
! J, a c2 |( X# z+ p) I2 Y; Z2 v - {
8 t! i; F* Z5 r2 L" {, |" g! ` - putstring("Exception: PendSV.\r\n");
8 m9 I8 [" G* l( `( u1 b, h - }
~) s- p! a5 O- ^2 Y3 Q% S3 o! }+ C - void SysTick_Handler(void)- v5 n: B2 ~& @8 @" Y8 [- ]
- {! x( o, ` t2 U6 H
- putstring("Exception: SysTick.\r\n");
- W. l& F) k5 q/ n) J( m - }
复制代码
2 E6 w) u6 G# V- p; j8 ]然后编译烧录运行,可以看到,串口一种打印硬件错误异常,这是因为没有将异常标志位清除的结果% A/ r( i7 Q. n) R3 M3 v
6 C3 N: S( ]' E- l- U' a- o$ x
2 b+ y$ N1 ?. C
. N% @' K: [# V- R4 w
另外,进入了硬件错误异常而不是未定义指令异常,是因为cortex M3/M4的如下机制,因为未定义指令属于"处理器操作相关的错误",如果没有使能"Usage Fault",发就会触发"Hard Fault"。2 r; U' G1 I' O& B) T
* W2 ]4 p7 a5 R$ i1 E* |, u! C2 j
' {' F" f# Y% o5 }. F' X/ ?6 X
7 o" K4 T+ P, f1 R# f, L; K+ O0 @那么要怎么使能"Usage Fault",我们要对SCB->SHCSR寄存器的第十八位写1来使能。即System Control Block的System Handler Control and State Register。7 B) y+ Y, L- a, l$ |
我们找到STM32库里的core_cm3.h文件可以看到这个寄存器的结构体定义,将这个文件加入我们的工程中! p/ g# C: v' |! A" m( L% r# g4 m
" [4 U7 B6 N O5 e+ b2 ^
* V; h# s3 g A& f% \. P0 E
* \2 E3 v- E0 ~* R. y然后在exception.c文件中添加exception_init()函数使能"Usage Fault",
1 A# N7 S% D. N# k' w H/ w0 Q; s6 O* ]
- void exception_init(void)
$ y9 B/ \# k6 D( E, { - {+ `" i) \( `: i; ^
- SCB->SHCSR |= (SCB_SHCSR_USGFAULTENA_Msk);% q% k4 Q3 F) O$ @& V& i, {
- }
复制代码 - V8 c! K& V- G. J* {3 D; L
然后在汇编文件中调用
' V. z) ?. j5 j
5 S0 ] q, G% X* s# h4 o
, n* e2 N, _) o% h5 d* F6 { g h4 h/ n* v) v
编译发现core_cm3.h有许多未定义的错误,
+ q# e& H H' f, t# l+ p& Z
0 r! C: _; `5 D$ M' r" V
( s9 x. t9 Q+ l/ z0 B8 y1 ^
$ S! p/ V8 r! z, {5 d0 O
我们不使用这些将其屏蔽掉即可,然后重新编译链接烧录运行,可以看到进入了使用错误异常, t2 ]! [$ H4 |& c; Q, ]; m
6 U# M' |, O/ P4 J' T% `) @
- e. ?1 b. T$ {0 u+ n: h5 N& B1 i8 ?8 G1 O( o$ F
————————————————' Y- r5 ~ l$ f; G' l8 O
转载:Willliam_william! I( p( x6 m6 x: p! N
" K1 ~3 \) M! P+ G$ z$ m/ s5 p* B0 x1 S( e1 w1 _! t! v
|