STM32基础--什么是寄存器

STM32 长啥样

我使用的芯片是 144pin 的 STM32F103ZET6,具体见图 STM32F103ZET6 实物图。这个就是我们接下来要学习的 STM32,它将带领我们进入嵌入式的殿堂。芯片正面是丝印,ARM 应该是表示该芯片使用的是 ARM 的内核,STM32F103ZET6 是芯片型号,后面的字应该是跟生产批次相关,最上面的是 ST 的 LOGO。
芯片四周是引脚,左下角的小圆点表示 1 脚,然后从 1 脚起按照逆时针的顺序排列(所有芯片的引脚顺序都是逆时针排列的)。开发板中把芯片的引脚引出来,连接到各种传感器上,然后在STM32 上编程(实际就是通过程序控制这些引脚输出高电平或者低电平)来控制各种传感器工作,通过做实验的方式来学习 STM32 芯片的各个资源。开发板是一种评估板,板载资源非常丰富,引脚复用比较多,力求在一个板子上验证芯片的全部功能。STM32F103ZET6 正面引脚图
在这里插入图片描述

查看芯片那边是正面的时候优先找小圆点,如果没有小圆点则是从文字的正方向左下角,开始数,两个小圆点则是优先选择较小的,当然最稳妥方法,直接从买的那块问。

芯片里面有什么

我们看到的 STM32 芯片是已经封装好的成品,主要由内核和片上外设组成。若与电脑类比,内核与外设就如同电脑上的 CPU 与主板、内存、显卡、硬盘的关系。STM32F103 采用的是 Cortex-M3 内核,内核即CPU,由 ARM 公司设计。ARM 公司并不生产芯片,而是出售其芯片技术授权。芯片生产厂商 (SOC) 如 ST、TI、Freescale,负责在内核之外设计部件并生产整个芯片,这些内核之外的部件被称为核外外设或片上外设。如GPIO、USART(串口)、I2C、SPI 等都叫做片上外设。具体见图 STM32 芯片架构简图。
在这里插入图片描述

芯片(这里指内核,或者叫 CPU)和外设之间通过各种总线连接,其中驱动单元有 4 个,被动单元也有 4 个,具体见图 STM32F10xx 系统框图。为了方便理解,我们都可以把驱动单元理解成是CPU 部分,被动单元都理解成外设。下面我们简单介绍下驱动单元和被动单元的各个部件。

说人话就是内核加外设,啥是内核,讲白了就类似于人的脑子,外设就是躯干。然后专业化的讲CPU五大部件,运算器,控制器,存储器,输入,输出,全在内核里面。

ICode 总线

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

将白了就是运送指令的玩意,也就是机组里面的总线英文好像是BUS

驱动单元

DCode 总线

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

不用怀疑就是数据总线

系统总线

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

DMA 总线

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

被动单元

内部的闪存存储器

内部的闪存存储器即 FLASH,我们编写好的程序就放在这个地方。内核通过 ICode 总线来取里面的指令。

内部的 SRAM

内部的 SRAM,即我们通常说的 RAM,程序的变量,堆栈等的开销都是基于内部的 SRAM。内核通过 DCode 总线来访问它。

FSMC

SMC 的英文全称是 Flexible static memory controller,叫灵活的静态的存储器控制器,是STM32F10xx 中一个很有特色的外设,通过 FSMC,我们可以扩展内存,如外部的 SRAM,NAND-FLASH 和 NORFLASH。但有一点我们要注意的是,FSMC 只能扩展静态的内存,即名称里面的S:static,不能是动态的内存,比如 SDRAM 就不能扩展。

AHB 到 APB 的桥

从 AHB 总线延伸出来的两条 APB2 和 APB1 总线,上面挂载着 STM32 各种各样的特色外设。我们经常说的 GPIO、串口、I2C、SPI 这些外设就挂载在这两条总线上,这个是我们学习 STM32 的重点,就是要学会编程这些外设去驱动外部的各种设备。
在这里插入图片描述

就是说黄色就是正常计算机组成原理讲的五大组成部分,红色就是外设。

存储器映射

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

也就是说对于编程人员来说,所谓的外设啊寄存器啊之类的,全都是操作那个地址,站在计算机的视角不是将外设看成外设,而是看成一个个的存储数据的单元,我们按照门牌号(也就是内存地址)去发送相应的指令。

存储器映射

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

存储器区域功能划分

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

序号作用地址范围
Block0Code0x0000 0000~0x1FFF FFFF(512MB)
Block1SRAM0x2000 0000~0x3FFF FFFF(512MB)
Block2片上外设0x4000 0000~0x5FFF FFFF(512MB))
Block3FSMC的bank1~bank20x6000 0000~0x7FFF FFFF(512MB)
Block4FSMC的bank3~bank40x8000 0000~0x9FFF FFFF(512MB)
Block5FSMC寄存器0xA000 0000~0xBFFF FFFF(512MB)
Block6没有使用0xC000 0000~0xDFFF FFFF(512MB)
Block7Cortex-M3内部外设0xE000 0000~0xFFFF FFFF(512MB)

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

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

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

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

Block1 用于设计片内的 SRAM。我们使用的 STM32F103ZET6和 STM32F103ZET6的 SRAM 都是 64KB,Block 内部区域的功能划分具体见表格存储器 Block1 内部区域功能划分。
在这里插入图片描述

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

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

寄存器映射

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

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

0x4001 0C0C 在我们看来是 GPIOB 端口 ODR 的地址,但是在编译器看来,这只是一个普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即 (unsigned int *)0x4001 0C0C,然后再对这个指针进行 * 操作。刚刚我们说了,通过绝对地址访问内存单元不好记忆且容易出错,我们可以通过寄存器的方式来操作。

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

为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面。
//GPIOB 端口全部输出 高电平

#define GPIOB_BASE					0x40010C00
#define GPIOB_ODR					*(unsigned int*)(GPIOB_BASE+0x0C)
GPIOB_ODR = 0xFF;

STM32 的外设地址映射

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

总线基地址

总线名称总线基地址相对外设基地址的偏移
APB10x4000 00000x0
APB10x4001 00000x0001 0000
AHB0x4001 80000x0001 8000

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

外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为“XX 外设基地址”,也叫 XX 外设的边界地址。具体有关STM32F10xx 外设的边界地址请参考《STM32F10xx参考手册》的 2.3 小节的存储器映射的表 1:STM32F10xx 寄存器边界地址。
这里面我们以 GPIO 这个外设来讲解外设的基地址,GPIO 属于高速的外设,挂载到 APB2 总线上,具体见表格外设 GPIO 基地址。

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

外设寄存器

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

寄存器名称寄存器地址相对GPIOB基地址的偏移
GPIOB_CRL0x4001 0C000x00
GPIOB _CRH0x4001 0C040x04
GPIOB_IDR0x4001 0C080x08
GPIOB_ODR0x4001 0C0C0x0C
GPIOB_BSRR0x4001 0C100x10
GPIOB_BRR0x4001 0C140x14
GPIOB_LCKR0x4001 0C180x18

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

  • ① 名称
    寄存器说明中首先列出了该寄存器中的名称,“(GPIOx_BSRR)(x=A⋯E)”这段的意思是该寄存器名为“GPIOx_BSRR”其中的“x”可以为 A-E,也就是说这个寄存器说明适用于 GPIOA、GPIOB 至 GPIOE,这些 GPIO 端口都有这样的一个寄存器。
  • ② 偏移地址
    偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是0x10,从参考手册中我们可以查到 GPIOA 外设的基地址为0x4001 0800 ,我们就可以算出 GPIOA 的这个 GPIOA_BSRR 寄存器的地址为:0x4001 0800+0x10;同理,由于 GPIOB 的外设基地址为 0x4001 0C00,可算出 GPIOB_BSRR 寄存器的地址为:0x4001 0C00+0x10。其他 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 的时候对应的引脚输出低电平即可 。所以,如果对 BR0 写入“1”的话,那么 GPIOx 的第 0 个引脚就会输出“低电平”,但是对 BR0 写入“0”的话,却不会影响 ODR0 位,所以引脚电平不会改变。要想该引脚输出“高电平”,就需要对“BS0”位写入“1”,寄存器位 BSy 与 BRy 是相反的操作。

C 语言对寄存器的封装

封装总线和外设基地址

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

/* 外设基地址 */
#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)

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

/* 控制 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 外设的状态。

封装寄存器列表

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

typedef unsignedint uint32_t; /* 无符号 32 位变量 */
typedef unsigned shortint 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 个字节,具体见图 GPIO_TypeDef 结构体成员的地址偏移。
在这里插入图片描述

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

GPIO_TypeDef * GPIOx;//定义一个 GPIO_TypeDef 型结构体指针 GPIOx
GPIOx = GPIOB_BASE;//把指针地址设置为宏 GPIOB_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端口的首地址,使用时我们直接用该宏访问寄存器即可,具体如下

/* 使用 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 语言对寄存器的封装。以此类推,其他外设也同样可以用这种方法来封装。好消息是,这部分工作都由固件库帮我们完成了,这里我们只是分析了下这个封装的过程,让大家知其然,也只其所以然。

修改寄存器的位操作方法

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

把变量的某位清零

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

//定义一个变量 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 位被清零,而其它位不变。

总结:就是说要给某位设置为0可以这样理解,选择你需要的位数,例如说第二位(代码中的方法是先选择最低位,然后左移位到第二位),则选择0000 0010,然后整体取反变成1111 1101,之后与原来的数进行与运算,则与1111 1101做与运算之后原来其他位的东西可以保持不变,则原来第二位的1变成。

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

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

//若把 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);

总结:原理同上,无外乎就是同上进行多个的类似运算,例如你要连续两位则是3,连续三位则是7(转成2进制最低的几位),然后进行与上面相同的移位和与运算。

对变量的某几位进行赋值

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

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

总结:如果对1位设置为1,同样是先选择你要赋值连续的几位,例如赋值1位则是1,两位为3,三位为7以此类推,然后寻找填入的位置。例如填入第三位则是1<<3(左移三位)变成0000 0100之后和原来的数字做或运算。代码中的方法是给两位同时置01,如果不熟悉可以选择不用,你完全可以选择给一位置0一位置1。(而且不推荐代码方法,因为代码方法是因为已经知道原来变量的值是多少,对于寄存器的控制我们往往不确定原来寄存器中的值是多少,而是由我们自己设置,也就是存在强制性,所以推荐的还是你一次性把要置为1的全部置完之后,再将要置为0的全部置位,而不是一起)。

对变量的某位取反

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

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

总结:采用亦或运算(^你键盘切成英文输入法后按键6上面),和上面的一样先选择你要改变的位数,之后进行亦或运算(亦或运算:1与0亦或为1,1与1亦或则为0)。

参考:https://doc.embedfire.com/products/link/zh/latest/index.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/721331.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

three.js如何实现简易3D机房?(一)基础准备-上

目录 一、tips 二、功能说明 1.模型初始化 2.功能交互 三、初始化准备 1.目录结构 2.创建三要素 3.创建轨道控制器 4.初始化灯光 5.适配 6.循环渲染 一、tips 1.three.js入门的相关基础性知识就不在此过多赘述了&#xff0c;可以自行提前了解 three.js docs&…

Pytest框架中的测试用例执行方式!

前言 本文将针对pytest的核心特性之一——测试用例的执行方式展开深入探讨&#xff0c;并通过详尽的实战示例展示如何在不同环境下灵活操控测试运行&#xff0c;同时全面解析pytest中常见的且极具实用价值的命令行选项。 一、从基础到进阶&#xff1a;pytest在命令行下的测试用…

苹果电脑专业的Mac垃圾清理工具CleanMyMac X4.14.7

CleanMyMac X是一款专业的Mac清理工具&#xff0c;它具有强大的功能和易用的界面&#xff0c;可以帮助用户快速清理Mac上的无用文件和垃圾&#xff0c;优化系统性能&#xff0c;提升电脑运行速度。 该软件的核心功能包括智能扫描与清理、应用程序管理、隐私保护和系统维护等。…

简单介绍SpeechPrompt、SpeechPrompt V2、SpeechGen

主要介绍SpeechPrompt、SpeechPrompt V2、SpeechGen SpeechPrompt 模型结构和原理&#xff08;语音到符号&#xff09; 整体思路&#xff1a;音频特征提取(HuBert/CPC)&#xff0c;离散–》deep prompt speechLM&#xff08;GSLM&#xff09;—》概率映射–>目标Verbaliz…

代码随想录刷题笔记-Day28

1. 重新安排行程 332. 重新安排行程https://leetcode.cn/problems/reconstruct-itinerary/给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯…

计算题--时标网络图

时标网络图相当于是双代号网络图和横道图的结合体&#xff0c;特点是多了虚线和波浪线〰️&#xff0c;虚线代表虚工作&#xff08;只能竖着画&#xff09;&#xff0c;波浪线代表自由时差&#xff08;横着画&#xff09;。 在时标网络图中 找关键路径&#xff0c;没有波浪线的…

07_mdioLinux内核模块

01_basicLinux内核模块-CSDN博客文章浏览阅读316次&#xff0c;点赞3次&#xff0c;收藏3次。环境IDubuntuMakefilemodules:clean:basic.creturn 0;运行效果。https://blog.csdn.net/m0_37132481/article/details/136157384my_mdio.c #include <linux/kernel.h> #includ…

【数据结构与算法】深入浅出:单链表的实现和应用

&#x1f331;博客主页&#xff1a;青竹雾色间. &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 ✨人生如寄&#xff0c;多忧何为 ✨ 目录 前言 单链表的基本概念 节点 头节点 尾节点 单链表的基本操作 创建单链表 头插法&#xff1a; 尾插法&#…

【数据结构】 简单认识包装类与泛型

文章目录 包装类基本数据类型和对应的包装类拆箱和装箱自动装箱和自动拆箱包装类面试题 什么是泛型为什么要使用泛型泛型类的创建语法泛型类的使用语法示例类型推导(Type Inference) 裸类型(Raw Type)泛型如何编译的擦除机制为什么不能实例化泛型类型数组 泛型的上界语法示例复…

【C语言】Leetcode 876. 链表的中间节点

主页&#xff1a;17_Kevin-CSDN博客 专栏&#xff1a;《Leetcode》 题目 通过题目的要求可以判断出有两种示例要解决&#xff0c;一种是偶数节点的链表&#xff0c;一种是奇数节点的链表&#xff0c;应对这两种情况我们需要使程序对二者都可以兼容。 解决思路 struct ListNode…

03. Nginx入门-Nginx虚拟主机

Nginx虚拟主机简介 yum安装与源码安装一样&#xff0c;只是Nginx配置文件路径不一致&#xff0c;这里用的yum安装的配置文件路径。 利用虚拟主机的功能&#xff0c;可以在一台Nginx服务器上部署一个或多个虚拟主机。 虚拟主机主配置文件 注意&#xff1a;配置完成Nginx主配置…

时间序列数据平稳性检验与随机性分析

1、实验内容: 分析1964年到1999年中国纱产量的时间序列&#xff0c;主要内容包括: (1)、通过图分析时间序列的平稳性&#xff0c;这个方法很直观&#xff0c;但比较粗糙; (2)、通过计算序列的自相关和偏自相关系数&#xff0c;绘出自相关图&#xff0c;根据平稳时间序列的性质分…

splay学习笔记重制版

以前写的学习笔记&#xff1a;传送门 但是之前写的比较杂乱&#xff0c;这里重制一下 问题背景 假设我们要维护一个数据结构&#xff0c;支持插入、删除、查询某个值的排名&#xff0c;查询第 k k k大的值等操作。 最直接的想法是用二叉搜索树&#xff0c;也就是左子树权值&l…

Java实现手机库存管理

一、实验任务 编写一个程序&#xff0c;模拟库存管理系统。该系统主要包括系统首页、商品入库、商品显示和删除商品功能。每个功能的具体要求如下&#xff1a; 1.系统的首页&#xff1a;用于显示系统所有的操作&#xff0c;并且可以选择使用某一个功能。 2.商品入库功能&…

《JAVA与模式》之访问者模式

系列文章目录 文章目录 系列文章目录前言一、分派的概念二、分派的类型三、访问者模式的结构四、访问者模式的优点五、访问者模式的缺点 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网…

ACWing: 730.机器人跳跃问题

二分、递推 #include <iostream> #include <cstring> #include <algorithm> using namespace std;const int N 1e5 10; int h[N] {0};int n,maxh0; // 当 E > maxh 时一定可以满足bool check(int mid){int E mid;for(int i 1;i < n;i){E 2 *…

电商直播大屏是什么?想搞这个怎么做?

随着电商行业的快速发展&#xff0c;直播带货已成为当下最热门的市场营销方式之一。为了更好地掌握直播数据&#xff0c;为企业决策提供有力支持&#xff0c;电商直播数据大屏应运而生。 一、电商直播数据大屏概述 电商直播数据大屏是一种集成了多种数据源的大屏幕可视化展示…

【管理咨询宝藏资料33】某头部咨询公司组织效能提升模型方案

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏资料33】某头部咨询公司组织效能提升模型方案 【关键词】战略规划、组织效能、管理咨询 【文件核心观点】 - 通过长期行业积累和市场洞察&#…

四电极测脂模块CSU18M91开发脂肪秤方案

一台脂肪秤通过测试体重、体脂、BMI、水分等数据并给出相应提示&#xff0c;并且许多人都将体脂检测数据作为身体健康指数衡量标准&#xff0c;辅助用户来关注身体健康&#xff0c;同时可以通过蓝牙与手机APP应用相连&#xff0c;记录日常身体变化情况&#xff0c;根据变化情况…

表单验证、属性绑定(一个属性根据另一个属性有无进行操作)

表单验证 一个属性根据另一个属性有无进行操作&#xff08;属性绑定&#xff09; 1、问题描述 ​ 需求&#xff1a;表单里面后两个属性需要根据前面一个属性进行有无判断。如果前面属性没有输入值&#xff0c;则不需要进行操作&#xff1b;如果前面属性有输入值&#xff0c;则…