
本帖最后由 zhdzhd-174422 于 2018-12-14 00:18 编辑 : U3 L n. t( o) a4 q3 a8 N# k, L 第六章:难啃的寄存器 --建议初学者配合火哥的视频学习 (下文中橙色表示要记住的,红色表示勘误,绿色表示程序注释,后面部分漏掉) 芯片和外设之间通过各种总线连接,其中驱动单元有4个,被动单元也有 4 个,可以把驱动单元理解成是CPU 部分,被动单元都理解成外设。 驱动单元: Icode总线:I 表示 Instruction,指令,它是专门用来取指令的。 DCode总线:D 表示 Data,数据,它是用来取数据的。 系统总线:主要是访问外设的寄存器。 DMA总线:主要是用来传输数据,可以是在某个外设的数据寄存器。 被动单元: 内部的闪存存储器:即 FLASH,我们编写好的程序就放在这个地方。 CPU-->Icode-->内部Flash 内部的 SRAM:即RAM,程序的变量、堆栈等的开销都是基于它;内核通过 DCode 总线来访问它。 CPU-->Dcode-->内部SRAM FSMC:英文全称是 Flexible static memory controller,即灵活的静态的存储器控制器,是 STM32F10xx 中一个特色外设,通过它,可以扩展内存,如外部的SRAM,NANDFLASH和 NORFLASH,FSMC只能扩展静态的内存,不能是动态的内存,比如 SDRAM。 APB1 GPIO AHB 到 APB 的桥-à 总线---àSTM32外设---> IIC 等 APB2 SPI ![]() 存储器映射:存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射,如果给存储器再分配一个地址就叫存储器重映射。 ![]() 存储器区域功能划分:4Gà8个块à512MBàBlock0~Block7 ![]() 有 3 个块非常重要: Block0 用来设计成内部FLASH,用来存放程序,地址是:0x0800 0000 ~ 0x0807 FFFF (512KB) Block1 用来设计成内部 RAM,就是SRAM,地址是:0x2000 0000 ~0x2000 FFFF(64KB) Block2 用来设计成片上的外设,APB1总线外设,地址是:0x4000 0000 ~ 0x4000 77FF APB2 总线外设,地址是:0x4001 0000 ~ 0x4001 3FFF AHB 总线外设,地址是:0x4001 8000 ~ 0x5003 FFFF $ P/ P7 U8 a8 e" U: {. O寄存器映射:给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射 我们找到GPIOB 端口的输出数据寄存器 ODR 的地址是0x4001 0C0C(详见STM32F10X中文参考手册),ODR 寄存器是 32bit,低16bit有效,对应着 16 个外部 IO,写 0/1对应的的 IO则输出低/高电平,通过 C语言指针的操作方式,让 GPIOB的16 个 IO 都输出高电平,通过绝对地址访问内存单元的代码是: # `1 Y5 d" q( ~*(unsignedint*)(0x4001 0C0C) = 0xFFFF; ; N* V9 y O3 c' F7 R通过寄存器别名方式访问内存单元的代码是: 3 D: _/ s* n, d6 g7 a #define GPIOB_ODR (unsignedint*)(GPIOB_BASE+0x0C) * GPIOB_ODR = 0xFF; 通过寄存器别名访问内存单元(把指针操作“*”也定义到寄存器别名里面)的代码是: 3 t. K" `& ]- r# I- ~# T# J #define GPIOB_ODR *(unsignedint*)(GPIOB_BASE+0x0C) GPIOB_ODR = 0xFF; , `* D% D- E$ H8 W* ]! x/ Y2 E外设地址映射 片上外设区分为三条总线,根据外设速度的不同,不同总线挂载着不同的外设, APB1挂载低速外设, APB2 和 AHB 挂载高速外设。 相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中 APB1 总线的地址最低,片上外设从这里开始,也叫外设基地址。 总线基地址: ![]() 外设基地址:总线所挂载的外设有自己的地址范围,特定外设的首个地址称为“XX 外设基地址”,也叫 XX 外设的边界地址。GPIO 属于高速的外设 ,挂载到APB2总线上 ![]() 外设寄存器:在XX 外设的地址范围内,分布着的就是该外设的寄存器。 & i. A+ ~2 p0 U/ p7 AGPIOà引脚输出高/低电平à限流电阻àLEDàSTM32控制该引脚的电平àLED亮灭 GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为 32bit,占四个字节,在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。(书中下表出现GPIOH,应更正为GPIOB,因是以GPIOB为例,此处已更正) ![]() 以“GPIO 端口置位/复位寄存器”为例,讲解寄存器的说明(应该是如何阅读手册), ![]() 1.名称(书中漏掉了“名”字) “(GPIOx_BSRR)”:寄存器名为“GPIOx_BSRR” “(x=A…E)”对应端口GPIOA~GPIOE,这些 GPIO端口都有这样的一个寄存器。 2.偏移地址(书中此段描述的偏移地址是0x18,此处已更正为0x10) 偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是 0x10, 比如:GPIOA外设的基地址为 0x4001 0800 ,则GPIOA_BSRR寄存器的地址为:0x4001 0800+0x10 ; GPIOB的外设基地址为 0x4001 0C00,则GPIOB_BSRR 寄存器的地址为:0x4001 0C00+0x10。 6 n' @% u! g: u3 Y& r3.寄存器位表 0-31位的名称及权限。 上方的数字为位编号, 中间为位名称, 最下方为读写权限,其中 w 表示只写,r 表示只读,rw 表示可读写。 此寄存器中的位权限都是 w,所以只能写,如果读此寄存器,是无法保证读取到它真正内容的。 4.位功能说明 位功能是寄存器说明中最重要的部分,它详细介绍了寄存器每一个位的功能。 此寄存器中有BRy及 BSy两种寄存器位,y 数值是 0-15,表示端口的引脚号,如 BR0、BS0 用于控制 GPIOx的第 0个引脚,若 x表示 GPIOA,那就是控制GPIOA的第 0引脚,而 BR1、BS1 就是控制 GPIOA 第 1个引脚。 其中 BRy 引脚的说明是:0:不会对相应的 ODRx 位执行任何操作;1:对相应 ODRx位进行复位。 “复位”是将该位设置为 0, “置位”表示将该位设置为1; 此处ODRx 是另一个寄存器的寄存器位,当 ODRx 位为 1 的时候,对应的引脚x 输出高电平,为 0 的时候对应的引脚输出低电平。 BR0=“1”, GPIOx的第0个引脚输出“低电平”, BR0=“0”,不会影响 ODR0 位,即引脚电平不会改变。寄存器位BSy与 BRy是相反的操作。 C 语言对寄存器的封装(此章的学习重点,关乎后面的学习) 封装总线和外设基地址 在编程上为了方便理解和记忆,我们把总线基地址和外设基地址都以相应的宏定义起来,总线或者外设都以他们的名字作为宏名,代码如下: --------------------------------------------------------------------------------------------------------- 1 /* 外设基地址 */ 2 #define PERIPH_BASE ((unsignedint)0x40000000) 3 4 /* 总线基地址 */ 5 #define APB1PERIPH_BASE PERIPH_BASE 6 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) 7 #define AHBPERIPH_BASE (PERIPH_BASE +0x00020000) 8 9 10 /* GPIO 外设基地址 */ 11 #define GPIOA_BASE (APB2PERIPH_BASE +0x0800) 12 #define GPIOB_BASE (APB2PERIPH_BASE +0x0C00) 13 #define GPIOC_BASE (APB2PERIPH_BASE +0x1000) 14 #define GPIOD_BASE (APB2PERIPH_BASE +0x1400) 15 #define GPIOE_BASE (APB2PERIPH_BASE +0x1800) 16 #define GPIOF_BASE (APB2PERIPH_BASE +0x1C00) 17 #define GPIOG_BASE (APB2PERIPH_BASE +0x2000) 18 19 20 /* 寄存器基地址,以 GPIOB 为例 */ 21 #define GPIOB_CRL (GPIOB_BASE+0x00) 22 #define GPIOB_CRH (GPIOB_BASE+0x04) 23 #define GPIOB_IDR (GPIOB_BASE+0x08) 24 #define GPIOB_ODR (GPIOB_BASE+0x0C) 25 #define GPIOB_BSRR (GPIOB_BASE+0x10) 26 #define GPIOB_BRR (GPIOB_BASE+0x14) 27 #define GPIOB_LCKR (GPIOB_BASE+0x18) --------------------------------------------------------------------------------------------------------- + U8 n- O( f7 N: p) j以上代码先定义了 “片上外设”基地址 PERIPH_BASE,接着在PERIPH_BASE 上加入各个总线的地址偏移,得到 APB1、APB2 总线的地址APB1PERIPH_BASE、APB2PERIPH_BASE,在其之上加入外设地址的偏移,得到 GPIOA-G的外设地址,最后在外设地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具体地址,就可以用指针读写。 ---------------------------------------------------------------------------------------------------------: C# d/ {; e% u; b- j9 A3 F0 I: ~ 1 /* 控制GPIOB 引脚 0 输出低电平(BSRR 寄存器的 BR0 置 1) */ 2 *(unsigned int *)GPIOB_BSRR =(0x01<<(16+0)); 3 4 /* 控制GPIOB 引脚 0 输出高电平(BSRR 寄存器的 BS0 置 1) */ 5 *(unsigned int *)GPIOB_BSRR =0x01<<0; 6 7 unsigned int temp; 8 /* 读取GPIOB 端口所有引脚的电平(读 IDR 寄存器) */ 9 temp = *(unsigned int*)GPIOB_IDR; ---------------------------------------------------------------------------------------------------------, [; H1 k9 `& m" E& x9 [2 s* k" e 以上代码使用(unsigned int *) 把 GPIOB_BSRR 宏的数值强制转换成了地址,然后再用“*”号做取指针操作,对该地址赋值,从而实现了写寄存器的功能。同样,读寄存器也是用取指针操作,把寄存器中的数据读取到变量里,从而获取 STM32 外设的状态。 * Z9 ~* ?% O8 r; o: i! s封装寄存器列表 为了更方便地访问寄存器,引入 C 语言中的结构体语法对寄存器进行封装。 ---------------------------------------------------------------------------------------------------------1typedef unsigned int uint32_t; /*无符号 32 位变量*/ 2typedef unsigned short intuint16_t; /*无符号16 位变量*/ 3 4 /*GPIO 寄存器列表 */ 5typedef struct { 6uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */ 7uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */ 8uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */ 9uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */ 10uint32_t BSRR; /*GPIO 位设置/清除寄存器地址偏移: 0x10 */ 11uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */ 12uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */ 13 } GPIO_TypeDef; ---------------------------------------------------------------------------------------------------------: D. d" B! O& l3 X2 l: t% |( M上面的代码用typedef 关键字声明了名为 GPIO_TypeDef的结构体类型,结构体内有 7 个成员变量,变量名正好对应寄存器的名字。C 语言的语法规定,结构体内变量的存储空间是连续的,其中 32 位的变量占用 4个字节,16位的变量占用 2 个字节,见下图(书中此图把CRH写成ORH,此处已更正) ![]() 定义的这个GPIO_TypeDef ,假如这个结构体的首地址为 0x4001 0C00(这也是第一个成员变量 CRL的地址), 那么结构体中第二个成员变量 CRH 的地址即为 0x4001 0C00 +0x04 ,加上的这个 0x04 ,正是代表 CRL 所占用的 4 个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移。 这样的地址偏移与STM32 GPIO 外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存 器,参考下面的代码: ---------------------------------------------------------------------------------------------------------1GPIO_TypeDef * GPIOx; //定义一个 GPIO_TypeDef 型结构体指针 GPIOx 2 GPIOx= GPIOB_BASE; //把指针地址设置为宏 GPIOH_BASE 地址 3GPIOx->IDR = 0xFFFF; 4GPIOx->ODR = 0xFFFF; 5 6 7uint32_t temp; 8 temp = GPIOx->IDR; //读取 GPIOB_IDR 寄存器的值到变量 temp 中 ---------------------------------------------------------------------------------------------------------/ [' m; V8 v- D' j( ?$ `0 j1 J4 k5 x( F& a; d0 ?7 o 上面的代码先用GPIO_TypeDef 类型定义一个结构体指针 GPIOx,并让指针指向地址GPIOB_BASE(0x4001 0C00),使用地址确定下来,然后根据 C 语言访问结构体的语法,用GPIOx->ODR 及 GPIOx->IDR等方式读写寄存器。 最后,我们更进一步,直接使用宏定义好 GPIO_TypeDef 类型的指针,而且指针指向各个 GPIO端口的首地址,使用时我们直接用该宏访问寄存器即可,参考下面的代码: --------------------------------------------------------------------------------------------------------- 1 /*使用 GPIO_TypeDef 把地址强制转换成指针*/ 2#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE) 3#define GPIOB ((GPIO_TypeDef *)GPIOB_BASE) 4#define GPIOC ((GPIO_TypeDef *)GPIOC_BASE) 5#define GPIOD ((GPIO_TypeDef *)GPIOD_BASE) 6#define GPIOE ((GPIO_TypeDef *)GPIOE_BASE) 7#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE) 8#define GPIOG ((GPIO_TypeDef *)GPIOG_BASE) 9#define GPIOH ((GPIO_TypeDef *)GPIOH_BASE) 10 11 12 13 /*使用定义好的宏直接访问*/ 14 /*访问 GPIOB 端口的寄存器*/ 15GPIOB->BSRR = 0xFFFF; //通过指针访问并修改 GPIOB_BSRR 寄存器 16GPIOB->CRL = 0xFFFF; //修改 GPIOB_CRL 寄存器 17GPIOB->ODR =0xFFFF; //修改 GPIOB_ODR 寄存器 18 19uint32_t temp; 20 temp= GPIOB->IDR; //读取 GPIOB_IDR 寄存器的值到变量 temp 中 21 22 /*访问 GPIOA 端口的寄存器*/ 23GPIOA->BSRR = 0xFFFF; 24GPIOA->CRL = 0xFFFF; 25GPIOA->ODR =0xFFFF; 26 27uint32_t temp; 28 temp = GPIOA->IDR; //读取 GPIOA_IDR 寄存器的值到变量 temp 中 ---------------------------------------------------------------------------------------------------------3 [& } `1 T) d" k$ @% [ 通过此章的学习,我大概了解了寄存的表示,名称,偏移地址,以及其在C中的封装方式,为了能读懂别人的C代码,提升了一个层次。以前看别人的代码,根本不知道它们分别代表了什么,必须要查手册,才能勉强看懂,通过上面的学习理解,现在才明白了一位大佬曾经跟我说过的一句话:STM32的学习其实就是学会控制GPIO引脚的高低电平。 其实在看此章的时候,确实感觉非常的枯燥,没有操作来的实际,要死记硬背这些名词和地址,要理解它的意思,要掌握它们在C中的封装形式,看的很累。但是当你慢慢理解了之后,感觉还是有收获的。相信接下的学习会更加有趣。继续加油吧! $ l3 K- _) @* B$ m |
ãSTM32åºå¼å宿æå-åºäºSTM32F103é¸éã读书ç¬è®°--ç»1.pdf
下载677.64 KB, 下载次数: 15
点评