STM32⼊门系列-使⽤库函数点亮LED,LED初始化函数
要点亮LED,需要完成LED的驱动, 在⼯程模板上新建⼀个led.c和led.h⽂件,将其存放在led⽂件夹内。这两个⽂件需要我们⾃⼰编写。通常xxx.c⽂件⽤于存放编写的驱动程序,xxx.h⽂件⽤于存放xxx.c内的stm32头⽂件、管脚定义、全局变量声明、函数声明等内容。 因此在led.c⽂件内编写如下代码:#include \"led.h\"
/******************************************************************************** 函 数 名 : LED_Init* 函数功能 : LED 初始化函数* 输 ⼊ : ⽆* 输 出 : ⽆
*******************************************************************************/void LED_Init(){
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);GPIO_InitStructure.GPIO_Pin=LED_PIN; //选择你要设置的 IO ⼝
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //设置推挽输出模式GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //设置传输速率GPIO_Init(LED_PORT,&GPIO_InitStructure); /* 初始化 GPIO */
GPIO_SetBits(LED_PORT,LED_PIN); //将 LED 端⼝拉⾼,熄灭所有 LED}
函数中的LED_PORT_RCC、LED_PIN和LED_PORT是我们定义的宏,其存放在led.h头⽂件内 。LED_PORT_RCC定义的是LED端⼝时钟(如RCC_APB2Periph_GPIOC),LED_PIN定义的是LED的引脚(如 GPIO_Pin_0),LED_PORT定义的是LED的端⼝(如GPIOC)。这样定义宏的好处是有效提⾼了程序的移植性,即使后续需要换其他端⼝,只需简单修改这⼏个宏就可以完成对LED的控制。在 led.h ⽂件内编写如下代码:#ifndef _led_H#define _led_H#include \"stm32f10x.h\"/* LED 时钟端⼝、引脚定义 */#define LED_PORT GPIOC#define LED_PIN
(GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)#define LED_PORT_RCC RCC_APB2Periph_GPIOCvoid LED_Init(void);#endif
LED_Init()函数就是对LED所接端⼝的初始化,是按照GPIO初始化步骤完成,这些内容在“寄存器点亮⼀个LED”章节中有介绍。下⾯我们主要看库函数是如何实现GPIO初始化的。
在库函数中实现 GPIO 的初始化函数是:
void GPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct);
这个函数具体有什么功能以及函数形参的意义,我们可以通过库函数帮助⽂档来查阅。GPIO_Init函数内有两个形参,第⼀个形参是
GPIO_TypeDef类型的指针变量,⽽GPIO_TypeDef是⼀个结构体类型,封装了GPIO外设的所有寄存器,所以给它传送GPIO外设基地址即可通过指针操作寄存器内容,第⼀个参数值可以为GPIOA、GPIOB、...GPIOG等,其实这些就是封装好的GPIO外设基地址,在stm32f10x.h⽂件中可以找到。第⼆个形参是GPIO_InitTypeDef类型的指针变量,⽽GPIO_InitTypeDef也是⼀个结构体类型,⾥⾯封装了GPIO外设的寄存器配置成员。我们初始化GPIO,其实就是对这个结构体配置。
如果想快速查看代码或参数可以⽤⿏标点击要查找的函数或者参数,然后右键⿏标选择“Go To Definition Of ...”即可进⼊所要查找的函数或参数内。
查找函数内变量类型也是同样的⽅法,但是如果发现此⽅法查找不出内容,那可能就是你所查找的东西在 KEIL5 软件认为是不正确的。在 LED 初始化函数中最开始调⽤的⼀个函数是:
RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);
此函数功能是使能GPIOC外设时钟, 在STM32中要操作外设必须将其外设时钟使能,否则即使其他的内容都配置好,也是徒劳⽆功。因为GPIO外设是挂接在APB2总线上,所以是对APB2总线时钟进⾏使能,函数内有两个参数,⼀个是⽤来选择外设时钟,⼀个是⽤来选择使能还是失能,使能:ENABLE,失能:DSIABLE。
在LED初始化函数内最后还调⽤了GPIO_SetBits(LED_PORT,LED_PIN)函数,此函数功能是让GPIOC端⼝的第0-7个引脚输出⾼电平,让LED处于熄灭状态,如果要对同⼀端⼝的多个引脚输出⾼电平,可以使⽤“|”运算符,相应的在对结构体初始化配置时管脚设置那⾥也要使⽤“|”将管脚添加进去,即在led.h⽂件内对LED引脚的定义。(前提条件是:要操作的多个引脚必须是配置同⼀种⼯作模式)例如:GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;//管脚设置GPIO_SetBits(GPIOC,GPIO_Pin_0|GPIO_Pin_1);
其实从函数名我们⼤致就可以知道函数的功能。函数内有两个参数,⼀个是端⼝的选择,⼀个是端⼝管脚的选择。如果要输出低电平的话可以使⽤如下库函数:
GPIO_ResetBits(GPIOC,GPIO_Pin_0);
这个函数功能和GPIO_SetBits是相反的,⼀个输出低电平,⼀个输出⾼电平,⾥⾯参数功能是⼀样的。GPIO输出函数还有好⼏个,例如:
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin,BitAction BitVal);void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);功能:设置端⼝管脚输出电平,这两个函数很少使⽤。
从 GPIO 内部结构可知,STM32 的 GPIO 还可以读取输⼊或输出引脚电平状态。其函数如下:
读取输⼊引脚
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_tGPIO_Pin);功能:读取端⼝中的某个管脚输⼊电平。底层是通过读取 IDR 寄存器。
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);功能:读取某组端⼝的输⼊电平。底层是通过读取 IDR 寄存器。
读取输出引脚
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_tGPIO_Pin);功能:读取端⼝中的某个管脚输出电平。底层是通过读取 ODR 寄存器。
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);功能:读取某组端⼝的输出电平。底层是通过读取 ODR 寄存器。在 led.h ⽂件中可以看到使⽤了⼀个定义头⽂件的结构,代码如下:
#ifndef _led_H#define _led_H
//此处省略头⽂件定义的内容#endif
它的功能是防⽌头⽂件被重复包含,避免引起编译错误。在头⽂件的开头,使⽤“#ifndef”关键字,判断标号“ _led_H”是否被定义,若没有被定义,则从“#ifndef”⾄“ #endif”关键字之间的内容都有效,也就是说,这个头⽂件若被其它⽂件“ #include”,它就会被包含到其该⽂件中,且头⽂件中紧接着使⽤“#define”关键字定义上⾯判断的标号“_led_H”。当这个头⽂件被同⼀个⽂件第⼆次“#include”包含的时候,由于有了第⼀次包含中的“ #define _led_H”定义,这时再判断“#ifndef _led_H”,判断的结果就是假了,从“#ifndef”⾄“#endif”之间的内容都⽆效,从⽽防⽌了同⼀个头⽂件被包含多次,编译时就不会出现“redefine(重复定义)”的错误了。
⼀般来说,我们不会直接在C的源⽂件写两个“#include”来包含同⼀个头⽂件,但可能因为头⽂件内部的包含导致重复,这种代码主要是避免这样的问题。如“led.h”⽂件中调⽤了#include “stm32f10x.h”头⽂件,可能我们写主程序的时候会在 main ⽂件开始处调⽤
#include“stm32f10x.h”和“led.h”,这个时候“stm32f10x.h”⽂件就被包含两次了,如果在头⽂件中没有这种机制,编译器就会报错。