一、 GPIO简介
GPIO 的定义及基本特性
- GPIO 是 General Purpose Input Output 的缩写,译为通用输入输出口,也就是俗称的 I/O 口GPIO 的引脚电平为 0 - 3.3 伏,数据 0 为低电平(0V),数据 1 为高电平(3.3V),部分引脚可容忍 5 伏输入,带 FT 的引脚可容忍 5 伏,不带 FT 的只能接入 3.3 伏电压。
- 在输出模式下可控制端口输出高低电平,用以驱动 LED、控制分频器、模拟通信协议输出时序等。如果控制功率较大的设备,需加入驱动电路。
- 输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据(接受通信线上的数据 )等
补充:
容忍 5V 的意思是可以在相应的端口输入 5 伏的电压,也会被认为是高电平。但对于输出而言,最大只能输出 3.3 伏。具体哪些端口能容忍 5 伏,可以参考 STM32 的引脚定义。
GPIO 的应用场景
GPIO输出模式:
- 可以用高低电平来进行控制的地方都可以用 GPIO 完成,如驱动 LED、控制分频器等。
- 可以用 GPIO 来模拟通信协议,如 I2C、SPI 或某个芯片特定的协议。
GPIO输入模式:
- 可以读取按键输入,读取外接模块电平信号,输入 ADC 电压采集,采集模拟通信协议接收数据
二、STM32 中 GPIO 的基本结构
1. STM32 中 GPIO 的整体结构
挂载位置:
在 STM32 中,所有的 GPIO 都是挂载在 APB2 外设总线上的。GPIO 外设的名称按照 GPIOA、GPIOB、GPIOC 等这样来命名。每个 GPIO 外设总共有 16 个引脚,编号是从 0 到 15。比如 GPA 的 D0 号引脚,一般称作 PA0,接着第 1 号就是 PA1,然后 PA2 依次类推一直到 PA15;GPIOB 也是一样,从 PB0 一直到 PB15 这样命名。
其中在每个GPIO模块内,主要包含了寄存器和驱动器这些东西。
寄存器:
-
功能:寄存器是一段特殊的存储器内核,可以通过 APB2 总线对寄存器进行读写,这样就可以完成输出电平和读取电平的功能。
-
对应关系:寄存器的每一位对应一个引脚,其中输出寄存器写 1 对应的引脚就会输出高电平,写 0 就是低电平。输入数据寄存器读取为 1,就证明对应的端口目前是高电平,读取为 0 就是低电平。
-
位数特点:因为 STM32 是 32 位的单片机,所以内部的寄存器都是 32 位的,但端口只有 16 位,所以这个寄存器只有低 16 位对应的有端口,高 16 位是没有用到的。
驱动器:
-
作用:驱动器是用来增加信号的驱动能力的。寄存器只负责存储数据,如果要进行点灯这样的操作的话,还是需要驱动器来负责增大驱动能力。
2. GPIO 中每一位的具体电路结构
输入部分:
-
保护二极管:I/O 引脚这里接了两个保护二极管,对输入电压进行限幅。上面的二极管接 VDD3.3 伏,下面接 VSS0 伏。如果输入电压比 3.3 伏还要高,上方二极管就会导通,输入电压产生的电流就会直接流入 VDD,而不会流入内部电路,避免过高的电压对内部电路产生伤害。如果输入电压比 0 伏(这里的电压是相对于VSS的电压,所以是可以有负电压的)还要低,这时下方二极管就会导通,电流会从 VSS 直接流出去,而不会从内部电路流过(不会从内部电路汲取电流),保护内部电路。如果输入电压在 0 - 3.3 伏之间,两个二极管均不会导通,对电路没有影响。
-
上拉电阻和下拉电阻:引脚连接一个上拉电阻和一个下拉电阻,上拉电阻接 VDD,下拉电阻接 VSS。这个开关是可以通过程序进行配置的,如果上面导通下面断开,就是上拉输入模式;如果下面导通上面断开,就是下拉输入模式;如果两个都断开,上拉和下拉电阻的作用是为输入提供一个默认的输入电平。因为对于一个数字的端口,输入不是高电平就是低电平,如果输入引脚啥都不接,这时输入就会处于一种浮空的状态(一种无法确定是高电平还是低电平的状态),引脚的输入电平极易受外界干扰而改变。接入上拉电阻,当引脚悬空时,有上拉电阻来保证引脚的高电平,所以上拉输入又可以称作是默认为高电平的输入模式;下拉输入同理,是默认为低电平的输入方式。上拉电阻和下拉电阻的阻值都是比较大的,是一种弱上拉和弱下拉,目的是尽量不影响正常的输入操作。
-
施密特触发器:这是一个对输入电压进行整形的器件。它的执行逻辑是如果输入电压大于某一阈值,输出就会瞬间升为高电平;如果输入电压小于某一阈值,输出就会瞬间降为低电平。比如有一个夹杂了波动的高低变化电平信号,没有施密特触发器很可能因为干扰而导致误判,有了施密特触发器,设定一个上限和下限阈值,高于上限输出高,低于下限输出低,这样经过整形的信号就很完美,可以有效地避免因信号波动造成的输出抖动现象。经过施密特触发器整形的波形就可以直接写入输入数据寄存器,之后我们再利用程序读取数据寄存器对应某一位的数据,就可以知道端口的输入电平了。
以上是施密特触发器的整形实例。
-
片上外设连接:上面还有两路线路连接片上外设的一些端口
-
其中有模拟输入,连接到 ADC 上,因为 ADC 需要接收模拟量,所以这根线是接到施密特触发器前面的;
-
另一个是复用功能输入,连接到其他需要读取端口的外设上,比如串口的输入引脚等,这根线接收的是数字量,所以在施密特触发器后面。
-
输出部分:
-
控制方式:数字部分可以由输出数据寄存器或片上外设控制两种控制方式,通过数据选择器接到输出控制部分。如果选择通过输出数据寄存器进行控制,就是普通的 I/O 口输出,写数据寄存器的某一位就可以操作对应的某个端口了。
-
位设置清除寄存器:左边有个叫做位设置/清除寄存器,可以用来单独操作输出数据寄存器的某一位而不影响其他位。因为输出数据寄存器同时控制 16 个端口,并且这个寄存器只能整体读写,所以如果想单独控制其中某一个端口而不影响其他端口的话,就需要一些特殊的操作方式。第一种方式是先读出这个计算器,然后用按位与和按位或的方式更改某一位,最后再将更改后的数据写回去,在 C 语言中就是 “&=” 和 “|=” 的操作,这种方法比较麻烦,效率不高,对于 I/O 口的操作而言不太合适。第二种方式是通过设置这个位设置和未清除寄存器,如果我们要对某一位进行置 1 的操作,在未设置寄存器的对应位写 1 即可,剩下不需要操作的位写 0,这样它内部就会有电路自动将输出数据寄存器对应位置为 1,而剩下位保持不变。如果想对某一位进行清零的操作,就在位清除寄存器的对应位写 1 即可,这样内部电路就会把这一位清零了。还有第三种操作方式是读写 STM32 中的位带区域,这个位带的作用就跟 51 单片机的位操作作用差不多,在 STM32 中专门分配的有一段地址区,这段地址映射了RAM和外设寄存器所有的位,所有的读写这段地址中的数据就相当于读写所映射位置的某一位,不过本文暂时不会用到这种方式,该系列文章主要使用的是库函数来操作的,库函数使用的就是读写位设置/位清除寄存器的方法。
-
MOS 管输出控制:输出控制之后接到了两个 MOS 管,上面是 PMOS,下面是 NMOS,MOS 管就是一种电子开关,信号来控制开关的导通和关闭,开关负责将 I/O 口接到 VDD 或者 VSS。在这里可以选择推挽输出、开漏输出或关闭三种输出方式。在推挽输出模式下,PMOS 和 NMOS 均有效,数据寄存器为 1 时上管导通,下管断开输出直接接到 VDD 就是输出高电平;数据寄存器为 0 时,上管断开,下管导通输出直接接到 VSS 就是输出低电平。这种模式下高低电平均有较强的驱动能力,所以推挽输出模式也可以叫强推输出模式。在推挽输出模式下,STM32 对 I/O 口具有绝对的控制权,高低电平都由 STM32 说了算。在开漏输出模式下,PMOS 是无效的,只有 NMOS 在工作,数据寄存器为 1 时下管断开,这时输出相当于断开,也就是高阻模式,数据寄存器为 0 时,下管导通输出直接接到 VSS,也就是输出低电平。这种模式下只有低电平有驱动能力,高电平是没有驱动能力的。开漏模式可以作为通信协议的驱动方式,比如 I2C 通信的引脚就是使用的开漏模式。在多机通信的情况下,这个模式可以避免各个设备的相互干扰。另外开漏模式还可以用于输出 5 伏的电平信号,比如在 I/O 外接一个上拉电阻到 5 伏的电源,当输出低电平时,由内部的 NMOS 直接接 VSS,当输出高电平时(此时下管仍然断开),由外部的上拉电阻拉高至 5 伏,这样就可以输出 5 伏的电平信号,用于兼容一些 5 伏电平的设备。剩下的一种状态就是关闭,这是当引脚配置为输入模式的时候,这两个 MOS 管都无效,也就是输出关闭,端口的电平由外部信号来控制。
三、GPIO 的八种工作模式
1. 浮空输入:
如果输入引脚啥都不接,就处于浮空状态,引脚的输入电平极易受外界干扰而改变。所以在使用浮空输入时,端口一定要接上一个连续的驱动源,不能出现浮空状态
2. 上拉输入:
引脚连接一个上拉电阻到 VDD,当引脚悬空时,有上拉电阻保证引脚为高电平,所以也可称作默认为高电平的输入模式。上拉电阻阻值较大,是弱上拉,尽量不影响正常的输入操作。
3. 下拉输入:
引脚连接一个下拉电阻到 VSS,当引脚悬空时,下拉电阻使引脚为低电平,即默认为低电平的输入方式。同样下拉电阻也是弱下拉。
这三个模式的电路结构基本是一样的,区别就是上拉电阻和下拉电阻的连接,它们都属于数字的输入口,特征就是,都可以读取端口的高低电平。
这里可以看到,在输入模式下,输出驱动器是断开的,端口只能输入不能输出。上面这两个电阻可以选择为上拉工作、下拉工作或者都不工作,对应的就是上拉输入、下拉输入和浮空输入,然后输入通过施密特触发器进行波形整形后,连接到输入数据寄存器。另外右边这个输入保护这里,上面写的是VDD或者VDD_FT,这就是 3.3V 端口和容忍 5V 端口的区别,下面可以看到,这里说VDD_FT对5V容忍lO脚是特殊的,它与VDD不同,至于为什么特殊手册并没有说明,所以不要求掌握。这个容忍5V的引脚,它的上边保护二极管要做一下处理,要不然这里直接接VDD3.3V的话,外部再接入5V电压就会导致上边三极管开启,并且产生比较大的电流,这个是不太妥当的
4. 模拟输入:
连接到 ADC 上,因为 ADC 需要接收模拟量,所以这根线是接到施密特触发器前面的。用于输入模拟信号,配合内部的 ADC 外设,直接读取端口的模拟电压。(这里输出是断开的,输入的施密特触发器也是关闭的无效状态)仅有这个模式会关闭数字的输入功能,其他的七个模式中所有的输入都是有效的
5. 推挽输出:
由 PMOS 和 NMOS 组成,数据寄存器为 1 时上管导通、下管断开,输出高电平至 VDD;数据为 0 时上管断开、下管导通,输出低电平至 VSS。
高低电平均有强驱动能力,STM32 对端口有绝对控制权。
6. 开漏输出:
只有 NMOS 工作,PMOS 无效。数据为 1 时下管断开呈高阻态;数据为 0 时,下管导通输出低电平至 VSS。
低电平有驱动能力,高电平无驱动能力,可用于 I2C 通信等,也可外接上拉电阻输出 5 伏电平信号。
这两个电路结构也基本一样,都是数字输出口,可以用于输出高低电平,区别就是开漏输出高电平呈现高阻态,没有驱动能力,而推挽输出的高低电平都是具有驱动能力的。同时在输出模式下输入模式也是有效的,但是之前的输入模式下输出模式都是无效的,这是因为一个端口只能有一个输出,但是可以有多个输入,所以当配置成输出模式的时候,内部也可以顺便输入一下,这个也是没啥影响的
7. 复用推挽输出:
与普通推挽输出类似,复用的输出引脚电平由片上外设控制。
8. 复用开漏输出:
与普通开漏输出类似,复用的输出引脚电平由片上外设控制。
这俩模式跟普通的开漏输出和推挽输出也差不多,只不过是复用的输出,引脚电平是由片上外设控制的
四、GPIO 的寄存器
GPIO 配置寄存器:
每一个端口的模式由四位进行配置,16 个端口需要 64 位,所以这里的配置寄存器有两个,一个是端口配置低寄存器,一个是端口配置高寄存器。
端口输入数据寄存器:
对应硬件的 16 个引脚,高 16 位没有使用。
端口输出数据寄存器:
同样低 16 位对应 16 个引脚,高 16 位没有使用。
端口位设置清除寄存器:
高 16 位是进行位清除的,低 16 位是进行位设置的。写1就是设置或者清除,写0就是不产生影响。
端口位清除寄存器:
功能与端口位设置清除寄存器的高 16 位和低 16 位部分功能一样,那么设置这个寄存器的作用就是方便操作设置。如果只想单一的进行未设置或者未清除,可以分别使用不同的寄存器;如果要对多个端口同时进行未设置和未清除,可使用上一个寄存器以保证同步性。
端口配置锁定寄存器:
可以对端口的配置进行锁定,防止意外更改。
五、GPIO 输出速度
GPIO 的输出速度可以限制输出引脚的最大翻转速度,设计目的是为了低功耗和稳定性。一般要求不高的时候,直接配置成 50 兆赫兹即可。
六、GPIO的输出
一、LED 的硬件电路
-
LED 的特性:
-
LED 即发光二极管,正向通电点亮,反向通电不亮。
-
电路符号左边是正极,右边是负极。实物图中长角是正极,短角是负极,通过内部也可判断正负极,较小的一半是正极,较大的一半是负极。
-
-
驱动电路:
-
低电平驱动电路:LED 正极接 3.3 伏,负极通过限流电阻接到 STM32 的 GPIO 口(如 PA0)上。当 PA0 输出低电平时,LED 两端产生电压差,形成正向导通电流,LED 点亮;当 PA0 输出高电平时,LED 两端都是 3.3 伏电压,不会形成电流,LED 熄灭。
-
高电平驱动电路:LED 负极接到 GND,正极通过限流电阻接到 PA0 上。此时高电平点亮,低电平熄灭。
-
-
限流电阻的作用:
-
一方面可以防止 LED 因电流过大而烧毁。
-
另一方面可以调整 LED 的亮度。如果觉得 LED 亮度比较刺眼,可以适当增大限流电阻的阻值。
-
-
驱动方式选择:
-
在 STM32 的推挽输出模式下,高低电平均有较强的驱动能力,两种接法均可。但在单片机电路中,一般倾向使用低电平驱动方式。这是因为很多单片机或芯片都使用了高电平弱驱动、低电平强驱动的规则,可以一定程度上避免高低电平打架。
-
二、蜂鸣器(以有源分频器为例)的硬件电路
-
有源分频器特性:
- 内部自带震荡源,将正负极接上直流电压即可持续发声,频率固定。
-
三极管驱动电路:
- 对于功率稍微大一点的蜂鸣器,直接用 GPIO 口驱动会导致 STM32 负担过重,这时可以用三极管驱动电路来完成驱动任务。
- PNP 三极管驱动电路:三极管左边是基极,带箭头的是发射极,剩下的是集电极。基极给低电平三极管导通,通过 3.3 伏和 GND 就可以给蜂鸣器提供驱动电流;基极给高电平三极管截止,蜂鸣器没有电流。
- NPN 三极管驱动电路:与 PNP 三极管驱动逻辑相反,基极给高电平导通,低电平断开。
-
连接注意事项:
- 蜂鸣器的三极管最好接在 PNP 三极管的上边,NPN 三极管的下边。这是因为三极管的通断需要在发射极和基极之间产生一定的开启电压,如果把负载接在发射极这边,可能会导致三极管不能开启。
三、LED闪烁&LED流水灯&蜂鸣器程序编写
1. 工程文件创建:
-
新建工程:打开 Keil5 软件,点击 “Project - New uVision Project”,选择存放工程的文件夹,新建一个文件夹并命名为 3_1 led 闪烁”,点进去后给工程起名 “project” 并保存。接着选择芯片 STM32F103C8。
-
复制文件:在工程文件夹中新建三个文件夹分别叫 start、library、user。打开固件库文件,找到启动文件全选复制放到 start 文件夹下;找到 sm32f10x 和 system 的两个文件复制粘贴到大文件夹下;找到 call 和 cm3 的两个文件复制粘贴到 start 文件夹下。找到标准外设驱动的文件夹,打开 src 全选复制粘贴到 library 文件夹下,再打开 inc 全选复制也粘贴到 library 文件夹下。最后打开 project 文件夹下后缀是 template 的文件夹,按住 ctrl 键选择 main.c 和两个.it 文件复制粘贴到 user 文件夹下。
-
添加文件到工程:回到 Keil5,点击三个箱子的工程文件管理按钮,新建三个组分别叫 start、library、user。选中 start 组,在右边点击添加文件,打开 start 文件夹,文件类型选所有文件,添加后缀为.s 的启动文件以及其他.c 和.h 文件。然后对 library 和 user 组进行同样操作,分别添加对应文件夹下的文件。
-
工程选项配置:点击魔术棒按钮打开工程选项,选择 C/C++,在 Include Paths 中添加自己建的文件夹路径 start、library、user。在 Define 里写上 “USE_STDPERIPH_DRIVER” 字符串。最后选择调试器为 ST-Link,并设置 flash 下载勾上 “复位并执行”。
-
编译测试与小工具介绍:打开 main.c 文件,把原来的代码删掉,右键添加头文件并写上主函数。编译测试无错无警告,下载也没问题。同时介绍一个批处理文件,可删除工程编译产生的中间文件,减小工程大小以便分享。
-
工具介绍:介绍了一个批处理文件,可以删除工程编译产生的中间文件,减小工程大小以便分享。
需要将文件分享给别人时可以先点击这个文件,再分享给别人,可以更加方便分享。
2. 实现LED闪烁
-
操作STM32的GPIO的三个步骤:
-
使用RCC开启GPIO时钟
-
使用GPIO_Init函数初始化GPIO
-
使用输出或者输入的函数控制GPIO口
在这里总共涉及了RCC和GPIO两个外设,我们先学习一下这两个外设都有哪些库函数
-
打开 library 中的 rcc.c 文件,查看 RCC (Reset and Clock Control,复位和时钟控制)的库函数,最常用的只有三个函数即 RCC_AHBPeriphClockCmd、RCC_APB2PeriphClockCmd、RCC_APB1PeriphClockCmd,分别用于控制不同总线上的外设时钟。
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriiph, Functionalstate Newstate); void RCC_APB2PeriphClockCmd(uint32 t RCC APB2Perriph, Functionalstate Newstate); void RCC_APBlPeriphClockCmd(uint32_t RCC_APBlPerriph,Functionalstate Newstate);
其中以上三个函数均是第一个参数选择外设,第二个参数使能或失能。
-
打开 gpio.h 文件查看 GPIO 的库函数,目前需要了解的主要有 GPIO_DeInit、GPIO_AFIODeInit、GPIO_Init、GPIO_StructInit 等函数以及八个读写函数。
-
GPIO_DeInit:参数可以写
GPIOA
、GPIOB
等,调用这个函数后,所指定的 GPIO 外设就会被复位。 -
GPIO_AFIODeInit:同样可以复位相关的复用功能外设,这里后面再讲。
-
GPIO_Init:这是非常重要的函数,作用是用结构体的参数来初始化 GPIO 端口。需要先定义一个结构体变量,然后给结构体赋值,最后调用这个函数。该函数内部会自动读取结构体的值,然后自动把外设的各个参数配置好。在 STM32 中,基本所有的外设都有类似的初始化函数。
-
GPIO_StructInit:这个函数可以把结构体变量赋一个默认值。
-
四个 GPIO 的读取函数:用于读取 GPIO 端口的状态。
-
四个 GPIO 的写入函数:可以实现读写 GPIO 端口的功能。
void GPIO_DeInit(GPIO_TypeDef* GPIOx);
void GPIO_AFIODeInit(void);
void GPIO_Init(GPIO_TypeDef* GPIOX, GPIO_InitTypeDef* GPIO_Initstruct);
void GPIO_Structinit(GPIO_InitTypeDef* GPIO_Initstruct);
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef*_GPIOx,uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadoutputDataBit(GPIO_TypeDef* GPIIOx,uintl6_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPI(o_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uintI6_t GSPIO_Pin);
void GPIO_WriteBit(GPIO TypeDef* GPIOx, uint16 t GPIO Pin, BitAction Bitval);
void GPIO_Write(GPIO_TypeDef* GPIOx, uintl6_t Portval);
-
开启时钟与初始化 GPIO:
- 在 main.c 文件中首先调用 RCC_APB2PeriphClockCmd 函数开启 GPIOA 的时钟,参数选择 “RCC_APB2Periph_GPIOA, ENABLE”。(注意:GPIOA即通用输入/输出端口A主要是控制自身的16个引脚即PA0~PA15,所以这里的第一个参数要选择GPIOA)
- 初始化GPIO
定义结构体变量:
-
先把结构体类型复制下来,在 “GPIO” 上面粘贴,起个名字叫 “GPIO_InitStructure”。这个结构体变量在代码中的作用是存储 GPIO 的初始化参数,以便后续传递给 GPIO_Init 函数进行实际的初始化操作。
-
在一些老的编译器中,要求所有的局部变量定义必须放到函数的最前面。如果编译器支持在函数中间定义变量,则可以放在合适的位置。
给结构体赋值:
-
通过右键跳转查看函数说明,复制粘贴参数来给结构体成员赋值。
-
选择 GPIO 的工作模式:通过 “GPIO_Mode_TypeDef” 枚举类型来选择工作模式。例如,在点灯操作中使用推挽输出模式,即复制 “OUT_PP”(推挽输出)这一项粘贴到 “GPIO_Mode” 这里。推挽输出模式可以使 GPIO 引脚在输出高电平和低电平时都有较强的驱动能力。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT_PP;
-
选择引脚:通过 “GPIO_Pin” 枚举类型来选择引脚。但是这里的“GPIO_Pin”有多个定义,我们选择member这一项,再选中"GPIO_pins_define",再Ctrl+F即可找到定义。如果连接的是 GPIOA 的 0 号引脚,就选择 “GPIO_Pin_0” 放到这里。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
-
选择输出速度:例如输出速度选择 50 兆赫兹,以满足特定的应用需求。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
调用 GPIO_Init 函数:
-
将 “GPIO_Init” 结构体的地址放到 “GPIO_Init” 函数的第二个参数。当这个函数执行完,指定的 GPIO 外设引脚就会自动被配置为相应的工作模式和速度。
综上所有代码如下
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
//使用各个外设前必须开启时钟,否则对外设的操作无效
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO引脚,赋值为第0号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
//实现GPIOA的初始化
在GPIO初始化之后就可以使用GPIO的输入输出函数了。
-
GPIO的输出函数
1. GPIO_ResetBits
-
参数:第一个参数是
GPIOx
,表示选择特定的 GPIO 外设,如GPIOA
、GPIOB
等;第二个参数是GPIOPin
,用于指定具体的引脚,例如GPIO_Pin_0
表示所选 GPIO 外设的 0 号引脚。 -
功能:这个函数可以把指定的端口设置为低电平。例如写入
GPIOA, GPIO_Pin_0
,这样就可以将PA0
端口设置为低电平,可用于控制连接在该端口上的设备,如点亮 LED(如果采用低电平点亮的方式)。
2. GPIO_SetBits
-
参数:与
GPIO_ResetBits
相同,第一个参数是GPIOx
,第二个参数是GPIOPin
。 -
功能:这个函数可以把指定的端口设置为高电平。可以用于控制连接在对应端口上的设备,使其处于特定的工作状态。
3. GPIO_WriteBit
-
参数:有三个参数,前两个参数也是指定端口的,即
GPIOx
和GPIOPin
;第三个参数是BitValue
,这个是根据第三个参数的值来设置指定的端口。第三个参数可以是Bit_RESET
(清除端口值,即置低电平)或Bit_SET
(设置端口值,即置高电平)。 -
功能:通过不同的参数组合,可以精确地控制特定 GPIO 端口的输出状态。
4. GPIO_Write
-
参数:第一个参数是
GPIOx
,选择外设;第二个参数是PortVal
。 -
功能:这个函数可以同时对 16 个端口进行写入操作。通过给参数赋值不同的十六进制值来实现不同 GPIO 端口的点亮和熄灭,可用于实现复杂的输出控制,如 LED 流水灯效果。
主循环中的 LED 闪烁控制
1. 设置低电平点亮 LED:
在主循环中,首先使用GPIO_ResetBits
函数将连接 LED 的 GPIO 端口设置为低电平。如果 LED 采用低电平点亮的方式,此时 LED 被点亮。
2. 第一次延时:
接着调用延时函数,目的是让 LED 保持点亮状态一段时间。这个延时的时间长短可以根据实际需求进行调整,以控制 LED 闪烁的频率。
3. 设置高电平熄灭 LED:
经过一段时间的延时后,使用GPIO_SetBits
函数将端口设置为高电平。此时 LED 熄灭,因为先前设定的是低电平点亮 LED。
4. 第二次延时:
再次调用延时函数,让 LED 保持熄灭状态一段时间。同样,这个延时时间也可以根据需要进行调整,以改变 LED 闪烁的效果。
5. 循环重复:
主循环会不断重复上述步骤,使得 LED 持续闪烁。通过调整两次延时函数的时间参数,可以控制 LED 闪烁的速度和节奏。
最终代码如下:
/*设置PA0引脚的高低电平,实现LED闪烁,下面展示3种方法*/
/*方法1:GPIO_ResetBits设置低电平,GPIO_SetBits设置高电平*/
GPIO_ResetBits(GPIOA, GPIO_Pin_0); //将PA0引脚设置为低电平
Delay_ms(500); //延时500ms
GPIO_SetBits(GPIOA, GPIO_Pin_0); //将PA0引脚设置为高电平
Delay_ms(500); //延时500ms
/*方法2:GPIO_WriteBit设置低/高电平,由Bit_RESET/Bit_SET指定*/
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET); //将PA0引脚设置为低电平
Delay_ms(500); //延时500ms
GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET); //将PA0引脚设置为高电平
Delay_ms(500); //延时500ms
/*方法3:GPIO_WriteBit设置低/高电平,由数据0/1指定,数据需要强转为BitAction类型*/
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0); //将PA0引脚设置为低电平
Delay_ms(500); //延时500ms
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1); //将PA0引脚设置为高电平
Delay_ms(500); //延时500ms
补充:
将准备好的Delay函数模块复制粘贴到新建的用于存放系统资源的文件夹叫Sytem中,回到Keil软件点击三个小箱子的按钮,将System作为新的组添加进去,并且将Delay的两个文件添加进去(将延时函数模块添加进Keil软件),再点击魔术棒按钮,“C/C++”中添加新文件夹的头文件路径,这样就完成了。
3. 有关推挽输出和开漏输出的驱动问题具体解释
推挽输出
-
在讲解 LED 闪烁的过程中,涉及到了推挽输出模式。推挽输出是一种具有较强驱动能力的输出模式。
-
当选择推挽输出模式时,高低电平均有驱动能力。例如,在将 LED 连接到 GPIO 引脚并设置为推挽输出模式时,通过控制引脚的高低电平可以有效地点亮或熄灭 LED。
-
在代码实现中,通过初始化 GPIO 引脚为推挽输出模式,可以确保输出信号有足够的电流和电压来驱动连接的外设。
开漏输出
-
相比推挽输出,开漏输出的驱动能力有所不同。
-
开漏输出高电平相当于高阻态,无驱动能力;低电平有驱动能力。
-
如果将 LED 连接到设置为开漏输出的 GPIO 引脚,当引脚输出高电平时,由于高阻态的特性,无法为 LED 提供足够的电流使其点亮。而当引脚输出低电平时,可以驱动 LED 点亮。
补充:在面包板中可以通过改变LED的引脚插入方向,控制LED高低电平的驱动方式,可以观察开漏输出和推挽输出的驱动问题。
4. LED流水灯
-
复制工程:复制 LED 闪烁的工程,改名为 “3_2 LED 流水灯”。
-
开启时钟:因为连接的都是 GPIOA 的端口,所以开启时钟的代码不用变。仍然使用
RCC_APB2PeriphClockCmd
函数开启 GPIOA 的时钟,参数选择 “RCC_APB2Periph_GPIOA, ENABLE”。 -
初始化多个端口:
-
在初始化端口部分,使用按位或操作方式一次性初始化 GPIOA 的 0 到 7 号端口为推挽输出模式。通过查看函数定义,了解到 GPIO_Pin、时钟控制项以及
GPIO_ResetBits
、GPIO_SetBits(这样可以同时设置多个引脚)
等函数都可以用按位或选择多个硬件同时操作。 -
具体实现方式为,定义一个
GPIO_InitTypeDef
结构体变量,然后给结构体赋值,选择推挽输出模式、选择 GPIOA 的 0 到 7 号引脚(通过按位或操作 “GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7” 实现)以及设置输出速度为 50 兆赫兹。最后将结构体地址作为参数传入GPIO_Init
函数。
-
-
主循环实现流水灯效果:
-
使用函数:在主循环中使用
GPIO_Write
函数同时控制 16 个端口,通过给参数赋值不同的十六进制值来实现不同 LED 的点亮和熄灭,达到流水灯效果。 -
参数设置:
-
第一个参数是
GPIOx
,选择外设,这里直接写GPIOA
。(操作的都是GPIOA的PA0到PA7口) -
第二个参数是指定写的输出数据寄存器的值,转到定义可以看到这个参数是直接写到 GPIO 的 ODR 寄存器里的。例如先写十六进制值 “~0x0001”,对应二进制就是 “1111111111111110”,因为是低电平点亮,所以前面加按位取反符号,这样就是第一个 LED 点亮,其他都熄灭。(其中最低位对应PA0以此类推)
-
接着可以通过更改这个十六进制值来依次点亮不同的 LED,如改为 “~0x0002” 等,对应二进制值不同,点亮的 LED 位置也不同。高八位暂时不用,只关注低八位对应 GPIOA 的 0 - 7 号端口连接的 LED。
-
-
延时控制:在每次设置不同的十六进制值之间,可以加上延时函数,如 “delay (500)” 表示延时 500ms,这样可以控制流水灯的速度。如果想让流水灯速度更快,可以减小延时时间,如改为 “delay (100)”。
-
多样化展示:如果想换一种形式的流水灯,可以定义一个数组,依次取出数组中的数据来进行花式点灯,为流水灯效果提供更多可能性。
-
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
//使用各个外设前必须开启时钟,否则对外设的操作无效
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; //GPIO引脚,赋值为所有引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
//实现GPIOA的初始化
/*主循环,循环体内的代码会一直循环执行*/
while (1)
{
/*使用GPIO_Write,同时设置GPIOA所有引脚的高低电平,实现LED流水灯*/
GPIO_Write(GPIOA, ~0x0001); //0000 0000 0000 0001,PA0引脚为低电平,其他引脚均为高电平,注意数据有按位取反
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0002); //0000 0000 0000 0010,PA1引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0004); //0000 0000 0000 0100,PA2引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0008); //0000 0000 0000 1000,PA3引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0010); //0000 0000 0001 0000,PA4引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0020); //0000 0000 0010 0000,PA5引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0040); //0000 0000 0100 0000,PA6引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0080); //0000 0000 1000 0000,PA7引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
}
}
5. 蜂鸣器
1. 复制工程与命名
-
复制 LED 闪烁的程序,改名为 “3-3 蜂鸣器”。这个名称用于区分不同的程序,同时也表明这个程序是与蜂鸣器相关的分频器示例。
2. 时钟设置
-
因为蜂鸣器连接到的是 PB 口(PB12 号口),所以需要将时钟控制部分改为开启 GPIOB 的时钟。具体代码为 “RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB, ENABLE)”。与 LED 闪烁和流水灯不同,它们可能使用的是 GPIOA 的时钟,这里根据连接蜂鸣器的端口进行了调整。
3. 端口初始化
-
引脚选择:
-
确定使用 PB12 号端口,所以在初始化部分,将引脚设置为 “GPIO_Pin_12”。这明确了要操作的具体 GPIO 引脚。
-
-
端口模式与速度:
-
端口模式仍然设置为推挽输出,速度为 50 兆赫兹。对 GPIOB 进行初始化,确保 PB12 号口被正确配置为推挽输出模式,以便能够有效地控制蜂鸣器。具体的初始化代码与之前对 GPIOA 的初始化类似,先定义一个
GPIO_InitTypeDef
结构体变量,然后给结构体赋值,选择推挽输出模式、指定引脚为 PB12 以及设置输出速度为 50 兆赫兹,最后将结构体地址作为参数传入GPIO_Init
函数。
-
4. 控制蜂鸣器输出
-
高低电平控制:
-
通过给 PB12 端口输出高电平或低电平控制蜂鸣器的响和不响。例如,给 PB12 输出低电平时,蜂鸣器就会响;输出高电平时,蜂鸣器就不响。
-
-
实现特定响停模式:
-
可以通过在代码中设置不同的延时和高低电平输出组合,实现各种蜂鸣器的发声效果。比如 “想 100ms 停 100ms 再响 100ms 再停 700ms” 这样的响停模式。具体实现方式是在代码中使用延时函数和对 PB12 端口的高低电平设置交替进行。例如先将 PB12 设置为低电平让蜂鸣器响,然后使用延时函数延时 100ms,接着将 PB12 设置为高电平让蜂鸣器停止发声,再延时 100ms,如此循环实现特定的响停模式。
-
七、GPIO的输入
一、按键介绍
-
按键的基本特性
-
按键是最常见的输入设备,按下导通松手断开。套件里的按键上面是白色按钮,下面有两个引脚,按钮按下去时引脚接通,松手后自动断开。
-
-
按键抖动现象及处理
-
在单片机中应用按键时会有抖动现象,按下和松手瞬间会伴随有一连串抖动,通常在 5 - 10ms 之间,人眼分辨不出来,但对于高速运行的单片机而言,这个时间比较漫长。
-
为避免按键按一下单片机却反映多次的现象,需要对抖动进行过滤,最简单的方法是加一段延时,把抖动时间耗过去。
-
二、传感器模块介绍
-
套件中的传感器模块种类
-
套件提供四种传感器模块,分别是光敏电阻传感器、热敏电阻传感器、对射式红外传感器和反射式红外传感器。它们的电路结构和工作原理都差不多。
-
-
传感器模块的工作原理
-
传感器模块利用传感器元件(如光敏电阻、热敏电阻、红外接收管等)的电阻会随外界模拟量变化而变化的特性。
-
通过与定值电阻串联分压得到模拟电压输出,对电路来说检测电压就非常容易。还可通过电压比较器对模拟电压进行二值化得到数字电压输出。
-
三、传感器模块电路分析
-
基本分压电路
-
以光敏电阻传感器模块的电路为例(如图三),传感器电阻(如光敏电阻 n1)和定值电阻 r1 串联,一端接在 VCC 正极,一端接在接地负极,构成基本的分压电路。左边的 C2 是滤波电容,为中间的电压输出进行滤波,滤除一些干扰,保证输出电压波形的平滑。
-
分析电路的小技巧:在分析电路时,可以先把滤波电容(一般形式为一端接地一端接在电路中)抹掉,使电路分析更加简单。抹掉电容后,整个电路的主要框架就是定值电阻和传感器电阻的分压电路。
-
可以用分压定理或上下拉电阻的思维来分析传感器电阻的阻值变化对输出电压的影响。当 n1 阻值变小时,下拉作用增强(下拉电阻越小,下拉作用越强),中间的 AO 端电压就会拉低;当 n1 阻值变大,下压作用减弱,中间引脚由于 r1 的上拉作用,电压就会升高。(重要)
-
-
数字电压输出
-
数字输出是对 AO 进行二值化的输出,通过芯片 lm393(电压比较器)完成二值化。
-
电压比较器的同向输入端接 AO(模拟电压端),反向输入端接一个电位器(也是分压电阻的原理),通过调节电位器生成一个可调的阈值电压,两个电压进行比较,最终输出结果就是数字电压输出 DO,DO 最终接到引脚的输出端。
-
右边还有两个指示灯,左边是电源指示灯,通电就亮;右边是 DO 输出指示灯,低电平点亮高电平熄灭。DO 这里还有一个上拉电阻,保证默认输出为高电平。
-
四、按键和传感器模块的硬件电路接法
-
按键的四种接法
-
视频中给出了按键的四种接法,上面两个是下接按键的方式,下面两个是上接按键的方式。一般来说按键都是用上两种方式(下接)。
-
第一种接法:选取一个 GPIO 口(如 PA0),通过按键接到 GND。当按键按下时,PA0 被直接下拉到 GND,此时读取 PA0 口的电压就是低电平;当按键松手时,PA0 被悬空,在这种接法下,必须要求 PA0 是上拉输入的模式,否则会出现引脚电压不确定的错误现象。
-
第二种接法:在第一种接法的基础上,外部接了一个上拉电阻。当按键松手时,引脚由于上拉作用自然保持为高电平;当按键按下时,引脚直接接到 GND,为低电平。这种状态下引脚不会出现悬空状态。此时引脚可以配置为浮空输入或者上拉输入,如果是上拉输入,那就是内外两个上拉电阻共同作用,高电平会更强,更稳定,但当引脚被强行拉到低时,损耗也会大一些。
-
第三种接法:PA0 通过按键接到 3.3 伏,要求 PA0 必须要配置成下拉输入的模式。当按键按下时,引脚为高电平,松手时引脚回到默认值低电平。但一般单片机可能不一定有下拉输入的模式,所以这种解法作为扩展部分,了解即可。
-
第四种接法:在第三种接法下面再外接一个下拉电阻,可自行分析。
-
总结:上面两种接法按键按下时引脚是低电平,松手是高电平;下面两种解法按键按下时是高电平,松手是低电平。左边两种解法必须要求引脚是上拉或下拉输入的模式,右边两种解法可以允许外置上拉电阻和下拉电阻。一般都用上面两种解法,下面两种解法用得比较少。(重要)
-
-
传感器模块的电路
-
传感器模块电路非常简单,VCC 接 3.3 伏,接地接接地用于供电,DO 数字输出随便接一个端口(如 PA0)用于读取数字量,AO 模拟输出等学 ADC 模数转换器的时候再讲,现在不用接。
-
五、按键控制LED&光敏电阻控制蜂鸣器的代码实现
1. 按键控制LED
-
LED文件的配置
1. 包含头文件
- 在 LED.c 文件的开头,包含必要的头文件,以便能够使用 STM32 的库函数和相关定义。
2. LED 初始化函数(LED_Init)
-
这个函数负责配置连接 LED 的 GPIO 引脚为输出模式。
-
首先,开启 GPIO 端口的时钟。例如,对于 GPIOA,使用 “RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA, ENABLE);” 开启时钟。
-
然后,设置 GPIO 引脚的模式为通用输出推挽模式。使用 “GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;” 进行设置。
-
最后,设置 GPIO 引脚的输出速度。例如,“GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;”。
void LED_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出
/*设置GPIO初始化后的默认电平*/
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2); //设置PA1和PA2引脚为高电平
}
3. LED 控制函数(LED_On 和 LED_Off)
-
LED_On 函数用于点亮指定的 LED。
-
根据传入的枚举类型参数,确定要点亮的 LED 对应的 GPIO 引脚,并将该引脚的电平设置为高电平。
-
LED_Off 函数用于熄灭指定的 LED。与 LED_On 函数类似,根据参数确定要熄灭的 LED 对应的 GPIO 引脚,并将该引脚的电平设置为低电平。
void LED1_ON (void)
{
GPIO_ResetBits (GPIOA, GPIO_Pin_1);
}
void LED1_OFF (void)
{
GPIO_SetBits (GPIOA, GPIO_Pin_1);
}
4. LED电平反转函数(LED_Turn)
-
LED_Turn函数用反转此时的LE电平状态,用于实现按键按下LED电平取反。
-
利用IF选择语句先检测对应端口电平,在对此端口进行取反操作即可。
/**
* 函 数:LED1状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为低电平
}
}
-
Key文件的配置
1. 包含头文件
-
在编写按键驱动函数的文件开头,包含必要的头文件。
2. 按键初始化函数(Key_Init)
-
函数目的
-
Key_Init 函数主要负责配置连接按键的 GPIO 引脚为输入模式,为后续读取按键状态做好准备。
-
-
具体步骤
-
开启 GPIO 端口时钟:确定按键连接的 GPIO 端口,这里的按键连接到 GPIOB,则使用 “RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB, ENABLE);” 开启 GPIOB 的时钟。
-
配置 GPIO 引脚模式:定义 GPIO_InitTypeDef 结构体变量,设置引脚为输入模式。可以根据实际情况选择上拉输入或下拉输入模式,这里需要读取按键设置为上拉输入模式 “GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;”。
-
指定引脚:设置要配置的 GPIO 引脚,这里的按键接在PB1和PB11口,所以可以使用 “GPIO_InitStructure.GPIO_Pin =GPIO_Pin_1 | GPIO_Pin_11;”。
-
设置引脚速度:可以根据需要设置 GPIO 引脚的速度,如 “GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;”。
-
初始化 GPIO:使用 “GPIO_Init (GPIOB, &GPIO_InitStructure);” 完成对按键连接 GPIO 端口的初始化。
-
/**
* 函 数:按键初始化
* 参 数:无
* 返 回 值:无
*/
void Key_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}
3. 按键读取函数(Key_GetNum)
-
函数目的
-
Key_GetNum 函数用于读取按键的状态,并返回按下按键的编号。
-
-
具体步骤
-
包含头文件:同样在函数所在文件开头包含必要的头文件。
-
读取按键状态:根据传入的参数(通常是按键编号),读取对应的 GPIO 引脚的电平状态。需要读取外部输入的一个端口值,可以使用 “GPIO_ReadInputDataBit” 函数来读取单个引脚的状态。例如,如果要检测第一个按键的状态,可以使用 “GPIO_ReadInputDataBit (GPIOB, GPIO_Pin_1);”。
-
判断按键状态并返回编号:根据读取到的电平状态判断按键是否按下。如果按下,则返回相应的按键编号;如果未按下,则返回特定的值表示没有按键按下。例如,如果检测到第一个按键按下,则返回 1;如果第二个按键按下,则返回 2;如果都未按下,则返回 0。
-
/**
* 函 数:按键获取键码
* 参 数:无
* 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 1; //置键码为1
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 2; //置键码为2
}
return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}
补充:GPIO四个读取函数:
-
GPIO_ReadInputDataBit 函数
-
功能:读取单个 GPIO 引脚的输入数据位(用于读取输入数据寄存器某一个端口的输入值)。
-
使用方法:该函数接受一个 GPIO 端口和一个引脚参数,返回该引脚的电平状态(0 或 1)。例如,要读取 GPIOA 的第 0 引脚状态,可以使用 “GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0)”。
-
应用场景:在按键控制中,可以用于读取连接按键的 GPIO 引脚状态,判断按键是否按下。
-
-
GPIO_ReadInputData 函数
-
功能:读取整个 GPIO 端口的输入数据。(用于读取整个输入数据寄存器端口的输入值)。
-
使用方法:该函数接受一个 GPIO 端口参数,用来指定外设,返回该端口的 16 位输入数据值。例如,要读取 GPIOB 的输入数据,可以使用 “GPIO_ReadInputData (GPIOB)”。
-
应用场景:当需要同时读取多个连接在同一 GPIO 端口上的输入设备状态时,可以使用这个函数。比如,多个按键连接到一个端口,通过读取该端口的数据值来判断各个按键的状态。
-
-
GPIO_ReadOutputDataBit 函数
-
功能:读取单个 GPIO 引脚的输出数据位。(用于读取输出数据寄存器某一个端口的输入值)。
-
使用方法:该函数接受一个 GPIO 端口和一个引脚参数,返回该引脚的输出电平状态(0 或 1)。例如,要读取 GPIOA 的第 1 引脚的输出状态,可以使用 “GPIO_ReadOutputDataBit (GPIOA, GPIO_Pin_1)”。一般用于输出模式下观察自己输出的是什么。
-
应用场景:在调试过程中,可以用于检查某个 GPIO 引脚的输出状态是否符合预期。在按键控制 LED 的场景中,可以在设置 LED 状态后,使用这个函数来确认 LED 对应的 GPIO 引脚输出是否正确。
-
-
GPIO_ReadOutputData 函数
-
功能:读取整个 GPIO 端口的输出数据。(用于读取整个输出数据寄存器端口的输入值)。
-
使用方法:该函数接受一个 GPIO 端口参数,返回该端口的 16 位输出数据值。例如,要读取 GPIOC 的输出数据,可以使用 “GPIO_ReadOutputData (GPIOC)”。
-
应用场景:当需要同时检查多个连接在同一 GPIO 端口上的输出设备状态时,可以使用这个函数。比如,多个 LED 连接到一个端口,通过读取该端口的输出数据值来确认各个 LED 的状态。
-
main程序的编写
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
uint8_t KeyNum; //定义用于接收按键键码的变量
int main(void)
{
/*模块初始化*/
LED_Init(); //LED初始化
Key_Init(); //按键初始化
while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键1按下
{
LED1_Turn(); //LED1翻转
}
if (KeyNum == 2) //按键2按下
{
LED2_Turn(); //LED2翻转
}
}
}
2. 光敏电阻控制蜂鸣器
硬件连接
-
当有光线照射时,光敏传感器的灯亮,输出指示灯亮代表输出低电平;当遮住光线时,输出指示灯灭代表输出高电平。
-
电位器可以调节高低电平的判断阈值。
工程准备
-
复制代码并改名
- 回到工程文件夹,复制 “3-4” 的代码,改名为 “3-5光敏传感器控制分频器”,然后打开工程。
-
驱动程序分装
- 主要步骤还是对驱动程序进行分装,以便更好地管理代码和提高可移植性。
分频器驱动分装
-
创建文件
-
在 “hardware” 目录下右键添加新的文件,选择 C 文件,名字起为 “buzzer”,路径选择 “hardware” 文件夹。
-
继续右键添加新的文件,选择 H 文件,名字起为 “buzzer.h”,加上反斜杠 “hardware” 路径。
-
-
添加头文件和定义宏
-
C 文件第一行添加上 “stm32f10x.h” 头文件。
-
H 文件加上 “#ifndef BUZZER_H”“#define BUZZER_H”,最后 “#endif”。
-
-
复制并修改函数
-
打开 “led.c” 文件,复制其中的函数,然后粘贴到蜂鸣器的 C 文件中,并改一下函数名,如叫 “buzzer_init”“buzzer_on”“buzzer_off”“buzzer_turn” 等。
-
修改函数中的引脚参数,第一个开启 GPIOB 的时钟,然后针对 GPIOB 的 PB12 引脚进行操作。
#include "stm32f10x.h" // Device header /** * 函 数:蜂鸣器初始化 * 参 数:无 * 返 回 值:无 */ void Buzzer_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB12引脚初始化为推挽输出 /*设置GPIO初始化后的默认电平*/ GPIO_SetBits(GPIOB, GPIO_Pin_12); //设置PB12引脚为高电平 } /** * 函 数:蜂鸣器开启 * 参 数:无 * 返 回 值:无 */ void Buzzer_ON(void) { GPIO_ResetBits(GPIOB, GPIO_Pin_12); //设置PB12引脚为低电平 } /** * 函 数:蜂鸣器关闭 * 参 数:无 * 返 回 值:无 */ void Buzzer_OFF(void) { GPIO_SetBits(GPIOB, GPIO_Pin_12); //设置PB12引脚为高电平 } /** * 函 数:蜂鸣器状态翻转 * 参 数:无 * 返 回 值:无 */ void Buzzer_Turn(void) { if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_12) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平 { GPIO_SetBits(GPIOB, GPIO_Pin_12); //则设置PB12引脚为高电平 } else //否则,即当前引脚输出高电平 { GPIO_ResetBits(GPIOB, GPIO_Pin_12); //则设置PB12引脚为低电平 } }
-
-
声明函数
-
把函数声明都放到头文件 “buzzer.h” 里,以便外部调用。
#ifndef __BUZZER_H #define __BUZZER_H void Buzzer_Init(void); void Buzzer_ON(void); void Buzzer_OFF(void); void Buzzer_Turn(void); #endif
-
-
编译测试
-
先编译一下,确保没有问题。然后到主函数中删除原来的分频器相关代码,包含 “buzzer.h” 头文件,在主函数中写 “buzzer_init” 初始化分频器,进行测试,如 “buzzer_on/off/turn” 等操作,编译下载,听到蜂鸣器正常工作说明驱动函数没有问题。
-
测试中使用的“buzzer_turn”函数是没有起到作用的,是无意义的。
-
光敏传感器驱动分装
-
创建文件
-
在 “hardware” 目录下右键添加新的文件,选择 C 文件,名字起为 “light_sensor”,路径加上 “hardware”。
-
继续右键添加新的文件,选择 H 文件,名字起为 “light_sensor.h”,加上反斜杠 “hardware” 路径。
-
-
添加头文件和定义宏
-
C 文件第一行添加上 “stm32f10x.h” 头文件。
-
H 文件加上 “#ifndef LIGHT_SENSOR_H”“#define LIGHT_SENSOR_H”,最后 “#endif”。
-
-
初始化函数
-
在 “light_sensor.c” 文件里写上 “void light_sensor_init (void)”,复制按键的初始化程序粘贴到这里并修改。
-
修改时钟,保持 GPIOB 不用改,GPIOMode 可以选择上拉输入模式,因为光敏传感器连接的是 PB13 口,所以将引脚参数改成 “GPIOPin_13”,下面初始化这里 GPIOB 也无需改动。
/** * 函 数:光敏传感器初始化 * 参 数:无 * 返 回 值:无 */ void LightSensor_Init(void) { /*开启时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟 /*GPIO初始化*/ GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB13引脚初始化为上拉输入 }
-
-
读取端口函数
-
写一个读取端口的函数,如 “uint8_t light_sensor_get_value (void)”,直接返回端口值,即 “return GPIO_ReadInputDataBit (GPIOB, GPIOPin_13);”。
/** * 函 数:获取当前光敏传感器输出的高低电平 * 参 数:无 * 返 回 值:光敏传感器输出的高低电平,范围:0/1 */ uint8_t LightSensor_Get(void) { return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13); //返回PB13输入寄存器的状态 }
-
-
声明函数
-
把函数的第一行复制放在头文件 “light_sensor.h” 里声明一下,编译确保没有问题。
-
主函数中的测试
-
包含头文件
-
在主函数里上面包含 “light_sensor.h” 头文件。
-
-
初始化和主循环测试
-
首先 “light_sensor_init” 初始化光敏传感器。
-
在主循环里直接判断,如果 “light_sensor_get_value () == 1”,代表光线比较暗的情况,那么就执行 “buzzer_on/off” 等操作。
-
-
编译下载测试
-
编译下载程序,然后遮住光敏电阻,蜂鸣器响;拿开光敏电阻,蜂鸣器不响,实现了演示程序的现象。
#include "stm32f10x.h" // Device header #include "Delay.h" #include "Buzzer.h" #include "LightSensor.h" int main(void) { /*模块初始化*/ Buzzer_Init(); //蜂鸣器初始化 LightSensor_Init(); //光敏传感器初始化 while (1) { if (LightSensor_Get() == 1) //如果当前光敏输出1 { Buzzer_ON(); //蜂鸣器开启 } else //否则 { Buzzer_OFF(); //蜂鸣器关闭 } } }
六、GPIO使用方法的总结
一、GPIO 使用方法概述
在这一节中,通过按键控制 LED 和光敏传感器控制蜂鸣器的实例,总结了 GPIO 的总体使用方法,旨在为开发者提供一个清晰的 GPIO 操作指南。
二、具体使用步骤
-
初始化时钟
-
这是使用 GPIO 的第一步,确保相关 GPIO 端口的时钟被正确开启。例如,对于特定的 GPIO 外设,如连接按键和 LED 的 GPIOA、连接按键的 GPIOB 等,需要通过相应的时钟控制函数开启其时钟。
-
-
定义结构体并赋值
-
定义一个
GPIO_InitTypeDef
结构体变量,用于配置 GPIO 的各种参数。 -
通过点运算符将结构体的成员引出并进行赋值。
-
例如:
-
GPIO_Mode
:可以选择输入输出模式,如 GPIO_Mode_IN_FLOATING(浮空输入)、GPIO_Mode_IPU(上拉输入)、GPIO_Mode_IPD(下拉输入)、GPIO_Mode_Out_PP(推挽输出)、GPIO_Mode_Out_OD(开漏输出)等八种模式。根据实际需求选择合适的输入或输出模式。 -
GPIO_Pin
:选择要操作的引脚,可以使用按位或的方式同时选中多个引脚。比如连接多个 LED 或按键时,可以同时选中多个引脚进行配置。 -
GPIO_Speed
:选择输出速度,一般如果要求不高,可以直接选择 50 兆赫兹。这个参数在一些对速度要求不高的应用中不是特别关键。
-
-
-
使用
GPIO_Init
函数初始化 GPIO-
将指定的 GPIO 外设进行初始化。例如,对于已经配置好的结构体变量,通过
GPIO_Init
函数将其应用到特定的 GPIO 端口上,完成 GPIO 的初始化操作。
-
-
读写 GPIO 的函数使用
-
在程序中,需要读写 GPIO 的状态时,可以使用特定的函数。这一节中提到了八个读取和写入的函数,主要用于读写 GPIO 的端口状态。
-
例如:
-
GPIO_ReadInputDataBit
:读取单个 GPIO 引脚的输入数据位,返回值为 0 或 1,代表低电平和高电平。 -
GPIO_ReadInputData
:读取整个 GPIO 端口的输入数据,返回一个 16 位的值,每一位代表一个端口的状态。 -
GPIO_ReadOutputDataBit
:读取单个 GPIO 引脚的输出数据位,一般用于输出模式下检查自己输出的状态。 -
GPIO_ReadOutputData
:读取整个输出寄存器。
-
-
三、实际应用中的注意事项
-
硬件驱动函数分装
-
在实际开发自己的产品时,应尽量将每个硬件的驱动函数单独提取出来,分装在
.c
和.h
文件里。 -
这样做有利于简化主函数的逻辑,使主函数更加清晰易读,同时也便于程序的移植和分工合作。
-
-
函数注释
-
为了方便使用这些硬件驱动函数的人快速上手,需要在函数的上面添加注释,说明函数的用途、参数的取值范围以及返回值的意义等。
-