文章目录
-
- MCU 和 GPIO
-
-
- 1. 单片机 MCU
* * 1.1 单片机和嵌入式系统
* 1.2 MCU
* 1.3 ARM 公司
* 1.4 市场主流 32 芯片
* 1.5 STM32 开发版概述
* 2. GPIO
* * 2.1 GPIO 概述
* 2.2 STM32F103ZET6 GPIO 相关内容
* 2.3 GPIO 开发流程
* 2.4 GPIO 控制 LED 灯
* 2.5 GPIO 端口内部基本电路情况
* * **2.5.1. 浮空输入模式(Floating Input)**
* **2.5.2. 上拉输入模式(Pull - up Input)**
* **2.5.3. 下拉输入模式(Pull - down Input)**
* **2.5.4. 模拟输入模式(Analog Input)**
* **2.5.5. 开漏输出模式(Open - Drain Output)**
* **2.5.6. 推挽输出模式(Push - Pull Output)**
* **2.5.7. 复用开漏输出模式(Alternate Function Open - Drain Output)**
* **2.5.8. 复用推挽输出模式(Alternate Function Push - Pull Output)**
* 2.6 时钟使能【小重点】
* **2.7 寄存器开发模式【重点】**
* * 2.7.1 时钟使能,对应寄存器 RCC
* 2.7.2 GPIO 对应引脚配置
* 2.7.3 GPIO 引脚输出高低电平配置
* 2.7.4 代码实现
* 2.7.5 程序烧录和重启
* 3. GPIO 控制案例
* * 3.1 Beep 蜂鸣器控制
* * 3.1.1 BEEP 原理图分析
* 3.1.2 代码实现
* 3.2 多文件编程
* 3.3 非精准延时控制函数
* 3.4 Key 按键控制
* * 3.4.1 原理图分析
* 3.4.2 开发流程
* 3.4.3 代码实现
- 1. 单片机 MCU
-
MCU 和 GPIO
1. 单片机 MCU
1.1 单片机和嵌入式系统
嵌入式系统
- 硬件 + 软件的嵌入式系统。嵌入到特定设备中,从而控制设备的执行。
- 硬件:处理单元,内存 + 硬盘
- 软件:裸机程序 或者 实时操作系统。
生活中实现的常见场景
- 智能家居,智能穿戴设备,无人设备,智能驾驶,军事
- 汽车主机厂,军工单位,智能化工厂
1.2 MCU
MCU ==> 微控制单元(Microcontroller Unit;MCU)
- 中央处理器,指令处理单元
- ROM 硬盘/存储空间
- RAM 运行内存
- MCU 可以认为是一个小型计算机,具备计算机的所有核心内容。
1.3 ARM 公司
ARM成立前的历史可追溯至45年前。1978年,由Chris Curry及Hermann Hauser共同创立Acorn Computers。这家新创公司获得建构及生产BBC Micro的权利。 这项英国政府计划的目标是让英国每间教室都设置电脑。Steve Furber教授及Sophie Wilson在这项计划中,设计出史上第一款ARM处理器ARM1。 [12]
ARM 主要进行芯片内核设计,提供给其他芯片制造商,生厂商,包括二级开发商,提供对应内核解决方案
- Cortex-A 内核,高性能内核,更新速度极快。主要用于手机 SoC 芯片开发,例如高通,小米,苹果…
- Cortex-R 内核,一般用于 MPU 实现,处理速度高,延时低,应用场景要求高稳定性,高可靠性。例如车辆控制 ECU 模块。
- Cortex-M 内核,小型化,低功耗化芯片内核。主要用于 MCU 市场,主要版本有 Cortex-M3 Cortex-M4 Cortex-M7 Cortex-M0.
- Cortex-X 内核,目前是 ARM 最新内核设计,主要用于 PC 和 移动端市场。

不同产品性能对比

ARM架构对应的处理器家族
- ARMv7 ==> Cortex-M 架构,目前市场主流 32 处理器架构。

1.4 市场主流 32 芯片
- 国内芯片
- GD 兆易创新,国内占用率较高的 MCU 32 芯片。主要用于汽车,军工,智能化控制领域,同时和 STM 32 芯片兼容。
- 乐鑫科技 ESP32 芯片,ESP32 性能较高,同时针对于通信设备和通信协议支持较多,包括 WiFi 蓝牙 Lora ZigBee。。。
- 华大半导体,灵动微电子
- 龙芯中科
- 目前国家倡导使用国产芯片实现产品开发,从而提供国产芯片生存土壤,也避免国外卡脖子。
- 国外芯片
- ST 系列,意法半导体公司
- Ti 协议,美国德州仪器公司,主要芯片是 DSP 数字信号处理芯片。
1.5 STM32 开发版概述

目前使用的开发版是基于 STM32F103ZET6 型号。
- ST 意法半导体公司产品
- M 使用 Cortex-M 内核
- 32 当前 MCU 为 32 位芯片
- F103
- F1 ==> Cortex-M3 内核
- 03 ==> F1 系列的型号,03 是增强型。
- ZET6
- Z ==> 引脚数目 144 引脚
- E ==> 闪存存储器大小(Flash) 512 KB
- T ==> 封装标准 LQFP 封装
- 6 ==> 工作温度范围 -40 ~ 85 ℃

2. GPIO
2.1 GPIO 概述
GPIO,全称为 通用输入输出,是一种存在于集成电路(如微控制器、单板计算机等)上的数字信号引脚。它的核心特征是 “通用”,意味着这些引脚没有预先设定的单一功能(比如专门用于UART通信或I2C通信),其具体行为(作为输入还是输出)可以通过软件进行动态配置。
在 OpenHarmony Hi3861 中利用 GPIO 实现控制
- LED 灯,BEEP 工作 ==> GPIO 输出信号控制
- KEY ==> GPIO 输入信号控制
- DHT11 ==> GPIO 输入输出状态转换,控制 + 数据读取、
对于 GPIO 而言
- GPIO 方式 输入 or 输出
- GPIO 电压情况,高电平 or 低电平
2.2 STM32F103ZET6 GPIO 相关内容
不同的 MCU 信号中,对应的 GPIO 数量不同,主要因素是对应 STM32 引脚数量
- GD32F103C8T6 对应 GPIO 有两组分别是 GPIOA 和 GPIOB,每一组 16 个通用GPIO,可编程 GPIO 有 32 个
- STM32F103ZET6 对应 GPIO 有七组 GPIOA ~ GPIOG,每一组 16 个通用GPIO,可编程 GPIO 有 16 * 7 = 112 个
硬件设计中的很多理念都遵循 2 进制思想,另外在硬件开发中,操作使用的二进制情况很多,需要重点掌握**【位操作】**相关内容
2.3 GPIO 开发流程
- 原理图分析
- 根据原理图,分析当前编程需要控制的引脚是哪一个,同时期望的现象需要当前 IO 对应工作状态。
- 寄存器控制开发
- 时钟使能,MCU 中所有的外部设备处于休眠状态。需要告知 MCU 当前外部设备需要工作,加入到 MCU 的执行周期中。
- 根据原理图分析的 GPIO 工作模式,对应 GPIO 进行配置。
* 明确 GPIO 分组
* 明确 GPIO 引脚编号
* 明确 GPIO 工作模式和高低电平状态。
* 根据以上信息进行配置。- 根据业务所需,进行相关代码实现。控制高低电平完成 LED 的控制
2.4 GPIO 控制 LED 灯
LED 原理图分析
- 对应引脚是 LED0 和 LED1
- 当前 LED 对应 IO 引脚
- IO 提供高电平,LED 灭
- IO 提供低电平,LED 亮
- 可以分析
- GPIO 要求可以提供高低电平切换。
- GPIO 对应输出模式。

引脚关系
- LED0 ==> PB5
- GPIO 分组 B 组中编号为 5 的引脚
- LED1 ==> PE5
- GPIO 分组 E 组中编号为 5 的引脚

2.5 GPIO 端口内部基本电路情况

在 STM32 中 GPIO 有八种工作模式
2.5.1. 浮空输入模式(Floating Input)
- 原理:在这种模式下,GPIO 引脚没有接上拉电阻或下拉电阻,其电平状态完全取决于外部电路。引脚处于高阻抗状态,输入电流几乎为零。根据当前 IO 口分压来判断高低电平数据。
- 应用场景:适用于外部信号已经有明确的驱动能力和电平状态的情况,比如连接按键,按键按下时直接将引脚接地,松开时引脚浮空,通过读取引脚电平判断按键状态。
2.5.2. 上拉输入模式(Pull - up Input)
- 原理:GPIO 引脚内部连接了上拉电阻,当外部电路没有对引脚进行驱动时,引脚电平被上拉到高电平。如果外部电路将引脚拉低,那么引脚电平就为低电平。
- 应用场景:常用于按键输入,当按键未按下时,引脚通过上拉电阻保持高电平;按键按下时,引脚接地变为低电平,避免了引脚浮空可能带来的电平不稳定问题。
2.5.3. 下拉输入模式(Pull - down Input)
- 原理:与上拉输入模式相反,GPIO 引脚内部连接了下拉电阻,当外部电路没有对引脚进行驱动时,引脚电平被下拉到低电平。如果外部电路将引脚拉高,那么引脚电平就为高电平。
- 应用场景:同样适用于按键输入等场景,当按键未按下时,引脚通过下拉电阻保持低电平;按键按下时,引脚接高电平。
2.5.4. 模拟输入模式(Analog Input)
- 原理:该模式下,GPIO 引脚用于模拟信号的输入,内部的数字逻辑电路被断开,引脚直接连接到模拟信号处理模块,如 ADC(模拟 - 数字转换器)。
- 应用场景:用于采集模拟信号,如温度传感器、压力传感器等输出的模拟电压信号,通过 ADC 将模拟信号转换为数字信号进行处理。
2.5.5. 开漏输出模式(Open - Drain Output)
- 原理:在开漏输出模式下,GPIO 引脚内部的输出级只有 N 沟道 MOS 管,当输出为低电平时,MOS 管导通,引脚接地;当输出为高电平时,MOS 管截止,引脚处于高阻态,需要外部接上拉电阻才能输出高电平。
- 应用场景:常用于实现线与功能、I2C 总线等通信协议,多个开漏输出引脚可以连接在一起,只要有一个引脚输出低电平,总线就为低电平。
2.5.6. 推挽输出模式(Push - Pull Output)
- 原理:推挽输出模式下,GPIO 引脚内部的输出级由 P 沟道 MOS 管和 N 沟道 MOS 管组成。当输出为高电平时,P 沟道 MOS 管导通,引脚输出高电平;当输出为低电平时,N 沟道 MOS 管导通,引脚输出低电平。
- 应用场景:适用于直接驱动一些负载,如 LED 灯,能够提供较强的驱动能力。
2.5.7. 复用开漏输出模式(Alternate Function Open - Drain Output)
- 原理:该模式下,GPIO 引脚的功能由片上外设控制,输出级采用开漏输出结构。与普通开漏输出模式类似,需要外部接上拉电阻才能输出高电平。
- 应用场景:常用于一些通信协议和外设接口,如 SPI 总线的某些引脚、I2C 总线等,将 GPIO 引脚复用为外设的特定功能。
2.5.8. 复用推挽输出模式(Alternate Function Push - Pull Output)
- 原理:此模式下,GPIO 引脚的功能由片上外设控制,输出级采用推挽输出结构,能够直接输出高电平和低电平。
- 应用场景:常用于一些需要较强驱动能力的外设接口,如 UART 通信的发送引脚、PWM 信号输出等。
根据当前 LED 电路原理图分析,和需求的 GPIO 功能分析。当前对应 GPIO PB5 和 PE5 需要设置为推挽输出模式。

2.6 时钟使能【小重点】
时钟是当前 MCU 的执行能力,主要的核心参数/内容
- 时钟频率:一般都是 MCU 和外部晶振提供,作为 MCU 处理任务的核心时钟参数,当前 STM32F103ZET6 芯片时钟是 72 MHz。芯片的运算速度可以认为是 13.88ns 时间周期执行一次计算。
- 时钟树:当前 MCU 内部的电路设计,将 MCU 的计算器能力提供多个时钟提供端口,将 MCU 执行能力提供给不同的功能模块,每一个模块都有固定的内部时钟管道。
- 类似于水厂/热力公司,根据不同的区域管道,提供自来水/热力。

2.7 寄存器开发模式【重点】
根据 STM32 内核提供的标准库函数,利用寄存器方式对模块进行配置和使用。
寄存器就类似于拨码开关,根据官方要求和限制,需要提供拨码开关的不同形式来进行配置开发。
2.7.1 时钟使能,对应寄存器 RCC
RCC ==> Reset & Clock Control
根据原理分析引脚关系和时钟树分析对应时钟分配情况,当前需要通过 RCC 控制 APB2 使能(Enable) GPIOB 和 GPIOE 两个 GPIO 组时钟,从而满足执行操作。


需要提供给 RCC_APB2ENR 寄存器数据可以采用两种方式,分别为
- 方式一: 直接赋值给寄存器数据
0x0000 0048- 方式二:
1RCC->APB2ENR |= (0x01 << 6); // 使能 GPIOE 2RCC->APB2ENR |= (0x01 << 3); // 使能 GPIOB
2.7.2 GPIO 对应引脚配置
根据原理图分析,对应引脚是 PB5 和 PE5,对应工作模式为 GPIO 推挽输出模式。利用 GPIO 寄存器对当前的 IO 引脚进行控制。
在 STM32 中的一组 GPIO 有 16 个 IO 口。内核将 16 个 IO 分为高低两组
- 高位寄存器 8 ~ 15
- 低位寄存器 0 ~ 7
因为多组 GPIO,在底层寄存器中,利用 GPIOx 提供不同的分组 GPIO 控制。例如 GPIOA,GPIOB…


当前 GPIOB ==> PB5 和 GPIOE ==> PE5 所需的工作模式为
- 通用推挽输出模式
需要对当前寄存器中的 CNF5 和 MODE5 进行组合配置,可以提供数据为
- 0001 ==> 推挽输出模式,速度 10 MHz
- 0011 ==> 推挽输出模式,速度 50 MHz
- 0010 ==> 推挽输出模式,速度 2 MHz
1GPIOB->CRL |= (0x03 << 20); // GPIOB 组中 PB5 引脚配置为通用推挽输出模式,速度 50 MHz 2GPIOE->CRL |= (0x03 << 20); // GPIOE 组中 PE5 引脚配置为通用推挽输出模式,速度 50 MHz 3
2.7.3 GPIO 引脚输出高低电平配置
通过 ODR 配置输出电平

- LED 亮,GPIO对应就为低电平
1GPIOB->ODR &= ~(0x01 << 5); 2GPIOE->ODR &= ~(0x01 << 5);
- LED 灭,GPIO对应就为高电平
1GPIOB->ODR |= (0x01 << 5); 2GPIOE->ODR |= (0x01 << 5);
2.7.4 代码实现
1#include "stm32f10x.h" 2/* 3STM32 核心头文件,没有当前头文件无法完成项目开发!!! 4stm32f10x.h 对应芯片系列为 5 F10x ==> F101 F102 F103 F105 F107.... 6*/ 7 8int main(void) 9{ 10 /* 11 1. 时钟使能 12 需要将 MCU 时钟提供给 GPIOB 和 GPIOE 13 对应寄存器是 RCC->APB2ENR 14 */ 15 RCC->APB2ENR |= (0x01 << 3); // 使能 GPIOB IO组 16 RCC->APB2ENR |= (0x01 << 6); // 使能 GPIOE IO组 17 18 /* 19 2. 对应 GPIO 引脚功能配置,需要对 PB5 和 PE5 引脚统一配置为 20 【通用推挽输出模式】 21 对应寄存器是 GPIOx->CRL 22 23 GPIOB->CRL &= ~(0x0F << 20); 24 GPIOB->CRL 32位寄存器 0100 0100 【????】 0100 0100 0100 0100 0100 25 0x0F ==> 0000 1111 执行左移 20 位 26 0000 1111 0000 0000 0000 0000 0000 27 ~(0x0F << 20) 28 1111 0000 1111 1111 1111 1111 1111 29 30 GPIOB->CRL &= ~(0x0F << 20); 31 0100 0100 ???? 0100 0100 0100 0100 0100 32 & 1111 0000 1111 1111 1111 1111 1111 33 0100 0100 0000 0100 0100 0100 0100 0100 34 */ 35 // PB5 配置 36 GPIOB->CRL &= ~(0x0F << 20); 37 GPIOB->CRL |= (0x03 << 20); // 0011 ==> 通用推挽输出模式,速度 50 MHz 38 39 // PE5 配置 40 GPIOE->CRL &= ~(0x0F << 20); 41 GPIOE->CRL |= (0x03 << 20); // 0011 ==> 通用推挽输出模式,速度 50 MHz 42 43 /* 44 3. 控制 GPIOx_ODR 寄存器,配置高低电平 45 高电平 ==> LED 灭 46 低电平 ==> LED 亮 47 */ 48 49 while (1) 50 { 51 // LED0 亮 LED1 灭 52 GPIOB->ODR &= ~(0x01 << 5); 53 GPIOE->ODR |= (0x01 << 5); 54 55 for (u32 i = 0; i < 10000000; i++) {} 56 57 GPIOB->ODR |= (0x01 << 5); 58 GPIOE->ODR &= ~(0x01 << 5); 59 for (u32 i = 0; i < 10000000; i++) {} 60 } 61} 62
2.7.5 程序烧录和重启
编译

烧录

连接方式

编译和烧录成功提示


重启

3. GPIO 控制案例
3.1 Beep 蜂鸣器控制
3.1.1 BEEP 原理图分析

3.1.2 代码实现
- 时钟使能,通过 RCC 对应 APB2ENR 控制 GPIOB 组时钟使能。
- 利用 GPIOx 中的 CRH 控制 PB8 引脚工作状态
- 控制 GPIOx 中的 ODR 寄存器,控制 MCU PB8 对外输出高低电平情况。

1#include "stm32f10x.h" 2 3int main(void) 4{ 5 // 1. GPIOB 对应 GPIO 组时钟使能 6 RCC->APB2ENR |= (0x01 << 3); 7 8 /* 9 2. 配置 GPIOB CRH 寄存器,控制 PB8 引脚的工作模式 10 所需工作模式为 【通用推挽输出模式】 11 */ 12 GPIOB->CRH &= ~(0x0F); // 清除对应 CNF8 MODE8 原本状态。 13 GPIOB->CRH |= 0x03; // PB8 对应通用推挽模式,速度 50 MHz 14 15 /* 16 3. 利用 ODR 控制高低电平,满足 BEEP 工作 17 高电平 ==> BEEP 工作 18 低电平 ==> BEEP 停止工作 19 */ 20 21 while (1) 22 { 23 GPIOB->ODR |= (0x01 << 8); 24 for (u32 i = 0; i < 10000000; i++) {} 25 26 GPIOB->ODR &= ~(0x01 << 8); 27 for (u32 i = 0; i < 10000000; i++) {} 28 } 29} 30
3.2 多文件编程
需要将开发中使用的设备进行模块化处理,方便后续组装 or 二开。一般情况下,模块内容都是对应
.h和.c。代码模块名称对应当前模块名,例如 led, beep
流程
- 新建文件添加到 Keil 项目中
- 新建文件和保存

新文件配置添加到 Keil 项目中

目标文件选中

多文件案例
led.h
1#ifndef _LED_H 2#define _LED_H 3 4#include "stm32f10x.h" 5 6// RCC 对应时钟使能控制寄存器标志位 7#define GPIOB_RCC_APB2_CLOCK_ENABLE (0x01 << 3) 8#define GPIOE_RCC_APB2_CLOCK_ENABLE (0x01 << 6) 9 10// GPIOP 推挽输出模式,50 MHz 预设宏 11#define Push_Pull_Out_50MHz (0x03) 12 13/** 14* @brief 当前 STM32F103ZET6 开发板对应 LED0 和 LED1 配置 15* LED0 和 LED1 对应 GPIO 引脚,配置为【通用推挽输出模式】 16*/ 17void Led_Init(void); 18 19/** 20* @brief LED0 控制函数,根据提供的外部参数,控制当前 LED 亮灭 21* 22* @param flag flag 为 1 对应 LED0 亮,0 对应 LED0 熄灭 23*/ 24void Led0_Ctrl(u8 flag); 25 26/** 27* @brief LED1 控制函数,根据提供的外部参数,控制当前 LED 亮灭 28* 29* @param flag flag 为 1 对应 LED1 亮,0 对应 LED1 熄灭 30*/ 31void Led1_Ctrl(u8 flag); 32 33#endif 34 35
led.c
1#include "led.h" 2 3void Led_Init(void) 4{ 5 // 1. LED0 和 LED1 对应 GPIO 时钟使能 6 RCC->APB2ENR |= GPIOB_RCC_APB2_CLOCK_ENABLE 7 | GPIOE_RCC_APB2_CLOCK_ENABLE; 8 9 // 2. PB5 和 PE5 通用推挽输出模式。速度 50 MHz 10 GPIOB->CRL &= ~(0x0F << 20); 11 GPIOB->CRL |= (Push_Pull_Out_50MHz << 20); 12 13 GPIOE->CRL &= ~(0x0F << 20); 14 GPIOE->CRL |= (Push_Pull_Out_50MHz << 20); 15} 16 17void Led0_Ctrl(u8 flag) 18{ 19 // 根据 Flag 控制当前 LED 工作情况 20 21 /* 22 【注意】 LED 要求 IO 引脚输出低电平灯亮,高电平灯灭 23 */ 24 if (flag) 25 { 26 GPIOB->ODR &= ~(0x01 << 5); 27 } 28 else 29 { 30 GPIOB->ODR |= (0x01 << 5); 31 } 32} 33 34void Led1_Ctrl(u8 flag) 35{ 36 if (flag) 37 { 38 GPIOE->ODR &= ~(0x01 << 5); 39 } 40 else 41 { 42 GPIOE->ODR |= (0x01 << 5); 43 } 44} 45
3.3 非精准延时控制函数
利用 _NOP 函数实现。非精准延时控制
因为当前 STM32F103ZET6 对应时钟频率为 72MHz,MCU 执行一次任务时间为 13.88ns,利用特征实现【us 单位延时】和【ms 单位延时】控制。
_NOP 函数 当前 MCU 空闲一个执行任务周期,对应时间为 13.88889 ns。如果一个函数执行 72 次**_NOP 函数** ,可以任务当前函数的执行时间为 13.88889 ns * 72 == 1 us
delay.h
1#ifndef _DELAY_H 2#define _DELAY_H 3 4#include "stm32f10x.h" 5 6/** 7* @brief 延时控制函数,对应的控制单位为 us 微秒 8* 9* @param us 延时控制微秒数 10*/ 11void Delay_us(u32 us); 12 13/** 14* @brief 延时控制函数,对应的控制单位为 ms 毫秒 15* 16* @param ms 延时控制毫秒数 17*/ 18void Delay_ms(u32 ms); 19 20#endif 21
delay.c
1#include "delay.h" 2 3void Delay_us(u32 us) 4{ 5 /* 6 根据 MCU 执行特征分析,任何一个任务执行都需要消耗周期时间 7 while 循环控制和 us-- 实际上也存在一定的 MCU 执行周期占用 8 当前延时控制【非精准控制】,存在一定的误差范围。 9 */ 10 while (us--) { 11 /* 12 一个 __NOP 函数占用 MCU 一次执行任务周期,对应时间为 13.8888 ns 13 当前 Delay_us 单位控制对应 72 个 __NOP 函数,执行时间可以认为是 1 us 14 */ 15 __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); 16 __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); 17 __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); 18 __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); 19 __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); 20 __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); 21 __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP(); 22 __NOP();__NOP(); 23 } 24} 25 26void Delay_ms(u32 ms) 27{ 28 Delay_us(ms * 1000); 29} 30
3.4 Key 按键控制
3.4.1 原理图分析



- 根据原理分析
- KEY0 KEY1 KEY2 按键按下之后对应引脚电平为【低电平】,如果需要根据按键的电平信号进行 MCU 控制和操作,要求对应 MCU IO 引脚默认电平为【高电平】。
* KEY0 ==> PE4 KEY1 ==> PE3 KEY0 ==> PE2
* PE2 PE3 PE4 要求 GPIO 的工作模式为输入模式。
* 同时为了满足电平切换可参考,可提取信息,对应为【上拉输入模式 高电平】- KEY_UP Or WK_UP 按键按下之后对应引脚电平为【高电平】,如果需要根据按键的电平信号进行 MCU 控制和操作,要求对应 MCU IO 引脚默认电平为【低电平】。
* KEY_UP Or WK_UP ==> PA0
* PA0 GPIO 的工作模式为输入模式。
* 同时为了满足电平切换可参考,可提取信息,对应为【下拉输入模式 低电平】- 代码流程分析
- 时钟使能【GPIOA】和【GPIOE】,通过 RCC 中的 APB2ENR 进行控制
- 对应 PE2 PE3 PE4 配置为上拉输入模式
- 对应 PA0 配置为下拉输入模式
- 按键输入电平读取操作
3.4.2 开发流程
时钟使能

GPIO 配置
- PA0 PE2 PE3 PE4 都是上拉/下拉输入模式
- 具体上拉还是下拉,需要使用 ODR 寄存器配置输出高低电平,决定当时输入工作模式为上拉还是下拉
- ODR = 1 ==> 上拉输入 ODR = 0 ==> 下拉输入

利用 ODR 寄存器配置高低电平,决定当前为上拉输入还是下拉输入

外部 KEY 按键之后,IO 口输入电平读取,读取 GPIOx_IDR 寄出去 (Input Data Register)

3.4.3 代码实现
key.h
1#ifndef _KEY_H 2#define _KEY_H 3 4#include "stm32f10x.h" 5 6#include "delay.h" 7 8#define GPIOA_RCC_APB2_CLOCK_ENABLE (0x01 << 2) 9#define GPIOE_RCC_APB2_CLOCK_ENABLE (0x01 << 6) 10 11#define Pull_Up_Or_Down_Input (0x08) 12 13// 利用枚举类型描述按键按键标记数据 14typedef enum key_value 15{ 16 KEY_0_VALUE, 17 KEY_1_VALUE, 18 KEY_2_VALUE, 19 KEY_UP_VALUE, 20 NO_KEY_PRESSED 21} GL_Key_Value; 22 23/** 24* @brief 当前开发板中按键初始化函数,配置 PA0 PE2 PE3 PE4 25* 所需的 GPIO 工作模式 26* PA0 ==> KEY_UP or WK_UP 下拉输入 27* PE2 ==> KEY2 上拉输入 28* PE3 ==> KEY1 上拉输入 29* PE4 ==> KEY0 上拉输入 30*/ 31void Key_Init(void); 32 33/** 34* @brief 获取当前开发板中,哪一个按键被按下 35* 36* @return 返回值是对应按键被按下的标记数据,对应类型为枚举 GL_Key_Value 37* 类型 38*/ 39u8 Key_GetValue(void); 40 41#endif 42
key.c
1#include "key.h" 2 3void Key_Init(void) 4{ 5 // 1. 时钟使能,GPIOA 和 GPIOE 6 RCC->APB2ENR |= GPIOA_RCC_APB2_CLOCK_ENABLE 7 | GPIOE_RCC_APB2_CLOCK_ENABLE; 8 9 /* 10 2. GPIO 配置 11 */ 12 /* 13 2.1 PA0 下拉输入配置 14 */ 15 GPIOA->CRL &= ~(0x0F); 16 GPIOA->CRL |= Pull_Up_Or_Down_Input; 17 GPIOA->ODR &= ~(0x01); // 利用 ODR 低电平明确限制下拉输入 18 19 /* 20 2.2 PE2 PE3 PE4 上拉输入配置 21 */ 22 GPIOE->CRL &= ~(0x0FFF << 8); 23 GPIOE->CRL |= (Pull_Up_Or_Down_Input << 16) 24 | (Pull_Up_Or_Down_Input << 12) 25 | (Pull_Up_Or_Down_Input << 8); 26 GPIOE->ODR |= (0x07 << 2); // 利用 ODR 高电平明确限制上拉输入 27} 28 29u8 Key_GetValue(void) 30{ 31 /* 32 KEY0 【上拉输入模式 默认高电平】按键判断是否按下 33 KEY0 ==> PE4 引脚 34 0x01 << 4 ==> 0000 0001 << 4 35 0001 0000 36 GPIOE->IDR & 0001 0000, 37 如果结果为 0 表示,对应 IDR4 位置为 0,按键已按下。 38 如果结果不为 0,表示 IDR4 位置为 1,按键尚未按下 39 */ 40 if (0 == (GPIOE->IDR & (0x01 << 4))) 41 { 42 // 消抖 43 Delay_ms(10); 44 45 if (0 == (GPIOE->IDR & (0x01 << 4))) 46 { 47 return KEY_0_VALUE; 48 } 49 } 50 51 // KEY1 按键对应 PE3 引脚,操作同理 KEY0 52 if (0 == (GPIOE->IDR & (0x01 << 3))) 53 { 54 // 消抖 55 Delay_ms(10); 56 57 if (0 == (GPIOE->IDR & (0x01 << 3))) 58 { 59 return KEY_1_VALUE; 60 } 61 } 62 63 // KEY2 按键对应 PE2 引脚,操作同理 KEY0 64 if (0 == (GPIOE->IDR & (0x01 << 2))) 65 { 66 // 消抖 67 Delay_ms(10); 68 69 if (0 == (GPIOE->IDR & (0x01 << 2))) 70 { 71 return KEY_2_VALUE; 72 } 73 } 74 75 /* 76 KEY_UP or WK_UP 按键对应 PA0 【下拉输入模式 默认低电平】 77 GPIOA->IDR GPIOA 组输入电平高低寄存器。 78 PA0 对应 IDR0 79 GPIOA->IDR & 0x01 80 如果 KEY_UP 按下,对应 IDR0 为 1,当前可以简化代码 & 操作为 81 0x01 & 0x01 ==> if 可以执行 82 如果 KEY_UP 未按下,对应 IDR0 为 0,当前可以简化代码 & 操作为 83 0x00 & 0x01 ==> 不满足 if 条件判断。 84 */ 85 if (GPIOA->IDR & 0x01) 86 { 87 // 消抖 88 Delay_ms(10); 89 if (GPIOA->IDR & 0x01) 90 { 91 return KEY_UP_VALUE; 92 } 93 } 94 95 /* 96 以上所有按键都未按下,返回 NO_KEY_PRESSED 告知外部。 97 */ 98 return NO_KEY_PRESSED; 99} 100 101
测试 main 函数代码
1#include "stm32f10x.h" 2 3#include "stdio.h" 4#include "stdlib.h" 5#include "string.h" 6 7#include "led.h" 8#include "beep.h" 9#include "delay.h" 10#include "key.h" 11 12int main(void) 13{ 14 // LED 模块初始化,完成时钟使能和 GPIO 配置 15 Led_Init(); 16 // BEEP 模块初始化 17 Beep_Init(); 18 // KEY 模块初始化 19 Key_Init(); 20 21 u8 key_value = 0; 22 23 // u8 flag = 1; 24 25 while (1) 26 { 27 key_value = Key_GetValue(); 28 29 if (KEY_0_VALUE == key_value) 30 { 31 Led0_Ctrl(1); 32 } 33 else if (KEY_1_VALUE == key_value) 34 { 35 Led0_Ctrl(0); 36 } 37 else if (KEY_2_VALUE == key_value) 38 { 39 Beep_Ctrl(1); 40 } 41 else if (KEY_UP_VALUE == key_value) 42 { 43 Beep_Ctrl(0); 44 } 45 46 Delay_ms(10); 47 } 48} 49
《STM32学习(MCU控制)(GPIO)》 是转载文章,点击查看原文。
