每一个STM32单片机出厂的时候都有全球唯一的ID,当在数据安全性比较高的地方,需要对每一个接入系统的芯片进行身份验证,那么这个芯片自身的ID号就可以作为它的身份信息。
$ m0 l* ]6 P9 |% Z7 \- ~* k
6 ~+ Y" f& O% a4 \8 `/ V, Y产品唯一的身份标识非常适合:
& B& \* l% S x8 v) G0 {( Y
$ `4 i( d, d& b3 _% ~' n用来作为序列号(例如USB字符序列号或者其他的终端应用)
; \1 a6 r# H/ D6 h用来作为密码,在编写闪存时,将此唯一标识与软件加解密算法结合使用,提高代码在闪存存储器内的安全性。
' w2 z* y6 m2 R0 M( |& U% g8 E5 u用来激活带安全机制的自举过程! W* E! C8 t. M
96位的产品唯一身份标识所提供的参考号码对任意一个STM32微控制器,在任何情况下都是唯一的。用户在何种情况下,都不能修改这个身份标识。
) B5 S C0 \9 @ [1 q1 z$ C$ |
$ Q& {8 S7 P2 O+ ?7 H8 `, t2 B6 U6 x0 q* n4 f
这个96位的产品唯一身份标识,按照用户不同的用法,可以以字节(8位)为单位读取,也可以以
; J# a" u8 P/ [ \" b) \半字(16位)或者全字(32位)读取。
! i: b; a+ T( t- f
2 K9 x6 f4 r) c# A$ A
8 v0 @. ^( B7 ^ w5 V! p/ g" P8 B' N# t" q; }/ i* X1 }
; F4 G) a6 T8 v* z
3 K/ p" q' T. ]4 k, E
) ~1 G; K& d. R" Q+ K* V! M' u
不仅可以读取到芯片的ID,还能读出芯片的存储器容量。
3 e% \3 U6 |" w3 @2 U1 w
( g: G. f' f. I8 D$ a' |
: F" }# R7 j6 {9 S( r) h& U' U% v( G1 f+ J1 E+ Z( x8 D) c
芯片型号也同样可以读出来。 j) y4 ^1 c# P$ t; n
1 H) z; F; F7 e3 A, r5 r# l6 D" [
$ F5 M2 F7 l6 m
2 @0 q/ `# q4 g5 H+ I: Z: [8 Q1 _ o
h7 E& v. p$ z+ s# |( r& v: ~
) `' v) b) K% Z7 X' C$ T: U1 O下面就通过代码来读取这几个数据。
) a) J! c1 K. ~; y" Q
3 F$ E m& L( u- H- //读取芯片ID
0 _! D% ~# A. i - void GetChipID ( void )" r. n% b, A ~7 i( ?
- {/ E% I4 O! Z4 i! F. x' E! e
- u32 CpuID[3]; //小端模式
$ w: z8 w% k; P- h; J( t2 P: `/ H - CpuID[0] = * ( vu32 * ) ( 0x1ffff7e8 ); //高32位地址3 ^, W0 o- G G8 ]# d/ ]% D% _
- CpuID[1] = * ( vu32 * ) ( 0x1ffff7ec ); //中32位地址- A" t/ I7 S# \2 M, O7 Z
- CpuID[2] = * ( vu32 * ) ( 0x1ffff7f0 ); //低32位地址
; q& }5 t. P( k - printf ( "此芯片唯一ID为:0x%x-%x-%x \r\n", CpuID[0], CpuID[1], CpuID[2] );
% L( A2 h3 e* Q) t/ V/ d* i - }& |- a/ N5 t' d, m
- //获取芯片Flash大小* N V. y; Q2 e+ x- U5 ^1 L
- void GetFlashSize ( void )' f7 q- b6 R1 F2 }/ [
- {
, r, A! j$ B% |! F+ Y - u16 stm32_Flash_Size;4 d2 t2 n( F1 `
- stm32_Flash_Size = * ( u16 * ) ( 0x1FFFF7E0 ); //闪存容量寄存器. I6 p( R2 X1 P; f1 x) T p8 V4 p
- printf ( "芯片闪存容量大小为:%x \r\n", stm32_Flash_Size );+ I# E5 K0 Q% D0 b8 Z
- }
4 J2 W, x; y$ i; K) `! U: U: S - 5 X, z: |2 Q' V8 M
- //读取芯片型号- O' j' L3 s' S* V6 c- Y2 W! n
- void Main_Chip_Type(void)
6 N- j3 q. o) w9 i& ] k" Y - {" @/ E) F$ _/ Q) S, T
- u32 type;
( }% y3 G8 p/ l$ O0 |# r" d - type = *( unsigned int * )( 0xE0042000 ); //MCU ID
; F1 X$ k1 |- G5 ~" p$ i - printf ( "芯片型号为:0x%x \r\n", type );
# E$ Y9 O9 ~) p - }) i! a( i+ T) h: }. @; \2 n0 [& W
+ G. a$ I, M( ~ \8 h; S& o* C( Z6 J- int main ( void )
4 d4 M% `/ n' Q( I6 i- n4 s - {
6 }8 }. Z9 N) i3 Q - NVIC_PriorityGroupConfig ( NVIC_PriorityGroup_2 );
: F' a9 W) e8 q2 b3 w) x! R - uart_init ( 115200 );6 e; F) X, U0 T0 m. [( S% a
- delay_init(); //延时函数初始化( [. b. o2 O% U4 m+ I/ D i3 q' K
- LED_Init(); //初始化与LED连接的硬件接口7 ]$ A( L9 R$ B+ f2 k' O9 \$ p
" ]( s& ]: x9 E8 d) Q' K- GetChipID();
* J" O# P3 L9 m+ v2 i, D7 ?9 ` - GetFlashSize();! t w3 s. `" L) u- C& ?
- Main_Chip_Type();
# m; B8 _! h. h( J' ^; A8 } - : R. \( X% L: v0 B# W6 `1 g
- while(1);
# K: h& R! |3 l. @ - }
复制代码 ) u5 G( o% G4 g: B
通过串口打印出芯片的ID* J* I+ H" j+ Y8 Z) P/ F# V* J
; r; C3 o7 \+ u9 V此芯片唯一ID为: 0x66eff51-51578248-87074312 * P2 T" s; b6 x0 f& c: N
芯片闪存容量大小为: 40 + G/ E, C) j) B" r; @9 D* g, b$ z
芯片型号为: 0x20036410 : E) P* x+ E0 U z
. M" a) ?$ ]4 W4 z
) Q4 v# `6 s5 ?# w' o- l0 C9 D# [% E7 i J) Z
然后通过单步调试,直接观察内存中的数据。
2 R1 t, o. e- T. ^; ]
& m! t' I: a# H- H% E3 g6 d$ \
. l; {" l* [! w$ ^9 ~5 y" b# n" }) H1 t2 s
然后观察内存中地址0x1FFFF7E0开始的数据6 ?+ x1 h. d8 R8 g) Y& R2 e# Q
& N# \# z. n4 k/ K- L
* w7 p$ |; F s7 ^+ M' h2 L/ Y
5 t2 E1 E. E& f+ Q/ L' D8 `1 ~
可以看出0x1FFFF7E0开始的16位数据为0x0040换算为10进制数据就是64,说明当前用的芯片的容量是64K。接下来从0x1ffff7e8地址开始的12个字节,也就是96位存储的就是芯片的ID。内存中ID号存储为低位在前高位在后,而打印出来的数据是高位在前,低位在后。打印的前32位是 06 6e ff 51对应在内存中的存储顺序51应该是第一位,06是最后一位。' d* z0 N2 H) O1 F3 l' C' a
$ Y- m' \) m8 ?) _3 D) Y: K, s
最后在0xE0042000地址查看单片机的型号。
8 k8 y @# z; K/ b$ I& _
, b. [# e/ L( y- f, t
2 s& z, ]3 V- N5 V
" Y* V- |2 o, C2 a3 y; P5 O
低12位数据是 0x410,也就是设备编码为0x410,通过对比文档可以看出这个是中容量产品。高16位是0x2003,对比文档可以看出,这个是中容量产品,版本号为Y。
+ V% S3 ^; S1 i* |
$ h9 ~6 M3 p2 p7 O+ p4 P由于这些ID号都存储在指定的内存地址中,而系统没有提供专用的读取这些ID号的函数,所以读取数据的时候,只能通过指针的方式去读取,首先将数据加一级指针转换为地址,然后加二级指针,表示读取当前地址中存储的内容。这种操作方法在库函数的头文件中使用的比较多。比如在stm32f10x.h中就可以看到对GPIOA的定义。* Q! R, G" s* `% c* C7 ~! M6 y
: z7 F3 `, b( k7 z! k5 U9 v. P5 [
- #define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
% J6 \* s" @: B. h* [3 M" ?, A0 h - % |, C' v1 L) j
- #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
' s2 V* j5 k/ f$ O7 k - & t0 Z2 L2 M7 x- M$ N
- #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)3 X% |: z7 X% ?- N* T
- 2 z+ O" L% a+ N( D @/ `% D9 R$ L
- #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
" B8 s n- @( l8 h2 v5 x
3 P' m# c- d) u( @5 V6 I. w- typedef struct
3 r0 U; z& [1 U1 S - {' P( r- Q0 h4 h& @: r/ T/ J2 X5 P
- __IO uint32_t CRL;
1 |1 T; O4 }; Y x$ e3 A - __IO uint32_t CRH;$ O. L K6 y$ S( p6 t6 \4 W
- __IO uint32_t IDR;
" {+ Y7 |8 t% I; ` - __IO uint32_t ODR;) P# m& Z+ U" o2 ?
- __IO uint32_t BSRR;
- Y5 N/ S% |. _, z0 y - __IO uint32_t BRR;
# F8 K9 T. }0 r( b0 ] - __IO uint32_t LCKR;, W/ A* }/ F9 u
- } GPIO_TypeDef;
复制代码 U* a) \5 [1 C# C- O- ]. d
将地址值逐个替换后为 #define GPIOA ((GPIO_TypeDef *) 0x4001 0800),相当于在 0x4001 0800 地址处定义了GPIOA的结构体。查阅芯片资料可知,GPIOA的起始地址就是0x4001 0800。
! R& C/ Q+ K. E# P9 Z4 H; X3 ^* q* {# ~0 X+ X8 k
F8 w4 i6 B: R& F' _2 c: s3 V; R3 S3 v2 f4 h3 d, Z9 P5 v' i. ^
这样操作GPIOA的的时候就和上面读取ID的方法是一样的,相当于直接读写对应地址中的值。. K& [) a! e9 N2 S7 j. W
: x+ g7 w6 P4 y* u" j8 V: ~& ]$ g: f
3 ~( B* _7 f; P _/ c
|