STM32学习(MCU控制)(GPIO)

作者:D.....l日期:2025/10/22

文章目录

    • 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 代码实现

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 ℃

stm32编号规则

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 需要设置为推挽输出模式。

led概述

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寄存器

需要提供给 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…

gpio

当前 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 配置输出电平

14-GPIOx_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)》 是转载文章,点击查看原文


相关推荐


【Node】认识multer库
你的人类朋友2025/10/21

前言 在 Node.js 中处理文件上传是一个常见的需求,比如用户上传头像或文档。 那么,如何简单、安全地接收并保存这些文件呢? 本文说说 multer 的库,它可以帮助我们快速实现这一功能。 小问题:为什么使用 multer 而不是 Node.js 原生模块来处理文件上传。 🤔 补充知识:原生模块如何实现文件上传? 使用 Node.js 原生模块实现文件上传需要手动解析 multipart/form-data 格式的请求体,处理数据流并识别边界符。 然后逐个字段提取文件内容和元数据,最后通


ip rule 策略路由
疯狂吧小飞牛2025/10/20

原文地址:ip rule 策略路由 欢迎参观我的网站:无敌牛 – 技术/著作/典籍/分享等 ip rule 是 Linux 策略路由(Policy-based Routing, PBR) 的核心命令,用于控制内核在查找路由时使用哪张路由表。 一、ip rule 基本语法 ip rule [list|add|del] [selectors] [action] selectors:匹配条件(如源 IP、入接口等)action:匹配后执行的动作(最常见的是 table N,即使用路由表


【汽车篇】AI深度学习在汽车轮胎X-ray缺陷检测应用方案
Dongsheng_20192025/10/19

一、行业痛点 轮胎安全=生命安全 起鼓、胎侧凹陷、开裂、微孔、气泡等内部缺陷肉眼不可见,传统人工敲听+抽检: 漏检率 2%–5%,一旦流出即面临召回及高额索赔; 节拍慢,单胎平均 45 s,无法满足 70 JPH 下线节奏; 结果无数据留存,难以追溯工艺根因。 二、技术方案 东声智能“AI+X-ray”在线检测系统,将穿透成像与深度学习合二为一,为轮胎内部质量打造零缺陷防线。 1.缺陷定位算法: 3D 模块精准标定起鼓、凹陷、气泡等 12 类缺陷中心坐标。 2.双擎决策:传统算法分


苦练Python第67天:光速读取任意行,linecache模块解锁文件处理新姿势
倔强青铜三 VIP.1 初学乍练2025/10/17

前言 大家好,我是 倔强青铜三。欢迎关注我,微信公众号: 倔强青铜三。点赞、收藏、关注,一键三连! 今天咱们把 Python 自带的“光速行读取器”—— linecache 模块,从开箱到实战一次性讲透。 一、为什么需要 linecache? 秒级随意读任意行:再也不用 for i, line in enumerate(f) 数行号。 内存缓存机制:同一个文件多次读取,只加载一次。 源码级调试神器:traceback、pdb 都在用它。 零依赖:官方出品,随 Python 一起安装。 li


Redis(65)如何优化Redis的AOF持久化?
Victor3562025/10/16

优化 Redis 的 AOF(Append-Only File)持久化机制可以提高数据持久化的效率和性能,并且能够确保数据的安全性和一致性。以下是关于如何优化 Redis AOF 持久化的详细指南,包括操作配置、代码示例及具体步骤。 1. AOF 持久化配置 Redis 提供了多个配置参数来调整 AOF 的行为,主要包括: appendonly appendfsync auto-aof-rewrite-percentage auto-aof-rewrite-min-size aof-load-


C#/.NET/.NET Core技术前沿周刊 | 第 57 期(2025年10.1-10.12)
追逐时光者2025/10/14

前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。 欢迎投稿、推荐或自荐优质文章、项目、学习资源等。 🏆技术前沿周刊Gitee开源地址: gitee.com/ysgdaydayup… 📰技术前沿周刊GitHub开源地址: github.com/YSGStudyHar… 👪DotNetGuid


优选算法-二分:19.搜索插入位置
CoderYanger2025/10/13

题目链接:35.搜索插入位置(简单) 算法思路: 二分:二分查找该元素 没找到就返回最后一次查找的左边界下标(即为插入位置) Java代码: /** * Created with IntelliJ IDEA. * Description: * User: 王洋 * Date: 2025-08-28 * Time: 09:24 */ class Solution { //35. 搜索插入位置 /*给定一个排序数组和一个目标值,在数组中找到目标值,


RabbitMQ核心机制
00后程序员张2025/10/11

MQ 概述 MQ,消息队列,一种在分布式系统中用于通信的关键组件 本质上是一个队列,遵循 FIFO(先入先出)原则,队列中存储的内容是消息(message) 消息可以非常简单,比如只包含文本字符串或 JSON 数据,也可以很复杂,如内嵌对象。MQ 主要用于分布式系统之间的通信,解决数据传递的效率和可靠性问题 1.2 系统间通信方式 在分布式系统中,系统之间的调用通常有两种方式: 1.同步通信: 直接调用对方的服务,数据从一端发出后立即到达另一端。这种方式响应快,但可能导致调用方阻塞,尤


还在纠结用v-if还是v-show?看完这篇彻底搞懂Vue渲染机制!
良山有风来2025/10/10

你是不是也曾经在写Vue时纠结过:这里到底该用v-if还是v-show? 或者更惨的是,明明代码逻辑没问题,列表渲染却总是出现各种诡异bug:删除一个项,结果删错了;切换数据,页面状态全乱了... 别担心,今天我就来帮你彻底搞懂Vue的条件渲染和列表渲染,让你写出更优雅、更高效的代码! v-if和v-show:看似相似,实则大不相同 先来看个最简单的例子: <!-- v-if 的用法 --> <div v-if="isVisible">我会在条件为真时渲染</div> <!-- v-show


JavaScript性能优化实战:从指标到落地的全链路方案
weixin_439647792025/10/8

JavaScript性能优化实战:从指标到落地的全链路方案 实际项目中,性能优化往往不是单一手段的应用,而是“指标监测-瓶颈定位-方案实施-效果验证”的全链路过程。本文将结合电商、管理系统等真实场景,提供可落地的性能优化闭环方案。 一、性能指标体系:从“感觉卡顿”到“数据说话” 性能优化的第一步是建立可量化的指标体系,避免凭主观感受判断优化效果。前端核心性能指标可分为三类: 1. 加载性能指标 LCP(最大内容绘制):衡量首屏加载速度,目标值<2.5sTTI(交互时间):页面可完全交互

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0