STM32学习 3:GPIO管脚配置与第一个STM32实验:LED灯闪烁

  • Post author:
  • Post category:其他


鄙人是在淘宝购置了一套STM32开发板,跟着随带的光盘学习起来的。碰到不懂的就在CSDN上浏览学习高手的博客,大神们不仅技术很好还乐于分享,蒙其惠泽,未感忘恩,所以自己开始写博客慢慢积累。

如果您是初学者,看到该系列文章后有所帮助,鄙人深感荣幸。如果您是大神,请您指出不足指出。如果没人看到,权当积累与记录了。

鄙人使用普中科技公司的开发板,在淘宝可以找到。芯片是STM32F103ZET6,固件库是V3.5,编译环境是Keil uVision4。


转载请注明完整出处。




1,GPIO管脚简介与配置



1.1GPIO框图

GOIO管脚是用来与外设进行信号传输的,所以和时钟一样,属于基本配置。下面是STM32的GPIO框图。

除了右边的二极管和I/O端口,其余左边的部分都是在芯片里面的,也就是开发板最中央的那个小处理器方块里面。



1.2GPIO不同模式

总的输入输出模式可以分为以下几种:

输入模式:

  • 输入浮空
  • 输入上拉
  • 输入下拉
  • 模拟输入

输出模式:

  • 开漏输出
  • 开漏复用功能
  • 推挽式输出
  • 推挽式复用功能

下面结合框图详细介绍不同的输入输出模式。


输入浮空

图示的蓝框和红框及其左边部分都是在芯片里面。电流从I/O端口进入后经过施密特触发器会变成数字信号保存在输入数据寄存器里面。芯片内部读取输入数据寄存器就知道I/O口输入了什么数据。I/O口高电平输入1,低电平输入0。


输入上拉

相比于输入浮空,可以看到输入上拉在1和2之间多连接了一个上拉电阻。当I/O口没有输入的时候由于上拉电阻接VDD,故而相当于I/O是高电压,只有当I/O是低电压的时候才会输入0,也就是说I/O口常1。


输入下拉

类比输入上拉,输入下拉是常0,只有当输入高电压的时候才会读取到1。


模拟输入

模拟输入信号虽然进入了芯片但是芯片并不能直接将其转化为数字信号,而是送入了片上的外设模块,然后经过A/D转换才变为数字信号读入。


开漏输出

从图中可以看到输出到I/O口之后,还要从输入电路读取输出数据,作为反馈。3处,由于只连接了N-MOS管,所以平时输出低电平,在外设处加上拉电阻可以输出高电平,上拉电阻决定功耗和速度,可以方便实现线与。


开漏复用功能

复用功能可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用),可以在核心板原理图里面看到每一个GPIO口旁边都有一个/符号,表示复用其他功能。


推挽式输出

推挽式输出由于3处有两个MOS管故可以输出0可以输出1。


推挽式复用功能

信号源为复用I/O口的输出,可0,可1。



1.3 GPIO寄存器

其中使用到的寄存器主要有以下几个:

  • 两个32位配置寄存器(GPIOx_CRL ,GPIOx_CRH)
  • 两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR)
  • 一个32位置位/ 复位寄存器(GPIOx_BSRR)
  • 一个16位复位寄存器(GPIOx_BRR)
  • 一个32位锁定寄存器(GPIOx_LCKR)
  • 每个I/O端口位可以自由编程,然而I/O端口寄存器必须按32位字被访问(不允许半字或字节访问)

我们重点关心GPIOx_CRL ,GPIOx_CRH和GPIOx_IDR、GPIOx_ODR。


端口配置低寄存器(GPIOx_CRL)

鄙人的STM32有ABCDEFG共7组I/O口,每一组有16个I/O口端口。如寄存器所示,为了控制每一个管脚需要用到四位数据,那么一组端口就需要16*4=64位。这样就需要两个32位寄存器去控制,所以就会分GPIOx_CRL与GPIOx_CRH,x表示GPIO管脚,分别控制低8个端口和高8个端口。如上图所示,每一个端口中,MODEy控制端口y是输出还是输入以及输出速度。可以设置速度是出于耗能与性能的考虑。CNFy控制y端口的输入输出模式。


端口输入数据寄存器(GPIOx_IDR)

端口输出数据寄存器(GPIOx_ODR)

端口输入数据寄存器和端口输出数据寄存器是1.2中图示的输入数据寄存器和输出数据寄存器,用来存储输入输出数据。



1.4 GPIO库函数配置

如果每次使用GPIO口,就要一个一个去想该怎么设置寄存器,这样效率是很底的。TI官方有相应的库函数,直接使用库函数就可以很方便的对GPIO端口进行初始化。

这个函数就是GPIO_Init(GPIO_TypeDef

GPIOx,GPIO_InitTypeDef

GPIO_InitStruct),可以看看《STM32固件库使用手册》中是怎么说明这个函数的。以下三张图片选自固件库手册的10.2节,为方便叙述在本小节称为第一张,第二张和第三张。

最好把这三张图片认真看几遍。

从第一个图片可以看到该函数的输入有两个,第一个是A,B,C…用来选择作用在哪个GPIO上。第二个输入是一个结构体指针GPIO_InitStruct,该指针指向结构体GPIO_InitTypeDef。

它有3个成员变量,分别是

u16 GPIO_Pin;//表示要选中哪个端口进行设置

GPIOSpeed_TypeDef GPIO_Speed;//表示输出速率

GPIOMode_TypeDef GPIO_Mode;//表示GPIO模式

总而言之在这个结构体里面存储了要把第一个输入的GPIO口设置成什么样的信息。为什么要用指针而不是整体上把结构体当做第二个参数呢,这是为了嵌入式的效率。具体内容可以参考鄙人C与CPP分类中讲解值传递、指针传递和引用传递的内容。

第二张和第三张图片介绍了怎么去赋值这个结构体的成员变量(最后一个表格是GPIO_MODE,不是GPIO_SPEED,手册在这里有错误)。请注意第一张图片的一个细节,GPIO_InitTypeDef定义于文件“stm32f10x_gpio.h”。这就告诉我们库文件就是干这个的,会帮助你提前设置好函数与变量,你只需要知道原理并且怎么去使用它就行了。如果你深入该库函数搞明白它是怎么实现的然后自己搞了一套GPIO的端口配置方法,那样虽然也可以用,但是并不能被其他人理解。所以用好TI官方给的库文件就OK了。

好了,现在我们给出一个范例,假如我要把GPIO_A的1端口和2端口设置为最高输出速率为50MHZ的推挽输出模式,代码如下:

//先声明一个GPIO_InitTypeDef的结构体变量
GPIO_InitTypeDef GPIO_InitStructure; 

GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2; //选择1端口和2端口,赋值方式在图2
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //设置推挽输出,赋值方式见图三
模式
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率,赋值方式见图4

GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化GPIO端口



2,LED灯闪烁实验

现在我们做第一个stm32实验,让开发板的LED灯亮灭交替进行。思路是控制连接8个LED灯的GPIO端口,使其交替输出高低电平。

先看看开发板原理图上的硬件接线图。

可以看到8个LED灯是接在PC0到PC7共8个端口的。那么我们要控制的就是C管脚的0到7这8个端口。输出高电平熄灭,输出低电平导通。

打开Keil uVersion4,将第一篇中创建的工程模板复制一份过来并在Keil中打开它。在User文件夹下新建一个文件夹LED,在LED中添加Led.c和Led.h,并如下图所示在Led.h中写入代码。宏定义是为了使用起来方便,在后面用的时候就明白了。以后每次创立了新的.h文件都要这样去写。

下面是写在Led.c中的代码。

#include "led.h"

void LED_Init()	  //端口初始化
{
	GPIO_InitTypeDef GPIO_InitStructure; //声明一个结构体变量,用来初始化GPIO

	SystemInit();	//时钟初始化

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//配置GPIO的模式和IO口

	GPIO_InitStructure.GPIO_Pin=LED;  //选择你要设置的IO口
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;	 //设置推挽输出模式
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;	  //设置传输速率
	GPIO_Init(GPIOC,&GPIO_InitStructure); 	   // 初始化GPIO 
}

void delay(u32 i)	  
{
	while(i--);
}

void led_display()
{
	GPIO_SetBits(GPIOC,LED);
	delay(6000000);//延时约为1s
	GPIO_ResetBits(GPIOC,LED);
	delay(6000000);
}

详细说说这几个函数。

void Led_Init()是编写的LED初始化函数,里面将GPIO管脚配置为50MHZ的推挽输出模式,这个在1.4中已经有了详细说明。SystemInit();是系统时钟初始化,具体定义在system_stm32f10x.c中可以找到。它的函数实现过程就不细说了,总体来说就是配置第二篇中讲的各种寄存器然后使得系统时钟为72MHZ,APB1是36MHZ,APB2是72MHZ。RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);用来使能APB2下的GPIOC端口。出于节能的考虑,用哪个端口使能哪个端口,不用的端口是没有电的。

delay(u32 i)是做延时用的,while(i–)就是让CPU空跑。72MHZ下delay(6000000)大概就是1秒。显然这种延时方式既浪费CPU资源又不够精确,在下一章会讲解定时器的使用,精确计时。

led_display()是LED闪烁函数,GPIO_SetBits是将指定的GPIO端口置1,GPIO_ResetBits指定的GPIO端口置0。这样就可以一秒灭一秒亮了。

按照C语言的语法规则,函数声明要添加在.h文件里面,所以最后要在LED.h里面写成如下代码。

#ifndef _led_H
#define _led_H
#include "stm32f10x.h"

#define LED GPIO_Pin_All	//管脚宏定义

void LED_Init(void);
void led_display(void);

#endif 

在public.h中include刚才写好的Led.h文件。这样在主函数中就可以调用LED_Init()和led_display()两个函数了。

#ifndef _public_H
#define _public_H

#include "stm32f10x.h"
#include "Led.h"

#endif

在main.c中这样写。

#include "public.h"
int main()
{
	LED_Init();		//LED端口及时钟初始化  
	while(1)
	{
		led_display(); //led显示			
	}
}


保存修改过的文件后

build一下编译通过,0 error ,0 warning。如果有错误,可以仔细看看错误在哪里,多半是单词拼错了或者字符间多了空格之类的。有警告是因为没有将光标打到文件的最后一行之后。下面说说怎么把程序送到STM32里面跑起来。




3 烧录程序

Buid通过后会在工程目录Output文件夹下生成一个与工程名同名的后缀为.hex的文件,如下图所示。它就是我们要烧录的目标文件。

烧录文件需要烧录软件,鄙人使用的是“普中自动下载软件”。打开后如图所示。该软件的下载链接在这里。


https://download.csdn.net/download/huagengpai1994/10843205

选择芯片类型为STM32F10XX seri。当把STM32插入电脑后上电串口号会自动出来。选择波特率是19200,波特率和串口有关,我们这是通过串口的方式把文件烧录进stm32中,这些内容以后再说。然后在文件路径中点击打开文件,找到Output下的工程模板.HEX(我的工程叫工程模板,您的工程叫什么就生成同名HEX文件)。最后点击程序下载,就会显示Program download success!表示下载成功。是不是看到了STM32上LED灯开始闪烁了,哈哈。程序只要下载进去STM32就会自动运行main函数的。

值得注意的是,该烧录软件还具备简单串口调试的功能,以后也许会用到。

至此,我们成功完成了STM32的第一个LED灯闪烁实验。下一章继续用本章的知识去完成一个按键与数码管的实验。



版权声明:本文为huagengpai1994原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。