一、stm32跑马灯实验
a.GPIO
-
general purpose input output
-
通用输入输出端口,可以做输入也可做输出,GPIO端口可通过程序配置成输入或输出。
-
STM32FXXXIGT6一共9组IO:PA~ PI,其中PA~ PH每组16个IO,PI只有PI0~PI11。16*8+12=140,一共140个IO口。
-
STM32大部分引脚除了当GPIO用,还可复用为外设功能引脚,比如串口。节省引脚资源。
b.GPIO的8种工作模式
- 4种输入模式:输入浮空、输入上拉、输入下拉、模拟输入
- 4种输出模式带上下拉:开漏输出(带上拉或下拉)、开漏复用功能、推挽式输出、推挽式复用
- 推挽输出可以输出强高低电平,连接数字器件
- 开漏输出只可以输出强低电平,高电平得靠外部电阻拉高。输出端相当于三级管的集电极
c.GPIO寄存器
4个32位配置寄存器
GPIOx_MODER 模式
GPIOx_OTYPER 输出类型
GPIOx_OSPEEDR 输出速度
GPIOx_PUPDR 上拉下拉
2个32位数据寄存器
GPIOx_IDR 输入数据
GPIOx_ODR 输出数据
1个32位置位/复位寄存器
GPIOx_BSRR 置位/复位
1个32位锁存寄存器
GPIOx_LCKR 配置锁存
2个32位复用功能寄存器
GPIOx_AFRL&GPIOx_AFRH 复用功能
- 每组IO口由10个寄存器组成
- 如果配置一个IO口需要2个位,32位寄存器配置一组IO口,16个IO口
- 如果配置一个IO口只需要1个位,高16位保留
1.寄存器定义
F767:stm32f767xx.h
文件中查找GPIO得到:
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;
再查找GPIO_TypeDef找到:
#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)
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
#define GPIOI ((GPIO_TypeDef *) GPIOI_BASE)
#define GPIOJ ((GPIO_TypeDef *) GPIOJ_BASE)
#define GPIOK ((GPIO_TypeDef *) GPIOK_BASE)
可以看出GPIOA是一个结构体指针,它指向基地址。
GPIOA->ODR即可访问GPIOA端口的ODR输出寄存器。
2.跑马灯硬件连接
可以看到,两个LED灯,一端连到LED0、LED1,另一端通过上拉电阻连到VCC。如果PB0输出0,那么LED1导通。
GPIO输出方式:采用可以输出高低电平的 推挽输出(上拉)
3.配置寄存器操作IO口步骤
-
初始化HAL库:HAL_Init();
-
初始化系统时钟:
stm32F767:Stm32_Clock_Init(431,25,2,9);
-
使能IO口时钟。配置IO口时钟使能寄存器:RCC->AHB1ENR
RCC AHB1 外设时钟寄存器 (RCC_AHB1ENR)
-
初始化IO口模式。配置四个配置寄存器
GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR、GPIOx_PUPDR
-
操作IO口,输出高低电平
配置寄存器GPIOX_ODR或者GPIOx_BSRR
4.手写跑马灯
在HALLIB里面的stm32f7xx_hal.c文件中可以找到HAL_Init函数,复制过来粘到main()文件中,完成了初始化HAL库。
在SYSTEM里面的sys.c文件中找到Stm32_Clock_Init函数,复制过来粘到main()文件中,完成了初始化系统时钟。
在HALLIB里面的stm32f7xx_hal.c文件中搜索RCC_TypeDef,可以看到:
typedef struct
{
__IO uint32_t CR; /*!< RCC clock control register, Address offset: 0x00 */
__IO uint32_t PLLCFGR; /*!< RCC PLL configuration register, Address offset: 0x04 */
__IO uint32_t CFGR; /*!< RCC clock configuration register, Address offset: 0x08 */
__IO uint32_t CIR; /*!< RCC clock interrupt register, Address offset: 0x0C */
__IO uint32_t AHB1RSTR; /*!< RCC AHB1 peripheral reset register, Address offset: 0x10 */
__IO uint32_t AHB2RSTR; /*!< RCC AHB2 peripheral reset register, Address offset: 0x14 */
__IO uint32_t AHB3RSTR; /*!< RCC AHB3 peripheral reset register, Address offset: 0x18 */
uint32_t RESERVED0; /*!< Reserved, 0x1C */
__IO uint32_t APB1RSTR; /*!< RCC APB1 peripheral reset register, Address offset: 0x20 */
__IO uint32_t APB2RSTR; /*!< RCC APB2 peripheral reset register, Address offset: 0x24 */
uint32_t RESERVED1[2]; /*!< Reserved, 0x28-0x2C */
__IO uint32_t AHB1ENR; /*!< RCC AHB1 peripheral clock register, Address offset: 0x30 */
__IO uint32_t AHB2ENR; /*!< RCC AHB2 peripheral clock register, Address offset: 0x34 */
__IO uint32_t AHB3ENR; /*!< RCC AHB3 peripheral clock register, Address offset: 0x38 */
uint32_t RESERVED2; /*!< Reserved, 0x3C */
__IO uint32_t APB1ENR; /*!< RCC APB1 peripheral clock enable register, Address offset: 0x40 */
__IO uint32_t APB2ENR; /*!< RCC APB2 peripheral clock enable register, Address offset: 0x44 */
uint32_t RESERVED3[2]; /*!< Reserved, 0x48-0x4C */
__IO uint32_t AHB1LPENR; /*!< RCC AHB1 peripheral clock enable in low power mode register, Address offset: 0x50 */
__IO uint32_t AHB2LPENR; /*!< RCC AHB2 peripheral clock enable in low power mode register, Address offset: 0x54 */
__IO uint32_t AHB3LPENR; /*!< RCC AHB3 peripheral clock enable in low power mode register, Address offset: 0x58 */
uint32_t RESERVED4; /*!< Reserved, 0x5C */
__IO uint32_t APB1LPENR; /*!< RCC APB1 peripheral clock enable in low power mode register, Address offset: 0x60 */
__IO uint32_t APB2LPENR; /*!< RCC APB2 peripheral clock enable in low power mode register, Address offset: 0x64 */
uint32_t RESERVED5[2]; /*!< Reserved, 0x68-0x6C */
__IO uint32_t BDCR; /*!< RCC Backup domain control register, Address offset: 0x70 */
__IO uint32_t CSR; /*!< RCC clock control & status register, Address offset: 0x74 */
uint32_t RESERVED6[2]; /*!< Reserved, 0x78-0x7C */
__IO uint32_t SSCGR; /*!< RCC spread spectrum clock generation register, Address offset: 0x80 */
__IO uint32_t PLLI2SCFGR; /*!< RCC PLLI2S configuration register, Address offset: 0x84 */
__IO uint32_t PLLSAICFGR; /*!< RCC PLLSAI configuration register, Address offset: 0x88 */
__IO uint32_t DCKCFGR1; /*!< RCC Dedicated Clocks configuration register1, Address offset: 0x8C */
__IO uint32_t DCKCFGR2; /*!< RCC Dedicated Clocks configuration register 2, Address offset: 0x90 */
} RCC_TypeDef;
可以找到AHB1ENR。由寄存器可知只需要改变位1。在main中写RCC->AHB1ENR |=1<<1(1左移一位,然后或上RCC->AHB1ENR可以把位1置为1),或者写成RCC->AHB1ENR=0x02。
- 配置GPIO端口模式寄存器GPIOx_MODER
因为需要配置的端口位是0和1,所以需要配置MODER1和MODER0.因为是推挽输出,所以设置最后四个位0101,那么也就是5,最终MODER寄存器设置成0x05。在main函数中写上GPIOB->MODER=0x05。
- 接着配置GPIO 端口输出类型寄存器 (GPIOx_OTYPER)
由于是推挽输出,所以端口0和1都是0。在main函数中写上GPIOB->OTYPER=0x00;
- 接着配置GPIO 端口输出速度寄存器 (GPIOx_OSPEEDR)
设置为高速的话后面两个端口的两个位也都是11。这样的话设置成GPIOB->OSPEEDR=0x0f;虽然只写了8位,但是他其实一共有32位,前面都是零,可以省略不写,但是要规范的话是需要把0全写上补满32位。
- 配置GPIO 端口上拉/下拉寄存器 (GPIOx_PUPDR)
这里要配置为上拉,所以后面两个端口都是0101,也就是要设置成GPIOB->PUPDR=0x05;
然后操作IO口,输出高低电平,配置寄存器GPIOX_ODR或者GPIOx_BSRR。
要配置端口1和0输出高电平,也就是需要BS1和BS0为1,其它位保持不变为0,也就是说要配置成0x03 。如果配置端口1和0输出低电平,也就是需要BR1和BR0为1进行复位,其它位保持不变为0,也就是说要配置成0x00030000 。写成代码也就是GPIOB->BSRR=0x00000003;//1,GPIOB->BSRR=0x00030000;//0。
然后由于开和关间隔时间过短,所以还需要一个delay函数。在SYSTEM-delay.c文件中可以找到delay.h,然后进入delay.h头文件,可看到void delay_init(u8 SYSCLK);void delay_ms(u16 nms);void delay_us(u32 nus);三个函数。使用delay首先进行初始化delay_init(216);其中216是stm32f767的系统时钟。然后就可以调用delay_ms(500);
最终的代码为:
#include "sys.h"
#include "delay.h"
#include "usart.h"
int main(void)
{
HAL_Init();
Stm32_Clock_Init(431,25,2,9);
delay_init(216);
RCC->AHB1ENR |= 1<<1;
GPIOB->MODER=0x05;
GPIOB->OTYPER=0x00;
GPIOB->OSPEEDR=0x0f;
GPIOB->PUPDR=0x05;
while(1)
{
GPIOB->BSRR=0x00000003;//1
delay_ms(500);
GPIOB->BSRR=0x00030000;//0
delay_ms(500);
}
}
5.使用HAL库
优点:方便在各个stm32平台移植。
HAL库很好的解决了程序移植的问题,不同型号的stm32芯片它的标准库是不一样的,例如在F4上开发的程序移植到F3上是不能通用的,而使用HAL库,只要使用的是相通的外设,程序基本可以完全复制粘贴,注意是相同外设。
在HALLIB文件夹下可以找到stm32f7xx_hal_gpio.c文件,然后找到stm32f7xx_hal_gpio.h文件,可以看到里面有几个函数:
/* Initialization and de-initialization functions *****************************/
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);//初始化函数
void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin);
/**
/* IO operation functions *****************************************************/
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//读取输入电平函数
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);//设置输出电平函数
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//电平翻转函数
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//引脚电平锁定函数
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin);//外部中断函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
-
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);
初始化一个或多个IO口(同一组)的工作模式,输出类型,速度以及上下拉方式。也就是一组IO口的4个配置寄存器。MODER、OSPEEDR、OTYPER、PUPDR。在stm32f7xx_hal_gpio.h找到HAL_GPIO_Init函数,然后go to definition,可以找到HAL_GPIO_Init的实现,可以发现他主要操作的是MODER、OSPEEDR、OTYPER、PUPDR寄存器。
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1; //PB1,0 GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速 HAL_GPIO_Init(GPIOB,&GPIO_Initure); //一个参数是设置哪个组,另一个参数是初始化结构体
-
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
读取某个GPIO的输入电平,实际操作是GPIOx_IDR寄存器
-
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
设置引脚输出电平
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET); //PB0置1 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET); //PB1置1
-
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
输出电平翻转函数
6.配置HAL库操作IO口步骤
-
初始化HAL库 HAL_Init()
-
初始化系统时钟 Stm32_Clock_Init()
-
使能IO口时钟 _HAL_RCC_GPIOB_CLK_ENABLE() ;配置IO口时钟使能寄存器:RCC->AHB1ENR
在HALLIB文件-stm32f7xx_hal_rcc.ex.h文件中可以找到:__HAL_RCC_GPIOA_CLK_ENABLE(),这个函数实际上操作的是RCC->AHB1ENR
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \ __IO uint32_t tmpreg; \ 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(0)
-
初始化IO口模式HAL_GPIO_Init() ;操作寄存器:GPIOx_MODER OTYPER OSPEEDR PUPDR
在stm32f7xx_hal_gpio.c文件找到HAL_GPIO_Init(),然后观察它的参数GPIO_TypeDef *GPIOx和GPIO_InitTypeDef *GPIO_Init,现在要弄明白GPIOx是什么,在下面的代码中找到对GPIOx的操作。 assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));然后选择IS_GPIO_ALL_INSTANCE点击go to definition找到:
#define IS_GPIO_ALL_INSTANCE(__INSTANCE__) (((__INSTANCE__) == GPIOA) || \ ((__INSTANCE__) == GPIOB) || \ ((__INSTANCE__) == GPIOC) || \ ((__INSTANCE__) == GPIOD) || \ ((__INSTANCE__) == GPIOE) || \ ((__INSTANCE__) == GPIOF) || \ ((__INSTANCE__) == GPIOG) || \ ((__INSTANCE__) == GPIOH) || \ ((__INSTANCE__) == GPIOI) || \ ((__INSTANCE__) == GPIOJ) || \ ((__INSTANCE__) == GPIOK))
可以发现该函数是对GPIOA之类的进行操作,要对GPIOB进行操作,所以GPIO_TypeDef *GPIOx参数设置成GPIOB。
下面要设置GPIO_InitTypeDef *GPIO_Init参数,可以发现这个是结构体类型。
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;
然后对这个结构体成员进行设置,首先要判断MODE要怎么设置,找到
assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
assert_param(IS_GPIO_PULL(GPIO_Init->Pull));go to definition一下IS_GPIO_MODE,可以发现mode可以设置成很多个类型,我们这里设置成推挽输出,所以设置成GPIO_MODE_OUTPUT_PP。
#define IS_GPIO_PIN_ACTION(ACTION) (((ACTION) == GPIO_PIN_RESET) || ((ACTION) == GPIO_PIN_SET)) #define IS_GPIO_PIN(PIN) (((PIN) & GPIO_PIN_MASK ) != (uint32_t)0x00) #define IS_GPIO_MODE(MODE) (((MODE) == GPIO_MODE_INPUT) ||\ ((MODE) == GPIO_MODE_OUTPUT_PP) ||\ ((MODE) == GPIO_MODE_OUTPUT_OD) ||\ ((MODE) == GPIO_MODE_AF_PP) ||\ ((MODE) == GPIO_MODE_AF_OD) ||\ ((MODE) == GPIO_MODE_IT_RISING) ||\ ((MODE) == GPIO_MODE_IT_FALLING) ||\ ((MODE) == GPIO_MODE_IT_RISING_FALLING) ||\ ((MODE) == GPIO_MODE_EVT_RISING) ||\ ((MODE) == GPIO_MODE_EVT_FALLING) ||\ ((MODE) == GPIO_MODE_EVT_RISING_FALLING) ||\ ((MODE) == GPIO_MODE_ANALOG)) #define IS_GPIO_SPEED(SPEED) (((SPEED) == GPIO_SPEED_LOW) || ((SPEED) == GPIO_SPEED_MEDIUM) || \ ((SPEED) == GPIO_SPEED_FAST) || ((SPEED) == GPIO_SPEED_HIGH)) #define IS_GPIO_PULL(PULL) (((PULL) == GPIO_NOPULL) || ((PULL) == GPIO_PULLUP) || \ ((PULL) == GPIO_PULLDOWN))
然后找到pin的设置,发现里面有个GPIO_PIN_MASK,选中GPIO_PIN_MASK点击go to definition。可以发现下面这些代码,所以这个可以设置成GPIO_PIN_0。
#define GPIO_PIN_0 ((uint16_t)0x0001U) /* Pin 0 selected */ #define GPIO_PIN_1 ((uint16_t)0x0002U) /* Pin 1 selected */ #define GPIO_PIN_2 ((uint16_t)0x0004U) /* Pin 2 selected */ #define GPIO_PIN_3 ((uint16_t)0x0008U) /* Pin 3 selected */ #define GPIO_PIN_4 ((uint16_t)0x0010U) /* Pin 4 selected */ #define GPIO_PIN_5 ((uint16_t)0x0020U) /* Pin 5 selected */ #define GPIO_PIN_6 ((uint16_t)0x0040U) /* Pin 6 selected */ #define GPIO_PIN_7 ((uint16_t)0x0080U) /* Pin 7 selected */ #define GPIO_PIN_8 ((uint16_t)0x0100U) /* Pin 8 selected */ #define GPIO_PIN_9 ((uint16_t)0x0200U) /* Pin 9 selected */ #define GPIO_PIN_10 ((uint16_t)0x0400U) /* Pin 10 selected */ #define GPIO_PIN_11 ((uint16_t)0x0800U) /* Pin 11 selected */ #define GPIO_PIN_12 ((uint16_t)0x1000U) /* Pin 12 selected */ #define GPIO_PIN_13 ((uint16_t)0x2000U) /* Pin 13 selected */ #define GPIO_PIN_14 ((uint16_t)0x4000U) /* Pin 14 selected */ #define GPIO_PIN_15 ((uint16_t)0x8000U) /* Pin 15 selected */ #define GPIO_PIN_All ((uint16_t)0xFFFFU) /* All pins selected */ #define GPIO_PIN_MASK ((uint32_t)0x0000FFFFU) /* PIN mask for assert test */
同理设置pull和speed
-
操作IO口,输出高低电平 HAL_GPIO_WritePin(); 配置寄存器GPIOX_ODR或者GPIOx_BSRR
最后在main中写:
int main()
{
GPIO_InitTypeDef GPIO_InitStructure;
HAL_Init();
delay_init(216);
Stm32_Clock_Init(431,25,2,9);
__HAL_RCC_GPIOB_CLK_ENABLE();//使能PB时钟
GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_PP;//推挽输出
GPIO_InitStructure.Pin=GPIO_PIN_0 | GPIO_PIN_1;
GPIO_InitStructure.Pull=GPIO_PULLUP;//上拉
GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;//高速
HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);
while(1){
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);//PB0=0
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);//PB1=0
delay_ms(500);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);//PB0=1
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);//PB1=1
delay_ms(500);
}
}
二、stm32按键实验
1.电路连接图
由图可知,KEY0、KEY1、KEY2需要设置成上拉,这样的话,如果按键KEY0、KEY1、KEY2按下,那么将得到低电平信号,如果KEY0、KEY1、KEY2没有按下,将是高电平信号。WK_UP设置为下拉,KEY_UP按下的话,将检测到高电平信号,没有按下将是低电平信号。KEY0->PH3上拉输入、KEY1->PH2上拉输入、KEY2->PC13上拉输入、WK_UP->PA0下拉输入。
2.步骤
1.使能按键对应IO口时钟
__HAL_RCC_GPIOx_CLK_ENABLE();
由电路连接图,可以发现,涉及到的是A、C、H口:
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟
__HAL_RCC_GPIOC_CLK_ENABLE(); //开启GPIOC时钟
__HAL_RCC_GPIOH_CLK_ENABLE(); //开启GPIOH时钟
2.初始化IO模式:上拉/下拉输入
HAL_GPIO_Init
根据KEY0->PH3上拉输入、KEY1->PH2上拉输入、KEY2->PC13上拉输入、WK_UP->PA0下拉输入。
GPIO_InitTypeDef GPIO_Initure;
GPIO_Initure.Pin=GPIO_PIN_0; //PA0
GPIO_Initure.Mode=GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull=GPIO_PULLDOWN; //下拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_13; //PC13
GPIO_Initure.Mode=GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_3; //PH2,3
HAL_GPIO_Init(GPIOH,&GPIO_Initure);
3.扫描IO口电平(库函数/寄存器)
HAL库函数:GPIO_PinState HAL_GPIO_ReadPin();
寄存器:GPIOx_IDR
4.编写按键扫描逻辑
按键支持连按,举个遥控器的例子,一直按,一直换台。如果不支持连按,就说明这个遥控器按下一次换台,如果后面也一直按,它不会换台。如果不支持连按的话,需要一个变量记录上一次的状态,如果是上一次是未按下,那么这一次检测到按下,说明这个按键算是按下了。如果检测上一次按下了,那么这次按不算,也就是说,按键按下了,没有松开,只能算一次。
按键扫描支持连按的思路:
u8 KEY_Scan(void){
if(KEY按下)
{
delay_ms(10);//延时防抖
if(KEY确实按下){
return KEY_Value;
}
return 无效值;
}
}
每次调用getValue函数之后,返回值是多少
int getValue()
{
static int flag = 0;
flag++;
return flag;
}
每一次调用,第一次返回1,第二次返回2…因为static变量存在,static修饰的变量只被初始化一次,并且保持最近的值,哪怕创建它的函数已经结束,这个变量也不会被释放,下次调用是同一个地址,所以里面的值是上次的。static定义的变量有记忆的作用。所以不支持连按的思路:注意key_up一定记录的是上一次的状态。
u8 KEY_SCAN(void)
{
static u8 key_up = 1;//没有按下
if(key_up && KEY按下)//上一次松开,这次按下
{
delay_ms(10);
key_up=0;
if(KEY确实按下){
return KEY_VALUE;
}
}
else if(KEY没有按下)
key_up=1;
}
3.代码
//按键初始化函数
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟
__HAL_RCC_GPIOC_CLK_ENABLE(); //开启GPIOC时钟
__HAL_RCC_GPIOH_CLK_ENABLE(); //开启GPIOH时钟
GPIO_Initure.Pin=GPIO_PIN_0; //PA0
GPIO_Initure.Mode=GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull=GPIO_PULLDOWN; //下拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_13; //PC13
GPIO_Initure.Mode=GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_3; //PH2,3
HAL_GPIO_Init(GPIOH,&GPIO_Initure);
}
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,WKUP按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>WK_UP!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1; //按键松开标志
if(mode==1)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1))
{
delay_ms(10);
key_up=0;
if(KEY0==0) return KEY0_PRES;
else if(KEY1==0) return KEY1_PRES;
else if(KEY2==0) return KEY2_PRES;
else if(WK_UP==1) return WKUP_PRES;
}else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1;
return 0; //无按键按下
}
int main(void)
{
u8 key;
u8 led0sta=1,led1sta=1; //LED0,LED1的当前状态
Cache_Enable(); //打开L1-Cache
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz
delay_init(216); //延时初始化
uart_init(115200); //串口初始化
LED_Init(); //初始化LED
KEY_Init(); //按键初始化
while(1)
{
key=KEY_Scan(0); //得到键值
if(key)
{
switch(key)
{
case WKUP_PRES: //控制LED0,LED1互斥点亮
led1sta=!led1sta;
led0sta=!led1sta;
break;
case KEY2_PRES: //控制LED0翻转
led0sta=!led0sta;
break;
case KEY1_PRES: //控制LED1翻转
led1sta=!led1sta;
break;
case KEY0_PRES: //同时控制LED0,LED1翻转
led0sta=!led0sta;
led1sta=!led1sta;
break;
}
LED0(led0sta); //控制LED0状态
LED1(led1sta); //控制LED1状态
}else delay_ms(10);
}
}