STM32学习(MCU控制)(GPIO)
文章目录
- 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 ℃
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
方式二:
RCC->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
GPIOB->CRL |= (0x03 << 20); // GPIOB 组中 PB5 引脚配置为通用推挽输出模式,速度 50 MHz GPIOE->CRL |= (0x03 << 20); // GPIOE 组中 PE5 引脚配置为通用推挽输出模式,速度 50 MHz
2.7.3 GPIO 引脚输出高低电平配置
通过 ODR 配置输出电平
LED 亮,GPIO对应就为低电平
GPIOB->ODR &= ~(0x01 << 5); GPIOE->ODR &= ~(0x01 << 5);
LED 灭,GPIO对应就为高电平
GPIOB->ODR |= (0x01 << 5); GPIOE->ODR |= (0x01 << 5);
2.7.4 代码实现
#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->CRLGPIOB->CRL &= ~(0x0F << 20);GPIOB->CRL 32位寄存器 0100 0100 【????】 0100 0100 0100 0100 01000x0F ==> 0000 1111 执行左移 20 位0000 1111 0000 0000 0000 0000 0000~(0x0F << 20)1111 0000 1111 1111 1111 1111 1111GPIOB->CRL &= ~(0x0F << 20);0100 0100 ???? 0100 0100 0100 0100 0100& 1111 0000 1111 1111 1111 1111 11110100 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 对外输出高低电平情况。
#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
#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
#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 MHzGPIOB->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
#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
#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
#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
#include "key.h"void Key_Init(void)
{// 1. 时钟使能,GPIOA 和 GPIOERCC->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 0000GPIOE->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 引脚,操作同理 KEY0if (0 == (GPIOE->IDR & (0x01 << 3))){// 消抖Delay_ms(10);if (0 == (GPIOE->IDR & (0x01 << 3))){return KEY_1_VALUE;}}// KEY2 按键对应 PE2 引脚,操作同理 KEY0if (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 对应 IDR0GPIOA->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 函数代码
#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);}
}