文章目录
一、GPIO简介
GPIO:General-Purpose Input/Output,即通用输入输出。GPIO 是控制或者采集外部器件的信息的外设,即负责输入输出。
1.1GPIO特点
1,不同型号,IO口数量可能不一样,可通过选型手册快速查询
2,快速翻转,每次翻转最快只需要两个时钟周期(F1最高速度可以到50Mhz)(本单片机72MHZ,翻转最快频率36MHZ)
3,每个IO口都可以做中断(51单片机只有两个IO口可以做中断)
4,支持8种工作模式
1.2GPIO电气特性
1,STM32工作电压范围:2 V ≤ VDD ≤ 3.6 V(正点原子的单片机3.3V)
2,GPIO识别电压范围:COMS端口:-0.3V ≤ VIL ≤ 1.164V(逻辑0) 1.833V ≤ VIH ≤ 3.6V(逻辑1)
3,GPIO输出电流:单个IO,最大25mA
(除了CMOS端口,还有TTL端口,TTL端口除了3.3V还兼容5V,在芯片数据手册中I/O电平标为FT的为TTL端口,否则为CMOS端口)
1.3GPIO引脚分布
它按组分配,每组 16 个 IO 口,组数视芯片而定。STM32F103ZET6 芯片是 144 脚的芯片,具有 GPIOA、GPIOB、GPIOC、 GPIOD、GPIOE、GPIOF 和 GPIOG 七组 GPIO 口,共有 112 个 IO 口可供我们编程使用。
二、GPIO基本结构
F1系列IO端口基本结构:
与F4及其他系列不同,F1的IO端口结构的上拉和下拉电阻在输入驱动器中,这样,使得只有输入才有上下拉电阻,输出没有
① 保护二极管:起保护作用,防止输入的电压过高或过低
② 内部上拉、下拉电阻:30-50kΩ,因电阻值很大,VDD为3.3V,故电流很小,称为弱上拉(下拉)电阻
③ 施密特触发器:施密特触发器就是一种整形电路,可以将非标准方波,整形成方波
当输入电压高于正向阈值电压,输出为高;
当输入电压低于负向阈值电压,输出为低;
当输入在正负向阈值电压之间,输出不改变。
④ P-MOS & N-MOS管:MOS管是压控型元件,通过控制栅源电压( Vgs )来实现导通或关闭
VDD为3.3V(逻辑1),则栅极(G)为0V时,Vgs小于0,P-MOS管导通
VSS为0V(逻辑0),则栅极(G)为3.3V时,Vgs大于0,N-MOS管导通
**输入:**外部信号经IO引脚输入到芯片内部,经过保护二极管保护,且可选择上拉电阻或者下拉电阻,之后有三种输入的目的地,模拟输入(DAC和ADC)和复用功能输入会将信号输入至片上外设,还可以通过输入数据寄存器(IDR)来读出输入的信号
**输出:**输出主要是通过MOS管来执行,输出1(3.3v),P-MOS管导通,输出0,则N-MOS管导通。数据的写入,可以通过写入位设置/清除寄存器(BSRR)进而写入输出数据寄存器(ODR),也可以直接对ODR进行写入(对ODR也可以读出),或者通过复用功能输出来进行数据输入,最后通过二选一选择器来选择。
三、GPIO功能模式
3.1输入浮空
输入用,完全浮空,状态不定
IO 口的电平完全是由外部电路决定。如果 IO 引脚没有连接其他的设备(高阻态),那么检测其输入电平是不确定的。该模式可以用于按键检测等场景。我觉得在数据通信中应该可以使用该模式。应为在数据通信中,我们直观的理解就是线路两端连接着发送端和接收端,他们都需要准确获取对方的信号电平,不需要外界的干预
上拉、下拉电阻关闭,施密特触发器打开,双MOS管不导通
IO引脚输入1,则输出1;IO引脚输入0,则输出0
上拉输入就是在输入电路上使用了上拉电阻。这种模式的好处在于我们什么都不输入时,由于内部上拉电阻的原因,我们的处理器会觉得我们输入了高电平,这就避免了不确定的输入。这在要求输入电平只要高低两种电平的情况下是很有用的。换句话说,当IO口为高阻态时(IO空闲时),由于接了上拉电阻(接了3.3V的VDD),故此时读出的是高电平。
上拉电阻打开、下拉电阻关闭,施密特触发器打开,双MOS管不导通
3.3输入下拉
和上拉输入类似,不过下拉输入时,在外部没有输入时,我们的处理器会觉得我们输入了低电平。换句话说,当IO口为高阻态时(IO空闲时),由于接了下拉电阻(接了VSS(接地)),故此时读出的是低电平。
上拉电阻关闭、下拉电阻打开,施密特触发器打开,双MOS管不导通
3.4模拟输入
输入时不经过输入数据寄存器,所以我们无法通过读取输入数据寄存器来获取模拟输入的值,我觉得这一点也是很好理解的,因为输入数据寄存器中存放的不是0就是1,而模拟输入信号不符合这一要求,所以自然不能放进输入数据寄存器。该输入模式,使我们可以获得外部的模拟信号。此时,只有模拟输入那一条路时通的。此模式专门用于模拟信号输入或输出,如:ADC和DAC
上拉、下拉电阻关闭,施密特触发器关闭,双MOS管不导通
3.5开漏输出
只能通过接芯片外部上拉电阻的方式,实现开漏输出模式下输出高电平。 如果芯片外部不接上拉电阻,那么开漏输出模式下,IO 无法输出高电平。也就是说,正常情况下不能输出高电平,必须有外部(或内部,这个内部上拉是针对更高系列如F4的,F1此模式下是禁止使用内部上、下拉的)上拉才能输出高电平。
上拉、下拉电阻关闭,施密特触发器打开,P-MOS管始终不导通,往ODR对应位写0,N-MOS管导通,写1则N-MOS管不导通。若N-MOS管不导通,而P-MOS管是始终不导通的,就是输出不通了,此时IO口的输出就是高阻态了,此时若芯片外部接一个上拉电阻,IO口才可以正常输出1。
注意:由于施密特触发器打开,故此模式下是可以通过读取IDR寄存器来读取IO引脚的状态的
3.6推挽输出
上拉、下拉电阻关闭,施密特触发器打开,往ODR对应位写0,N-MOS管导通(IO口输出0),写1则P-MOS管导通(IO口输出1)
补充:当往ODR对应位写0时,N-MOS导通,但是N-MOS管的栅极为1时N-MOS管才能导通,说明输出控制里面有个反相器,将输入的0转换为1输出到N-MOS管的栅极
推挽电路速度快,输出(驱动)能力强(因为从MOS管的VDD或VSS到IO口没有电阻,电流大,故驱动能力强),直接输出高电平或者低电平。
注意:与开漏输出模式一样,由于施密特触发器打开,故此模式下是可以通过读取IDR寄存器来读取IO引脚的状态的
3.7开漏复用
该模式是对GPIO的复用
与开漏输出模式一样,不能输出高电平,必须有外部(或内部)上拉才能输出高电平
该模式下,IO口由其他外设控制输出
上拉、下拉电阻关闭,施密特触发器打开,P-MOS管始终不导通,N-MOS管是否导通是由片上外设输出决定的。
注意:与开漏输出模式一样,由于施密特触发器打开,故此模式下是可以通过读取IDR寄存器来读取IO引脚的状态的
3.8推挽复用
该模式是对GPIO的复用
上拉、下拉电阻关闭,施密特触发器打开,P-MOS管和N-MOS管是否导通是由片上外设输出决定的
1、可输出高低电平, 驱动能力强(与推挽输出模式一样)
2、由其他外设控制输出
注意:与开漏输出模式一样,由于施密特触发器打开,故此模式下是可以通过读取IDR寄存器来读取IO引脚的状态的
3.9总结
在STM32中选用IO模式
(1)浮空输入_IN_FLOATING
——浮空输入,可以做KEY识别,RX1
(2)带上拉输入_IPU
——IO内部上拉电阻输入,默认是高电平
(3)带下拉输入_IPD
—— IO内部下拉电阻输入,默认是低电平
(4)模拟输入_AIN
——应用ADC模拟输入,或者低功耗下省电
(5)开漏输出_OUT_OD
——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能(可用于软件IIC的SDA、SCL等)
(6)推挽输出_OUT_PP
——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的
(7)复用功能的推挽输出_AF_PP
——片内外设功能(可用于硬件I2C的SCL,SDA引脚等)
(8)复用功能的开漏输出_AF_OD
——片内外设功能(TX1,MOSI,MISO.SCK.SS)
参考:http://47.111.11.73/forum.php?mod=viewthread&tid=315111&highlight=GPIO
补充:
1,F1在输出模式,禁止使用内部上下拉。F4/F7/H7在输出模式,可以使用内部上下拉
2,不同系列IO翻转速度可能不同
四、GPIO寄存器
寄存器概览
F1系列GPIO通用寄存器GPIOX_yyy
CRL | CRH | IDR | ODR | BSRR | BRR | LCKR |
---|---|---|---|---|---|---|
配置工作模式,输出速度 | 配置工作模式,输出速度 | 输入数据 | 输出数据 | 设置ODR寄存器的值 | F4之后没有这个寄存器,考虑代码兼容性的话不建议使用 | 配置锁定,用得不多 (锁定CRL和CRH的值) |
每个GPIO都有一组这样的寄存器,例如,CRL这个寄存器就有7个一模一样的CRL寄存器,分别是GPIOA_CRL~GPIOG_CRL,分别控制GPIOA~GPIOG。
端口配置高寄存器(CRH)与端口配置低寄存器(CRL)
CRH和CRL用于设置GPIO的工作模式和输出速度
CRH:
CRL:
两个寄存器共64位,每4位控制一个IO口,共控制16个IO口,例如GPIOA_CRL和GPIOA_CRH控制PA0~PA15。GPIOA_CRL的CNF0[1:0]和MODE0[1:0]控制PA0,GPIOA_CRL的CNF3[1:0]和MODE3[1:0]控制PA3,GPIOA_CRH的CNF15[1:0]和MODE15[1:0]控制PA15,以此类推。
例如,想设置PA0位模拟输入模式,则设置MODE0[1:0]为00(输入模式),再设置CNF[1:0]为00(模拟输入模式)即可
例如,想设置PA5位开漏输出模式,则设置MODE5[1:0]为11(输出模式,为01、10也可以,只是输出速度不同),再设置CNF[1:0]为01(开漏输出模式)即可
但是,在输入模式下,当CNF[1:0]为10时,为上拉/下拉输入模式,没有区分到底是上拉还是下拉模式,这个需要通过对应的ODR寄存器来设置,如下图所示,ODR为0时为下拉输入模式,ODR为1时为上拉输入模式
例如,设置PB2为上拉输入模式,则设置GPIOB_CRL的MODE2[1:0]为00,CNF[1:0]为10,GPIOB_ODR的ODR2为1即可
端口输入数据寄存器(IDR)
用于判断IO引脚的电平
例如,对于GPIOB_IDR寄存器,若读出IDR0为1,则说明PB0为1
端口输出数据寄存器(ODR)
用于设置IO引脚输出的电平
例如,若想PC10输出1,则设置GPIOC_ODR的ODR10为1
端口位设置/清除寄存器(BSRR)
用于设置ODR寄存器
例如,设置PA10为0,则可以设置GPIOA_BSRR的BR10为1,则此时GPIOA_ODR的ODR10为0,即PA10为0
例如,设置PA10为1,则可以设置GPIOA_BSRR的BS10为1,则此时GPIOA_ODR的ODR10为1,即PA10为1
补充:ODR和BSRR寄存器控制输出有什么区别?
ST官方给的答案:使用ODR,在读和修改访问之间产生中断时,可能会发生风险;BSRR则无风险。
例如,设置PB3为1
GPIOB->ODR |= 1 << 3; /* PB3 = 1 ODR修改:读 改 写*/
GPIOB->BSRR = 0x00000008; /* PB3 = 1 BSRR修改: 写*/
由于ODR寄存器是可读可写的,所以可以通过或运算,将ODR的值读出来,进行或运算将倒数第3位设置为1,再写入ODR
而BSRR是只读的,不能通过或运算读出其寄存器的值,只能直接赋值
所谓的风险就是在对ODR寄存器读和写之间发生了中断,在中断程序中也对该寄存器进行了赋值,那么当中断返回时,再对ODR寄存器进行写,则会覆盖在中断程序中对该寄存器的赋值,进而发生风险(写入的数据被覆盖了,没了,就发生了风险了呗)。其实对BSRR寄存器的赋值理论上也是有风险的,但是由于只是对该寄存器直接进行赋值,速度较快,很难在这期间发生中断,也就是发生中断概率极低,可视为无风险。
总的来说,建议大家使用BSRR寄存器控制输出!
五、GPIO配置步骤
1、GPIO的配置步骤
1、使能GPIO时钟时钟 : __HAL_RCC_GPIOx_CLK_ENABLE()
(x为A~G)
主要操作RCC_APB2ENR寄存器,来开启时钟(GPIO挂载在APB2总线)
STM32 在使用任何外设之前,我们都要先使能其时钟
2、设置GPIO工作模式 HAL_GPIO_Init()
主要操作CRL、CRH、ODR三个寄存器,初始化GPIO
本实验 GPIO 使用推挽输出模式,控制 LED 亮灭
3、设置GPIO输出状态(可选)
HAL_GPIO_WritePin()
写引脚,控制IO输出高/低电平 HAL_GPIO_TogglePin()
翻转引脚,每次调用IO输出电平翻转一次
这两个函数都是操作BSRR寄存器
4、读取GPIO输入状态(可选) HAL_GPIO_ReadPin()
主要操作IDR寄存器,读取IO电平
2、相关库函数简介
__HAL_RCC_GPIOx_CLK_ENABLE()
定义在stm32f1xx_hal_rcc.h文件中
以__HAL_RCC_GPIOA_CLK_ENABLE()
为例
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\
/* Delay after an RCC peripheral clock enabling */\
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\
UNUSED(tmpreg); \
} while(0U)
核心代码SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);
操作RCC->APB2ENR寄存器
SET_BIT函数定义如下:进行或运算
#define SET_BIT(REG, BIT) ((REG) |= (BIT))
RCC_APB2ENR_IOPAEN定义如下,为1<<2
#define RCC_APB2ENR_IOPAEN_Pos (2U)
#define RCC_APB2ENR_IOPAEN_Msk (0x1UL << RCC_APB2ENR_IOPAEN_Pos) /*!< 0x00000004 */
#define RCC_APB2ENR_IOPAEN RCC_APB2ENR_IOPAEN_Msk /*!< I/O port A clock enable */
总的运算为RCC->APB2ENR |= (1<<2) ,即将RCC_APB2ENR寄存器的位2置1
根据参考手册,位2置1可使得IO端口A时钟开启,也就是说__HAL_RCC_GPIOA_CLK_ENABLE() 这个宏定义就是开启GPIOA的时钟
HAL_GPIO_Init()
外设 GPIO 的初始化函数,用于配置 GPIO 功能模式,还可以设置 EXTI 功能
定义在stm32f1xx_hal_gpio.h文件下
/**
* @brief Initializes the GPIOx peripheral according to the specified parameters in the GPIO_Init.
* @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
* @param GPIO_Init: pointer to a GPIO_InitTypeDef structure that contains
* the configuration information for the specified GPIO peripheral.
* @retval None
*/
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
形参1:GPIOx 范围GPIOA~GPIOG GPIOx_BASE为GPIO寄存器的基地址
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *)GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *)GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *)GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *)GPIOG_BASE)
形参2:GPIO_Init 类型:GPIO_InitTypeDef (PULL为上下拉设置)
typedef struct
{
uint32_t Pin; /*引脚号!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
uint32_t Mode; /*模式设置!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIO_mode_define */
uint32_t Pull; /*上下拉设置!< Specifies the Pull-up or Pull-Down activation for the selected pins.
This parameter can be a value of @ref GPIO_pull_define */
uint32_t Speed; /*速度!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;
(查找某个变量在哪定义的小技巧:选择@ref后面的内容可快速查找,例如选中注释中的GPIO_pins_define,加ctrl+f,再进行全文搜索即可)
Pin
/** @defgroup GPIO_Exported_Constants GPIO Exported Constants
* @{
*/
/** @defgroup GPIO_pins_define GPIO pins define
* @{
*/
#define GPIO_PIN_0 ((uint16_t)0x0001) /* Pin 0 selected */
#define GPIO_PIN_1 ((uint16_t)0x0002) /* Pin 1 selected */
#define GPIO_PIN_2 ((uint16_t)0x0004) /* Pin 2 selected */
#define GPIO_PIN_3 ((uint16_t)0x0008) /* Pin 3 selected */
#define GPIO_PIN_4 ((uint16_t)0x0010) /* Pin 4 selected */
#define GPIO_PIN_5 ((uint16_t)0x0020) /* Pin 5 selected */
#define GPIO_PIN_6 ((uint16_t)0x0040) /* Pin 6 selected */
#define GPIO_PIN_7 ((uint16_t)0x0080) /* Pin 7 selected */
#define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */
#define GPIO_PIN_9 ((uint16_t)0x0200) /* Pin 9 selected */
#define GPIO_PIN_10 ((uint16_t)0x0400) /* Pin 10 selected */
#define GPIO_PIN_11 ((uint16_t)0x0800) /* Pin 11 selected */
#define GPIO_PIN_12 ((uint16_t)0x1000) /* Pin 12 selected */
#define GPIO_PIN_13 ((uint16_t)0x2000) /* Pin 13 selected */
#define GPIO_PIN_14 ((uint16_t)0x4000) /* Pin 14 selected */
#define GPIO_PIN_15 ((uint16_t)0x8000) /* Pin 15 selected */
#define GPIO_PIN_All ((uint16_t)0xFFFF) /* All pins selected */
#define GPIO_PIN_MASK 0x0000FFFFu /* PIN mask for assert test 基本不用*/
/**
* @}
*/
Mode
/** @defgroup GPIO_mode_define GPIO mode define
* @brief GPIO Configuration Mode
* Elements values convention: 0xX0yz00YZ
* - X : GPIO mode or EXTI Mode
* - y : External IT or Event trigger detection
* - z : IO configuration on External IT or Event
* - Y : Output type (Push Pull or Open Drain)
* - Z : IO Direction mode (Input, Output, Alternate or Analog)
* @{
*/
#define GPIO_MODE_INPUT (0x00000000U) /* 输入模式 */
#define GPIO_MODE_OUTPUT_PP (0x00000001U) /* 推挽输出 */
#define GPIO_MODE_OUTPUT_OD (0x00000011U) /* 开漏输出 */
#define GPIO_MODE_AF_PP (0x00000002U) /* 推挽式复用 */
#define GPIO_MODE_AF_OD (0x00000012U) /* 开漏式复用 */
#define GPIO_MODE_AF_INPUT GPIO_MODE_INPUT
#define GPIO_MODE_ANALOG (0x00000003U) /* 模拟模式 */
#define GPIO_MODE_IT_RISING (0x10110000u) /* 外部中断,上升沿触发检测 */
#define GPIO_MODE_IT_FALLING (0x10210000u) /* 外部中断,下降沿触发检测 */
/* 外部中断,上升和下降双沿触发检测 */
#define GPIO_MODE_IT_RISING_FALLING (0x10310000u)
#define GPIO_MODE_EVT_RISING (0x10120000U) /*外部事件,上升沿触发检测 */
#define GPIO_MODE_EVT_FALLING (0x10220000U) /*外部事件,下降沿触发检测 */
/* 外部事件,上升和下降双沿触发检测 */
#define GPIO_MODE_EVT_RISING_FALLING (0x10320000U)
PULL
/** @defgroup GPIO_pull_define GPIO pull define
* @brief GPIO Pull-Up or Pull-Down Activation
* @{
*/
#define GPIO_NOPULL 0x00000000u /*!< 无上下拉No Pull-up or Pull-down activation */
#define GPIO_PULLUP 0x00000001u /*!< 上拉Pull-up activation */
#define GPIO_PULLDOWN 0x00000002u /*!< 下拉Pull-down activation */
/**
* @}
*/
Speed
/** @defgroup GPIO_speed_define GPIO speed define
* @brief GPIO Output Maximum frequency
* @{
*/
#define GPIO_SPEED_FREQ_LOW (GPIO_CRL_MODE0_1) /*!< Low speed */
#define GPIO_SPEED_FREQ_MEDIUM (GPIO_CRL_MODE0_0) /*!< Medium speed */
#define GPIO_SPEED_FREQ_HIGH (GPIO_CRL_MODE0) /*!< High speed */
/**
* @}
*/
HAL_GPIO_WritePin()
是 GPIO 口的写引脚函数
定义在stm32f1xx_hal_gpio.h文件下
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
GPIO_PinState
/**
* @brief GPIO Bit SET and Bit RESET enumeration
*/
typedef enum
{//RESET低电平 SET高电平
GPIO_PIN_RESET = 0u,
GPIO_PIN_SET
} GPIO_PinState;
/**
* @}
*/
HAL_GPIO_TogglePin()
是 GPIO 口的电平翻转函数
定义在stm32f1xx_hal_gpio.h文件下
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
HAL_GPIO_ReadPin()
定义在stm32f1xx_hal_gpio.h文件下,通过IDR寄存器读取GPIO的引脚状态
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
六、编程实战1:点亮一个LED灯
LED原理图:
根据原理图可知,LED0对应的引脚为PB5,LED1对应的引脚为PE5
以点亮LED0为例:
1)使能对应 GPIO 时钟
STM32 在使用任何外设之前,我们都要先使能其时钟。本实验用到 PB5 这个IO 口,因此需要先使能 GPIOB 的时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
2)设置对应 GPIO 工作模式(推挽输出)
本实验是点亮LED灯,为输出模式,如果选择开漏输出模式,因为开漏本身无法输出高电平,并且该电路没有外部上拉电阻,故开漏输出模式无法输出高电平,IO口只能为低电平或者高阻态,若LED像上图那样连接,其实开漏输出模式也是可以的,IO口输出低电平,LED亮,IO口为高阻态,LED灭。但是,当LED像下图那样连接就不行了。无论IO口为低电平还是高阻态,LED都不亮。
而推挽输出模式可以直接输出高低电平,对应LED的亮灭。故GPIO 使用推挽输出模式,控制 LED 亮灭,通过函数 HAL_GPIO_Init 设置实现。
3)控制 GPIO 引脚输出高低电平
在配置好 GPIO 工作模式后,我们就可以通过 HAL_GPIO_WritePin 函数控制 GPIO 引脚输出高低电平,从而控制 LED 的亮灭了。
led.c
void led_init(void){
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin=GPIO_PIN_5;
gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;//推挽输出
gpio_init_struct.Pull=GPIO_NOPULL;//由于是输出,不需要给Pull属性初始化,这里赋值是为了保持程序的健壮性
gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW;//高、中、低速都可以
HAL_GPIO_Init(GPIOB,&gpio_init_struct);
//置1,LED0灭(让灯的初始化是灭的状态)
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);
}
led.h
#ifndef __LED_H
#define __LED_H
#include "./SYSTEM/sys/sys.h"
void led_init(void);
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_init(); /* LED初始化 */
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET); /* PB5置0,LED0亮 */
while(1)
{
}
}
七、编程实战2:按键控制LED灯的亮灭
1、独立按键简介
几乎每个开发板都会板载有独立按键,因为按键用处很多。常态下,独立按键是断开的, 按下的时候才闭合。每个独立按键会单独占用一个 IO 口,通过 IO 口的高低电平判断按键的状 态。但是按键在闭合和断开的时候,都存在抖动现象,即按键在闭合时不会马上就稳定的连接, 断开时也不会马上断开。这是机械触点,无法避免。独立按键抖动波形图如下:
图中的按下抖动和释放抖动的时间一般为 5~10ms,如果在抖动阶段采样,其不稳定状态可 能出现一次按键动作被认为是多次按下的情况。为了避免抖动可能带来的误操作,我们要做的 措施就是给按键消抖(即采样稳定闭合阶段)。消抖方法分为硬件消抖和软件消抖,我们常用软 件的方法消抖。
软件消抖:方法很多,我们例程中使用最简单的延时消抖。检测到按键按下后,一般进行 10ms 延时,用于跳过抖动的时间段,如果消抖效果不好可以调整这个 10ms 延时,因为不同类型的按键抖动时间可能有偏差。待延时过后再检测按键状态,如果没有按下,那我们就判断这是抖动或者干扰造成的;如果还是按下,那么我们就认为这是按键真的按下了。对按键释放的判断同理。
硬件消抖:利用 RC 电路的电容充放电特性来对抖动产生的电压毛刺进行平滑出来,从而 实现消抖,但是成本会更高一点,本着能省则省的原则,我们推荐使用软件消抖即可。
2、原理图
开发指南原话:这里需要注意的是:KEY0 和 KEY1 是低电平有效的,而 KEY_UP 则是高电平有效的,并 且外部都没有上下拉电阻,所以需要在 STM32F103 内部设置上下拉,来确定设置空闲电平状态。这里需要注意的是:KEY0 和 KEY1 是低电平有效的(即一端接地),所以我们要设置为内部上拉,而 KEY_UP 是高电平有效的(即一端接电源),所以我们要设置为内部下拉。
对于KEY_UP,当按键按下时,PA0输出1,按键没有按下时为高阻态,若PA0为输入浮空模式,则PA0的值不确定,若PA0为输入上拉模式(接上拉电阻),则PA0为1,也不行。只有PA0为输入下拉模式(接下拉电阻)时,才能为0。
对于KEY0和KEY1,则是相反,以KEY0为例,当按键按下时,PE4输出0,按键没有按下时为高阻态,若PE4为输入浮空模式,则PE4的值不确定,若PE4为输入上拉模式(接上拉电阻),则PE4输出1,可以。PE4为输入下拉模式(接下拉电阻)时,输出为0,不行。
3、例程功能
KEY0 控制 LED0的翻转。(按下一次KEY0,LED0的状态翻转一次)
4、代码实现
key.c
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
void key_init(void)
{
__HAL_RCC_GPIOE_CLK_ENABLE();
GPIO_InitTypeDef gpio_init_struct;
gpio_init_struct.Pin=GPIO_PIN_4;
gpio_init_struct.Mode=GPIO_MODE_INPUT;//输入模式
gpio_init_struct.Pull=GPIO_PULLUP;//上拉
gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW;//由于是输入模式,故速度无效,可不设置
HAL_GPIO_Init(GPIOE,&gpio_init_struct);
}
//按键扫描函数,判断按键是否按下
uint8_t key_scan(void){
uint8_t isKey = 0;//isKey=0按键没有按下 isKey=1按键按下
if( HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==0){
delay_ms(10);//延迟10ms来防抖
if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==0){
while(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)==0);
delay_ms(10);
isKey=1;//按键按下
}
}
return isKey;
}
key.h
#ifndef __KEY_H
#define __KEY_H
#include "./SYSTEM/sys/sys.h"
void key_init(void);
uint8_t key_scan(void);
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
int main(void)
{
uint8_t t = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_init(); /* 初始化LED */
key_init(); /* 初始化KEY */
while (1)
{
if(key_scan())//按键按下
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);//翻转LED0的状态
}
else
{
delay_ms(10);//等待10ms后再检测
}
}
}
八、正点原子例程
正点原子例程1:跑马灯实验
LED 灯:LED0 和 LED1 每过 500ms 一次交替闪烁,实现类似跑马灯的效果。
led.h
#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define LED0_GPIO_PORT GPIOB
#define LED0_GPIO_PIN GPIO_PIN_5
#define LED0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define LED1_GPIO_PORT GPIOE
#define LED1_GPIO_PIN GPIO_PIN_5
#define LED1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
/******************************************************************************************/
/* LED端口定义 */
#define LED0(x) do{ x ? \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED0翻转 */
#define LED1(x) do{ x ? \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* LED1翻转 */
/* LED取反定义 */
#define LED0_TOGGLE() do{ HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); }while(0) /* 翻转LED0 */
#define LED1_TOGGLE() do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0) /* 翻转LED1 */
/******************************************************************************************/
/* 外部接口函数*/
void led_init(void); /* 初始化 */
#endif
led.c
#include "./BSP/LED/led.h"
/**
* @brief 初始化LED相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void led_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
LED0_GPIO_CLK_ENABLE(); /* LED0时钟使能 */
LED1_GPIO_CLK_ENABLE(); /* LED1时钟使能 */
gpio_init_struct.Pin = LED0_GPIO_PIN; /* LED0引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct); /* 初始化LED0引脚 */
gpio_init_struct.Pin = LED1_GPIO_PIN; /* LED1引脚 */
HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct); /* 初始化LED1引脚 */
LED0(1); /* 关闭 LED0 */
LED1(1); /* 关闭 LED1 */
}
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟,72M */
delay_init(72); /* 初始化延时函数 */
led_init(); /* 初始化LED */
while(1)
{
LED0(0); /* LED0 亮 */
LED1(1); /* LED1 灭 */
delay_ms(500);
LED0(1); /* LED0 灭 */
LED1(0); /* LED1 亮 */
delay_ms(500);
}
}
正点原子例程2:蜂鸣器实验
正点原子STM32F103 开发板板载的蜂鸣器是电磁式的有源蜂鸣器。这里的有源不是指电源的“源”,而是指有没有自带震荡电路,有源蜂鸣器自带了震荡电路, 一通电就会发声;无源蜂鸣器则没有自带震荡电路,必须外部提供 2~5Khz 左右的方波驱动, 才能发声
1、例程功能
蜂鸣器每隔 300ms 响或者停一次。LED0 每隔 300ms 亮或者灭一次。LED0 亮的时候蜂鸣 器不叫,而 LED0 熄灭的时候,蜂鸣器叫。
2、原理图
当 PB8 输出高电平的时候, 蜂鸣器将发声,当 PB8 输出低电平的时候,蜂鸣器停止发声。
3、程序设计流程
(1)系统级别的初始化: HAL库、系统时钟和 延时函数初始化
(2)用户初始化: LED灯和蜂鸣器初始化
(3)LED0点亮、蜂鸣器关 闭、延时300ms
(4)LED0熄灭、蜂鸣器打 开、延时300ms
(5)重复循环第三步和第四步
4、实现代码
beep.h
#ifndef __BEEP_H
#define __BEEP_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define BEEP_GPIO_PORT GPIOB
#define BEEP_GPIO_PIN GPIO_PIN_8
#define BEEP_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/******************************************************************************************/
/* 蜂鸣器控制 */
#define BEEP(x) do{ x ? \
HAL_GPIO_WritePin(BEEP_GPIO_PORT, BEEP_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(BEEP_GPIO_PORT, BEEP_GPIO_PIN, GPIO_PIN_RESET); \
}while(0)
/* BEEP状态翻转 */
#define BEEP_TOGGLE() do{ HAL_GPIO_TogglePin(BEEP_GPIO_PORT, BEEP_GPIO_PIN); }while(0) /* BEEP = !BEEP */
void beep_init(void); /* 初始化蜂鸣器 */
#endif
beep.c
#include "./BSP/BEEP/beep.h"
/**
* @brief 初始化BEEP相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void beep_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
BEEP_GPIO_CLK_ENABLE(); /* BEEP时钟使能 */
gpio_init_struct.Pin = BEEP_GPIO_PIN; /* 蜂鸣器引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(BEEP_GPIO_PORT, &gpio_init_struct); /* 初始化蜂鸣器引脚 */
BEEP(0); /* 关闭蜂鸣器 */
}
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/BEEP/beep.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟,72M */
delay_init(72); /* 初始化延时函数 */
led_init(); /* 初始化LED */
beep_init(); /* 初始化蜂鸣器 */
while (1)
{
LED0(0);
BEEP(0);
delay_ms(300);
LED0(1);
BEEP(1);
delay_ms(300);
}
}
正点原子例程3:按键输入实验
功能:通过开发板上的三个独立按键控制 LED 灯和蜂鸣器: KEY_UP 控制蜂鸣器翻转, KEY1控制 LED1 翻转, KEY0 控制 LED0/LED1 同时翻转。
key.h
#ifndef __KEY_H
#define __KEY_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define KEY0_GPIO_PORT GPIOE
#define KEY0_GPIO_PIN GPIO_PIN_4
#define KEY0_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define KEY1_GPIO_PORT GPIOE
#define KEY1_GPIO_PIN GPIO_PIN_3
#define KEY1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
#define WKUP_GPIO_PORT GPIOA
#define WKUP_GPIO_PIN GPIO_PIN_0
#define WKUP_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
/******************************************************************************************/
#define KEY0 HAL_GPIO_ReadPin(KEY0_GPIO_PORT, KEY0_GPIO_PIN) /* 读取KEY0引脚 */
#define KEY1 HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_GPIO_PIN) /* 读取KEY1引脚 */
#define WK_UP HAL_GPIO_ReadPin(WKUP_GPIO_PORT, WKUP_GPIO_PIN) /* 读取WKUP引脚 */
#define KEY0_PRES 1 /* KEY0按下 */
#define KEY1_PRES 2 /* KEY1按下 */
#define WKUP_PRES 3 /* KEY_UP按下(即WK_UP) */
void key_init(void); /* 按键初始化函数 */
uint8_t key_scan(uint8_t mode); /* 按键扫描函数 */
#endif
key.c
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
/**
* @brief 按键初始化函数
* @param 无
* @retval 无
*/
void key_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
KEY0_GPIO_CLK_ENABLE(); /* KEY0时钟使能 */
KEY1_GPIO_CLK_ENABLE(); /* KEY1时钟使能 */
WKUP_GPIO_CLK_ENABLE(); /* WKUP时钟使能 */
gpio_init_struct.Pin = KEY0_GPIO_PIN; /* KEY0引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY0_GPIO_PORT, &gpio_init_struct); /* KEY0引脚模式设置,上拉输入 */
gpio_init_struct.Pin = KEY1_GPIO_PIN; /* KEY1引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY1_GPIO_PORT, &gpio_init_struct); /* KEY1引脚模式设置,上拉输入 */
gpio_init_struct.Pin = WKUP_GPIO_PIN; /* WKUP引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct); /* WKUP引脚模式设置,下拉输入 */
}
/**
* @brief 按键扫描函数
* @note 该函数有响应优先级(同时按下多个按键): WK_UP > KEY1 > KEY0!!
* @param mode:0 / 1, 具体含义如下:
* @arg 0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
* 必须松开以后, 再次按下才会返回其他键值)
* @arg 1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
* @retval 键值, 定义如下:
* KEY0_PRES, 1, KEY0按下
* KEY1_PRES, 2, KEY1按下
* WKUP_PRES, 3, WKUP按下
*/
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; /* 按键按松开标志 */
uint8_t keyval = 0;
if (mode) key_up = 1; /* 支持连按 */
if (key_up && (KEY0 == 0 || KEY1 == 0 || WK_UP == 1)) /* 按键松开标志为1, 且有任意一个按键按下了 */
{
delay_ms(10); /* 去抖动 */
key_up = 0;
if (KEY0 == 0) keyval = KEY0_PRES;
if (KEY1 == 0) keyval = KEY1_PRES;
if (WK_UP == 1) keyval = WKUP_PRES;
}
else if (KEY0 == 1 && KEY1 == 1 && WK_UP == 0) /* 没有任何按键按下, 标记按键松开 */
{
key_up = 1;
}
return keyval; /* 返回键值 */
}
这里的是否支持按键长按可以这么理解:
若形参mode为0,不支持连续按,由于key_scan函数会在主函数的while(1)循环中反复执行,而key_up是static型变量,当第一次调用key_scan函数时,key_up赋值为1,若某个按键按下,则会将key_up置为0,keyval会赋值为按键值。之后再次调用key_scan时,由于key_up为static型变量,故不会再次赋值,仍为0,此时即使按键按下,也不会执行相应条件语句。也就是不支持按键连续按,即按键长按时只能返回一次不为0的键值。
若形参mode为1,支持连续按,当第二次调用key_scan函数时,由于mode为1,故key_up会被置为1,此时按键长按时会返回多次不为0的键值。
main.c
#include "./stm32f1xx_it.h"
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/KEY/key.h"
int main(void)
{
uint8_t key;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_init(); /* 初始化LED */
beep_init(); /* 初始化蜂鸣器 */
key_init(); /* 初始化按键 */
LED0(0); /* 先点亮LED0 */
while(1)
{
key = key_scan(0); /* 得到键值 */
if (key)
{//有按键按下
switch (key)
{
case WKUP_PRES: /* 控制蜂鸣器 */
BEEP_TOGGLE(); /* BEEP状态取反 */
break;
case KEY1_PRES: /* 控制LED1(GREEN)翻转 */
LED1_TOGGLE(); /* LED1状态取反 */
break;
case KEY0_PRES: /* 同时控制LED0, LED1翻转 */
LED0_TOGGLE(); /* LED0状态取反 */
LED1_TOGGLE(); /* LED1状态取反 */
break;
}
}
else
{//没有按键按下
delay_ms(10);
}
}
}
九、STM32CubeMX实现
新建工程步骤
示例1:跑马灯实验
与视频内容相同:第33讲 基础篇-新建STM32CubeMX工程步骤_哔哩哔哩_bilibili
示例2:蜂鸣器实验
功能:蜂鸣器每隔 300ms 响或者停一次。LED0 每隔 300ms 亮或者灭一次。LED0 亮的时候蜂鸣器不叫,而 LED0 熄灭的时候,蜂鸣器叫。
实验原理见正点原子例程2:蜂鸣器实验
注:时钟源的配置见上述视频
1、将PB5(LED0引脚)和PB8(蜂鸣器引脚)设置为输出模式
2、设置PB5引脚参数
点击Pinout&Configuration下的System Core下的GPIO来进行设置,初始电平为低,推挽输出,上拉,低速(高速、中速都行)
3、设置PB8引脚参数
初始电平为低,推挽输出,上拉,低速(高速、中速都行)
4、设置Debug
我使用的是SW来进行Debug
注:配置工程具体操作见上述视频
5、生成代码
在while循环中写对应逻辑
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);
HAL_Delay(300);
HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET);
HAL_Delay(300);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
示例3:按键输入实验
功能:通过开发板上的三个独立按键控制 LED 灯和蜂鸣器: KEY_UP 控制蜂鸣器翻转, KEY1控制 LED1 翻转, KEY0 控制 LED0/LED1 同时翻转。
实验原理见七、编程实战2:按键控制LED灯的亮灭
在示例2的基础上进行配置
1、将PE5(LED1引脚)设置为输出模式,将PE4、PE3和PA0设置为输入模式
2、配置PE5引脚
2、配置PE4引脚
3、配置PE3引脚
4、配置PA0引脚
5、生成代码,修改gpio.h文件
/* USER CODE BEGIN Private defines */
#define KEY0 HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin) /* 读取KEY0引脚 */
#define KEY1 HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) /* 读取KEY1引脚 */
#define WK_UP HAL_GPIO_ReadPin(KEY_UP_GPIO_Port, KEY_UP_Pin) /* 读取WKUP引脚 */
#define KEY0_PRES 1 /* KEY0按下 */
#define KEY1_PRES 2 /* KEY1按下 */
#define WKUP_PRES 3 /* KEY_UP按下(即WK_UP) */
/* USER CODE END Private defines */
/* USER CODE BEGIN Prototypes */
uint8_t key_scan(uint8_t mode); /* 按键扫描函数 */
/* USER CODE END Prototypes */
6、修改gpio.c文件
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; /* 按键按松开标志 */
uint8_t keyval = 0;
if (mode) key_up = 1; /* 支持连按 */
if (key_up && (KEY0 == 0 || KEY1 == 0 || WK_UP == 1)) /* 按键松开标志为1, 且有任意一个按键按下了 */
{
HAL_Delay(10); /* 去抖动 */
key_up = 0;
if (KEY0 == 0) keyval = KEY0_PRES;
if (KEY1 == 0) keyval = KEY1_PRES;
if (WK_UP == 1) keyval = WKUP_PRES;
}
else if (KEY0 == 1 && KEY1 == 1 && WK_UP == 0) /* 没有任何按键按下, 标记按键松开 */
{
key_up = 1;
}
return keyval; /* 返回键值 */
}
/* USER CODE END 2 */
7、在main.c中的while循环中添加逻辑
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
key = key_scan(0); /* 得到键值 */
if (key)
{//有按键按下
switch (key)
{
case WKUP_PRES: /* 控制蜂鸣器 */
HAL_GPIO_TogglePin(BEEP_GPIO_Port,BEEP_Pin); /* BEEP状态取反 */
break;
case KEY1_PRES: /* 控制LED1(GREEN)翻转 */
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin); /* LED1状态取反 */
break;
case KEY0_PRES: /* 同时控制LED0, LED1翻转 */
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin); /* LED0状态取反 */
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin); /* LED1状态取反 */
break;
}
}
else
{//没有按键按下
HAL_Delay(10);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}