IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 野火学习笔记(2) —— 映射 -> 正文阅读

[嵌入式]野火学习笔记(2) —— 映射




1. 存储器映射

被控单元的 FLASH, RAM, FSMC 和 AHB 到 APB 的桥(即片上外设),这些功能部件共同排列在一个 4GB 的地址空间内。我们在编程的时候,可以通过他们的地址找到他们,然后来操作他们(通过 C 语言对它们进行数据的读和写)。


1.1 存储器映射

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


在这里插入图片描述图 6-5 存储器映射(摘自参考手册-存储器映射章节)


1.1.1 存储器区域功能划分

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


表格 6-1 存储器功能分类

序号用途地址范围
Block 0Code0x0000 0000 ~ 0x1FFF FFFF(512MB)
Block 1SRAM0x2000 0000 ~ 0x3FFF FFFF(512MB)
Block 2片上外设0x4000 0000 ~ 0x5FFF FFFF(512MB)
Block 3FSMC 的 bank1 ~ bank20x6000 0000 ~ 0x7FFF FFFF(512MB)
Block 4FSMC 的 bank3 ~ bank40x8000 0000 ~ 0x9FFF FFFF(512MB)
Block 5FSMC 寄存器0xA000 0000 ~ 0xCFFF FFFF(512MB)
Block 6没有使用0xD000 0000 ~ 0xDFFF FFFF(512MB)
Block 7Cortex-M3 内部外设0xD000 0000 ~ 0xDFFF FFFF(512MB)

在这 8 个 Block 里面,有 3 个块非常重要,也是我们最关心的三个块。 Block0 用来设计成内部 FLASH, Block1 用来设计成内部 RAM, Block2 用来设计成片上的外设。


存储器 Block0 内部区域功能划分
Block0 主要用于设计片内的 FLASH,我们使用的 STM32F103ZET6(霸道)和STM32F103VET6(指南者)的 FLASH 都是 512KB,属于大容量。要在芯片内部集成更大的 FLASH 或者 SRAM 都意味着芯片成本的增加,往往片内集成的 FLASH 都不会太大,
ST 能在追求性价比的同时做到 512KB,实乃良心之举。 Block 内部区域的功能划分具体见 表格 6-2。


表格 6-2 存储器 Block0 内部区域功能划分

用途说明地址范围
Block0预留0x1FFE C008 ~ 0x1FFF FFFF
Block0选项字节:用于配置读写保护、BOR 级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。当芯片不小心被锁住之后,我们可以从 RAM 里面启动来修改这部分相应的寄存器位。0x1FFF F800 - 0x1FFF F80F
Block0系统存储器:里面存的是 ST 出厂时烧写好的 isp 自举程序( 即Bootloader),用户无法改动。串口下载的时候需要用到这部分程序。0x1FFF F000- 0x1FFF F7FF
Block0预留0x0808 0000 ~ 0x1FFF EFFF
Block0FLASH:我们的程序就放在这里。0x0800 0000 ~ 0x0807 FFFF (512KB)
Block0预留0x0008 0000 ~ 0x07FF FFFF
Block0取决于 BOOT 引脚,为 FLASH、系统存储器、 SRAM 的别名。0x0000 0000 ~ 0x0007 FFFF

储存器 Block1 内部区域功能划分
Block1 用于设计片内的 SRAM 。 我们使用的 STM32F103ZET6( 霸 道 )和 STM32F103VET6(指南者)的 SRAM 都是 64KB, Block 内部区域的功能划分具体见 表格 6-3。


表格 6-3 存储器 Block1 内部区域功能划分

用途说明地址范围
Block1预留0x2001 0000 ~ 0x3FFF FFFF
Block1SRAM 64KB0x2000 0000 ~0x2000 FFFF

储存器 Block2 内部区域功能划分
Block2 用于设计片内的外设,根据外设的总线速度不同, Block 被分成了 APB 和 AHB 两部分,其中 APB 又被分为 APB1 和 APB2,具体见 表格 6-4。


表格 6-4 存储器 Block2 内部区域功能划分

用途说明地址范围
Block2APB1 总线外设0x4000 0000 ~ 0x4000 77FF
Block2APB2 总线外设0x4001 0000 ~ 0x4001 3FFF
Block2AHB 总线外设0x4001 8000 ~ 0x5003 FFFF



2. 寄存器映射

存储器本身没有地址,给存储器分配地址的过程叫存储器映射。
在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射
比如,我们找到 GPIOB 端口的输出数据寄存器 ODR 的地址是 0x4001 0C0C(至于这个地址如何找到可以先跳过,后面我们会有详细的讲解), ODR 寄存器是 32bit,低 16bit 有效,对应着 16 个外部 IO,写 0/1 对应的的 IO 则输出低/高电平。现在我们通过 C 语言指针的操作方式,让 GPIOB 的 16 个 IO 都输出高电平,具体见 代码 6-1 。


代码 6-1 通过绝对地址访问内存单元

// GPIOB 端口全部输出 高电平
*(unsigned int*)(0x4001 0C0C) = 0xFFFF;

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

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


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

// GPIOB 端口全部输出 高电平
#define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C)
* GPIOB_ODR = 0xFF;

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


代码 6-3 通过寄存器别名访问内存单元

// GPIOB 端口全部输出 高电平
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
GPIOB_ODR = 0xFF;


2.1 STM32 的外设地址映射

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


2.1.1 总线基地址

表格 6-5 总线基地址

总线名称总线基地址总线基地址
APB10x4000 00000x0
APB20x4001 00000x0001 0000
AHB0x4001 80000x0001 8000

表格 6-5 的“相对外设基地址偏移”即该总线地址与“片上外设”基地址 0x4000 0000 的差值。


2.1.2 外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为 “ XX 外设基地址 ” ,也叫 XX 外设的边界地址。
这里面我们以 GPIO 这个外设来讲解外设的基地址, GPIO 属于高速的外设 ,挂载到 APB2 总线上,具体见 表格 6-6。


表格 6-6 外设 GPIO 基地址

外设名称外设基地址相对 APB2 总线的地址偏移
GPIOA0x4001 08000x0000 0800
GPIOB0x4001 0C000x0000 0C00
GPIOC0x4001 10000x0000 1000
GPIOD0x4001 14000x0000 1400
GPIOE0x4001 18000x0000 1800
GPIOF0x4001 1C000x0000 1C00
GPIOG0x4001 20000x0000 2000

2.1.3 外设寄存器

在 XX 外设的地址范围内,分布着的就是该外设的寄存器。以 GPIO 外设为例, GPIO是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,基本功能是控制引脚输出高电平或者低电平。最简单的应用就是把 GPIO 的引脚连接到 LED 灯的阴极, LED 灯的阳极接电源,然后通过 STM32 控制该引脚的电平,从而实现控制 LED 灯的亮灭。
GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为 32bit ,占四个字节,在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。这里我们以 GPIOB 端口为例,来说明 GPIO 都有哪些寄存器,具体见 表格 6-7 。


表格 6-7 GPIOB 端口的 寄存器地址列表

寄存器名称寄存器地址相对 GPIOB 基址的偏移
GPIOB_CRL0x4001 0C000x00
GPIOB_CRH0x4001 0C040x04
GPIOB_IDR0x4001 0C080x08
GPIOB_ODR0x4001 0C0C0x0C
GPIOH_BSRR0x4001 0C100x10
GPIOH_BRR0x4001 0C140x14
GPIOH_LCKR0x4001 0C180x18

有关外设的寄存器说明可参考 《STM32F10xx 参考手册》中具体章节的寄存器描述部分,在编程的时候我们需要反复的查阅外设的寄存器说明。
这里我们以 “GPIO 端口置位/复位寄存器”为例,教大家如何理解寄存器的说明,具体见 图 6-6。


在这里插入图片描述
图 6-6 GPIO 端口置位/复位寄存器说明


①名称
寄存器说明中首先列出了该寄存器中的名称, “(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 端口以此类推即可。


(PS:个人理解)
{
例如:GPIOB 为例
在《STM32F1xx中文参考手册.pdf》中第28页,可以找到 GPIOB 外设的基地址为 0X4001 0C00(个人理解图 6-1)


在这里插入图片描述
个人理解图 6-1


端口输出数据寄存器 (GPIOx_ODR) 寄存器的偏移地址是 0X0C(个人理解图 6-2)
在这里插入图片描述
个人理解图 6-2


可以算出 GPIOB 的这个 GPIOB_ODR 寄存器的地址为:0X4001 0C00 + 0X0C = 4001 0C0C?
}


③寄存器位表
紧接着的是本寄存器的位表,表中列出它的 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 的时候对应的引脚输出低电平即可。所以,如果对 BR0 写入 “1” 的话,那么 GPIOx 的第 0 个引脚就会输出“低电平”,但是对 BR0 写入 “0” 的话,却不会影响 ODR0 位,所以引脚电平不会改变。要想该引脚输出“高电平”,就需要对 “BS0” 位写入 “1” ,寄存器位 BSy 与 BRy 是相反的操作。


2.2 C 语言对寄存器的封装

2.2.1 封装总线和外设基地址

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


代码 6-4 总线和外设基址宏定义

/* 外设基地址 */
#define PERIPH_BASE 	((unsigned int)0x40000000)

/* 总线基地址 */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define AHBPERIPH_BASE 	(PERIPH_BASE + 0x00020000)

/* GPIO 外设基地址 */
#define GPIOA_BASE 		(APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE 		(APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE 		(APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE 		(APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE 		(APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE 		(APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE 		(APB2PERIPH_BASE + 0x2000)

/* 寄存器基地址,以 GPIOB 为例 */
#define GPIOB_CRL 		(GPIOB_BASE+0x00)
#define GPIOB_CRH 		(GPIOB_BASE+0x04)
#define GPIOB_IDR 		(GPIOB_BASE+0x08)
#define GPIOB_ODR 		(GPIOB_BASE+0x0C)
#define GPIOB_BSRR 		(GPIOB_BASE+0x10)
#define GPIOB_BRR 		(GPIOB_BASE+0x14)
#define GPIOB_LCKR 		(GPIOB_BASE+0x18)

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


代码 6-5 使用指针控制 BSRR 寄存器

/* 控制 GPIOB 引脚 0 输出低电平(BSRR 寄存器的 BR0 置 1) */
*(unsigned int *)GPIOB_BSRR = (0x01<<(16+0));

/* 控制 GPIOB 引脚 0 输出高电平(BSRR 寄存器的 BS0 置 1) */
*(unsigned int *)GPIOB_BSRR = 0x01<<0;

unsigned int temp;
/* 读取 GPIOB 端口所有引脚的电平(读 IDR 寄存器) */
temp = *(unsigned int *)GPIOB_IDR;

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


2.2.2 封装寄存器列表

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


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

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

/* GPIO 寄存器列表 */
typedef struct {
    uint32_t CRL; 	/*GPIO 端口配置低寄存器 地址偏移: 0x00 */
    uint32_t CRH; 	/*GPIO 端口配置高寄存器 地址偏移: 0x04 */
	uint32_t IDR; 	/*GPIO 数据输入寄存器 地址偏移: 0x08 */
    uint32_t ODR; 	/*GPIO 数据输出寄存器 地址偏移: 0x0C */
    uint32_t BSRR; 	/*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
    uint32_t BRR; 	/*GPIO 端口位清除寄存器 地址偏移: 0x14 */
    uint16_t LCKR; 	/*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */
} GPIO_TypeDef;    

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

在这里插入图片描述
图 6-7 GPIO_TypeDef 结构体成员的地址偏移


也就是说,我们定义的这个 GPIO_TypeDef ,假如这个结构体的首地址为 0x4001 0C00(这也是第一个成员变量 CRL 的地址),那么结构体中第二个成员变量 CRH 的地址即为 0x4001 0C00 + 0x04,加上的这个 0x04,正是代表 CRL 所占用的 4 个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给。
这样的地址偏移与 STM32 GPIO 外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存器,具体见 代码 6-7。


代码 6-7 通过结构体指针访问寄存器

GPIO_TypeDef * GPIOx; 	//定义一个 GPIO_TypeDef 型结构体指针 GPIOx
GPIOx = GPIOB_BASE; 	//把指针地址设置为宏 GPIOH_BASE 地址
GPIOx->IDR = 0xFFFF;
GPIOx->ODR = 0xFFFF;


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

这段代码先用 GPIO_TypeDef 类型定义一个结构体指针 GPIOx ,并让指针指向地址 GPIOB_BASE(0x4001 0C00) ,使用地址确定下来,然后根据 C 语言访问结构体的语法,用 GPIOx->ODR 及 GPIOx->IDR 等方式读写寄存器。
最后,我们更进一步,直接使用宏定义好 GPIO_TypeDef 类型的指针,而且指针指向各个 GPIO 端口的首地址,使用时我们直接用该宏访问寄存器即可,具体 代码 6-8。


代码 6-8 定义好 GPIO 端口首地址址针

/*使用 GPIO_TypeDef 把地址强制转换成指针*/
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)

/*使用定义好的宏直接访问*/
/*访问 GPIOB 端口的寄存器*/
GPIOB->BSRR = 0xFFFF; 	//通过指针访问并修改 GPIOB_BSRR 寄存器
GPIOB->CRL = 0xFFFF; 	//修改 GPIOB_CRL 寄存器
GPIOB->ODR =0xFFFF; 	//修改 GPIOB_ODR 寄存器

uint32_t temp;
temp = GPIOB->IDR; 		//读取 GPIOB_IDR 寄存器的值到变量 temp 中

/*访问 GPIOA 端口的寄存器*/
GPIOA->BSRR = 0xFFFF;
GPIOA->CRL = 0xFFFF;
GPIOA->ODR =0xFFFF;

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

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


2.3 修改寄存器的位操作方法

使用 C 语言对寄存器赋值时,我们常常要求只修改该寄存器的某几位的值,且其它的寄存器位不变,这个时候我们就需要用到 C 语言的位操作方法了。

2.3.1 把变量的某位清零

此处我们以变量 a 代表寄存器,并假设寄存器中本来已有数值,此时我们需要把变量 a 的某一位清零,且其它位不变,方法见 代码 6-1


代码 6-1 对某位清零

//定义一个变量 a = 1001 1111 b (二进制数)
unsigned char a = 0x9f;

//对 bit2 清零

a &= ~(1<<2);

//括号中的 1 左移两位, (1<<2)得二进制数: 0000 0100 b
//按位取反, ~(1<<2)得 1111 1011 b
//假如 a 中原来的值为二进制数: a = 1001 1111 b
//所得的数与 a 作”位与&”运算, a = (1001 1111 b)&(1111 1011 b),
//经过运算后, a 的值 a=1001 1011 b
// a 的 bit2 位被被零,而其它位不变。

2.3.2 把变量的某几个连续位清零

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


代码清单 6-2 对某几个连续位清零

//若把 a 中的二进制位分成 2 个一组
//即 bit0、 bit1 为第 0 组, bit2、 bit3 为第 1 组,
//   bit4、 bit5 为第 2 组, bit6、 bit7 为第 3 组
//要对第 1 组的 bit2、 bit3 清零

 a &= ~(3<<2*1);

//括号中的 3 左移两位, (3<<2*1)得二进制数: 0000 1100 b
//按位取反, ~(3<<2*1)得 1111 0011 b
//假如 a 中原来的值为二进制数: a = 1001 1111 b
//所得的数与 a 作”位与&”运算, a = (1001 1111 b)&(1111 0011 b),
//经过运算后, a 的值 a=1001 0011 b
// a 的第 1 组的 bit2、 bit3 被清零,而其它位不变。

//上述(~(3<<2*1))中的(1)即为组编号;如清零第 3 组 bit6、 bit7 此处应为 3
//括号中的(2)为每组的位数,每组有 2 个二进制位;若分成 4 个一组,此处即为 4
//括号中的(3)是组内所有位都为 1 时的值;若分成 4 个一组,此处即为二进制数“1111 b”

//例如对第 2 组 bit4、 bit5 清零
a &= ~(3<<2*2);

2.3.3 对变量的某几位进行赋值

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


代码清单 6-3 对某几位进行赋值

//a = 1000 0011 b
//此时对清零后的第 2 组 bit4、 bit5 设置成二进制数“01 b ”

a |= (1<<2*2);
//a = 1001 0011 b,成功设置了第 2 组的值,其它组不变

2.3.4 对变量的某位取反

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


代码清单 6-4 对某位进行取反操作

//a = 1001 0011 b
//把 bit6 取反,其它位不变

a ^=(1<<6);
//a = 1101 0011 b
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-07-11 16:46:50  更:2021-07-11 16:48:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 12:07:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码