STM32 第12讲 GPIO:结构/8种工作模式/寄存器/驱动模型/配置步骤/实验

GPIO简介

General Purpose Input Output: 通用输入输出端口。负责采集外部器件的信息或者控制外部器件工作。

GPIO特点

  • 不同型号的IO口数量不同,查阅选型手册进行查询
  • 快速翻转功能,每次翻转最快只需要两个时钟周期
  • 每个IO口都可以用作中断
  • 具有8种工作模式

电气特性

  • STM32工作电压范围:2V ≤ VDD ≤ 3.6V
  • GPIO识别电压范围:
    对于COMS端口: -0.3V ≤ VIL ≤ 1.164V 1.833V ≤ VIH ≤ 3.6V
  • GPIO输出电流: 单个IO最大25mA

GPIO引脚分布

对于STM32F407ZGT6芯片,一共有144个引脚,其中有112个可供编程的IO口,分为7组:GPIOA、GPIOB、GPIOC、GPIOD、GPIOE、GPIOF 和 GPIOG。
查找数据手册,对于定义了FT的引脚可以兼容5V电压,可以直接接 5V 的外设(注意:如果引脚设置的是模拟输入模式,则不能接 5V!)
在这里插入图片描述

GPIO8种工作模式

GPIO的基本结构

在这里插入图片描述

  • 1.保护二极管
    当引脚输入电压高于
    VDD 时,上面的二极管导通,当引脚输入电压低于 VSS 时,下面的二极管导通,从而使输入芯片内部的电压处于比较稳定的值,但这样保护是有限的。
  • 2.上拉、下拉电阻
    它们阻值大概在 30~50K 欧之间,可以通过上、下两个对应的开关控制,这两个开关由寄存器控制。当引脚外部的器件没有干扰引脚的电压时,即没有外部的上、下拉电压,引脚的电平由引脚内部上、下拉决定,开启内部上拉电阻工作,引脚电平为高,开启内部下拉电阻工作,则引脚电平为低。同样,如果内部上、下拉电阻都不开启,这种情况就是我们所说的浮空模式。浮空模式下,引脚的电平是不可确定的。引脚的电平可以由外部的上、下拉电平决定。需要注意的是,STM32 的内部上拉是一种**“弱上拉”**,这样的上拉电流很弱,如果有要求大电流还是得外部上拉。
  • 3.施密特触发器
    施密特触发器是一种整形电路,可以将非标准的方波,整形成方波。
    当输入电压高于正向阈值电压,输出为高;
    当输入电压低于负向阈值电压,输出为低;
    当输入在正负向阈值电压之间,输出不改变。
    下图为A–比较器与B–施密特触发器的工作比较,可以看出施密特触发器具有滞回特性,具有一定的抗干扰能力。
    在这里插入图片描述
  • 4.P-MOS管与N-MOS管
    MOS管是压控型元件,通过控制栅源电压( Vgs )来实现导通或关闭。
    在这里插入图片描述
    开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。推挽输出:这两只对称的 MOS 管每次只有一只导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载拉电流。推拉式输出既能提高电路的负载能力,又能提高开关速度。

8种工作模式

在这里插入图片描述

  • 1.输入浮空
    ①上拉电阻关闭
    ②下拉电阻关闭
    ③施密特触发器打开
    ④双MOS管不导通
    特点:空闲时,IO状态不确定,由外部环境决定
    应用:按键检测等
    在这里插入图片描述
  • 2.输入上拉
    ①上拉电阻打开
    ②下拉电阻关闭
    ③施密特触发器打开
    ④双MOS管不导通
    特点:空闲时,IO呈现高电平
    应用:在需要外部上拉电阻的时候,可以使用内部上拉电阻,这样可以节省一个外部电阻,但是内部上拉电阻的阻值较大,所以只是“弱上拉”,不适合做电流型驱动
    在这里插入图片描述
  • 3.输入下拉
    ①上拉电阻关闭
    ②下拉电阻打开
    ③施密特触发器打开
    ④双MOS管不导通
    特点:空闲时,IO呈现低电平
    应用:在需要外部下拉电阻的时候,可以使用内部下拉电阻,这样可以节省一个外部电阻,但是内部下拉电阻的阻值较大,所以不适合做电流型驱动。
    在这里插入图片描述
  • 4.模拟功能
    ①上拉电阻关闭
    ②下拉电阻关闭
    ③施密特触发器关闭
    ④双MOS管不导通
    特点:专门用于模拟信号输入或输出,如:ADC和DAC
    在这里插入图片描述
  • 5.开漏输出
    ①P-MOS 被“输出控制”控制在截止状态,因此 IO 的状态取决于 N-MOS 的导通状况。
    ②只有N-MOS还受控制于输出寄存器,“输出控制器”对输入信号进行了逻辑非的操作。
    ③IO 到输入电路的采样电路仍被打开,且可以选择是否使用上下拉电阻。
    特点:不能输出高电平,只能接入上拉电阻后才能输出高电平。

⚫如果输出数据寄存器设置为 0 时,经过“输出控制”的逻辑非操作后,输出逻辑 1 到 N-MOS 管的栅极,这时 N-MOS 管就会导通,使得 I/O 引脚接到 VSS,即输出低电平。
⚫如果输出数据寄存器设置为 1 时,经过“输出控制器”的逻辑非操作后,输出逻辑 0 到 N-MOS 管的栅极,这时 N-MOS 管就会截止。因为 P-MOS 管是一直截止的,使得 I/O 引脚呈现高阻态,即不输出低电平,也不输出高电平。因此要 I/O 引脚输出高电平就必须接上拉电阻。
这时可以接内部上拉电阻,或者接一个外部上拉电阻。由于内部上拉电阻的阻值较大,所以只是“弱上拉”。需要大电流驱动,请接外部的上拉电阻。此外,上拉电阻具有线与特性,即如果有很多开漏模式的引脚连在一起的时候,只有当所有引脚都输出高阻态,电平才为 1,只要有其中一个为低电平时,就等于接地,使得整条线路都为低电平 0。我们的 IIC 通信(IIC_SDA)就用到这个原理。

在开漏输出模式下,施密特触发器是打开的,所以 IO 口引脚的电平状态会被采集到输入数据寄存器中,如果对输入数据寄存器进行读访问可以得到 IO 口的状态。也就是说开漏输出模式下,我们可以对 IO 口进行读数据。
在这里插入图片描述

  • 6.开漏式复用功能
    一个 IO 口可以是通用的 IO 口功能,还可以是其他外设的特殊功能引脚,这就是 IO 口的复用功能。
    当选择复用功能时,引脚的状态是由对应的外设控制,而不是输出数据寄存器。
    ①施密特触发器打开
    ② P-MOS管始终不导通
    特点:
    1、不能输出高电平,必须有外部(或内部)上拉才能输出高电平
    2、由其他外设控制输出

在开漏式复用功能模式下,施密特触发器也是打开的,我们可以读取 IO 口的电平状态,同时外设可以读取 IO 口的信息
在这里插入图片描述

  • 7.推挽输出
    ①施密特触发器打开
    ②往ODR对应位写0,N-MOS管导通,写1则P-MOS管导通
    特点:可输出高低电平,驱动能力强

可以把“输出控制”简单地等效为一个非门。
⚫如果输出数据寄存器设置为 0 时,经过“输出控制”的逻辑非操作后,输出逻辑 1 到 P-MOS管的栅极,这时 P-MOS 管就会截止,同时也会输出逻辑 1 到 N-MOS 管的栅极,这时 N-MOS管就会导通,使得 I/O 引脚接到 VSS,即输出低电平。
⚫如果输出数据寄存器设置为 1 时,经过“输出控制”的逻辑非操作后,输出逻辑 0 到 N-MOS 管的栅极,这时 N-MOS 管就会截止,同时也会输出逻辑 0 到 P-MOS 管的栅极,这时 P-MOS 管就会导通,使得 I/O 引脚接到 VDD,即输出高电平。

在推挽输出模式下,施密特触发器也是打开的,我们可以读取 IO 口的电平状态。
在这里插入图片描述

  • 8.推挽式复用功能
    特点:
    1、可输出高低电平, 驱动能力强
    2、由其他外设控制输出

在这里插入图片描述

GPIO寄存器

STM32F4系列寄存器包括:
4 个 32 位配置寄存器(MODER、OTYPER、OSPEEDR 和 PUPDR)
2 个 32 位数据寄存器(IDR 和 ODR)
1 个 32 位置位/复位寄存器 (BSRR)
1 个 32 位锁定寄存器 (LCKR)
2 个 32 位复用功能选择寄存器(AFRH 和 AFRL)

GPIO端口模式寄存器(GPIOx_MODER)

在这里插入图片描述

GPIO端口输出类型寄存器(GPIOx_OTYPER)

在这里插入图片描述

GPIO端口输出速度寄存器(GPIOx_OSPEEDR)

在这里插入图片描述

GPIO端口上拉/下拉寄存器(GPIOx_PUPDR)

在这里插入图片描述
上面这 4 个配置寄存器就是用来配置 GPIO 的相关模式和状态,它们通过不同的配置组合方法,就决定我们所说的 8 种工作模式。
在这里插入图片描述

端口输入数据寄存器(GPIOx_IDR)

在这里插入图片描述

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

在这里插入图片描述

端口置位/复位寄存器(GPIOx_BSRR)

在这里插入图片描述
首先 BSRR 是只写权限,而 ODR 是可读可写权限。我们首先需要读出来 ODR 寄存器的值,然后对整个 ODR 寄存器重新赋值来达到设置某个或者某些 IO 口的目的,而 BSRR 寄存器,我们就不需要先读,而是直接设置即可,这在多任务实时操作系统中作用很大。BSRR 寄存器还有一个好处,就是 BSRR 寄存器改变引脚状态的时候,不会被中断打断,而 ODR 寄存器有被中断打断的风险。

通用外设驱动模型

  • 初始化(时钟设置、参数设置、IO设置、中断设置)
  • 读函数(从外设读取数据)
  • 写函数(往外设写入数据)
  • 中断服务函数(根据中断标志,处理外设各种中断事务)

GPIO配置步骤

使能时钟(__HAL_RCC_GPIOx_CLK_ENABLE())

#define __HAL_RCC_GPIOA_CLK_ENABLE()   do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)

设置工作模式(HAL_GPIO_Init())

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init)
{
 uint32_t position;
 uint32_t ioposition = 0x00U;
 uint32_t iocurrent = 0x00U;
 uint32_t temp = 0x00U;

 /* Check the parameters */
 assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));
 assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
 assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
 assert_param(IS_GPIO_PULL(GPIO_Init->Pull));

 /* Configure the port pins */
 for(position = 0U; position < GPIO_NUMBER; position++)
 {
   /* Get the IO position */
   ioposition = 0x01U << position;
   /* Get the current IO position */
   iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;

   if(iocurrent == ioposition)
   {
     /*--------------------- GPIO Mode Configuration ------------------------*/
     /* In case of Output or Alternate function mode selection */
     if(((GPIO_Init->Mode & GPIO_MODE) == MODE_OUTPUT) || \
         (GPIO_Init->Mode & GPIO_MODE) == MODE_AF)
     {
       /* Check the Speed parameter */
       assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
       /* Configure the IO Speed */
       temp = GPIOx->OSPEEDR; 
       temp &= ~(GPIO_OSPEEDER_OSPEEDR0 << (position * 2U));
       temp |= (GPIO_Init->Speed << (position * 2U));
       GPIOx->OSPEEDR = temp;

       /* Configure the IO Output Type */
       temp = GPIOx->OTYPER;
       temp &= ~(GPIO_OTYPER_OT_0 << position) ;
       temp |= (((GPIO_Init->Mode & GPIO_OUTPUT_TYPE) >> 4U) << position);
       GPIOx->OTYPER = temp;
      }

     if((GPIO_Init->Mode & GPIO_MODE) != MODE_ANALOG)
     {
       /* Activate the Pull-up or Pull down resistor for the current IO */
       temp = GPIOx->PUPDR;
       temp &= ~(GPIO_PUPDR_PUPDR0 << (position * 2U));
       temp |= ((GPIO_Init->Pull) << (position * 2U));
       GPIOx->PUPDR = temp;
     }

     /* In case of Alternate function mode selection */
     if((GPIO_Init->Mode & GPIO_MODE) == MODE_AF)
     {
       /* Check the Alternate function parameter */
       assert_param(IS_GPIO_AF(GPIO_Init->Alternate));
       /* Configure Alternate function mapped with the current IO */
       temp = GPIOx->AFR[position >> 3U];
       temp &= ~(0xFU << ((uint32_t)(position & 0x07U) * 4U)) ;
       temp |= ((uint32_t)(GPIO_Init->Alternate) << (((uint32_t)position & 0x07U) * 4U));
       GPIOx->AFR[position >> 3U] = temp;
     }

     /* Configure IO Direction mode (Input, Output, Alternate or Analog) */
     temp = GPIOx->MODER;
     temp &= ~(GPIO_MODER_MODER0 << (position * 2U));
     temp |= ((GPIO_Init->Mode & GPIO_MODE) << (position * 2U));
     GPIOx->MODER = temp;

     /*--------------------- EXTI Mode Configuration ------------------------*/
     /* Configure the External Interrupt or event for the current IO */
     if((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
     {
       /* Enable SYSCFG Clock */
       __HAL_RCC_SYSCFG_CLK_ENABLE();

       temp = SYSCFG->EXTICR[position >> 2U];
       temp &= ~(0x0FU << (4U * (position & 0x03U)));
       temp |= ((uint32_t)(GPIO_GET_INDEX(GPIOx)) << (4U * (position & 0x03U)));
       SYSCFG->EXTICR[position >> 2U] = temp;

       /* Clear EXTI line configuration */
       temp = EXTI->IMR;
       temp &= ~((uint32_t)iocurrent);
       if((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
       {
         temp |= iocurrent;
       }
       EXTI->IMR = temp;

       temp = EXTI->EMR;
       temp &= ~((uint32_t)iocurrent);
       if((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
       {
         temp |= iocurrent;
       }
       EXTI->EMR = temp;

       /* Clear Rising Falling edge configuration */
       temp = EXTI->RTSR;
       temp &= ~((uint32_t)iocurrent);
       if((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
       {
         temp |= iocurrent;
       }
       EXTI->RTSR = temp;

       temp = EXTI->FTSR;
       temp &= ~((uint32_t)iocurrent);
       if((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
       {
         temp |= iocurrent;
       }
       EXTI->FTSR = temp;
     }
   }
 }
}

对于关键结构体

typedef struct
{
 __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
 __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
 __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
 __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
 __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
 __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
 __IO uint32_t BSRR;     /*!< GPIO port bit set/reset register,      Address offset: 0x18      */
 __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
 __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

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 */

 uint32_t Alternate;  /*!< Peripheral to be connected to the selected pins. 
                           This parameter can be a value of @ref GPIO_Alternate_function_selection */
}GPIO_InitTypeDef;

设置输出状态(HAL_GPIO_WritePin()/HAL_GPIO_Togglepin())

void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));
  assert_param(IS_GPIO_PIN_ACTION(PinState));

  if(PinState != GPIO_PIN_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }
  else
  {
    GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;
  }
}

void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  uint32_t odr;

  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  /* get current Ouput Data Register value */
  odr = GPIOx->ODR;

  /* Set selected pins that were at low level, and reset ones that were high */
  GPIOx->BSRR = ((odr & GPIO_Pin) << GPIO_NUMBER) | (~odr & GPIO_Pin);
}

设置输入状态(HAL_GPIO_ReadPin())

GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  GPIO_PinState bitstatus;

  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  if((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET)
  {
    bitstatus = GPIO_PIN_SET;
  }
  else
  {
    bitstatus = GPIO_PIN_RESET;
  }
  return bitstatus;
}

跑马灯实验

硬件原理图

在这里插入图片描述
在此原理图中,使用开漏输出或推挽输出均可。

代码

led.h文件

#ifndef __LED_H
#define __LED_H

#include "./SYSTEM/sys/sys.h"


/******************************************************************************************/
/* 引脚 定义 */

#define LED0_GPIO_PORT                  GPIOF
#define LED0_GPIO_PIN                   GPIO_PIN_9
#define LED0_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)             /* PF口时钟使能 */

#define LED1_GPIO_PORT                  GPIOF
#define LED1_GPIO_PIN                   GPIO_PIN_10
#define LED1_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)             /* PF口时钟使能 */

/******************************************************************************************/

/* 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 = RED */

#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 = GREEN */

/* LED取反定义 */
#define LED0_TOGGLE()    do{ HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); }while(0)       /* LED0 = !LED0 */
#define LED1_TOGGLE()    do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0)       /* LED1 = !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/usart/usart.h"
#include "./SYSTEM/delay/delay.h"


void led_init(void);                    /* LED初始化函数声明 */

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
    delay_init(168);                    /* 延时初始化 */
    led_init();                         /* 初始化LED */
    
    while(1)
    {
        HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);     /* LED0 亮 */
        HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);      /* LED1 灭 */
        delay_ms(500);
        HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);       /* LED0 灭 */
        HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);    /* LED1 亮 */
        delay_ms(500);
    }
}

下载验证

在这里插入图片描述
Code:表示程序所占用 FLASH 的大小(FLASH)
RO-data:即 Read Only-data,表示程序定义的常量(FLASH)
RW-data:即 Read Write-data,表示已被初始化的变量(FLASH + RAM)
ZI-data:即 Zero Init-data,表示未被初始化的变量(RAM)

通过按键控制LED亮灭

按键在闭合和断开的时候,都存在抖动现象,即按键在闭合时不会马上就稳定的连接,断开时也不会马上断开。这是机械触点,无法避免。独立按键抖动波形图如下:
在这里插入图片描述
软件消抖:通过延时跳过抖动的时间段,再判断IO输入电平。
硬件消抖:利用 RC 电路的电容充放电特性来对抖动产生的电压毛刺进行平滑出来,从而实现消抖,但是成本会更高一点,本着能省则省的原则,我们推荐使用软件消抖即可。

在该实验中最重要的是按键的去抖操作,其余配置和上面的实验类似。

  • 原理图
    在这里插入图片描述
    按键扫描函数
/**
* @brief 按键扫描函数
* @note 该函数有响应优先级(同时按下多个按键): WKUP > KEY2 > KEY1 > KEY0!!
* @param mode:0 / 1, 具体含义如下:
* @arg 0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
* 必须松开以后, 再次按下才会返回其他键值)
* @arg 1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
* @retval 键值, 定义如下:
* KEY0_PRES, 1, KEY0 按下
* KEY1_PRES, 2, KEY1 按下
* KEY2_PRES, 3, KEY2 按下
* WKUP_PRES, 4, 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 || KEY2 == 0 || WK_UP == 1))
 { /* 按键松开标志为 1, 且有任意一个按键按下了 */
 delay_ms(10); /* 去抖动 */
 key_up = 0;
 if (KEY0 == 0) keyval = KEY0_PRES;
 if (KEY1 == 0) keyval = KEY1_PRES;
 if (KEY2 == 0) keyval = KEY2_PRES;
 if (WK_UP == 1) keyval = WKUP_PRES;
 }
 else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0)
 { /* 没有任何按键按下, 标记按键松开 */
 key_up = 1;
  }
 return keyval; /* 返回键值 */
}
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值