第5章 什么是寄存器

第5章     什么是寄存器

全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn

野火视频教程优酷观看网址:http://i.youku.com/firege

本章参考资料:《STM32F4xx 中文参考手册》、《STM32F429xx数据手册》、

学习本章时,配合《STM32F4xx 中文参考手册》"存储器和总线架构"、"嵌入式FLASH接口"及"通用I/O(GPIO)"章节一起阅读,效果会更佳,特别是涉及到寄存器说明的部分。

5.1 什么是寄存器

我们经常说寄存器,那么什么是寄存器?这是我们本章需要讲解的内容,在学习的过程中,大家带着这个疑问好好思考下,到最后看看大家能否用一句话给寄存器下一个定义。

5.2 STM32长啥样

我们开发板中使用的芯片是176pin的STM32F429IGT6,具体见图 51。这个就是我们接下来要学习的STM32,它讲带领我们进入嵌入式的殿堂。

芯片正面是丝印,ARM应该是表示该芯片使用的是ARM的内核,STM32F429IGT6是芯片型号,后面的字应该是跟生产批次相关,最下面的是ST的LOGO。

芯片四周是引脚,左下角的小圆点表示1脚,然后从1脚起按照逆时针的顺序排列(所有芯片的引脚顺序都是逆时针排列的)。开发板中把芯片的引脚引出来,连接到各种传感器上,然后在STM32上编程(实际就是通过程序控制这些引脚输出高电平或者低电平)来控制各种传感器工作,通过做实验的方式来学习STM32芯片的各个资源。开发板是一种评估板,板载资源非常丰富,引脚复用比较多,力求在一个板子上验证芯片的全部功能。

图 51 STM32F429IGT6 实物图

图 52 STM32F429IGT6正面引脚图

5.3 芯片里面有什么

我们看到的STM32芯片已经是已经封装好的成品,主要由内核和片上外设组成。若与电脑类比,内核与外设就如同电脑上的CPU与主板、内存、显卡、硬盘的关系。

STM32F429采用的是Cortex-M4内核,内核即CPU,由ARM公司设计。ARM公司并不生产芯片,而是出售其芯片技术授权。芯片生产厂商(SOC)如ST、TI、Freescale,负责在内核之外设计部件并生产整个芯片,这些内核之外的部件被称为核外外设或片上外设。如GPIO、USART(串口)、I2C、SPI等都叫做片上外设。具体见图 53。

图 53 STM32芯片架构简图

芯片和外设之间通过各种总线连接,其中主控总线有8条,被控总线有7条,具体见图 54。主控总线通过一个总线矩阵来连接被控总线,总线矩阵用于主控总线之间的访问仲裁管理,仲裁采用循环调度算法。总线之间交叉的时候如果有个圆圈则表示可以通信,没有圆圈则表示不可以通信。比如S0:I总线只有跟M0、M2和M6这三根被控总线交叉的时候才有圆圈,就表示S0只能跟这三根被控总线通信。从功能上来理解,I总线是指令总线,用来取指,指令指的是编译好的程序指令。我们知道STM32有三种启动方式,从FLASH启动(包含系统存储器),从内部SRAM启动,从外部RAM启动,这三种存储器刚好对应的就是M0、M2和M6这三条总线。

图 54 STM32F42xxx 和 STM32F43xxx 器件的总线接口

5.4 存储器映射

在图 54中,连接被控总线的是FLASH,RAM和片上外设,这些功能部件共同排列在一个4GB的地址空间内。我们在编程的时候,操作的也正是这些功能部件。

5.4.1 存储器映射

存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射,具体见图 55。如果给存储器再分配一个地址就叫存储器重映射。

图 55 存储器映射

1.    存储器区域功能划分

在这4GB的地址空间中,ARM已经粗线条的平均分成了8个块,每块512MB,每个块也都规定了用途,具体分类见表格 51。每个块的大小都有512MB,显然这是非常大的,芯片厂商在每个块的范围内设计各具特色的外设时并不一定都用得完,都是只用了其中的一部分而已。

表格 51 存储器功能分类


序号


用途


地址范围


Block 0


SRAM


0x0000 0000 ~ 0x1FFF FFFF(512MB)


Block 1


SRAM


0x2000 0000 ~ 0x3FFF FFFF(512MB)


Block 2


片上外设


0x4000 0000 ~ 0x5FFF FFFF(512MB)


Block 3


FMC的bank1 ~ bank2


0x6000 0000 ~ 0x7FFF FFFF(512MB)


Block 4


FMC的bank3 ~ bank4


0x8000 0000 ~ 0x9FFF FFFF(512MB)


Block 5


FMC


0xA000 0000 ~ 0xCFFF FFFF(512MB)


Block 6


FMC


0xD000 0000 ~ 0xDFFF FFFF(512MB)


Block 7


Cortex-M4内部外设


0xE000 0000 ~ 0xFFFF FFFF(512MB)

在这8个Block里面,有3个块非常重要,也是我们最关心的三个块。Boock0用来设计成内部FLASH,Block1用来设计成内部RAM,Block2用来设计成片上的外设,下面我们简单的介绍下这三个Block里面的具体区域的功能划分。

存储器Block0内部区域功能划分

Block0主要用于设计片内的FLASH, F429系列片内部FLASH最大是2MB,我们使用的STM32F429IGT6的FLASH是1MB。要在芯片内部集成更大的FLASH或者SRAM都意味着芯片成本的增加,往往片内集成的FLASH都不会太大,ST能在追求性价比的同时做到1MB以上,实乃良心之举。Block内部区域的功能划分具体见表格 52。

表格 52 存储器Block0 内部区域功能划分



用途说明


地址范围


Block0


预留


0x1FFF C008 ~ 0x1FFF FFFF


OTP区域:其中512个字节只能写一次,用于存储用户数据,额外的16个字节用于锁定对应的OTP数据块。


0x1FFF C000 ~ 0x1FFF C00F


预留


0x1FFF 7A10 ~ 0x1FFF 7FFF


系统存储器:里面寸的是ST出厂时烧写好的isp自举程序,用户无法改动。串口下载的时候需要用到这部分程序。


0x1FFF 0000 ~ 0x1FFF 7A0F


预留


0x1FFE C008 ~ 0x1FFE FFFF


选项字节:用于配置读写保护、BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。当芯片不小心被锁住之后,我们可以从RAM里面启动来修改这部分相应的寄存器位。


0x1FFE C000 ~ 0x1FFE C0FF


预留


0x1001 0000 ~ 0x1FFE BFFF


CCM数据RAM:64K,CPU直接通过D总线读取,不用经过总线矩阵,属于高速的RAM。


0x1000 0000 ~ 0x1000 FFFF


预留


0x0820 0000 ~ 0x000F FFFF


FLASH:我们的程序就放在这里。


0x0800 0000 ~ 0x081F FFFF (2MB)


预留


0x0020 0000 ~ 0x07FF FFFF


取决于BOOT引脚,为FLASH、系统存储器、SRAM的别名。


0x0000 0000 ~ 0x001F FFFF

储存器Block1内部区域功能划分

Block1用于设计片内的SRAM。F429 内部SRAM的大小为256KB,其中64KB的CCM RAM 位于 Block0,剩下的192KB位于Block1,分SRAM1 112KB,SRAM2 16KB,SRAM3 64KB,Block内部区域的功能划分具体见表格 53。

表格 53 存储器Block1 内部区域功能划分



用途说明


地址范围


Block1


预留


0x2003 0000 ~ 0x3FFF FFFF


SRAM3 64KB


0x2002 0000 ~ 0x2002 FFFF


SRAM2 16KB


0x2001 C000~ 0x2001 FFFF


SRAM1 112KB


0x2000 0000 ~0x2001 BFFFF

储存器Block2内部区域功能划分

Block2用于设计片内的外设,根据外设的总线速度不同,Block被分成了APB和AHB两部分,其中APB又被分为APB1和APB2,AHB分为AHB1和AHB2,具体见表格 54。还有一个AHB3包含了Block3/4/5/6,这四个Block用于扩展外部存储器,如SDRAM,NORFLASH和NANDFLASH等。

表格 54 存储器Block2 内部区域功能划分



用途说明


地址范围


Block2


APB1总线外设


0x4000 0000 ~ 0x4000 7FFF


预留


0x4000 8000 ~ 0x4000 FFFF


APB2总线外设


0x4001 0000 ~ 0x4001 6BFF


预留


0x4001 6C00 ~ 0x4001 FFFF


AHB1总线外设


0x4002 0000 ~ 0x4007 FFFF


预留


0x4008 0000 ~ 0x4FFF FFFF


AHB2总线外设


0x5000 0000 ~ 0x5006 0BFF


预留


0x5006 0C00 ~ 0x5FFF FFFF

5.5 寄存器映射

我们知道,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫寄存器映射?寄存器到底是什么?

在存储器Block2这块区域,设计的是片上外设,它们以四个字节为一个单元,共32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过C语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

比如,我们找到GPIOH端口的输出数据寄存器ODR的地址是0x4002 1C14(至于这个地址如何找到可以先跳过,后面我们会有详细的讲解),ODR寄存器是32bit,低16bit有效,对应着16个外部IO,写0/1对应的的IO则输出低/高电平。现在我们通过C语言指针的操作方式,让GPIOH的16个IO都输出高电平,具体见代码 51。

代码 51 通过绝对地址访问内存单元

1 // GPIOH 端口全部输出高电平

2 *(unsigned int*)(0x4002 1C14) = 0xFFFF;

0x4002 1C14在我们看来是GPIOH端口ODR的地址,但是在编译器看来,这只是一个普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即(unsigned int *)0x4002 1C14,然后再对这个指针进行 * 操作。

刚刚我们说了,通过绝对地址访问内存单元不好记忆且容易出错,我们可以通过寄存器的方式来操作,具体见代码 52。

代码 52 通过寄存器别名方式访问内存单元

1 // GPIOH 端口全部输出高电平

2 #define GPIOH_ODR (unsigned int*)(GPIOH_BASE+0x14)

3 * GPIOH_ODR = 0xFF;

为了方便操作,我们干脆把指针操作"*"也定义到寄存器别名里面,具体见代码 53。

代码 53 通过寄存器别名访问内存单元

1 // GPIOH 端口全部输出高电平

2 #define GPIOH_ODR *(unsigned int*)(GPIOH_BASE+0x14)

3 GPIOH_ODR = 0xFF;

5.5.1 STM32的外设地址映射

片上外设区分为四条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB挂载低速外设,AHB挂载高速外设。相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中APB1总线的地址最低,片上外设从这里开始,也叫外设基地址。

1.    总线基地址

表格 55 总线基地址


总线名称


总线基地址


相对外设基地址的偏移


APB1


0x4000 0000


0x0


APB2


0x4001 0000


0x0001 0000


AHB1


0x4002 0000


0x0002 0000


AHB2


0x5000 0000


0x1000 0000


AHB3


0x6000 0000


已不属于片上外设

表格 55的"相对外设基地址偏移"即该总线地址与"片上外设"基地址0x4000 0000的差值。关于地址的偏移我们后面还会讲到。

2.    外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为"XX外设基地址",也叫XX外设的边界地址。具体有关STM32F4xx外设的边界地址请参考《STM32F4xx参考手册》的2.3小节的存储器映射的表2:STM32F4xx 寄存器边界地址。或者参考《STM32F4xx参考手册》的存储器映射章节,这两个手册都有详细的讲解。

这里面我们以GPIO这个外设来讲解外设的基地址,具体见表格 56。

表格 56 外设GPIO基地址


外设名称


外设基地址


相对AHB1总线的地址偏移


GPIOA


0x4002 0000


0x0


GPIOB


0x4002 0400


0x0000 0400


GPIOC


0x4002 0800


0x0000 0800


GPIOD


0x4002 0C00


0x0000 0C00


GPIOE


0x4002 1000


0x0000 1000


GPIOF


0x4002 1400


0x0000 1400


GPIOG


0x4002 1800


0x0000 1800


GPIOH


0x4002 1C00


0x0000 1C00

从表格 56看到,GPIOA的基址相对于AHB1总线的地址偏移为0,我们应该就可以猜到,AHB1总线的第一个外设就是GPIOA。

3.    外设寄存器

在XX外设的地址范围内,分布着的就是该外设的寄存器。以GPIO外设为例,GPIO是通用输入输出端口的简称,简单来说就是STM32可控制的引脚,基本功能是控制引脚输出高电平或者低电平。最简单的应用就是把GPIO的引脚连接到LED灯的阴极,LED灯的阳极接电源,然后通过STM32控制该引脚的电平,从而实现控制LED灯的亮灭。

GPIO有很多个寄存器,每一个都有特定的功能。每个寄存器为32bit,占四个字节,在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。这里我们以GPIOH端口为例,来说明GPIO都有哪些寄存器,具体见表格 57。

表格 57 GPIOH端口的寄存器地址列表


寄存器名称


寄存器地址


相对GPIOH基址的偏移


GPIOH_MODER


0x4002 1C00


0x00


GPIOH_OTYPER


0x4002 1C04


0x04


GPIOH_OSPEEDR


0x4002 1C08


0x08


GPIOH_PUPDR


0x4002 1C0C


0x0C


GPIOH_IDR


0x4002 1C10


0x10


GPIOH_ODR


0x4002 1C14


0x14


GPIOH_BSRR


0x4002 1C18


0x18


GPIOH_LCKR


0x4002 1C1C


0x1C


GPIOH_AFRL


0x4002 1C20


0x20


GPIOH_AFRH


0x4002 1C24


0x24

有关外设的寄存器说明可参考《STM32F4xx参考手册》中具体章节的寄存器描述部分,在编程的时候我们需要反复的查阅外设的寄存器说明。

这里我们以"GPIO端口置位/复位寄存器"为例,教大家如何理解寄存器的说明,具体见图 56。

图 56 GPIO端口置位/复位寄存器说明

?    ①名称

寄存器说明中首先列出了该寄存器中的名称,"(GPIOx_BSRR)(x=A…I)"这段的意思是该寄存器名为"GPIOx_BSRR"其中的"x"可以为A-I,也就是说这个寄存器说明适用于GPIOA、GPIOB至GPIOI,这些GPIO端口都有这样的一个寄存器。

?    ②偏移地址

偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是0x18,从参考手册中我们可以查到GPIOA外设的基地址为0x4002 0000 ,我们就可以算出GPIOA的这个GPIOA_BSRR寄存器的地址为:0x4002 0000+0x18 ;同理,由于GPIOB的外设基地址为0x4002 0400,可算出GPIOB_BSRR寄存器的地址为:0x4002 0400+0x18 。其他GPIO端口以此类推即可。

?    ③寄存器位表

紧接着的是本寄存器的位表,表中列出它的0-31位的名称及权限。表上方的数字为位编号,中间为位名称,最下方为读写权限,其中w表示只写,r表示只读,rw表示可读写。本寄存器中的位权限都是w,所以只能写,如果读本寄存器,是无法保证读取到它真正内容的。而有的寄存器位只读,一般是用于表示STM32外设的某种工作状态的,由STM32硬件自动更改,程序通过读取那些寄存器位来判断外设的工作状态。

?    ④位功能说明

位功能是寄存器说明中最重要的部分,它详细介绍了寄存器每一个位的功能。例如本寄存器中有两种寄存器位,分别为BRy及BSy,其中的y数值可以是0-15,这里的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的时候对应的引脚输出低电平即可(感兴趣的读者可以查询该寄存器GPIOx_ODR的说明了解)。所以,如果对BR0写入"1"的话,那么GPIOx的第0个引脚就会输出"低电平",但是对BR0写入"0"的话,却不会影响ODR0位,所以引脚电平不会改变。要想该引脚输出"高电平",就需要对"BS0"位写入"1",寄存器位BSy与BRy是相反的操作。

5.5.2 C语言对寄存器的封装

以上所有的关于存储器映射的内容,最终都是为大家更好地理解如何用C语言控制读写外设寄存器做准备,此处是本章的重点内容。

1.    封装总线和外设基地址

在编程上为了方便理解和记忆,我们把总线基地址和外设基地址都以相应的宏定义起来,总线或者外设都以他们的名字作为宏名,具体见代码 54。

代码 54 总线和外设基址宏定义

1 /* 外设基地址 */

2 #define PERIPH_BASE ((unsigned int)0x40000000)

3

4 /* 总线基地址 */

5 #define APB1PERIPH_BASE PERIPH_BASE

6 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)

7 #define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)

8 #define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000)

9

10 /* GPIO外设基地址 */

11 #define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)

12 #define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400)

13 #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800)

14 #define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00)

15 #define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000)

16 #define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)

17 #define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800)

18 #define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)

19

20 /* 寄存器基地址,以GPIOH为例 */

21 #define GPIOH_MODER (GPIOH_BASE+0x00)

22 #define GPIOH_OTYPER (GPIOH_BASE+0x04)

23 #define GPIOH_OSPEEDR (GPIOH_BASE+0x08)

24 #define GPIOH_PUPDR (GPIOH_BASE+0x0C)

25 #define GPIOH_IDR (GPIOH_BASE+0x10)

26 #define GPIOH_ODR (GPIOH_BASE+0x14)

27 #define GPIOH_BSRR (GPIOH_BASE+0x18)

28 #define GPIOH_LCKR (GPIOH_BASE+0x1C)

29 #define GPIOH_AFRL (GPIOH_BASE+0x20)

30 #define GPIOH_AFRH (GPIOH_BASE+0x24)

代码 54首先定义了"片上外设"基地址PERIPH_BASE,接着在PERIPH_BASE上加入各个总线的地址偏移,得到APB1、APB2等总线的地址APB1PERIPH_BASE、APB2PERIPH_BASE,在其之上加入外设地址的偏移,得到GPIOA、GPIOH的外设地址,最后在外设地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具体地址,就可以用指针操作读写了,具体见代码 55。

代码 55 使用指针控制BSRR寄存器

1 /* 控制GPIOH 引脚10输出低电平(BSRR寄存器的BR10置1) */

2 *(unsigned int *)GPIOH_BSRR = (0x01<<(16+10));

3

4 /* 控制GPIOH 引脚10输出高电平(BSRR寄存器的BS10置1) */

5 *(unsigned int *)GPIOH_BSRR = 0x01<<10;

6

7 unsigned int temp;

8 /* 控制GPIOH 端口所有引脚的电平(读IDR寄存器) */

9 temp = *(unsigned int *)GPIOH_IDR;

该代码使用 (unsigned int *) 把GPIOH_BSRR宏的数值强制转换成了地址,然后再用"*"号做取指针操作,对该地址的赋值,从而实现了写寄存器的功能。同样,读寄存器也是用取指针操作,把寄存器中的数据取到变量里,从而获取STM32外设的状态。

2.    封装寄存器列表

用上面的方法去定义地址,还是稍显繁琐,例如GPIOA-GPIOH都各有一组功能相同的寄存器,如GPIOA_MODER/GPIOB_MODER/GPIOC_MODER等等,它们只是地址不一样,但却要为每个寄存器都定义它的地址。为了更方便地访问寄存器,我们引入C语言中的结构体语法对寄存器进行封装,具体见代码 56。

代码 56 使用结构体对GPIO寄存器组的封装

1 typedef unsigned int uint32_t; /*无符号32位变量*/

2 typedef unsigned short int uint16_t; /*无符号16位变量*/

3

4 /* GPIO寄存器列表 */

5 typedef struct {

6 uint32_t MODER; /*GPIO模式寄存器地址偏移: 0x00 */

7 uint32_t OTYPER; /*GPIO输出类型寄存器地址偏移: 0x04 */

8 uint32_t OSPEEDR; /*GPIO输出速度寄存器地址偏移: 0x08 */

9 uint32_t PUPDR; /*GPIO上拉/下拉寄存器地址偏移: 0x0C */

10 uint32_t IDR; /*GPIO输入数据寄存器地址偏移: 0x10 */

11 uint32_t ODR; /*GPIO输出数据寄存器地址偏移: 0x14 */

12 uint16_t BSRRL; /*GPIO置位/复位寄存器低16位部分地址偏移: 0x18 */

13 uint16_t BSRRH; /*GPIO置位/复位寄存器高16位部分地址偏移: 0x1A */

14 uint32_t LCKR; /*GPIO配置锁定寄存器地址偏移: 0x1C */

15 uint32_t AFR[2]; /*GPIO复用功能配置寄存器地址偏移: 0x20-0x24 */

16 } GPIO_TypeDef;

这段代码用typedef 关键字声明了名为GPIO_TypeDef的结构体类型,结构体内有8个成员变量,变量名正好对应寄存器的名字。C语言的语法规定,结构体内变量的存储空间是连续的,其中32位的变量占用4个字节,16位的变量占用2个字节,具体见图 57。

图 57 GPIO_TypeDef结构体成员的地址偏移

也就是说,我们定义的这个GPIO_TypeDef ,假如这个结构体的首地址为0x4002 1C00(这也是第一个成员变量MODER的地址),那么结构体中第二个成员变量OTYPER的地址即为0x4002 1C00 +0x04 ,加上的这个0x04 ,正是代表MODER所占用的4个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给出,其中的BSRR寄存器分成了低16位BSRRL和高16位BSRRH,BSRRL置1引脚输出高电平,BSRRH置1引脚输出低电平,这里分开只是为了方便操作。

这样的地址偏移与STM32 GPIO外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存器了,具体见代码 57。

代码 57 通过结构体指针访问寄存器

1 GPIO_TypeDef * GPIOx; //定义一个GPIO_TypeDef型结构体指针GPIOx

2 GPIOx = GPIOH_BASE; //把指针地址设置为宏GPIOH_BASE地址

3 GPIOx->BSRRL = 0xFFFF; //通过指针访问并修改GPIOH_BSRRL寄存器

4 GPIOx->MODER = 0xFFFFFFFF; //修改GPIOH_MODER寄存器

5 GPIOx->OTYPER =0xFFFFFFFF; //修改GPIOH_OTYPER寄存器

6

7 uint32_t temp;

8 temp = GPIOx->IDR; //读取GPIOH_IDR寄存器的值到变量temp中

这段代码先用GPIO_TypeDef类型定义一个结构体指针GPIOx,并让指针指向地址GPIOH_BASE(0x4002 1C00),使用地址确定下来,然后根据C语言访问结构体的语法,用GPIOx->BSRRL、GPIOx->MODER及GPIOx->IDR等方式读写寄存器。

最后,我们更进一步,直接使用宏定义好GPIO_TypeDef类型的指针,而且指针指向各个GPIO端口的首地址,使用时我们直接用该宏访问寄存器即可,具体代码 58。

代码 58 定义好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 /*访问GPIOH端口的寄存器*/

15 GPIOH->BSRRL = 0xFFFF; //通过指针访问并修改GPIOH_BSRRL寄存器

16 GPIOH->MODER = 0xFFFFFFF; //修改GPIOH_MODER寄存器

17 GPIOH->OTYPER =0xFFFFFFF; //修改GPIOH_OTYPER寄存器

18

19 uint32_t temp;

20 temp = GPIOH->IDR; //读取GPIOH_IDR寄存器的值到变量temp中

21

22 /*访问GPIOA端口的寄存器*/

23 GPIOA->BSRRL = 0xFFFF; //通过指针访问并修改GPIOA_BSRRL寄存器

24 GPIOA->MODER = 0xFFFFFFF; //修改GPIOA_MODER寄存器

25 GPIOA->OTYPER =0xFFFFFFF; //修改GPIOA_OTYPER寄存器

26

27 uint32_t temp;

28 temp = GPIOA->IDR; //读取GPIOA_IDR寄存器的值到变量temp中

这里我们仅是以GPIO这个外设为例,给大家讲解了C语言对寄存器的封装。以此类推,其他外设也同样可以用这种方法来封装。好消息是,这部分工作都由固件库帮我们完成了,这里我们只是分析了下这个封装的过程,让大家知其然,也只其所以然。

5.6 每课一问

1、    什么是存储器映射?什么是存储器重映射?

2、    什么是寄存器?

原文地址:https://www.cnblogs.com/tianqiang/p/8414514.html

时间: 2024-09-30 19:56:52

第5章 什么是寄存器的相关文章

汇编入门学习笔记 (二)—— 寄存器(内存访问)、栈

疯狂的暑假学习之  汇编入门学习笔记 (二) 参考:<汇编语言> 王爽  第三章 一.寄存器(内存访问) 1.DS和[address] DS 数据段寄存器,用来存放数据段地址 [address] 用来表示数据段的偏移地址 同样跟CS一样,不可以通过 mov ds, 1000 给ds赋值 要通过通用寄存器ax等. 例如: mov ax, 1000 mov ds, ax mov al, [0] 把1000:0的内容存进al mov bx, 1000 mov ds, bx mov [0], al 把

汇编语言程序设计第二章小结

一.通用寄存器:AX.BX.CX和DX.(均为16位,可存放2个字节).都可以分为两个独立的8位寄存器.(eg:AH和AL,其中AL为低8位,AH为高8位.) 二.字在寄存器中的存储 1字节(byte)=8bit,1个字=2字节=高位字节+地位字节,分别对应存在高位寄存器和低位寄存器中. 三.汇编指令 注意:在写一条汇编指令或一个寄存器名称时不用区分大小写. 引入:mov和add 溢出问题:AH和AL是两个不相关到的寄存器,当操作AL中有进位时,AL不会向AH进位. (eg: mov ax,00

关于寄存器的总结

本章学习围绕寄存器展开,学习了CPU概述.通用寄存器.字在寄存器中的存放.汇编指令: mov, add.8086给出物理地址的方法.段寄存器CS和指令指针寄存器IP这几个知识点.以下我便逐个写出我认为该知识点中的值得注意的地方. 1.CPU中有字长与位宽两个概念,字长表示CPU在单位时间内能一次处理的二进制位数.该指标反映出CPU内部运算处理的速度.位宽表示CPU通过外部数据总线与内存之间一次能够传送的数据位.我们常说的x位寄存器其实指的便是CPU的字长. 2.8086CPU的14个寄存器中只有

汇编第二章梳理(期末复习)

(留着几个课后任务没写,临近学期末,全当复习了.因为是为复习用的,所以文章比较自闭,) 第二章标题叫寄存器,那么大概就是讲和寄存器有关的东西,准确说是单纯寄存器之间操作,还有 debug 的操作(在实验部分). 8086有14个寄存器,AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW.这些寄存器都是16位的,可以存放两个字节.其中,AX,BX,CX,DX 用来存放一般的数据,为通用寄存器.为了兼容性,这四个寄存器分别可以拆分为两个8位寄存器独立使用.高位字节为

独家全功能USB2.0开发板,最强CY7C68013A-128核心板,超强资料不断提供更新服务

       学习USB开发再也不用买书啦,也不用花费重金去上培训班啦,IFLabs开创USB学习开发新模式,你所需要的知识.技术.范例.代码等等统统都在这里.IFLabs打造全网最丰富.最权威的USB开发平台和资料. 热卖的IFLabs精品USB2.0核心板套件再升级,全网独家推出最强.最全功能的Cypress USB 2.0 CY7C68013A-128AXC核心板开发板,全网最全配件和全网最全开发资料.只需这一次投入,即可实现USB接口开发的从入门到精通!并且有长期的全网最全开发手册更新支

小白日记17:kali渗透测试之缓冲区溢出实例-windows,POP3,SLmail

缓冲区溢出实例 缓冲区溢出原理:http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html 空间存储了用户程序的函数栈帧(包括参数.局部数据等),实现函数调用机制,它的数据增长方向是低地址方向.堆空间存储了程序运行时动态申请的内存数据等,数据增长方向是高地址方向.除了代码段和受操作系统保护的数据区域,其他的内存区域都可能作为缓冲区,因此缓冲区溢出的位置可能在数据段,也可能在堆.栈段.如果程序的代码有软件漏洞,恶意程序

(5)MSP430F5529 统一时钟系统UCS

之前有一点漏说明了,先补充上:还有一个模块时钟源:MODOSC,产生MODCLK时钟源信号,一般只为闪存控制模块和ADC12模块提供服务. 该模块不被使用时自动关闭,任何模块对该时钟源提出使用要求时,MODOSC无需被使能即可响应该请求.430F5529中MODCLK为5MHZ. *************************************************************** MSP430F5529有多个时钟源,而且很多模块其时钟源都是可以自由选择的.此外,由于一般

msp430f5529时钟源

***************************************************************还有一个模块时钟源:MODOSC,产生MODCLK时钟源信号,一般只为闪存控制模块和ADC12模块提供服务. 该模块不被使用时自动关闭,任何模块对该时钟源提出使用要求时,MODOSC无需被使能即可响应该请求.430F5529中MODCLK为5MHZ. *************************************************************

《逆向工程核心原理》

<逆向工程核心原理> 基本信息 作者: (韩)李承远    译者: 武传海 丛书名: 图灵程序设计丛书 出版社:人民邮电出版社 ISBN:9787115350183 上架时间:2014-4-15 出版日期:2014 年5月 开本:16开 页码:679 版次:1-1 所属分类:计算机 > 软件工程及软件方法学 > 综合 更多关于>>><逆向工程核心原理>   编辑推荐     韩国逆向分析领域龙头之作!逆向分析师必知核心原理大全!本书集中讲解逆向分析技术