STM32学习笔记(二)流水灯
这次我们来实现LED流水灯成为点灯大师 。
使用的核心板的MCU型号为STM32F103ZET6,使用标准库函数来实现。
一、 原理部分
1.1 LED原理
其中 PWR 是系统电源指示灯,为蓝色。 LED0和 LED1分别接在 PB5 和 PE5 上,LED0为红色,LED1为绿色。
它们采用共阳极接法,所以端口置为低电平,LED灯就会亮起。
所以思路很明确,我们点亮一个LED灯后,每隔一段时间翻转它对应的IO口,就可以看到它在不断地闪烁了。
1.2 GPIO原理
在配置 STM32 外设的时候,任何时候都要先使能该外设的时钟。GPIO 是挂载在 APB2 总线上的外设,
在固件库中对挂载在 APB2 总线上的外设时钟使能是通过函数 RCC_APB2PeriphClockCmd()来实现的。
)
使能APB2外设时钟后,我们需要初始化IO参数,使用GPIO_Init()函数来实现。
完成以上的操作,我们才可以操作对应的IO口来控制LED灯的亮灭。
二、工程部分
我们先把之前建的工程模板复制,放到一个新的文件夹,文件夹可以取名为"LED流水灯",然后打开工程。
首先我们要先初始化 LED灯的IO端口,然后才能对它们进行操作。
之前谈到的操作IO口的基本三个步骤,可以总结为:
1.RCC开启对应的GPIO时钟
2.使用GPIO_Init函数初始化对应GPIO
3.使用GPIO输出/输入函数来控制对应GPIO口
所以第一步,我们要先使用RCC_APB2PeriphClockCmd()开启外设时钟。
//使能外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//对应在PB口的LED0
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);//对应在PE口的LED1
第二步就是使用GPIO_Init函数初始化对应GPIO参数了。
该函数的定义:
void GPIO_Init (GPIO_TypeDef* GPIOx, GPIO_InitTypeDef*GPIO_InitStruct);
这个函数有两个参数,第一个参数是用来指定 GPIO,取值范围为 GPIOA~GPIOG。
第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。
该结构体的定义为:
typedef struct
{
uint16_t GPIO_Pin; //对应要初始化的IO口
GPIOSpeed_TypeDef GPIO_Speed; //IO口输出速度设置
GPIOMode_TypeDef GPIO_Mode; //IO口输入或输出模式
}GPIO_InitTypeDef;
对于简单的IO口操作,速度没有太大的要求,一般取50MHz。然后GPIO的输出模式设置为推挽输出,在这种模式下,
IO口的驱动能力很强。
接下来就可以使用 GPIO_Init()函数来初始化GPIO了。
//配置外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
//结构体变量
GPIO_InitTypeDef GPIO_InitStruct;
//结构体对应参数
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //设置为推挽输出模式
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
//GPIO初始化,两个LED灯对应的都是Pin_5,所以可以用同一个结构体来初始化
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_Init(GPIOE,&GPIO_InitStruct);
通过以上代码,我们就是实现了LED的IO口初始化的操作。这些跟硬件相关的内容我们可以放在"HardWare"文件下。
事实上,每次我们初始化LED灯的GPIO都是相同的操作,我们可以在"HardWare"文件下,创建并加入"LED.h"以及"LED.c"两个文件,并且把以上的代码封装在函数"LED_Init()"中。
先在头文件"LED.h"中声明函数:
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h" // Device header
//函数声明
void LED_Init(void);
#endif
然后是"LED.c"中定义函数:
上面的函数就是我们初始化LED灯的GPIO的函数,你可能会注意到末尾我添加了两句新的代码:
//两个LED灯端口置为高电平,让它们在初始化后保持熄灭状态
GPIO_SetBits(GPIOB,GPIO_Pin_5);
GPIO_SetBits(GPIOE,GPIO_Pin_5);
这就是我们初始化IO口后,对IO口操作的函数了,它们的意思也很好理解,GPIO_SetBits(GPIOB,GPIO_Pin_5)即对GPIOB的5号端口置高电平(位操作),我们之前说过,这块板子的LED灯是共阳极接法,所以如果它们对应IO口输出被置为高电平,它们就会保持熄灭状态。
如果你想点亮LED灯的话,可以使用GPIO_ResetBits(GPIOx,GPIO_Pin),它们把指定的GPIO口输出置为低电平,对应的LED灯就会亮起。
现在我们就可以在主函数中编写流水灯的程序了,我们先让LED0亮起(红色),LED1熄灭;然后延时一段时间,让LED0熄灭,LED1亮起(绿色)。然后不断地循环这个过程,这就实现了LED流水灯。
先在主函数中插入必要的头文件:
#include “stm32f10x.h”
#include “LED.h” //这里面有我们需要的初始化LED灯的GPIO的函数
然后我们需要一个延时函数,这里采用最简单的延时方法,通过让代码跑空循环来实现延时,这样的延时精度不会很高。
void Delay(uint32_t time)
{
uint32_t i,j;
for(i=0;i<time;++i)
{
for(j=0;j<10000;++j)
{
}
}
}
接下来就是main函数里面的部分了,写一个while循环,然后轮流让两个灯亮起即可。
int main(void)
{
//初始化LED
LED_Init();
while(1)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_5);//PB5号口置为低电平,对应的LED0亮起
GPIO_SetBits(GPIOE,GPIO_Pin_5);//PE5号口置为高电平,LED1熄灭
Delay(500); //延时一段时间
GPIO_SetBits(GPIOB,GPIO_Pin_5);//PB5号口置为高电平,LED0熄灭
GPIO_ResetBits(GPIOE,GPIO_Pin_5);//PE5号口置为低电平,LED1亮起
Delay(500); //延时一段时间
}
}
将代码烧录进单片机,开始点灯!
<iframe allowfullscreen="true" data-mediaembed="csdn" frameborder="0" id="XD9rPM2y-1717239332299" src="https://live.csdn.net/v/embed/395176"></iframe>LED流水灯
三、加入宏定义
你在看别人的代码的时候,有没有发现同样是点灯,但是别人的程序好像更简洁、直观一点?这有很大的原因是因为类似"LED1_ON"、"LED2_OFF"这样的宏定义的使用。
还是之前的main函数,但是可以是下面这个形式:
int main(void)
{
//初始化LED
LED_Init();
while(1)
{
LED0_ON;
LED1_OFF;
Delay(500); //延时一段时间
LED0_OFF;
LED1_ON;
Delay(500); //延时一段时间
}
}
其实操作很简单,只需要在"LED.h"的头文件加入一些宏定义即可。
/* 调用标准库函数方法 */
#define LED0_OFF GPIO_SetBits(LED0_GPIO,LED0_GPIO_PIN)
#define LED0_ON GPIO_ResetBits(LED0_GPIO,LED0_GPIO_PIN)
#define LED1_OFF GPIO_SetBits(LED1_GPIO,LED1_GPIO_PIN)
#define LED1_ON GPIO_ResetBits(LED1_GPIO,LED1_GPIO_PIN)
这就相当于
LED0_ON=GPIO_ResetBits(LED0_GPIO,LED0_GPIO_PIN); //LED0对应的GPIOB_Pin_5置0,点灯
LED0_OFF=GPIO_SetBits(LED0_GPIO,LED0_GPIO_PIN);//LED0对应的GPIOB_Pin_5置1,灭灯
LED1_ON =GPIO_ResetBits(LED1_GPIO,LED1_GPIO_PIN);//LED1对应的GPIOE_Pin_5置0,点灯
LED1_OFF =GPIO_SetBits(LED1_GPIO,LED1_GPIO_PIN);//LED1对应的GPIOE_Pin_5置1,灭灯
其中
LED0_GPIO=GPIOB,LED0_GPIO_PIN=GPIO_Pin_5
LED1_GPIO=GPIOE,LED1_GPIO_PIN=GPIO_Pin_5
它们也是宏定义的变量,这样程序就会更直观易懂,并且移植工程非常方便,甚至有时候只需要修改对应的几个宏定义就可以完美运行。
"LED.h"中涉及的宏定义如下,
#ifndef __LED_H
#define __LED_H
#include "stm32f10x.h" // Device header
/* 宏定义 */
#define LED0_RCC_CLOCKGPIO RCC_APB2Periph_GPIOB
#define LED0_GPIO_PIN GPIO_Pin_5
#define LED0_GPIO GPIOB
#define LED1_RCC_CLOCKGPIO RCC_APB2Periph_GPIOE
#define LED1_GPIO_PIN GPIO_Pin_5
#define LED1_GPIO GPIOE
/* 调用标准库函数方法 */
#define LED0_OFF GPIO_SetBits(LED0_GPIO,LED0_GPIO_PIN)
#define LED0_ON GPIO_ResetBits(LED0_GPIO,LED0_GPIO_PIN)
#define LED1_OFF GPIO_SetBits(LED1_GPIO,LED1_GPIO_PIN)
#define LED1_ON GPIO_ResetBits(LED1_GPIO,LED1_GPIO_PIN)
/*函数声明*/
void LED_Init(void);
#endif
此外,再对"LED_Init"函数中的输入的变量用宏定义的变量替换,这样修改宏定义的时候,对应的初始化函数也会随之修改,这样就能十分方便地移植了。
标签:LED1,LED0,LED,PIN,Pin,笔记,STM32,流水,GPIO From: https://blog.csdn.net/savage_hurricane/article/details/137005840