面向应用学习stm32(1)-GPIO输出点亮灯

前导:本文的目的与,意在于面向应用的学习单片机,故不会涉及太多的原理知识,例如寄存器之类的。

主要目的在于面向应用的学习单片机,学会单片机的基础用法,开发板采取野火的指南者f103。

作者大二小白,写的不好的地方轻点喷,欢迎评论区交流
所有的代码和笔记 开源在 Gitee仓库

1 单片机简介

1.1简单介绍

单片微型计算机-简称单片机(MCU(MicrbControl Unit))。它和我们个人电脑的差别是,我们的电脑里,CPU,内存,硬盘,输入输出等等的都是独立的,把这些零部件安装到主板上,再接上电源,屏幕,鼠标,键盘等等的外设,组成了我们的电脑。而单片机则是将CPU,内存,硬盘,输入输出集成到了一块芯片上。再接上其他外设进行操作。

它有着比普通的PC电脑更低廉的价格,更小的占用空间,但与之对应的是,性能相对电脑较弱等。可是在很多的电子元器件中,我们往往并不需要那么强大的性能,需要更多的考虑价格,体积之类的商业因素。那么单片机的用武之地就来了。

1.2 STM32

是意法半导体(ST)推出一款32位的单片机。是目前最常见的也是各大高校应用教学最多的单片机。

基于官方提供的简单易用的库函数开发方式只要开发者具备一定的C语言基础,就可以很快的上手它

同时因为他的价格并不昂贵,提供的性能也足够强大,生态环境优秀,所以也较为适合初学者入门使用

STM32的产品型号命名如下

例如 STM32F103C8T6 指的就是

基础型的48引脚,内存容量64kb,QFP封装,工作在-40-85摄氏度的32位单片机

img

关于单片机的地址总线,数据总线等的底层硬件知识,由于这里主要讲的是面向应用的开发,底层涉及的很少,所以不讲了。

2 开发环境搭建

关于开发环境可以参考这个

开发环境搭建好后,复制一份我的工程目录底下的Template1就可以准备开始点亮我们的第一个LED灯啦。

3 GPIO输出

3.1简介

在做我们的第一份工程之前,先对GPIO进行最简单的介绍

GPIO(General-Purpose IO ports) ,也就是通用的输入输出IO口。

我们可以通过控制不同引脚的GPIO的输出,驱动我们需要的电路,或发送数据。

同时,也可以通过读取不同引脚的输入,接收数据,判断状态等等的。

好,那么开始准备写代码吧首先,将原理图的pdf和stm32库函数的中文翻译手册下载下来。同时复制我的工程目录下的Template1。下载地址

到你们的文件夹中,改成你们想要的名字,我改成了LED,复制完后点击打开,再点击Project。最后点击这个小绿图标,就进入了工程。

image-20220504140741755

进入工程后首先引入我们眼中的是这样的界面,边上那一串就是STM10x_开头的头文件,就是ST官方为我们提供的库函数,将来我们将会大量使用

image-20220504141101603

3.2思路分析

看完整体开发环境,我们回到需求,进行如下分析。

我们需要亮灯,根据电路的原理,自然我们要知道灯泡连接的是哪个引脚。

这个时候大家打开F103指南者原理图的pdf,找到其中的LED模块,有如下发现。

首先我们可以看到,他是三个灯,分别连接了 PB0,PB1,PB5。(并且后面还写了红绿蓝的分布)至此,灯泡引脚确定了。

接下来能写代码亮灯了吗?显然不行,我们还需要知道灯泡是低电平点亮还是高电平

image-20220504141340087

观察原理图。

可见,PB5输出高电平的话。根据电路分析,3.3V的电压无法从右边流向左边,二极管将无法导通。

PB5低电平的话,3.3V从右往左通过,经过二极管后,灯泡被点亮,是红色灯,因为原理图上注明了R,另外两个则是G(Green)和B(Blue)。

分析完毕,思路如下

  • PB0,1,5需要是输出的模式
  • 需要将输出的电平设置为低电平

3.3 代码实现

我们回到我们的工程代码当中,

首先我们需要开启GPIOB的时钟,为什么要开先不做解释,总之,使用任意一个GPIO或者任意模块,都要开启他们的时钟。

时钟相关的操作,位于stm32f10x_rcc.h之中。我们点开他,可以在里面发现两个东西

image-20220504143028911

在这边可以发现宏定义了GPIOB的时钟,挂载在APB2上,继续往底下翻。

image-20220504143227890

发现有一行使能APB2总线上的某一个时钟源的函数。传的参数就是我们之前看到的宏定义的时钟,将他们复制到主函数while(1)前面

//主函数
int main(void)
{
	//使能GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	while(1)
	{
			
	}
}

开启时钟后,我们准备开始配置GPIOB的0,1,5端口。进入stm32f10x_gpio.h

看到了这样的一个结构体GPIO_InitTypeDef,里面的参数就是,引脚,速度,模式。根据这个结构体以及内部的变量我们就可以初始化GPIO口。

image-20220504150200325

同样,这些模式,Pin口,速度等等也被定义在了里面,如下图所示,枚举里都是定义好的参数,拿来用就可以了。

image-20220504150211983

这里的GPIO_MODE可以看到有八个模式,这里我们先记住点亮LED要用推挽输出,也是我们最常见的输出模式

输入模式

-输入浮空(GPIO_Mode_IN_FLOATING)
-输入上拉(GPIO_Mode_IPU)
-输入下拉(GPIO_Mode_IPD)
-模拟输入(GPIO_Mode_AIN)

输出模式

-开漏输出(GPIO_Mode_Out_OD)
-开漏复用功能(GPIO_Mode_AF_OD)
-推挽式输出(GPIO_Mode_Out_PP)//最为常用
-推挽式复用功能(GPIO_Mode_AF_PP)

image-20220504144001116

看完以上消息后,我们开始写代码,声明GPIO初始化结构体,并给结构体成员赋值

	//声明GPIO的初始化结构体
	GPIO_InitTypeDef init;
	//使能GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	//配置GPIO属性
	init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5;//引脚用 | 一起配置
	init.GPIO_Mode = GPIO_Mode_Out_PP //推挽输出模式
	init.GPIO_Speed = GPIO_Speed_10MHz;//输出速度,亮灯比较不关心这个

现在有个问题,我怎么知道我配置的是哪个0,1,5。我需要的是GPIOB的0,1,5,可是代码里并没有声明啊。

ok我们还是回到头文件里

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HrkrsYdk-1651651676133)(1.1GPIO_Output_Led.assets/image-20220504144945462.png)]

在头文件的最后可以发现有这些函数,我用红圈勾出了我们待会需要的

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);// GPIO的初始化。

参数有GPIOx,和GPIO初始化结构体的指针,也就是说。

这个GPIOx,就是指定你要配置的端口,并把初始化结构体里的配置写入其中

那么主函数加入这行后,最终我们初始化程序如下
初始化到这里就完成了

//主函数
int main(void)
{
	//声明GPIO的初始化结构体
	GPIO_InitTypeDef init;
	//使能GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	//配置GPIO属性
	init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5;//引脚用 | 一起配置
	init.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出模式
	init.GPIO_Speed = GPIO_Speed_10MHz;//输出速度,亮灯比较不关心这个
	GPIO_Init(GPIOB,&init);//完成初始化
	while(1)
	{
			
	}
}

完成初始化后,我们准备亮灯了,回到之前说的,PB5低电平亮红色等。

刚刚那个图片中圈出了GPIO_SetBits和GPIO_ResetBits函数,前者是设置高电平,后者是设置低电平。

参数1是GPIOx,参数2是GPIO_Pin,那么我们要的操作是 PB5,低电平,代码如下

//主函数
int main(void)
{
    //...省略前面初始化部分...
    
    //先全部置高电平,关灯,防止引脚默认悬空电压影响
	GPIO_SetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5);
    //亮红灯
	GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	while(1)
	{
			
	}
}

这些代码的使用,功能等等,在固件库的中文翻译版里有,大家可以去查询,例子里也有对应用法,例如

image-20220504151144513

1651648069274.jpg

4 三个小实验

4.1需求

首先提出以下的需求。

  • 需要亮更多颜色的灯
  • 闪烁灯
  • 流水灯

4.2 亮更多颜色灯

首先第一点我们好解决,他的灯是RGB灯,那么我们只需要排列组合2的三次方,8种情况就可以亮出所有的灯。

看到这其实就想提出来 函数的概念

我们需要使用一个 控制颜色的函数,来控制8种情况(八个if),这样的话代码的可读性也高一点,不然看着一堆set,Reset真的很难看懂是亮什么灯

于是我写出了如下函数和宏定义

4.2.1亮灯函数

#define LED_GREEN 0
#define LED_BLUE  1
#define LED_RED 	2
#define LED_WHITE 3
#define LED_PURPLE 4
#define LED_YELLOW 5
#define LED_CYAN 6
#define LED_OFF   7

void LED_Color(int color)
{
	
	if(color==0){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_SetBits(GPIOB,GPIO_Pin_1);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==1){
		GPIO_SetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==2){
		GPIO_SetBits(GPIOB,GPIO_Pin_0);
		GPIO_SetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==3){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==4){
		GPIO_SetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==5){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_SetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==6){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==7)
	{
		GPIO_SetBits(GPIOB,GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5);
	}
}

现在,我们只需要一行函数,就可以控制灯的各自颜色了,根据不同的宏定义亮不同的颜色

//主函数
int main(void)
{
	//声明GPIO的初始化结构体
	GPIO_InitTypeDef init;
	//使能GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	//配置GPIO属性
	init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5;
	init.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出模式
	init.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&init);
    //亮红灯
	LED_Color(LED_RED);
	while(1)
	{
			
	}
}

然后可以发现,配置部分也很长很麻烦,于是我决定把配置部分也给移出来

void LED_Init()
{
	//声明GPIO的初始化结构体
	GPIO_InitTypeDef init;
	//使能GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	//配置GPIO属性
	init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5;
	init.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出模式
	init.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&init);
}

4.2.2 完整代码

#include "stm32f10x_gpio.h" 

#define LED_GREEN 0
#define LED_BLUE  1
#define LED_RED 	2
#define LED_WHITE 3
#define LED_PURPLE 4
#define LED_YELLOW 5
#define LED_CYAN 6
#define LED_OFF   7

void Delay(int i){
	for(;i>0;i--);
}

void LED_Init();
void LED_Color(int color);
//主函数
int main(void)
{

	LED_Init();
	LED_Color(LED_YELLOW);
	while(1)
	{
			
	}
}

void LED_Init()
{
	//声明GPIO的初始化结构体
	GPIO_InitTypeDef init;
	//使能GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	//配置GPIO属性
	init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5;
	init.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出模式
	init.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&init);
}
void LED_Color(int color)
{
	
	if(color==0){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_SetBits(GPIOB,GPIO_Pin_1);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==1){
		GPIO_SetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==2){
		GPIO_SetBits(GPIOB,GPIO_Pin_0);
		GPIO_SetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==3){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==4){
		GPIO_SetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==5){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_SetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==6){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==7)
	{
		GPIO_SetBits(GPIOB,GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5);
	}
}

4.2.3分文件改造

其实你看到末尾的函数实现体,还是会觉得很长,毕竟一个程序来到了84行的长度,那未来呢?main.c将不断膨胀,所以我决定分文件编程。

  • 去到项目的目录下,进入User,新建led.c,led.h。
  • 在工程中添加刚刚新建的led.c文件
  • 包含led.h头文件将头文件,宏定义和函数声明放入led.h,将函数定义放入led.c

image-20220504152655157

双击User,将刚刚的led.c添加就去

image-20220504152717051

image-20220504152815877

在led.c里先包含led.h然后编译一遍

image-20220504152902977

编译完后可以看到led.h被包括进来了

image-20220504152931824

从主函数剪切以下代码到led.h里

#include "stm32f10x_gpio.h" 

#define LED_GREEN 0
#define LED_BLUE  1
#define LED_RED 	2
#define LED_WHITE 3
#define LED_PURPLE 4
#define LED_YELLOW 5
#define LED_CYAN 6
#define LED_OFF   7

void LED_Init();
void LED_Color(int color);

将我们刚刚的主函数实现体剪切到led.c里面,并在上方包含led.h

#include "led.h"

void LED_Init()
{
	GPIO_InitTypeDef init;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	init.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5;
	init.GPIO_Mode = GPIO_Mode_Out_PP;
	init.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&init);
}
void LED_Color(int color)
{
	
	if(color==0){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_SetBits(GPIOB,GPIO_Pin_1);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==1){
		GPIO_SetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==2){
		GPIO_SetBits(GPIOB,GPIO_Pin_0);
		GPIO_SetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==3){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==4){
		GPIO_SetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==5){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_SetBits(GPIOB,GPIO_Pin_1);
		GPIO_ResetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==6){
		GPIO_ResetBits(GPIOB,GPIO_Pin_0);
		GPIO_ResetBits(GPIOB,GPIO_Pin_1);
		GPIO_SetBits(GPIOB,GPIO_Pin_5);
	}
	if(color==7)
	{
		GPIO_SetBits(GPIOB,GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_5);
	}
}

最后,我们回到主函数,包含led.h文件,整个主函数结构如下,现在主函数真正的简洁了,只有短短18行代码

并且未来我们要使用led的话,也只需要把led.c和led.h复制到项目里,包含头文件,调用函数就可以了

先调用LED_Init();进行GPIO的配置和初始化

再调用LED_Color(int color)这里面传入你要的颜色(在led.h里宏定义了),就可以对应显示了。
例子:
image-20220504153301029

4.3 闪烁灯

有了刚刚的函数,闪烁灯的颜色其实就很好控制了,闪烁本质上就是开和关。

而我们需要不断的开和关就好了,那我就要在while(1)这个死循环里不断开关了,我写出第一份代码

#include "led.h"
void Delay(int i){
	for(;i>0;i--);
}
int main(void)
{
	LED_Init();
	while(1)
	{
			LED_Color(LED_YELLOW);
			LED_Color(LED_OFF);
	}
}

不断开关完成了,但我们会发现没有效果,为什么?

因为程序执行的实在是太快了,开关的速度快到我们无法感受到。人眼无法捕捉。

所以我们加入延时,开一会儿,关一会儿,再重新开一会儿,关一会儿。这里的延时采用最简单的for循环延时,传入参数,减到0,for循环才能退出。

#include "led.h"
void Delay(int i){
	for(;i>0;i--);
}
//主函数
int main(void)
{
	LED_Init();
	while(1)
	{
		LED_Color(LED_YELLOW);
		Delay(5000000);
		LED_Color(LED_OFF);
		Delay(5000000);
	}
}

到这,我们的效果就已经完成了,但我还是不满意。闪烁灯是一个功能,我希望一个功能就是一个函数。

所以我先把延时先分文件编写了(如之前led一样的操作),因为延时这个功能本质上是不属于led的。

然后再把闪烁灯变成一个函数

延时分文件这里不再讲了,详细操作和前面的led是差不多的。讲一讲闪烁函数的实现吧。

我希望闪烁的颜色能被控制,所以要传入一个参数 也就是颜色

参数类型就和我前面LED_Color();这个颜色控制函数的参数值一样就行。

首先去led.h里声明函数,增加一行,由于我们还需要包含延时,所以延时的头文件也包括进来

#include "delay.h"
void LED_Flashing(int color);//闪烁函数

在led.c里增加实现体

void LED_Flashing(int color)
{
		LED_Color(color);//亮对应颜色
		Delay(5000000);
		LED_Color(LED_OFF);
		Delay(5000000);
}

主函数调用

#include "led.h"

int main(void)
{
	LED_Init();
	while(1)
	{
		 LED_Flashing(LED_Red);
	}
}

4.4 流水灯

在有了前面那些基础过后,我们直接分析如何写流水灯函数吧

首先,流水灯函数要做到的就是把所有颜色流水显示一遍,这里可以用到for循环的思想。

结合我前面的LED_Color控制函数就可以了

可以看到,写这个LED_Color函数给我们带来的好处实在是太多了

led.h增加一行

void LED_Stream();

led.c增加函数

void LED_Stream()
{
	int i;
	for(i = 0; i<8; i++)
	{
		LED_Color(i);
		Delay(5000000);
	}
}

完事,主函数调用

#include "led.h"

int main(void)
{
	LED_Init();
	while(1)
	{
		LED_Stream();
	}
}

5 总结

使用GPIO口输出的步骤(结构体 函数等的,可以在库函数的头文件里找到)

  • 开启对应时钟
  • 配置初始化结构体对应参数
  • 调用GPIO_Init初始化
  • 设置输出电平
  • 抽象成函数使用

到这里,功能就全部完成了,并且结构清晰,我们只需要看led.h的头文件,并在主函数里包含头文件,使用就可以了。

宏定义为需要传参时传入的颜色
image-20220504162211250

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这里煤球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值