正点原子STM32F1系列学习笔记之GPIO(HAL库+Keil5+STM32CubeMX)超详细

一、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端口)

image-20230810145240143
1.3GPIO引脚分布

它按组分配,每组 16 个 IO 口,组数视芯片而定。STM32F103ZET6 芯片是 144 脚的芯片,具有 GPIOA、GPIOB、GPIOC、 GPIOD、GPIOE、GPIOF 和 GPIOG 七组 GPIO 口,共有 112 个 IO 口可供我们编程使用。

二、GPIO基本结构

F1系列IO端口基本结构:

与F4及其他系列不同,F1的IO端口结构的上拉和下拉电阻在输入驱动器中,这样,使得只有输入才有上下拉电阻,输出没有

image-20230810151040130

① 保护二极管:起保护作用,防止输入的电压过高或过低

② 内部上拉、下拉电阻:30-50kΩ,因电阻值很大,VDD为3.3V,故电流很小,称为弱上拉(下拉)电阻

③ 施密特触发器:施密特触发器就是一种整形电路,可以将非标准方波,整形成方波

image-20230810153626385

当输入电压高于正向阈值电压,输出为高;

当输入电压低于负向阈值电压,输出为低;

当输入在正负向阈值电压之间,输出不改变。

④ P-MOS & N-MOS管:MOS管是压控型元件,通过控制栅源电压( Vgs )来实现导通或关闭

image-20230810154157499

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

image-20230810193036847 ##### 3.2输入上拉

上拉输入就是在输入电路上使用了上拉电阻。这种模式的好处在于我们什么都不输入时,由于内部上拉电阻的原因,我们的处理器会觉得我们输入了高电平,这就避免了不确定的输入。这在要求输入电平只要高低两种电平的情况下是很有用的。换句话说,当IO口为高阻态时(IO空闲时),由于接了上拉电阻(接了3.3V的VDD),故此时读出的是高电平。

上拉电阻打开、下拉电阻关闭,施密特触发器打开,双MOS管不导通

image-20230810193310565
3.3输入下拉

和上拉输入类似,不过下拉输入时,在外部没有输入时,我们的处理器会觉得我们输入了低电平。换句话说,当IO口为高阻态时(IO空闲时),由于接了下拉电阻(接了VSS(接地)),故此时读出的是低电平。

上拉电阻关闭、下拉电阻打开,施密特触发器打开,双MOS管不导通

image-20230810193725706
3.4模拟输入

输入时不经过输入数据寄存器,所以我们无法通过读取输入数据寄存器来获取模拟输入的值,我觉得这一点也是很好理解的,因为输入数据寄存器中存放的不是0就是1,而模拟输入信号不符合这一要求,所以自然不能放进输入数据寄存器。该输入模式,使我们可以获得外部的模拟信号。此时,只有模拟输入那一条路时通的。此模式专门用于模拟信号输入或输出,如:ADC和DAC

上拉、下拉电阻关闭,施密特触发器关闭,双MOS管不导通

image-20230810193955759
3.5开漏输出

只能通过接芯片外部上拉电阻的方式,实现开漏输出模式下输出高电平。 如果芯片外部不接上拉电阻,那么开漏输出模式下,IO 无法输出高电平。也就是说,正常情况下不能输出高电平,必须有外部(或内部,这个内部上拉是针对更高系列如F4的,F1此模式下是禁止使用内部上、下拉的)上拉才能输出高电平。

上拉、下拉电阻关闭,施密特触发器打开,P-MOS管始终不导通,往ODR对应位写0,N-MOS管导通,写1则N-MOS管不导通。若N-MOS管不导通,而P-MOS管是始终不导通的,就是输出不通了,此时IO口的输出就是高阻态了,此时若芯片外部接一个上拉电阻,IO口才可以正常输出1。

image-20230810200512574

注意:由于施密特触发器打开,故此模式下是可以通过读取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口没有电阻,电流大,故驱动能力强),直接输出高电平或者低电平。

image-20230810201656529

注意:与开漏输出模式一样,由于施密特触发器打开,故此模式下是可以通过读取IDR寄存器来读取IO引脚的状态的

3.7开漏复用

该模式是对GPIO的复用

与开漏输出模式一样,不能输出高电平,必须有外部(或内部)上拉才能输出高电平

该模式下,IO口由其他外设控制输出

上拉、下拉电阻关闭,施密特触发器打开,P-MOS管始终不导通,N-MOS管是否导通是由片上外设输出决定的。

image-20230810201005315

注意:与开漏输出模式一样,由于施密特触发器打开,故此模式下是可以通过读取IDR寄存器来读取IO引脚的状态的

3.8推挽复用

该模式是对GPIO的复用

上拉、下拉电阻关闭,施密特触发器打开,P-MOS管和N-MOS管是否导通是由片上外设输出决定的

1、可输出高低电平, 驱动能力强(与推挽输出模式一样)

2、由其他外设控制输出

image-20230810202540535

注意:与开漏输出模式一样,由于施密特触发器打开,故此模式下是可以通过读取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

CRLCRHIDRODRBSRRBRRLCKR
配置工作模式,输出速度配置工作模式,输出速度输入数据输出数据设置ODR寄存器的值F4之后没有这个寄存器,考虑代码兼容性的话不建议使用配置锁定,用得不多 (锁定CRL和CRH的值)

每个GPIO都有一组这样的寄存器,例如,CRL这个寄存器就有7个一模一样的CRL寄存器,分别是GPIOA_CRL~GPIOG_CRL,分别控制GPIOA~GPIOG。

端口配置高寄存器(CRH)与端口配置低寄存器(CRL)

CRH和CRL用于设置GPIO的工作模式和输出速度

CRH:

image-20230810204938982

CRL:

image-20230810205023646

两个寄存器共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即可

image-20230810212021069
端口输入数据寄存器(IDR)

用于判断IO引脚的电平

image-20230810212414255

例如,对于GPIOB_IDR寄存器,若读出IDR0为1,则说明PB0为1

端口输出数据寄存器(ODR)

用于设置IO引脚输出的电平

image-20230810212630846

例如,若想PC10输出1,则设置GPIOC_ODR的ODR10为1

端口位设置/清除寄存器(BSRR)

用于设置ODR寄存器

image-20230810213219107

例如,设置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的时钟

image-20230810221621785
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原理图:

image-20230811080333442

根据原理图可知,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都不亮。

image-20230811080946833

而推挽输出模式可以直接输出高低电平,对应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 口的高低电平判断按键的状 态。但是按键在闭合和断开的时候,都存在抖动现象,即按键在闭合时不会马上就稳定的连接, 断开时也不会马上断开。这是机械触点,无法避免。独立按键抖动波形图如下:

image-20230401144539987

图中的按下抖动和释放抖动的时间一般为 5~10ms,如果在抖动阶段采样,其不稳定状态可 能出现一次按键动作被认为是多次按下的情况。为了避免抖动可能带来的误操作,我们要做的 措施就是给按键消抖(即采样稳定闭合阶段)。消抖方法分为硬件消抖和软件消抖,我们常用软 件的方法消抖。

软件消抖:方法很多,我们例程中使用最简单的延时消抖。检测到按键按下后,一般进行 10ms 延时,用于跳过抖动的时间段,如果消抖效果不好可以调整这个 10ms 延时,因为不同类型的按键抖动时间可能有偏差。待延时过后再检测按键状态,如果没有按下,那我们就判断这是抖动或者干扰造成的;如果还是按下,那么我们就认为这是按键真的按下了。对按键释放的判断同理。

硬件消抖:利用 RC 电路的电容充放电特性来对抖动产生的电压毛刺进行平滑出来,从而 实现消抖,但是成本会更高一点,本着能省则省的原则,我们推荐使用软件消抖即可。

2、原理图
image-20230401144752521

开发指南原话:这里需要注意的是: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、原理图
image-20230401140359458

当 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实现
新建工程步骤
image-20230811155315029
示例1:跑马灯实验

与视频内容相同:第33讲 基础篇-新建STM32CubeMX工程步骤_哔哩哔哩_bilibili

示例2:蜂鸣器实验

功能:蜂鸣器每隔 300ms 响或者停一次。LED0 每隔 300ms 亮或者灭一次。LED0 亮的时候蜂鸣器不叫,而 LED0 熄灭的时候,蜂鸣器叫。

实验原理见正点原子例程2:蜂鸣器实验

注:时钟源的配置见上述视频

1、将PB5(LED0引脚)和PB8(蜂鸣器引脚)设置为输出模式

image-20230822120608063 image-20230822120636668

2、设置PB5引脚参数

点击Pinout&Configuration下的System Core下的GPIO来进行设置,初始电平为低,推挽输出,上拉,低速(高速、中速都行)

image-20230822120924317

3、设置PB8引脚参数

初始电平为低,推挽输出,上拉,低速(高速、中速都行)

image-20230822121216126

4、设置Debug

我使用的是SW来进行Debug

image-20230822121355555

注:配置工程具体操作见上述视频

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引脚

image-20230822153625750

2、配置PE4引脚

image-20230822153506570

3、配置PE3引脚

image-20230822153529022

4、配置PA0引脚

image-20230822153551367

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 */
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值