STM32 指南者

STM32 指南者

文章目录

一、P1——3 如何使用DAP仿真器下载程序

1、仿真器简介

2、硬件连接

3、仿真器配置

4、下载程序

二、初识STM32

1、什么是STM32
image-20230404101553360
2、STM32能做什么
image-20230404101626243
3、STM32怎么选型
image-20230404101704061

可以看到引脚的就是QFP封装 看不到的引脚的是BGA(球形)封装,引脚在底下(手机)

image-20230404101726547

引脚定义解读✨✨✨

image-20230404101750578

三、什么是寄存器

1、芯片里边有是么
image-20230404101918698

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

1.1、ICode总线

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

1.2、驱动单元
1.2.1、Dcode总线(Icode是独立的)

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

1.2.2系统总线

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

1.2.3DMA总线

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

1.3、被动单元
1.3.1、内部的闪存存储器

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

1.3.2、内部的SRAM

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

1.3.3、FSMC

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

1.3.4、AHB到APB的桥

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

image-20230404102441755
2、存储器映射
2.1寄存器映射

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

2.1.1寄存器映射

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

Block2—Block5映射出来的外设时32GB的。

外设分别是由APB1、APB2、AHB操作的。(如下图)

image-20230404102641686 image-20230402113937767
2.1.2存储器区域功能划分

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

image-20230402114311957

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

2.1.2.1存储器Block0内部区域功能划分
image-20230402124801374
2.1.2.2存储器Block1内部区域功能划分

Block1用于涉及片内的SRAM。我们使用的STM32F103RCT6(MINI)的SRAM为48KB, STM32F103VET6(指南者和附拂晓)和STM32F103VET6(指南者)的SRAM都是6KB,Block内部区 域的功能划分具体见下图

image-20230402125257924
2.1.2.3存储器Block2内部区域功能划分

Block2用于设计片内的外设,根据外设总线的速度不同,Block被分为了APB和AHB两部分,其中APB又 被分成了APB1和APB2,具体见下图。

image-20230402125530511
3、寄存器映射

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

在存储器Block2这块区域,涉及的是片上外设,它们以四个字节为一个单元,共32bit,每个单元对应不 同的功能,(单片机八位即8bit(两个字节),STM32三十二位即32bit(八个字节)STM32寄存器声 明的时候好像可以省略高二位的偏移量✨✨✨),当我们控制这些单元时就可以驱动外设工作,我们 可以找到每个单元的起始地址,然后通过C语言指针操作方式来访问这些单元,如果每次都是通过这种地 址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这 个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内 存单元取别名的过程就叫做寄存器映射。(省流:Block2区域内有着许多不同地址的功能单元,我们给 这些单元分别取别名且用以区分调用,叫寄存器映射)

比如,我们找到GPIOB端口的输出数据寄存器ODR的地址是0x40010C0C(至于这个地址如何找到可以 先跳过,后面有详解),ODR寄存器是32bit,低16bit有效,对应着16个外部IO,写0/1对应的IO则输 出低/高电平。(这就是为什么STM32命名GPIOB_ODR的时候写入四个字节(16bit的原因),因为只 有低16位有效,高16位无效可以省去)下载我们通过C语言指针的操作方式,让GPIOB的16个IO都输出 高电平,具体见以下代码。

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

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

刚刚我们说了,通过绝对地址访问内存单元不好记忆容易出错,我们可以通过寄存器别名的方式来操作,具体见以下代码。(其中GPIO_BASE指的是GPIO端口的基地址,稍后会讲到)。

列表 2: 代码 52 通过寄存器别名方式访问内存单元
// GPIOB 端口全部输出 高电平
#define GPIOB_ODR (unsigned int *)(GPIOB_BASE+0x0C)
* GPIOB_ODR = 0xFF;

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

列表 3: 代码 53 通过寄存器别名访问内存单元
//GPIOB 端口全部输出 高电平
#define GPIOB_ODR *(unsigned int *)(GPIOB_BASE+0x0C)
GPIOB_ODR = 0xFF;
5.5.1 STM32 的外设地址映
3.1、STM32 的外设地址映射

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

3.1.1总线基地址
3.1、STM32 的外设地址映射

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

3.1.1总线基地址
3.1、STM32 的外设地址映射

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

3.1.1总线基地址
image-20230402132942753
3.1.2、外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为“XX外设基地址”,也叫XX外设的边界地址。具体有关的STM32F10xx外设边界弟子请参考《STN32F10xx参考手册》的2.3小节的除尘器映射表1:STM32F10xx寄存器边界地址。

这里面我们以GPIO这个外设来讲解外设的基地址,GPIO属于告诉的外设,挂载到APB2总线上,具体见下表。

image-20230402133612303
3.1.3、外设寄存器

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

GPIO有很多个寄存器,每一个都有特定的功能。**每个寄存器位32bit,占4个字节,(见CSDN)**在该外设的基地址上按照顺序排序,寄存器的位置都以相对该外设基地址的偏移地址来描述。这里我们以GPIOB端口为例,来说明GPIO都有那些寄存器,见下面表格。

image-20230402134424692 image-20230402134748300

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

image-20230402144527318
  1. 名称:寄存器说明中首先列出了该寄存器中的名称,“(GPIO_BSRR)(x=A…E)”这段的意思是该寄存器名为“GPIO_BSRR”其中的“ x ”可以为A–E,也就是说这个寄存器适用于GPIOA、GPIOB至GPIOE,这些GPIO端口都有一个这样的寄存器。
  2. 偏移地址:偏移地址是指本寄存器相对于这个外设的基地址的偏移本寄存器的偏移地址是0x10,从参考手册中我们可以查到GPIOA外设的基地址位0x4001 0800,我们就可以算出GPIOA的这个GPIOA_BSRR寄存器的地址为:0x4001 0800+0x10;同理,由于GPIOB的外设基地址为0x4001 0C00,可算出GPIOB_BSSR寄存器的的地址为:0x4001 0C00+0x10。其他GPIO端口以此类推即可。
  3. **寄存器位表:紧接着是本寄存器的位表,表中列出它的 0–31位的名称及权限。表上方的数字为位编号,中间为位名称,最下方为读写权限,其中 w 表示只写,r 表示只读,rw 表示可读写。**本寄存器中的位权限都是w,所以只能写,如果读本寄存器,是无法保证读取到它真正内容的。而有的寄存器位只读,一般是用于表示STM32外设的某种工作状态的,由于STM32硬件自动更改,程序通过读取那些寄存器位来判断外设的工作状态。
  4. **位功能说明:**位功能说明是寄存器中最重要的部分,它详细介绍了寄存器每一个位的功能。例如本寄存器中有两种寄存器位,分别为 BRy 及 BSy,其中的y数值可以是 0–15表示端口的引脚号,如BR0、BS0用于控制GPIOx的第0个引脚,若x表示GPIOA,那就是控制GPIOA的第0引脚,而BR1、BS1就是控制GPIOA第一个引脚。
  5. **续:**其中BRy引脚的说明是“0:不会对相应的ODRy为执行如何操作;1:相对应ODRy位进行复位”。这里的“复位”是将该位设置为0的意思,而“置位”表示将该位设置为1;说明中的ODRy是了一个寄存器的寄存器位,我们只需要知道ODRy位为1的时候,对应引脚y输出高电平,为0的时候对应引脚位输出低电平即可(感兴趣的读者可以查询该寄存器GPIOx_ODR的说明了解)。所以,如果对BR0写入“1”的话,那么GPIOx的第0引脚就会输出“低电平”,但是对BR0写入“0”的话,却不会影响ODR0位,所以引脚电平不会改变。想要该引脚输出“高电平”,就需对“BS0”位写入“1”,寄存器BSy与BRy是相反的操作。
3.2、C语言对寄存器的封装(于以上知识基础上重点)

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

3.2.1、封装总线和外设基地址

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

列表 4: 代码 54 总线和外设基址宏定义
/* 外设基地址 */
#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)
/*都是地址偏移哈,在Block2内*/

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

列表 5: 代码 55 使用指针控制 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;

对比下面理解BR0跟BS0的作用

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

image-20230402144527318
3.2.2、封装寄存器列表

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

列表 6: 代码 56 使用结构体对 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位的变量占用了两个字节,具体见下图。

image-20230402150153209

也就是说,我们定义的这个GPIO_TypeDef,假如这个结构体的首地址为0x4001 0C00(这也是第一个成员变量CRL的地址),那么结构体中的第二个成员CRH的地址即为0x4001 0C00+0x04,加上的这个0x04,正是代表CRL所占用的4个字节地址的偏移量,其它成员变量相对于结构体首地址偏移,在其上述代码右侧注释已给。

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

列表 7: 代码 57 通过结构体指针访问寄存器
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及GPIO -> IDR等方式读写寄存器。

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

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

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

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

3.3.1、把变量的某位或某几个位进行清零(按位与&)

此处我们以变量a代表一个8位的寄存器(STM32的寄存器时32位的),并假设寄存器中本来已有数值,此时我们需要把变量a的某一位清零,且其他未不变;**由于寄存器中有时也会存在连续几个寄存器位用与控制某个功能的情况,所以我们也需要假设需要把寄存器的某几个连续位清零,且其他位不变。**方法见下表。

列表 9: 代码清单 51 对某位清零、以及对某几位清零
/* 对某位清零 */
//定义一个变量 a = 1001 1111 b (二进制数)
unsigned char a = 0x9f;
//对 bit2 清零
a &= ~(1<<2);
//上面一行代码右边括号中的 1 左移两位,(1<<2) 得二进制数:0000 0100 b(这个便是位 2
的掩码)
//然后按位取反,~(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 位就被清零,而其它位不变。
/* 下面是对某几个连续位清零 */
//同样我们首先定义一个变量 a = 1001 1111 b (二进制数)
unsigned char a = 0x9f;
//若把 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 的掩码)
//然后按位取反,~(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,即要左移
6//括号中的 2 为每组的位数,每组有 2 个二进制位; 若分成 4 个一组,此处即为 4
//括号中的 3 是组内所有位都为 1 时的值; 若分成 4 个一组,此处即为二进制数“1111 b”
//例如对第 2 组 bit4、bit5 清零,3 就要左移 4 位
a &= ~(3<<2*2);
3.3.2、对变量的某一位或某几位进行赋值(按位或|)

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

列表 10: 代码清单 53 对某几位进行赋值
/* 对某位赋值 */
//假设 a = 1000 0011 b
a |= (1<<4);
//此时对变量 a 的 bit4 置 1
//置 1 后,即 a = 1001 0011 
/* 对某几位进行赋值 */
//假设 a = 1000 0011 b
a |= (1<<2*2);
//此时对清零后的第 2 组 bit4、bit5 设置成二进制数“01 b ”(也就是“01 b”左移 4 位)
//即 a = 1001 0011 b,成功设置了第 2 组的值,其它组不变
3.3.3、对变量的某位取反(按位异或^)

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

列表 11: 代码清单 54 对某位进行取反操作
//a = 1001 0011 b
//把 bit6 取反,其它位不变
a ^=(1<<6);
//a = 1101 0011 b

四、GPIOx_XXX 寄存器的理解

结合下图阅读

image-20230403155042903

1、设置时钟使能(心脏)

image-20230404104450602 image-20230404104502989 image-20230404104514374

Pace 2:

2、GOIOx_CRL(A-E),是控制GPIOx低0八位引脚的(0-7)
image-20230404104534019
3、GPIOx_CRH(A-E),是控制GPIOx高八位引脚的(8-15)
image-20230404104549865

首先控制端口置位,再控制端口模式(32bit,每个端口控制四个比特,(一个字节)通过配置CNF跟MODE来确定驱动电路,配置的时候要用 |= 跟 &=~ ,不能直接赋值像0x00000001,这样会影响其他位)

Pace3:

4、GPIO_ODR:端口输出数据寄存器(GPIO_ODR)(x=A-E)

说明:可以操作GPIOx口输出高/低电平(1/0)

image-20230404104608034

BSRR跟BRR是控制ODR的开关,相当ODR的上级,可以指挥可不指挥。

5、GPIOx_BSRR(x=A–E):端口设置/清除寄存器,可以分别地对各个ODR位进行独立设置/清除。(高16位写1对ODR进行清0,低16位写1对ODR置一)
image-20230404104620052
实验现象如下:

image-20230403170359877ORD输出位0,LED亮image-20230403170458602ORD输出1,LED灭。

6、GPIOx_BRR (x=A-E):端口位清除寄存器,功能相当于BSSR寄存器的高十六位功能
image-20230403151808425 image-20230404104658294

oss-cn-shenzhen.aliyuncs.com/PigGo/202304041046643.png" alt=“image-20230403170458602” style=“zoom:50%;” />ORD输出1,LED灭。

6、GPIOx_BRR (x=A-E):端口位清除寄存器,功能相当于BSSR寄存器的高十六位功能
image-20230403151808425 image-20230404104658294
BSRR跟BRR两者是一山不容二虎的关系
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值