目录
1.简介
通过上一个博客介绍的HAL库操作GPIO的函数,来实现LED流水的的实验。笔者使用的开发板是STM32F103ZET6为MCU的正点原子精英板,读者可以使用自己的开发板来复现实验。开发板不是重点,重点是学习HAL库的使用。如果读者没学习过GPIO以及HAL库操作GPIO,可以通过下面的链接进行简单的学习一下:
2.LED模块简介
LED(Light Emitting Diode),即发光二极管,是一种能够将电能转化为可见光的半导体器件。LED的核心部分是由P型半导体和N型半导体组成的晶片,在P型半导体和N型半导体之间有一个过渡层,称为PN结。当电流通过导线作用于这个晶片时,电子和空穴在PN结复合,能量以光子的形式释放出来,从而发出光芒。不同材料的半导体可以发出不同颜色的光,导通压降也不同。
3.硬件连接
笔者的STM32开发板上有两个LED灯,分别连接在了PB5和PE5引脚上,如下图所示:
PB5连接的是红灯,LED0;PE5是绿灯,LED1。另外的一个LED灯用作电源指示灯。
首先,两个灯的正极分别连接了一个电阻,用于限流,防止电流过大烧毁,然后并联到3.3V的VCC电源上。阴极分别连接STM32的PB5和PE5上。(这两个灯的导通压降我忘记了。。)
4.编写代码
4.1led.h编写
首先复制一份模板工程,在/Drivers/BSP目录下创建一个文件夹,起名为LED,用于存放驱动LED灯的代码,并在该文件夹中创建led.c和led.h文件,如下图所示:
之后打开工程编写led.h的代码。
首先编写预编译处理的语句如下图所示:
#ifndef _LED_H_:这是一个预处理指令,用于检查是否已经定义了 _LED_H_ 这个宏。如果这个宏没有被定义,那么编译器会执行接下来的代码。
#define _LED_H_:如果 _LED_H_ 没有被定义,那么这个指令会定义 _LED_H_ 宏。这通常用于确保头文件只被包含一次,避免重复定义的问题。
#endif:这是预处理指令的结束标志,表示如果 _LED_H_ 宏没有被定义,那么执行到这里就结束了。如果 _LED_H_ 已经被定义,那么整个代码块都会被忽略。
这种结构通常用于C语言的头文件中,以确保头文件只被包含一次,避免重复定义的问题。
使用宏定义先定义出LED灯使用的GPIO端口和引脚,如下图所示:
之后定义了枚举值,枚举出灯的状态,灯的号码,等只有亮灭两个状态,所以代码如下图所示:
之后,思考需要封装哪些函数,LED灯初始化,点亮某个LED灯,关闭某个LED灯,全部打开,全部关闭,流水灯,某个灯闪烁,全部闪烁。
4.2LED灯初始化
相当于初始化GPIO引脚首先要打开所用外设的时钟,之后配置成推挽输出模式,输出速度慢速,引脚和上下拉电阻,最后写入初始值。代码如下:
/**
* @brief LED初始化函数
* @param 无
* @retval 无
*/
void LED_Init(void)
{
/* 1.打开时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
/* 2.配置GPIO */
GPIO_InitTypeDef gpio_initure = {0};
gpio_initure.Pin = LED0_PIN; //LED0引脚
gpio_initure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
gpio_initure.Speed = GPIO_SPEED_FREQ_LOW; //慢速
gpio_initure.Pull = GPIO_NOPULL; //不使用上下拉电阻,推挽输出模式下,上下拉电阻通常无实际作用
HAL_GPIO_Init(LED0_PORT, &gpio_initure); //调用GPIO初始化函数
gpio_initure.Pin = LED1_PIN; //LED0引脚
HAL_GPIO_Init(LED1_PORT, &gpio_initure); //调用GPIO初始化函数
/* 3.写入初始值,默认关灯 */
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_RESET);
}
注意:
1.推挽输出模式下,通常不需要配置上拉或下拉电阻,因为GPIO输出驱动器已直接控制电平。当输出高电平时,引脚由内部电路驱动到VCC,当输出低电平时,引脚由内部电路驱动到GND。
2.开漏输出模式下,GPIO只能拉低电平或处于高阻态。必须外接上拉电阻,否则无法输出高电平。典型应用:I²C总线、多设备共享线路(避免电平冲突)。
4.3点亮、关闭LED灯
要使LED灯被点亮,需要LED两端的电压差大于导通条件。查看上面原理图,正极接的3.3V,负极接在GPIO引脚,要使得LED灯被点亮,需要做的是GPIO输出低电平,这样LED灯的两端电压差就大于导通压降了。同理,熄灭LED灯就输出高电平。
封装几个函数,想要什么操作就调用什么函数即可。将打开、关闭、全部打开、全部关闭分别封装成函数,代码如下:
/**
* @brief 打开LEDx灯
* @param 无
* @retval 无
*/
void LED_TurnOn(LED_Num ledx)
{
if(ledx == LED0)
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_RESET);
else if(ledx == LED1)
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_RESET);
}
/**
* @brief 打开全部LED灯
* @param 无
* @retval 无
*/
void LED_TurnOnAll(void)
{
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_RESET);
}
/**
* @brief 关闭LEDx灯
* @param 无
* @retval 无
*/
void LED_TurnOff(LED_Num ledx)
{
if(ledx == LED0)
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_SET);
else if(ledx == LED1)
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_SET);
}
/**
* @brief 关闭全部LED灯
* @param 无
* @retval 无
*/
void LED_TurnOffAll(void)
{
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_SET);
}
4.4切换LED灯状态
直接使用HAL_GPIO_TogglePin函数来完成操作。代码如下:
/**
* @brief 切换LEDx灯状态
* @param 无
* @retval 无
*/
void LED_Toggle(LED_Num ledx)
{
if(ledx == LED0)
HAL_GPIO_TogglePin(LED0_PORT, LED0_PIN);
else if(ledx == LED1)
HAL_GPIO_TogglePin(LED1_PORT, LED1_PIN);
}
/**
* @brief 切换全部的LED灯状态
* @param 无
* @retval 无
*/
void LED_ToggleAll(void)
{
HAL_GPIO_TogglePin(LED0_PORT, LED0_PIN);
HAL_GPIO_TogglePin(LED1_PORT, LED1_PIN);
}
4.5查询LED灯状态
再封装一个函数,将某个LED灯的枚举值作为参数,函数的返回值是灯的状态。
这样就使用查询的方式来改变LED灯的状态,如果查询的关闭状态,就打开;如果的打开状态,就关闭。
代码如下:
/**
* @brief 查询LEDx灯状态
* @param 无
* @retval LED_State,LED_OFF为关闭状态,LED_ON为开启状态
*/
LED_State LED_CheckStatus(LED_Num ledx)
{
GPIO_PinState bitstatus = (GPIO_PinState)0;
LED_State status = LED_OFF;
if(ledx == LED0)
bitstatus = HAL_GPIO_ReadPin(LED0_PORT, LED0_PIN);
else if(ledx == LED1)
bitstatus = HAL_GPIO_ReadPin(LED1_PORT, LED1_PIN);
if(bitstatus == GPIO_PIN_SET)
status = LED_OFF;
else if(bitstatus == GPIO_PIN_RESET)
status = LED_ON;
return status;
}
5.全部代码
5.1led.h
我把led.h的代码放在这里,供大家参考。
#ifndef __LED_H__
#define __LED_H__
/*****************************************************
头文件
******************************************************/
#include "sys.h"
/*****************************************************
宏定义
******************************************************/
#define LED0_PORT GPIOB
#define LED0_PIN GPIO_PIN_5
#define LED1_PORT GPIOE
#define LED1_PIN GPIO_PIN_5
/*****************************************************
数据类型定义
******************************************************/
typedef enum
{
LED_OFF = 0u,
LED_ON
}LED_State;
typedef enum
{
LED0 = 0u,
LED1
}LED_Num;
/*****************************************************
函数声明
******************************************************/
void LED_Init(void);
void LED_TurnOn(LED_Num ledx);
void LED_TurnOnAll(void);
void LED_TurnOff(LED_Num ledx);
void LED_TurnOffAll(void);
void LED_Toggle(LED_Num ledx);
void LED_ToggleAll(void);
LED_State LED_CheckStatus(LED_Num ledx);
#endif
5.2led.c
封装的函数:
#include "led.h"
/**
* @brief LED初始化函数
* @param 无
* @retval 无
*/
void LED_Init(void)
{
/* 1.打开时钟 */
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
/* 2.配置GPIO */
GPIO_InitTypeDef gpio_initure = {0};
gpio_initure.Pin = LED0_PIN; //LED0引脚
gpio_initure.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出
gpio_initure.Speed = GPIO_SPEED_FREQ_LOW; //慢速
gpio_initure.Pull = GPIO_NOPULL; //不使用上下拉电阻,推挽输出模式下,上下拉电阻通常无实际作用
HAL_GPIO_Init(LED0_PORT, &gpio_initure); //调用GPIO初始化函数
gpio_initure.Pin = LED1_PIN; //LED0引脚
HAL_GPIO_Init(LED1_PORT, &gpio_initure); //调用GPIO初始化函数
/* 3.写入初始值,默认关灯 */
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_SET);
}
/**
* @brief 打开LEDx灯
* @param 无
* @retval 无
*/
void LED_TurnOn(LED_Num ledx)
{
if(ledx == LED0)
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_RESET);
else if(ledx == LED1)
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_RESET);
}
/**
* @brief 打开全部LED灯
* @param 无
* @retval 无
*/
void LED_TurnOnAll(void)
{
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_RESET);
}
/**
* @brief 关闭LEDx灯
* @param 无
* @retval 无
*/
void LED_TurnOff(LED_Num ledx)
{
if(ledx == LED0)
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_SET);
else if(ledx == LED1)
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_SET);
}
/**
* @brief 关闭全部LED灯
* @param 无
* @retval 无
*/
void LED_TurnOffAll(void)
{
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, GPIO_PIN_SET);
}
/**
* @brief 切换LEDx灯状态
* @param 无
* @retval 无
*/
void LED_Toggle(LED_Num ledx)
{
if(ledx == LED0)
HAL_GPIO_TogglePin(LED0_PORT, LED0_PIN);
else if(ledx == LED1)
HAL_GPIO_TogglePin(LED1_PORT, LED1_PIN);
}
/**
* @brief 切换全部的LED灯状态
* @param 无
* @retval 无
*/
void LED_ToggleAll(void)
{
HAL_GPIO_TogglePin(LED0_PORT, LED0_PIN);
HAL_GPIO_TogglePin(LED1_PORT, LED1_PIN);
}
/**
* @brief 查询LEDx灯状态
* @param 无
* @retval LED_State,LED_OFF为关闭状态,LED_ON为开启状态
*/
LED_State LED_CheckStatus(LED_Num ledx)
{
GPIO_PinState bitstatus = (GPIO_PinState)0;
LED_State status = LED_OFF;
if(ledx == LED0)
bitstatus = HAL_GPIO_ReadPin(LED0_PORT, LED0_PIN);
else if(ledx == LED1)
bitstatus = HAL_GPIO_ReadPin(LED1_PORT, LED1_PIN);
if(bitstatus == GPIO_PIN_SET)
status = LED_OFF;
else if(bitstatus == GPIO_PIN_RESET)
status = LED_ON;
return status;
}
5.3流水灯 -- main.c
在这里实现流水灯,另外,main函数还有测试其他函数的代码,被我用#if 0 ...... #endif注释了,这里面的代码不会被编译执行。
代码如下:
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
uart1_init(115200);
LED_Init();
while(1)
{
//流水灯
LED_TurnOn(LED0);
LED_TurnOff(LED1);
delay_ms(500);
LED_TurnOn(LED1);
LED_TurnOff(LED0);
delay_ms(500);
#if 0
LED_TurnOn(LED0);
delay_ms(500);
LED_TurnOff(LED0);
delay_ms(500);
LED_TurnOn(LED1);
delay_ms(500);
LED_TurnOff(LED1);
delay_ms(500);
LED_TurnOnAll();
delay_ms(500);
LED_TurnOffAll();
delay_ms(500);
LED_Toggle(LED1);
delay_ms(500);
LED_Toggle(LED0);
delay_ms(500);
LED_ToggleAll();
delay_ms(500);
if(LED_CheckStatus(LED0) == LED_ON)
{
LED_Toggle(LED0);
delay_ms(500);
}
else if(LED_CheckStatus(LED0) == LED_OFF)
{
LED_Toggle(LED0);
delay_ms(500);
}
#endif
}
}