STM32单片机——GPIO外设

使用模块化编程与板级支持包的想法进行GPIO相关外设的构建

GPIO口配置步骤

GPIO口的初始化

在Drivers驱动中有stm32 hal库的相关文件,我们可以找到stm32f1xx_hal_gpio.c文件,查找初始化各个GPIO口的方法

1
2
3
4
5
6
7
8
9
10
11
//__HAL_RCC_GPIOx_CLK_ENABLE();使能对应端口 x可以替换为各个GPIO口
//例如 如果我想要初始化GPIOA口有关设备,配置的时候我就应该先
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_Init; //创建一个GPIO_InitTypeDef结构体对象,用来存放相应配置
GPIO_Init.Pin = ;
GPIO_Init.Mode = ;
GPIO_Init.Pull = ;
GPIO_Init.Speed = ;

//设置完成端口以后,就使用函数
HAL_GPIO_Init(GPIOA,&GPIO_Init);//使用GPIO_Init的数据配置GPIOA端口
GPIO_Init.Pin GPIO_PIN_k(k∈[0,15])
激活引脚号 引脚编号
GPIO.Init.Mode GPIO_MODE_INPUT GPIO_MODE_OUTPUT_PP GPIO_MODE_OUTPUT_OD
模式 输入模式 推挽输出 开漏输出
调控端口模式 从引脚读取数据 推挽输出的方式输出,控制一般外设用这个 开漏输出的方式输出,一般用于通信协议中?
GPIO_Init.Speed GPIO_SPEED_FREQ_LOW GPIO_SPEED_FREQ_MEDIUM GPIO_SPEED_FREQ_HIGH
模式 低速 中速 高速
GPIO_Init.Pull GPIO_NOPULL GPIO_PULLUP GPIO_PULLDOWN
模式 无上下拉 有上拉 有下拉
功能说明(用于输入模式时) 没有输入时,电平处于不稳定状态 没有输入时,处于高电平状态 没有输入时,处于低电平状态

GPIO口的读取

To get the level of a pin configured in input mode use HAL_GPIO_ReadPin().

1
2
3
4
5
6
7
8
9
10
//使用函数
/**
* @brief Reads the specified input port pin.
* @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
* @param GPIO_Pin: specifies the port bit to read.
* This parameter can be GPIO_PIN_x where x can be (0..15).
* @retval The input port pin value.
*/
HAL_GPIO_ReadPin(GPIOx,GPIO_Pin_x);//GPIOx,x可取ABCD等端口字母,GPIO_Pin_x x可取
//具体端口编号

可以使用上述函数,读取调用此函数时对应引脚的电平状态

GPIO口写入

To set/reset the level of a pin configured in output mode use
HAL_GPIO_WritePin()/HAL_GPIO_TogglePin().

可以使用上述两个函数给处于输出状态的端口写入值

方法一:写具体状态

1
void HAL_GPIO_WritePin(引脚字母GPIOx,引脚编号GPIO_PIN_x, 电平状态(GPIO_SET or GPIO_RESET))

GPIO_PinState 可以取

  1. GPIO_RESET = 0 (低电平)

  2. GPIO_SET = 1 (高电平)

方法二:反转电平状态

1
HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_12) //以A12口为例,翻转A12口电平状态

构建板级支持包

可以通过在工程存放的文件夹下,创建一个User的空间,用于存放各种外设。

用各个外设的名字来命名User的子文件夹,在子文件夹中存放.c and .h文件

记住:要将文件夹所在目录添加到#include配置中,然后可以用相对路径法索引.h文件

要把.c文件添加到工程(中的组——方便管理)中!!否则无法编译

第一个外设:二极管点亮

特点

引脚长的那一端接高电平,引脚短的那一端 接到低电平时,二极管会被点亮

二极管控制代码

LED_GPIO.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#ifndef __LED_GPIO_H_
#define __LED_GPIO_H_

#include "stm32f1xx_hal.h" //每个板级支持包文件都要先include hal库头文件
//通过define 将函数操作与字符等同,更方便 ,注意:define放在.h文件而不是.c文件
#define LED_R_ON do{HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET);}while(0)
#define LED_R_OFF do{HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET);}while(0)
#define LED_R_CHANGE do{HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1);}while(0)

#define LED_B_ON do{HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_RESET);}while(0)
#define LED_B_OFF do{HAL_GPIO_WritePin(GPIOA,GPIO_PIN_3,GPIO_PIN_SET);}while(0)
#define LED_B_CHANGE do{HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_3);}while(0)

#define LED_G_ON do{HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);}while(0)
#define LED_G_OFF do{HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);}while(0)
#define LED_G_CHANGE do{HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_4);}while(0)


//在头文件中声明函数先,然后在别的.c文件下编写函数具体内容,具体
//存放这个函数体的.c文件叫啥无所谓,只要那个.c文件#include 了这个.h文件
//编译器就知道,你这个.c函数里面放的就是.h文件里面声明的这个玩意,最后编译放在一起
//main函数只要也#include 了这个.h文件,就也能使用编译出来的
void LED_INIT(void);

#endif

//记得要留一行出来

LED_GPIO.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "./LED/LED_GPIO.h"

void LED_INIT(void){
//初始化时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_Init;
GPIO_Init.Pin = GPIO_PIN_1 | GPIO_PIN_3 |GPIO_PIN_4;
GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init.Pull= GPIO_NOPULL;
GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA,&GPIO_Init);

}

这样,我们在使用各种外设的时候就可以直接使用,而不需要

第二个外设:蜂鸣器

特点

在给I/O口低电平的时候,蜂鸣器会响起

蜂鸣器控制

bee.h文件

1
2
3
4
5
6
7
8
9
10
#ifndef __BEE_H_
#define __BEE_H_

#include "stm32f1xx_hal.h"

void BEE_GPIO_INIT(void); //初始化控制蜂鸣器的端口
void BEE_CHANGE(void); // 改变蜂鸣器端口的状态


#endif

bee.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "./BEE/bee.h"

void BEE_GPIO_INIT(void){
//初始化时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef GPIO_Init;
GPIO_Init.Pin = GPIO_PIN_12;
GPIO_Init.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_Init.Pull= GPIO_NOPULL;
GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB,&GPIO_Init);
return;

}
void BEE_CHANGE(void){

HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_12);
return;
}

第三个外设:按键

特点

按键按下的时候会有抖动,由于连接方式,我们选择了将GND与端口相连,因此我们需要配置上拉输入模式,用来保持没有按下的时候读取的值为高电平,按下了之后读取的是低电平

每次检测到电平变化时(按下or松开导致的)时,需要Delay()一下,消除抖动

实质上:我在检测到电平变化的时候已经能够确定哪个按键被按下或者松开了,我还要delay而不是马上进行操作的原因就是防止按键带来的抖动对系统会造成的潜在影响,所以别急,先delay一下,也耽误不了多少时间

按键控制

key.h

1
2
3
4
5
6
7
8
9
#ifndef __KEY_H_
#define __KEY_H_

#include "stm32f1xx_hal.h"

void KEY_GPIO_INIT(void); //按键(对应端口)初始化
uint8_t KEY_READ(void); //读取哪个按键被按下

#endif

key.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "./KEY/key.h"  //包含.h文件
void KEY_GPIO_INIT(void){
//使能时钟
__HAL_RCC_GPIOB_CLK_ENABLE();
//创建结构体
GPIO_InitTypeDef KEY_GPIO_Init;
KEY_GPIO_Init.Mode = GPIO_MODE_INPUT; //设置为输入模式
KEY_GPIO_Init.Pin = GPIO_PIN_0 | GPIO_PIN_1;
KEY_GPIO_Init.Pull = GPIO_PULLUP; //设置为上拉输入模式
KEY_GPIO_Init.Speed = GPIO_SPEED_FREQ_LOW;
//初始化
HAL_GPIO_Init(GPIOB,&KEY_GPIO_Init);

}

/** @brief 用于读取返回特定按键的值
* @parm i:按键编号
* @retval GPIO_PinState
*/
uint8_t KEY_READ(void){ //检测两个按键中哪个被按下或者都没有被按下
if (HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET){
HAL_Delay(20); //消除按下抖动
while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET); //不操作 直到按钮松开
HAL_Delay(20); //消除松手抖动
return 1;
}
else if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET){
HAL_Delay(20); //消除按下抖动
while(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET); //不操作 直到按钮松开
HAL_Delay(20); //消除松手抖动
return 2;
}
return 0;

}