STM32中文手册的阅读

初学stm32,读手册是十分重要的。由于买了野火的开发板,最近也在学习,所以记录下学习的内容。
关于寄存器的描述:
首先对于STM32芯片基础知识的了解
STM32 芯片是已经封装好的成品,主要由内核和片上外设组成。若与电脑类比,内核与外设就如同电脑上的 CPU 与主板、内存、显卡、硬盘的关系。
STM32F103 采用的是 Cortex-M3 内核,内核即 CPU,由 ARM 公司设计。ARM 公司并不生产芯片,而是出售其芯片技术授权。芯片生产厂商(SOC)如 ST、TI、Freescale,负责在内核之外设计部件并生产整个芯片,这些内核之外的部件被称为核外外设或片上外设。如 GPIO、USART(串口)、I2C、SPI 等都叫做片上外设。
芯片(这里指内核,或者叫 CPU)和外设之间通过各种总线连接,其中驱动单元有 4个,被动单元也有 4 个,为了方便理解,我们都可以把驱动单元理解成是CPU 部分,被动单元都理解成外设。

1. ICode 总线
ICode 中的 I 表示 Instruction,即指令。我们写好的程序编译之后都是一条条指令,存放在 FLASH 中,内核要读取这些指令来执行程序就必须通过 ICode 总线,它几乎每时每刻都需要被使用,它是专门用来取指的。

2. 驱动单元
DCode 总线

DCode 中的 D 表示 Data,即数据,那说明这条总线是用来取数的。我们在写程序的时候,数据有常量和变量两种,常量就是固定不变的,用 C 语言中的 const 关键字修饰,是放到内部的 FLASH 当中的,变量是可变的,不管是全局变量还是局部变量都放在内部的SRAM。因为数据可以被 Dcode 总线和 DMA 总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数。

系统总线
系统总线主要是访问外设的寄存器,我们通常说的寄存器编程,即读写寄存器都是通过这根系统总线来完成的。

DMA 总线
DMA 总线也主要是用来传输数据,这个数据可以是在某个外设的数据寄存器,可以在SRAM,可以在内部的 FLASH。因为数据可以被 Dcode 总线和 DMA 总线访问,所以为了避免访问冲突,在取数的时候需要经过一个总线矩阵来仲裁,决定哪个总线在取数。

3. 被动单元
内部的闪存存储器

内部的闪存存储器即 FLASH,我们编写好的程序就放在这个地方。内核通过 ICode 总线来取里面的指令。
内部的 SRAM
内部的 SRAM,即我们通常说的 RAM,程序的变量,堆栈等的开销都是基于内部的SRAM。内核通过 DCode 总线来访问它。
FSMC
FSMC 的英文全称是 Flexible static memory controller,叫灵活的静态的存储器控制器,是STM32F10xx 中一个很有特色的外设,通过 FSMC,我们可以扩展内存,如外部的SRAM,NANDFLASH NORFLASH。但有一点我们要注意的是,FSMC 只能扩展静态的内存,即名称里面的 S:static,不能是动态的内存,比如 SDRAM 就不能扩展。
AHB 到 APB 的桥
从 AHB 总线延伸出来的两条 APB2 和 APB1 总线,上面挂载着 STM32 各种各样的特色外设。我们经常说的 GPIO、串口、I2C、SPI 这些外设就挂载在这两条总线上,这个是我们学习 STM32 的重点,就是要学会编程这些外设去驱动外部的各种设备。

存储器映射
被控单元的 FLASH,RAM,FSMC 和 AHB 到 APB 的桥(即片上外设),这些功能部件共同排列在一个 4GB 的地址空间内。我们在编程的时候,可以通过他们的地址找到他们,然后来操作他们(通过 C 语言对它们进行数据的读和写)。
存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射。如果给存储器再分配一个地址就叫存储器重映射。

存储器功能分类
序号 用途 地址范围
Block 0 Code 0x0000 0000 ~ 0x1FFF FFFF(512MB)
Block 1 SRAM 0x2000 0000 ~ 0x3FFF FFFF(512MB)
Block 2 片上外设 0x4000 0000 ~ 0x5FFF FFFF(512MB)
Block 3 FSMC 的 bank1 ~ bank2 0x6000 0000 ~ 0x7FFF FFFF(512MB)
Block 4 FSMC 的 bank3 ~ bank4 0x8000 0000 ~ 0x9FFF FFFF(512MB)
Block 5 FSMC 寄存器 0xA000 0000 ~ 0xCFFF FFFF(512MB)
Block 6 没有使用 0xD000 0000 ~ 0xDFFF FFFF(512MB)
Block 7 Cortex-M3 内部外设 0xE000 0000 ~ 0xFFFF FFFF(512MB)

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

存储器 Block0 内部区域功能划分
Block0 主要用于设计片内的 FLASH,我们使用的 STM32F103ZET6(霸道)和STM32F103VET6(指南者)的 FLASH 都是 512KB,属于大容量。要在芯片内部集成更大的 FLASH 或者 SRAM 都意味着芯片成本的增加,往往片内集成的 FLASH 都不会太大,ST 能在追求性价比的同时做到 512KB,实乃良心之举。
Block 内部区域的功能划分具体见表格 6-2
存储器 Block0 内部区域功能划分
块 用途说明 地址范围
预留 0x1FFE C008 ~ 0x1FFF FFFF
选项字节: 用 于 配 置 读 写 保 护 、 0x1FFF F800 - 0x1FFF F80F
BOR 级别、软件/硬件看门狗以及器
件处于待机或停止模式下的复位。当
芯片不小心被锁住之后,我们可以从
RAM 里面启动来修改这部分相应的寄
存器位。
Block0 系统存储器:里面存的是 ST 出厂时 0x1FFF F000- 0x1FFF F7FF
烧 写 好 的 isp 自 举 程 序 ( 即
Bootloader),用户无法改动。串口
下载的时候需要用到这部分程序。
预留 0x0808 0000 ~ 0x1FFF EFFF
FLASH:我们的程序就放在这里。 0x0800 0000 ~ 0x0807 FFFF (512KB)
预留 0x0008 0000 ~ 0x07FF FFFF
取决于 BOOT 引脚,为 FLASH、系 0x0000 0000 ~ 0x0007 FFFF
统存储器、SRAM 的别名。

储存器 Block1 内部区域功能划分
Block1 用 于 设 计 片 内 的 SRAM 。 我 们 使 用 的 STM32F103ZET6 ( 霸 道 ) 和
STM32F103VET6(指南者)的 SRAM 都是 64KB,Block 内部区域的功能划分具体见表格
6-3。
块 用途说明 地址范围
Block1 预留 0x2001 0000 ~ 0x3FFF FFFF
SRAM 64KB 0x2000 0000 ~0x2000 FFFF

储存器 Block2 内部区域功能划分
Block2 用于设计片内的外设,根据外设的总线速度不同,Block 被分成了 APB 和 AHB
两部分,其中 APB 又被分为 APB1 和 APB2,具体见表格 6-4。
表格 6-4 存储器 Block2 内部区域功能划分
块 用途说明 地址范围
APB1 总线外设 0x4000 0000 ~ 0x4000 77FF
Block2 APB2 总线外设 0x4001 0000 ~ 0x4001 3FFF
AHB 总线外设 0x4001 8000 ~ 0x5003 FFFF

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

代码 6-1 通过绝对地址访问内存单元
1 // GPIOB 端口全部输出 高电平
2 (unsigned int)(0x4001 0C0C) = 0xFFFF;

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

刚刚我们说了,通过绝对地址访问内存单元不好记忆且容易出错,我们可以通过寄存
器的方式来操作。
代码 6-2 通过寄存器别名访问内存单元
1 // GPIOB 端口全部输出 高电平
2 #define GPIOB_ODR (unsigned int)(GPIOB_BASE+0x0C)
3 GPIOB_ODR = 0xFF;

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

总线基地址
总线名称 总线基地址 相对外设基地址的偏移
APB1 0x4000 0000 0x0
APB2 0x4001 0000 0x0001 0000
AHB 0x4001 8000 0x0001 8000
“相对外设基地址偏移”即该总线地址与“片上外设”基地址 0x4000 0000的差值。

外设基地址
总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为
“XX 外设基地址”,也叫 XX 外设的边界地址。具体有关 STM32F10xx 外设的边界地址
请参考《STM32F10xx 参考手册》的 2.3 小节的存储器映射的表 1:STM32F10xx 寄存器边
界地址。
这里面我们以 GPIO 这个外设来讲解外设的基地址,GPIO 属于高速的外设 ,挂载到
APB2 总线上,具体见表格 6-6。
表格 6-6 外设 GPIO 基地址
外设名称 外设基地址 相对 APB2 总线的地址偏移
GPIOA 0x4001 0800 0x0000 0800
GPIOB 0x4001 0C00 0x0000 0C00
GPIOC 0x4001 1000 0x0000 1000
GPIOD 0x4001 1400 0x0000 1400
GPIOE 0x4001 1800 0x0000 1800
GPIOF 0x4001 1C00 0x0000 1C00
GPIOG 0x4001 2000 0x0000 2000

外设寄存器
在 XX 外设的地址范围内,分布着的就是该外设的寄存器。以 GPIO 外设为例,GPIO
通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,基本功能是控制引脚输
出高电平或者低电平。最简单的应用就是把 GPIO 的引脚连接到 LED 灯的阴极,LED 灯的
阳极接电源,然后通过 STM32 控制该引脚的电平,从而实现控制 LED 灯的亮灭。
GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为 32bit,占四个字节,
在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。
表格 6-7 GPIOB 端口的 寄存器地址列表
寄存器名称 寄存器地址 相对 GPIOB 基址的偏移
GPIOB_CRL 0x4001 0C00 0x00
GPIOB_CRH 0x4001 0C04 0x04
GPIOB_IDR 0x4001 0C08 0x08
GPIOB_ODR 0x4001 0C0C 0x0C
GPIOH_BSRR 0x4001 0C10 0x10
GPIOH_BRR 0x4001 0C14 0x14
GPIOH_LCKR 0x4001 0C18 0x18

①名称
寄存器说明中首先列出了该寄存器中的名称,“(GPIOx_BSRR)(x=A…E)”这段的意
思是该寄存器名为“GPIOx_BSRR”其中的“x”可以为 A-E,也就是说这个寄存器说明适
用于 GPIOA、GPIOB 至 GPIOE,这些 GPIO 端口都有这样的一个寄存器。
②偏移地址
偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是 0x18,
从参考手册中我们可以查到 GPIOA 外设的基地址为 0x4001 0800 ,我们就可以算出
GPIOA 的这个 GPIOA_BSRR 寄存器的地址为:0x4001 0800+0x18 ;同理,由于 GPIOB 的
外设基地址为 0x4001 0C00,可算出 GPIOB_BSRR 寄存器的地址为:0x4001 0C00+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 是相反的操作。

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

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 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)

首先定义了 “片上外设”基地址 PERIPH_BASE,接着在 PERIPH_BASE 上
加入各个总线的地址偏移,得到 APB1、APB2 总线的地址 APB1PERIPH_BASE、
APB2PERIPH_BASE,在其之上加入外设地址的偏移,得到 GPIOA-G 的外设地址,最后在
外设地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具体地址,就可
以用指针读写。使用指针控制 BSRR 寄存器。

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;

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

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

1 typedef unsigned int uint32_t; /*无符号 32 位变量*/
2 typedef unsigned short int uint16_t; /*无符号 16 位变量*/
3
4 /* GPIO 寄存器列表 */
5 typedef struct {6 uint32_t CRL;                /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
7 uint32_t CRH;                /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
8 uint32_t IDR;                /*GPIO 数据输入寄存器 地址偏移: 0x08 */
9 uint32_t ODR;                /*GPIO 数据输出寄存器 地址偏移: 0x0C */
10 uint32_t BSRR;              /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
11 uint32_t BRR;               /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
12 uint16_t LCKR;              /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */
} GPIO_TypeDef;

这段代码用 typedef 关键字声明了名为 GPIO_TypeDef 的结构体类型,结构体内有 7 个
成员变量,变量名正好对应寄存器的名字。
C 语言的语法规定,结构体内变量的存储空间
是连续的,其中 32 位的变量占用 4 个字节,16 位的变量占用 2 个字节。
也就是说,我们定义的这个 GPIO_TypeDef ,假如这个结构体的首地址为 0x4001
0C00(这也是第一个成员变量 CRL 的地址), 那么结构体中第二个成员变量 CRH 的地址
即为 0x4001 0C00 +0x04 ,加上的这个 0x04 ,正是代表 CRL 所占用的 4 个字节地址的偏
移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给。
这样的地址偏移与 STM32 GPIO 外设定义的寄存器地址偏移一一对应,只要给结构体
设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存
器。

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

1 GPIO_TypeDef * GPIOx; //定义一个 GPIO_TypeDef 型结构体指针 GPIOx
2 GPIOx = GPIOB_BASE; //把指针地址设置为宏 GPIOH_BASE 地址
3 GPIOx->IDR = 0xFFFF;
4 GPIOx->ODR = 0xFFFF;
5
6
7 uint32_t temp;
8 temp = GPIOx->IDR; //读取 GPIOB_IDR 寄存器的值到变量 temp 中

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

最后,我们更进一步,直接使用宏定义好 GPIO_TypeDef 类型的指针,而且指针指向
各个 GPIO 端口的首地址,使用时我们直接用该宏访问寄存器即可
定义好 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 端口的寄存器*/
15 GPIOB->BSRR = 0xFFFF; //通过指针访问并修改 GPIOB_BSRR 寄存器
16 GPIOB->CRL = 0xFFFF; //修改 GPIOB_CRL 寄存器
17 GPIOB->ODR =0xFFFF; //修改 GPIOB_ODR 寄存器
18
19 uint32_t temp;
20 temp = GPIOB->IDR; //读取 GPIOB_IDR 寄存器的值到变量 temp 中
21
22 /*访问 GPIOA 端口的寄存器*/
23 GPIOA->BSRR = 0xFFFF;  //通过指针访问并修改GPIOA_BSRR寄存器
24 GPIOA->CRL = 0xFFFF;   //修改GPIOA_CRL寄存器
25 GPIOA->ODR =0xFFFF;    //修改GPIOA_odr寄存器
26
27 uint32_t temp;
28 temp = GPIOA->IDR; //读取 GPIOA_IDR 寄存器的值到变量 temp 中

修改寄存器的位操作方法
使用 C 语言对寄存器赋值时,我们常常要求只修改该寄存器的某几位的值,且其它的
寄存器位不变,这个时候我们就需要用到 C 语言的位操作方法了。
1. 把变量的某位清零
此处我们以变量 a 代表寄存器,并假设寄存器中本来已有数值,此时我们需要把变量
a 的某一位清零,且其它位不变,方法见代码清单 6-1。
代码清单 6-1 对某位清零

1 //定义一个变量 a = 1001 1111 b (二进制数)
2 unsigned char a = 0x9f;
3
4 //对 bit2 清零
5
6 a &= ~(1<<2);
7
8 //括号中的 1 左移两位,(1<<2)得二进制数:0000 0100 b
9 //按位取反,~(1<<2)得 1111 1011 b
10 //假如 a 中原来的值为二进制数: a = 1001 1111 b
11 //所得的数与 a 作”位与&”运算,a = (1001 1111 b)&(1111 1011 b),
12 //经过运算后,a 的值 a=1001 1011 b
13 // a 的 bit2 位被被零,而其它位不变。

把变量的某几个连续位清零
由于寄存器中有时会有连续几个寄存器位用于控制某个功能,现假设我们需要把寄存
器的某几个连续位清零,且其它位不变,方法见代码清单 6-2。
代码清单 6-2 对某几个连续位清零

1
2 //若把 a 中的二进制位分成 2 个一组
3 //即 bit0、bit1 为第 0 组,bit2、bit3 为第 1 组,
4 // bit4、bit5 为第 2 组,bit6、bit7 为第 3 组
5 //要对第 1 组的 bit2、bit3 清零
6
7 a &= ~(3<<2*1);
8
9 //括号中的 3 左移两位,(3<<2*1)得二进制数:0000 1100 b
10 //按位取反,~(3<<2*1)得 1111 0011 b
11 //假如 a 中原来的值为二进制数: a = 1001 1111 b
12 //所得的数与 a 作”位与&”运算,a = (1001 1111 b)&(1111 0011 b),
13 //经过运算后,a 的值 a=1001 0011 b
14 // a 的第 1 组的 bit2、bit3 被清零,而其它位不变。
15
16 //上述(~(3<<2*1))中的(1)即为组编号;如清零第 3 组 bit6、bit7 此处应为 3
17 //括号中的(2)为每组的位数,每组有 2 个二进制位;若分成 4 个一组,此处即为 4
18 //括号中的(3)是组内所有位都为 1 时的值;若分成 4 个一组,此处即为二进制数“1111 b”
19
20 //例如对第 2 组 bit4、bit5 清零
21 a &= ~(3<<2*2);

对变量的某几位进行赋值。
寄存器位经过上面的清零操作后,接下来就可以方便地对某几位写入所需要的数值了,
且其它位不变,方法见代码清单 6-3,这时候写入的数值一般就是需要设置寄存器的位参
数。
代码清单 6-3 对某几位进行赋值

1 //a = 1000 0011 b
2 //此时对清零后的第 2 组 bit4、bit5 设置成二进制数“01 b ”
3
4 a |= (1<<2*2);
5 //a = 1001 0011 b,成功设置了第 2 组的值,其它组不变

对变量的某位取反
某些情况下,我们需要对寄存器的某个位进行取反操作,即 1 变 0 ,0 变 1,这可以直
接用如下操作,其它位不变,见代码清单 6-4。
代码清单 6-4 对某位进行取反操作

1 //a = 1001 0011 b
2 //把 bit6 取反,其它位不变
3
4 a ^=(1<<6);
5 //a = 1101 0011 b

关于GPIO寄存器:
端口配置低寄存器(GPIOx_CRL) (x=A…E)
偏移地址:0x00
复位值:0x4444 4444
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
CNF7[1:0] MODE7[1:0] CNF6[1:0] MODE6[1:0] CNF5[1:0] MODE5[1:0] CNF4[1:0] MODE4[1:0]
rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
CNF3[1:0] MODE3[1:0] CNF2[1:0] MODE2[1:0] CNF1[1:0] MODE1[1:0] CNF0[1:0] MODE0[1:0]
rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw rw

位31:30 CNFy[1:0]:端口x配置位(y = 0…7) (Port x configuration bits)
27:26 软件通过这些位配置相应的I/O端口,请参考表17端口位配置表。
23:22 在输入模式(MODE[1:0]=00):
19:18 00:模拟输入模式
15:14 01:浮空输入模式(复位后的状态) 11:10 10:上拉/下拉输入模式 7:6
11:保留 3:2
在输出模式(MODE[1:0]>00):
00:通用推挽输出模式
01:通用开漏输出模式
10:复用功能推挽输出模式
11:复用功能开漏输出模式

位29:28 MODEy[1:0]:端口x的模式位(y = 0…7) (Port x mode bits)
25:24 软件通过这些位配置相应的I/O端口,请参考表17端口位配置表。
21:20 00:输入模式(复位后的状态)
17:16 01:输出模式,最大速度10MHz
13:12 10:输出模式,最大速度2MHz
9:8, 5:4 11:输出模式,最大速度50MHz 1:0

STM32中文手册解读(1)相关推荐

  1. STM32中文参考手册下载地址

    STM32中文参考手册下载地址 进入官网后点击"设计资源" 选择你要下载的芯片的手册 然后找到有"完整的存储器和外设信息"字眼的文档进行下载 很多人下载的下面这 ...

  2. STM32中文参考手册_V10

    STM32中文参考手册_V10 链接:https://pan.baidu.com/s/1AZXyanPyiazpVvVrJwXvRg 提取码:uwi5

  3. stm8s005k6引脚图_stm8s005k6 中文手册 005超市盘点手册.doc

    stm8s005k6 中文手册 005超市盘点手册 stm8s005k6 中文手册 005超市盘点手册 盘点手册 二零零八年 内部资料 严禁外传 编号:0005 目 录 前言 盘点概述 盘点作业流程 ...

  4. Smarty中文手册,Smarty教程,Smarty模板的入门教材

    Smarty中文手册,Smarty教程,Smarty模板的入门教材 首先,这份Smarty中文手册的翻译工作是由喜悦国际村村民自发组织的,不代表任何人的意见和观点.对他们的无私奉献精神,我们表示感谢, ...

  5. man nfsd(rpc.nfsd中文手册)

    本人译作集合:http://www.cnblogs.com/f-ck-need-u/p/7048359.html rpc.nfsd(8) System Manager's Manual rpc.nfs ...

  6. CSS2.0中文手册(CHM版)

    Div+Css是现在网站架设的一个趋势,应用Css对于网站有诸多的好处.本教程是沈小雨2002年制作的 Css2.0中文手册,对学习CSS和查询CSS属性非常有帮助. Css2.0中文手册针对的是已有 ...

  7. iPhone开发环境搭建全过程 iPhone手机开发内容,中文手册

    http://3g.edu.csdn.net/kecheng/iphone.html  iPhone手机开发内容 http://www.docin.com/p-34874880.html# iPhon ...

  8. Apache2.2中文手册

    Apache2.2中文手册 CHM格试 转载于:https://blog.51cto.com/zjcookies/114174

  9. PostgreSQL9.3中文手册的在线纠错

    PostgreSQL9.3中文手册已经在PG中国社区的官网上正式发布了,下面是网址: http://www.postgres.cn/docs/9.3 然而翻译中难免会有一些小错误.所以我们在在线手册的 ...

最新文章

  1. Win10的64位操作系统,Visual Studio 2019配置OpenCV4.1.0
  2. PG SQL数据库读写分离的思路
  3. MySQL总结连接查询
  4. 亿级流量网站架构核心技术_关于大型网站系统架构你不得不懂的10个问题
  5. synchronized 和 reentrantlock 区别是什么_JUC源码系列之ReentrantLock源码解析
  6. [深度学习-实践]GAN基于手写体Mnist数据集生成新图片
  7. FTP 编程 5 文件传输与目录切换
  8. 一家独大的亚马逊,让人恐慌?
  9. 工程思想——继电器特性抖动处理
  10. PTA 程序设计天梯赛(21~40题)
  11. 3种终极方法,彻底解决CDR不显示缩略图!
  12. 细丝菲涅尔衍射MATLAB,工程光学系列之六: 菲涅尔衍射  matlab仿真
  13. Exploring Plain Vision Transformer Backbones for Object Detection 论文阅读笔记
  14. Flask渲染Jinja2模板
  15. charles(抓包神器)
  16. 中北大学计算机应用基础课后答案,中北大学计算机与控制工程学院研究生导师介绍:秦品乐...
  17. Ardiuno智能电蚊拍
  18. python列表中的索引问题:从左到右由0开始;从右到左由-1开始
  19. python制作英语小词典_如何用python(django)创建英语词典应用程序?
  20. 从0到将突破5000亿美金,阿里18年组织是如何进化的?

热门文章

  1. 大家注意咯!incaseformat蠕虫病毒爆发
  2. Mac Terminal 快捷键
  3. 使用Vue实现数据可视化大屏功能(二)
  4. 腾讯面试题:买200返100优惠券,实际上折扣是多少?
  5. 【以太网模块调试记录】ZLG EPORTM集成式RJ45调试记录(STM32/GD32)
  6. 软工网络15个人作业3(201521123007谭燕)
  7. yipin project 02
  8. 基于Centos环境使用宝塔面板,搭建nextcloud
  9. 集成学习方法及应用,破解AI实践难题
  10. R语言之plot()画图