文章目录
-
- [MCU 和 GPIO](#MCU 和 GPIO)
-
-
- [1. 单片机 MCU](#1. 单片机 MCU)
-
- [1.1 单片机和嵌入式系统](#1.1 单片机和嵌入式系统)
- [1.2 MCU](#1.2 MCU)
- [1.3 ARM 公司](#1.3 ARM 公司)
- [1.4 市场主流 32 芯片](#1.4 市场主流 32 芯片)
- [1.5 STM32 开发版概述](#1.5 STM32 开发版概述)
- [2. GPIO](#2. GPIO)
-
- [2.1 GPIO 概述](#2.1 GPIO 概述)
- [2.2 STM32F103ZET6 GPIO 相关内容](#2.2 STM32F103ZET6 GPIO 相关内容)
- [2.3 GPIO 开发流程](#2.3 GPIO 开发流程)
- [2.4 GPIO 控制 LED 灯](#2.4 GPIO 控制 LED 灯)
- [2.5 GPIO 端口内部基本电路情况](#2.5 GPIO 端口内部基本电路情况)
-
- [**2.5.1. 浮空输入模式(Floating Input)**](#2.5.1. 浮空输入模式(Floating Input))
- [**2.5.2. 上拉输入模式(Pull - up Input)**](#2.5.2. 上拉输入模式(Pull - up Input))
- [**2.5.3. 下拉输入模式(Pull - down Input)**](#2.5.3. 下拉输入模式(Pull - down Input))
- [**2.5.4. 模拟输入模式(Analog Input)**](#2.5.4. 模拟输入模式(Analog Input))
- [**2.5.5. 开漏输出模式(Open - Drain Output)**](#2.5.5. 开漏输出模式(Open - Drain Output))
- [**2.5.6. 推挽输出模式(Push - Pull Output)**](#2.5.6. 推挽输出模式(Push - Pull Output))
- [**2.5.7. 复用开漏输出模式(Alternate Function Open - Drain Output)**](#2.5.7. 复用开漏输出模式(Alternate Function Open - Drain Output))
- [**2.5.8. 复用推挽输出模式(Alternate Function Push - Pull Output)**](#2.5.8. 复用推挽输出模式(Alternate Function Push - Pull Output))
- [2.6 时钟使能【小重点】](#2.6 时钟使能【小重点】)
- [**2.7 寄存器开发模式【重点】**](#2.7 寄存器开发模式【重点】)
-
- [2.7.1 时钟使能,对应寄存器 RCC](#2.7.1 时钟使能,对应寄存器 RCC)
- [2.7.2 GPIO 对应引脚配置](#2.7.2 GPIO 对应引脚配置)
- [2.7.3 GPIO 引脚输出高低电平配置](#2.7.3 GPIO 引脚输出高低电平配置)
- [2.7.4 代码实现](#2.7.4 代码实现)
- [2.7.5 程序烧录和重启](#2.7.5 程序烧录和重启)
- [3. GPIO 控制案例](#3. GPIO 控制案例)
-
- [3.1 Beep 蜂鸣器控制](#3.1 Beep 蜂鸣器控制)
-
- [3.1.1 BEEP 原理图分析](#3.1.1 BEEP 原理图分析)
- [3.1.2 代码实现](#3.1.2 代码实现)
- [3.2 多文件编程](#3.2 多文件编程)
- [3.3 非精准延时控制函数](#3.3 非精准延时控制函数)
- [3.4 Key 按键控制](#3.4 Key 按键控制)
-
- [3.4.1 原理图分析](#3.4.1 原理图分析)
- [3.4.2 开发流程](#3.4.2 开发流程)
- [3.4.3 代码实现](#3.4.3 代码实现)
-
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
方式二:
cRCC->APB2ENR |= (0x01 << 6); // 使能 GPIOE RCC->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
cGPIOB->CRL |= (0x03 << 20); // GPIOB 组中 PB5 引脚配置为通用推挽输出模式,速度 50 MHz GPIOE->CRL |= (0x03 << 20); // GPIOE 组中 PE5 引脚配置为通用推挽输出模式,速度 50 MHz
2.7.3 GPIO 引脚输出高低电平配置
通过 ODR 配置输出电平

LED 亮,GPIO对应就为低电平
cGPIOB->ODR &= ~(0x01 << 5); GPIOE->ODR &= ~(0x01 << 5);
LED 灭,GPIO对应就为高电平
cGPIOB->ODR |= (0x01 << 5); GPIOE->ODR |= (0x01 << 5);
2.7.4 代码实现
c
#include "stm32f10x.h"
/*
STM32 核心头文件,没有当前头文件无法完成项目开发!!!
stm32f10x.h 对应芯片系列为
F10x ==> F101 F102 F103 F105 F107....
*/
int main(void)
{
/*
1. 时钟使能
需要将 MCU 时钟提供给 GPIOB 和 GPIOE
对应寄存器是 RCC->APB2ENR
*/
RCC->APB2ENR |= (0x01 << 3); // 使能 GPIOB IO组
RCC->APB2ENR |= (0x01 << 6); // 使能 GPIOE IO组
/*
2. 对应 GPIO 引脚功能配置,需要对 PB5 和 PE5 引脚统一配置为
【通用推挽输出模式】
对应寄存器是 GPIOx->CRL
GPIOB->CRL &= ~(0x0F << 20);
GPIOB->CRL 32位寄存器 0100 0100 【????】 0100 0100 0100 0100 0100
0x0F ==> 0000 1111 执行左移 20 位
0000 1111 0000 0000 0000 0000 0000
~(0x0F << 20)
1111 0000 1111 1111 1111 1111 1111
GPIOB->CRL &= ~(0x0F << 20);
0100 0100 ???? 0100 0100 0100 0100 0100
& 1111 0000 1111 1111 1111 1111 1111
0100 0100 0000 0100 0100 0100 0100 0100
*/
// PB5 配置
GPIOB->CRL &= ~(0x0F << 20);
GPIOB->CRL |= (0x03 << 20); // 0011 ==> 通用推挽输出模式,速度 50 MHz
// PE5 配置
GPIOE->CRL &= ~(0x0F << 20);
GPIOE->CRL |= (0x03 << 20); // 0011 ==> 通用推挽输出模式,速度 50 MHz
/*
3. 控制 GPIOx_ODR 寄存器,配置高低电平
高电平 ==> LED 灭
低电平 ==> LED 亮
*/
while (1)
{
// LED0 亮 LED1 灭
GPIOB->ODR &= ~(0x01 << 5);
GPIOE->ODR |= (0x01 << 5);
for (u32 i = 0; i < 10000000; i++) {}
GPIOB->ODR |= (0x01 << 5);
GPIOE->ODR &= ~(0x01 << 5);
for (u32 i = 0; i < 10000000; i++) {}
}
}
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 对外输出高低电平情况。

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

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

目标文件选中

多文件案例
led.h
h
#ifndef _LED_H
#define _LED_H
#include "stm32f10x.h"
// RCC 对应时钟使能控制寄存器标志位
#define GPIOB_RCC_APB2_CLOCK_ENABLE (0x01 << 3)
#define GPIOE_RCC_APB2_CLOCK_ENABLE (0x01 << 6)
// GPIOP 推挽输出模式,50 MHz 预设宏
#define Push_Pull_Out_50MHz (0x03)
/**
* @brief 当前 STM32F103ZET6 开发板对应 LED0 和 LED1 配置
* LED0 和 LED1 对应 GPIO 引脚,配置为【通用推挽输出模式】
*/
void Led_Init(void);
/**
* @brief LED0 控制函数,根据提供的外部参数,控制当前 LED 亮灭
*
* @param flag flag 为 1 对应 LED0 亮,0 对应 LED0 熄灭
*/
void Led0_Ctrl(u8 flag);
/**
* @brief LED1 控制函数,根据提供的外部参数,控制当前 LED 亮灭
*
* @param flag flag 为 1 对应 LED1 亮,0 对应 LED1 熄灭
*/
void Led1_Ctrl(u8 flag);
#endif
led.c
c
#include "led.h"
void Led_Init(void)
{
// 1. LED0 和 LED1 对应 GPIO 时钟使能
RCC->APB2ENR |= GPIOB_RCC_APB2_CLOCK_ENABLE
| GPIOE_RCC_APB2_CLOCK_ENABLE;
// 2. PB5 和 PE5 通用推挽输出模式。速度 50 MHz
GPIOB->CRL &= ~(0x0F << 20);
GPIOB->CRL |= (Push_Pull_Out_50MHz << 20);
GPIOE->CRL &= ~(0x0F << 20);
GPIOE->CRL |= (Push_Pull_Out_50MHz << 20);
}
void Led0_Ctrl(u8 flag)
{
// 根据 Flag 控制当前 LED 工作情况
/*
【注意】 LED 要求 IO 引脚输出低电平灯亮,高电平灯灭
*/
if (flag)
{
GPIOB->ODR &= ~(0x01 << 5);
}
else
{
GPIOB->ODR |= (0x01 << 5);
}
}
void Led1_Ctrl(u8 flag)
{
if (flag)
{
GPIOE->ODR &= ~(0x01 << 5);
}
else
{
GPIOE->ODR |= (0x01 << 5);
}
}
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
h
#ifndef _DELAY_H
#define _DELAY_H
#include "stm32f10x.h"
/**
* @brief 延时控制函数,对应的控制单位为 us 微秒
*
* @param us 延时控制微秒数
*/
void Delay_us(u32 us);
/**
* @brief 延时控制函数,对应的控制单位为 ms 毫秒
*
* @param ms 延时控制毫秒数
*/
void Delay_ms(u32 ms);
#endif
delay.c
c
#include "delay.h"
void Delay_us(u32 us)
{
/*
根据 MCU 执行特征分析,任何一个任务执行都需要消耗周期时间
while 循环控制和 us-- 实际上也存在一定的 MCU 执行周期占用
当前延时控制【非精准控制】,存在一定的误差范围。
*/
while (us--) {
/*
一个 __NOP 函数占用 MCU 一次执行任务周期,对应时间为 13.8888 ns
当前 Delay_us 单位控制对应 72 个 __NOP 函数,执行时间可以认为是 1 us
*/
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();
__NOP();__NOP();
}
}
void Delay_ms(u32 ms)
{
Delay_us(ms * 1000);
}
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
h
#ifndef _KEY_H
#define _KEY_H
#include "stm32f10x.h"
#include "delay.h"
#define GPIOA_RCC_APB2_CLOCK_ENABLE (0x01 << 2)
#define GPIOE_RCC_APB2_CLOCK_ENABLE (0x01 << 6)
#define Pull_Up_Or_Down_Input (0x08)
// 利用枚举类型描述按键按键标记数据
typedef enum key_value
{
KEY_0_VALUE,
KEY_1_VALUE,
KEY_2_VALUE,
KEY_UP_VALUE,
NO_KEY_PRESSED
} GL_Key_Value;
/**
* @brief 当前开发板中按键初始化函数,配置 PA0 PE2 PE3 PE4
* 所需的 GPIO 工作模式
* PA0 ==> KEY_UP or WK_UP 下拉输入
* PE2 ==> KEY2 上拉输入
* PE3 ==> KEY1 上拉输入
* PE4 ==> KEY0 上拉输入
*/
void Key_Init(void);
/**
* @brief 获取当前开发板中,哪一个按键被按下
*
* @return 返回值是对应按键被按下的标记数据,对应类型为枚举 GL_Key_Value
* 类型
*/
u8 Key_GetValue(void);
#endif
key.c
c
#include "key.h"
void Key_Init(void)
{
// 1. 时钟使能,GPIOA 和 GPIOE
RCC->APB2ENR |= GPIOA_RCC_APB2_CLOCK_ENABLE
| GPIOE_RCC_APB2_CLOCK_ENABLE;
/*
2. GPIO 配置
*/
/*
2.1 PA0 下拉输入配置
*/
GPIOA->CRL &= ~(0x0F);
GPIOA->CRL |= Pull_Up_Or_Down_Input;
GPIOA->ODR &= ~(0x01); // 利用 ODR 低电平明确限制下拉输入
/*
2.2 PE2 PE3 PE4 上拉输入配置
*/
GPIOE->CRL &= ~(0x0FFF << 8);
GPIOE->CRL |= (Pull_Up_Or_Down_Input << 16)
| (Pull_Up_Or_Down_Input << 12)
| (Pull_Up_Or_Down_Input << 8);
GPIOE->ODR |= (0x07 << 2); // 利用 ODR 高电平明确限制上拉输入
}
u8 Key_GetValue(void)
{
/*
KEY0 【上拉输入模式 默认高电平】按键判断是否按下
KEY0 ==> PE4 引脚
0x01 << 4 ==> 0000 0001 << 4
0001 0000
GPIOE->IDR & 0001 0000,
如果结果为 0 表示,对应 IDR4 位置为 0,按键已按下。
如果结果不为 0,表示 IDR4 位置为 1,按键尚未按下
*/
if (0 == (GPIOE->IDR & (0x01 << 4)))
{
// 消抖
Delay_ms(10);
if (0 == (GPIOE->IDR & (0x01 << 4)))
{
return KEY_0_VALUE;
}
}
// KEY1 按键对应 PE3 引脚,操作同理 KEY0
if (0 == (GPIOE->IDR & (0x01 << 3)))
{
// 消抖
Delay_ms(10);
if (0 == (GPIOE->IDR & (0x01 << 3)))
{
return KEY_1_VALUE;
}
}
// KEY2 按键对应 PE2 引脚,操作同理 KEY0
if (0 == (GPIOE->IDR & (0x01 << 2)))
{
// 消抖
Delay_ms(10);
if (0 == (GPIOE->IDR & (0x01 << 2)))
{
return KEY_2_VALUE;
}
}
/*
KEY_UP or WK_UP 按键对应 PA0 【下拉输入模式 默认低电平】
GPIOA->IDR GPIOA 组输入电平高低寄存器。
PA0 对应 IDR0
GPIOA->IDR & 0x01
如果 KEY_UP 按下,对应 IDR0 为 1,当前可以简化代码 & 操作为
0x01 & 0x01 ==> if 可以执行
如果 KEY_UP 未按下,对应 IDR0 为 0,当前可以简化代码 & 操作为
0x00 & 0x01 ==> 不满足 if 条件判断。
*/
if (GPIOA->IDR & 0x01)
{
// 消抖
Delay_ms(10);
if (GPIOA->IDR & 0x01)
{
return KEY_UP_VALUE;
}
}
/*
以上所有按键都未按下,返回 NO_KEY_PRESSED 告知外部。
*/
return NO_KEY_PRESSED;
}
测试 main 函数代码
c
#include "stm32f10x.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "led.h"
#include "beep.h"
#include "delay.h"
#include "key.h"
int main(void)
{
// LED 模块初始化,完成时钟使能和 GPIO 配置
Led_Init();
// BEEP 模块初始化
Beep_Init();
// KEY 模块初始化
Key_Init();
u8 key_value = 0;
// u8 flag = 1;
while (1)
{
key_value = Key_GetValue();
if (KEY_0_VALUE == key_value)
{
Led0_Ctrl(1);
}
else if (KEY_1_VALUE == key_value)
{
Led0_Ctrl(0);
}
else if (KEY_2_VALUE == key_value)
{
Beep_Ctrl(1);
}
else if (KEY_UP_VALUE == key_value)
{
Beep_Ctrl(0);
}
Delay_ms(10);
}
}