STM32F407(野火霸天虎v2)学习理解记录(持续更新)

一.寄存器映射以及存储器映射(与51单片机不同之处)

  1.在说寄存器/存储器映射之前,简单介绍一下寄存器和存储器,RAM,ROM,flash的关系

(1)寄存器:

首先说明寄存器不是RAM,虽然都有着掉电丢失的特性,但是寄存器是寄存器,属于中央控制器(cpu)的组成部分,RAM是属于存储器的一种,寄存器的原理可以去其他博主那里好好看看,我个人暂时仅仅对其有个大概的理解。寄存器的本质功能是暂时存储数据和快速读写的能力,主要用于处理数据,掉电丢失数据。

(2)存储器(内存储器):

存储器主要用于存储数据,根据不同的存储器,所实现的功能不同。ram和rom以及flash都是存储器,各自的功能不同,但他们的主要功能在于存储。

(3)ram和rom以及flash

他们均属于存储器。

ram具有可读写,掉电丢失数据的特性。

rom具有只读特性,掉电不丢失数据,但一旦写入数据就不可更改。

flash集合了ram和rom的优点,既可以读写,数据掉电还不丢失,目前市场上的嵌入式设备的存储器大部分都替换成了flash。

2.存储器映射

给存储器分配地址的过程叫存储器映射,再分配一个地址叫重映射,一般情况下存储器映射是厂家已经做好的,用户不可修改。

3.寄存器映射

寄存器映射实际上是在存储器映射(内存块地址已经分配好)的基础上进行的。

(1)在51单片机中,对各个引脚进行编辑电平状态的时候,寄存器映射是预备好的,储存在程序预编译指令中(#include <regx51>),因此对其各个引脚的电平状态可以直接编辑,例如P1=0xFF;

(2)在stm32当中,寄存器映射并不是提前准备好的,需要自己对照数据手册已经分配好的内存地址,通过指针设定相应地址的寄存器映射,再对某些个管脚进行处理。然后我们进行引脚输出举例。

在举例之前,要说明一下内存被分为什么样子了,也就是内存的地址到底都有啥?

内存地址分为基地址,偏移地址,绝对地址。

我个人的理解是  基地址+偏移地址=绝对地址。

基地址就是手册中你首先要查找的:

比如我要找GPIOF,那么在图中就可以看到基地址0x4002 1400(内存地址是以16进制位存储的)

那么就可以再手册中查找GPIOA输出数据寄存器的偏移地址。

那么我们所需要的绝对地址就是 0x4002 1400+0x14=0x4002 1414。

找到了绝对地址,就可以开始举例了。

我要让GPIOF输出高电平(三十二位但是十六位有效),所以我要对GPIOF的十六个引脚均置1,换算为十六进制就是写入0xFFFF;但是由于没有寄存器映射,所以我要先创建一个寄存器映射

方法一:*(unsigned int *)(0x4002 1414)=0xFFFF;

这里创建了一个int类型的指针,指向地址为0x4002 1414,其地址内部存放数据0xFFFF

这是方法一,比较透彻,直接编辑了引脚的电平状态,但这样不好多次操作,可以采用将寄存器取别名的方法

方法二:#define GPIOF_ODR           *(unsigned int *)(0x40021414)

                GPIOF_ODR=0xFFFF;

这种方法把指针定义赋值的操作放在了程序预编译处理阶段,使用了宏定义,更加方便多次操作

二.c语言对寄存器的封装

1.此处需要宏定义和结构体指针,以及类型取别名的基础知识,浅浅介绍一下

(1)宏定义变量:

#define     名字        定义内容

用法:出现定义名字的地方就用定义内容去替换

(2)结构体指针:

仅说明带typedef的

A.结构体类型定义方法:

typedef struct{

成员列表;

}结构体名字;

B.结构体指针定义方法:

结构体名* 结构体指针名;

C.结构体指针成员调用方法:

结构体指针名->成员列表

2.类型取别名

typedef 数据类型类型名=别名;

3.举例:如何通过宏定义和结构体指针,定义GPIO的全部引脚

#define GPIOA _BASE    (0x4002 0000)//宏定义各GPIO口基地址

#define GPIOB _BASE    (0x4002 0400)

#define GPIOC _BASE    (0x4002 0800)

#define GPIOD _BASE    (0x4002 0C00)

#define GPIOE _BASE    (0x4002 1000)

#define GPIOF _BASE    (0x4002 1400)

#define GPIOG _BASE    (0x4002 1800)

#define GPIOH _BASE    (0x4002 1C00)

#define GPIOI _BASE     (0x4002 2000)

typedef unsigned short int uint16_t;//用uint16_t同名替换数据类型
typedef unsigned int uint32_t;//用uint32_t同名替换数据类型

typedef struct{

    uint32_t MODER;
    uint32_t OTYPER;
    uint32_t OSPEEDR;
    uint32_t PUPDR;
    uint32_t IDR;
    uint32_t ODR;
    uint32_t BSRR;
    uint32_t LCKR;
    uint16_t AFRL;
    uint16_t AFRH;    //成员列表为各寄存器外设

}GPIO;

#define   GPIOA   (GPIO*)GPIOA _BASE//宏定义结构体指针,指向各GPIO口的基地址

#define   GPIOB   (GPIO*)GPIOB _BASE

#define   GPIOC   (GPIO*)GPIOC _BASE

#define   GPIOD   (GPIO*)GPIOD _BASE

#define   GPIOE   (GPIO*)GPIOE _BASE

#define   GPIOF   (GPIO*)GPIOF _BASE

#define   GPIOG   (GPIO*)GPIOG _BASE

#define   GPIOH   (GPIO*)GPIOH_BASE

#define   GPIOI   (GPIO*)GPIOI_BASE

GPIOA->ODR=0xFFFF;//调用外设

三.简单通过GPIO(通用输入输出端口)寄存器点灯

1.先来个普通点灯,控制一个颜色的灯亮灭(无头文件宏定义)

 步骤如下:

(1)打开开发板原理图,查看LED灯如何开启

这里就拿LED_R来举例,可以看到左侧接3.3v(单片机TTL逻辑认为1.8v以上为高电平,1.8v-0v为低电平,常见的高电平为3.3v和5.0v),那个小方块里头两个点是跳线帽,可以当作一个手动开关,插上就是闭合的状态,LED_R的右侧可以看到与PF6(GPIOF_六号管脚)相连,如果想要导通就需要从高电平到低电平,所以右侧接低电平(具体原理我也不太懂,但是我一般就是这么理解,方便看图)

(2)确定目标为将PF6管脚置低电平之后,就可以查询手册对寄存器进行操作了,先介绍非传统的点灯流程(简化版本)

简化版本的点灯流程为:打开对应时钟寄存器-调整对应端口模式寄存器为输出模式-对应端口输出寄存器置低电平

A.将各寄存器的基地址在手册的存储器映射中找到(GPIOF是挂载在AHB1总线上的,所以对应时钟寄存器就是RCC-AHB1)

RCC时钟-0x4002 3800

GPIOF-0x4002 1400

B.打开对应时钟(不像51点灯一样,stm32的时钟默认是关闭的,置1才能打开)

那么编写的代码就是:

*(unsigned int *)(0x40023800+0x30)|=(1<<5);

这样用或等于 和 移位操作的目的是不影响其他引脚的原始电平状态(RCC默认是低电平)

C.调整GPIOF的端口模式寄存器为通用输出模式(因为我们要让PF6管脚输出低电平嘛)

编写的代码就是:

//2:调整pf6端口模式为输出模式
	*(unsigned int*)(0x40021400+0x00)&=~(0x03<<2*6);
	*(unsigned int*)(0x40021400+0x00)|=(1<<2*6);

为啥要有第一步呢,这个是逻辑上的问题,第一步将PF6对应的MODER6两个小管脚先置00这是为了第二步在对其置01的时候不被这两个小管脚的原始状态所影响。

        假设原本是00那对第二步没影响,01与上00还是01。

        但是假设原本是11的话,01与上11就变成11了,这不是我们想要的结果。        

一般初学者还有个疑问,为啥非要用移位操作和与等于的这种方式调整管脚电平状态呢?跟51一样直接对这部分赋值不就好了?

        再次强调,不是十分详细且精准的查阅手册的情况下,咱们并不清楚管脚的默认状态,更何况stm32大部分管脚的默认状态是浮空态(既不是低电平,也不是高电平),有的管脚还是外围电路决定电平状态,在我们调整部分管脚的情况下尽量还是不要影响到其他管脚,要不那些个BUG会让人怀疑人生。

D:PF6对应端口输出寄存器置低电平

编写的代码就是:

//3:调整pf6输出端口为低电平
	*(unsigned int*)(0x40021400+0x14)|=(1<<6);
	*(unsigned int*)(0x40021400+0x14)&=~(1<<6);*/

这里我实际上并没有先让他置低电平,我还是先置高电平再置低电平,和上一步的理由差不多,都是为了防止出错

整体的代码为:

int main(void)
{
	
	
	//目标,给pf6口输出低电平,开ledR
	//1:打开对应时钟(pf对应AHB1的rcc)
	*(unsigned int *)(0x40023800+0x30)|=(1<<5);
	//2:调整pf6端口模式为输出模式
	*(unsigned int*)(0x40021400+0x00)&=~(0x03<<2*6);
	*(unsigned int*)(0x40021400+0x00)|=(1<<2*6);
	//3:调整pf6输出端口为低电平
	*(unsigned int*)(0x40021400+0x14)|=(1<<6);
	*(unsigned int*)(0x40021400+0x14)&=~(1<<6);
	
}

到这里效果就是led亮红灯啦!(写博客好累==)

四.GPIO的原理(我会说一说硬件,很简单!顺路写个传统点灯的标准流程嗷)

1.简介:GPIO就是通用输入输出端口,能输入也能输出。

2.咱从GPIO的组成硬件部分一步一步讲,也能对传统的寄存器操作流程有个大概的了解。

这图看着挺吓人,别慌,老简单了一点点来。

分割线-------------------------------------------------------------------------------------------------------------

        先从右侧看,看到一个保护二极管和VDD_FT,这个FT其实就是个5v的标准,任何大于5v的电压都会直接引入到这个保护二极管支路里,把内部的硬件干废。

分割线————————————————————————————————————————

        另外一个保护二极管和VSS也是用来保护内部硬件的,VSS就是0v的标准,如果有低于0v的电压就会直接引入这个支路里。

分割线————————————————————————————————————————

        这两个上拉和下拉的作用实际上就是再IO口接芯片时决定初始电平状态的,有相应的端口寄存器,如下图。每两位决定一个端口

分割线————————————————————————————————————————

        接下来进入内部嗷,先讲一下下面的输出部分(这里就有点灯传统流程的原理了)。

        可以看到从左边的第一个置位/复位寄存器,先看图。

        这里分为高16位和低16位,他们共同控制着16个端口,特点是置1才有效,如果向高16位(16-31号位,也就是RESET)置1,那么输出低电平0,如果向低16位(0-15号位,也就是SET)置1,那么输出高电平。

分割线————————————————————————————————————————

        

        输出数据寄存器就比较简单了,先看下图。

        16位控制着16个端口,写啥电平就是啥电平。

分割线————————————————————————————————————————

        这个玩意可就洋气了啊,叫输出控制驱动器,上图看着不太容易理解,咱先看下图介绍一下这玩意。

        先从寄存器角度来说一下,十六位控制十六个端口的输出模式,一个叫推免输出,一个叫开漏输出,配置高电平就是推免,配置低电平就是开漏,下面介绍一下驱动器的推免输出和开漏输出的硬件部分。

推免简单来讲就是进啥电平出啥电平,原理的话直接举例说明:

A.比如INT一个高电平,经过这个反相器(大三角加一个小圈,作用时把电平反过来)变成低电平,然后UG<US,右边OUT端就和上面VDD导通了。

B.比如INT一个低电平,经过这个反相器(大三角加一个小圈,作用时把电平反过来)变成高电平,然后UG>US,右边OUT端就和下面接地导通了。

开漏输出的特点也举例说明:

A.假设输入一个低电平,经反相器变为高电平,UG>US导通,OUT和地端导通,输出低电平

B.假设输入一个高电平,经反相器变为低电平,UG<US不导通,但如果不另加上拉电路,OUT端不会输出高电平。

总结并补充一下开漏输出的特点:

(1)、只能输出低电平,不能输出高电平。 (2)、如果要输出高电平,则需要外接上拉。 (3)、开漏输出具有“线与”功能,一个为低,全部为低,多用于I2C和SMBUS总线。

输出部分这里就讲完了!!!!!!!!!!

分割线———————————————————————————————————————

        再来简单说一说输入部分

        先看这个TTL施密特触发器,他的作用是处理数字信号,有的时候IO口输入的并不全是3.3v和0v的数字信号,比如2.8v啊,1.3v啊都有,他的作用就是按照TTL标准(1.8v以上是高电平,1.8v-0v是低电平)处理信号,让左侧一端出来的就是高电平和低电平。

        可以看到触发器右侧还有一个模拟线,这个是模拟信号去的方向。

        输入数据寄存器和输出数据寄存器类似,就不多说了,直接干图片。

3.GPIO的标准初始化流程(传统点灯的步骤原理!)

传统点灯程序如下,有兴趣就看看,是有宏定义操作的,不会的稍微看一看宏定义,不多说了,累麻了。

头文件内容

/*片上外设基地址  */
#define PERIPH_BASE           ((unsigned int)0x40000000)                          

/*总线基地址 */
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000)	

/*GPIO外设基地址*/
#define GPIOF_BASE            (AHB1PERIPH_BASE + 0x1400)


/* GPIOF寄存器地址,强制转换成指针 */
#define GPIOF_MODER				*(unsigned int*)(GPIOF_BASE+0x00)
#define GPIOF_OTYPER			*(unsigned int*)(GPIOF_BASE+0x04)
#define GPIOF_OSPEEDR			*(unsigned int*)(GPIOF_BASE+0x08)
#define GPIOF_PUPDR				*(unsigned int*)(GPIOF_BASE+0x0C)
#define GPIOF_IDR					*(unsigned int*)(GPIOF_BASE+0x10)
#define GPIOF_ODR					*(unsigned int*)(GPIOF_BASE+0x14)
#define GPIOF_BSRR					*(unsigned int*)(GPIOF_BASE+0x18)
#define GPIOF_LCKR					*(unsigned int*)(GPIOF_BASE+0x1C)
#define GPIOF_AFRL					*(unsigned int*)(GPIOF_BASE+0x20)
#define GPIOF_AFRH					*(unsigned int*)(GPIOF_BASE+0x24)

/*RCC外设基地址*/
#define RCC_BASE              (AHB1PERIPH_BASE + 0x3800)

/*RCC的AHB1时钟使能寄存器地址,强制转换成指针*/
#define RCC_AHB1ENR				*(unsigned int*)(RCC_BASE+0x30)
	

main.c内容

/*
	使用寄存器的方法点亮LED灯
  */
#include "stm32f4xx.h" 


/**
  *   主函数
  */
int main(void)
{	
	/*开启 GPIOF 时钟,使用外设时都要先开启它的时钟*/
	RCC_AHB1ENR |= (1<<5);	
	
	/* LED 端口初始化 */
	
	/*GPIOF MODER6清空*/
	GPIOF_MODER  &= ~( 0x03<< (2*6));	
	/*PF6 MODER6 = 01b 输出模式*/
	GPIOF_MODER |= (1<<2*6);
	
	/*GPIOF OTYPER6清空*/
	GPIOF_OTYPER &= ~(1<<1*6);
	/*PF6 OTYPER6 = 0b 推挽模式*/
	GPIOF_OTYPER |= (0<<1*6);
	
	/*GPIOF OSPEEDR6清空*/
	GPIOF_OSPEEDR &= ~(0x03<<2*6);
	/*PF6 OSPEEDR6 = 0b 速率2MHz*/
	GPIOF_OSPEEDR |= (0<<2*6);
	
	/*GPIOF PUPDR6清空*/
	GPIOF_PUPDR &= ~(0x03<<2*6);
	/*PF6 PUPDR6 = 01b 上拉模式*/
	GPIOF_PUPDR |= (1<<2*6);
	
	/*PF6 BSRR寄存器的 BR6置1,使引脚输出低电平*/
	GPIOF_BSRR |= (1<<16<<6);
	
	/*PF6 BSRR寄存器的 BS6置1,使引脚输出高电平*/
	//GPIOF_BSRR |= (1<<6);

	while(1);

}

// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{	
}






/*********************************************END OF FILE**********************/

五_1.库函数雏形——自己写库函数(从这开始,不再主要使用寄存器,开始向固件库过渡)

1.在stm32当中,寄存器编程,实际上十分困难,在学校还可以,但是步入项目工程中,实际调用的外设相当多,一步一步的对寄存器进行编程十分费力,需要不断的去查询手册的相关参数。

        所以,一般来讲,大部分项目中,不论是学校的项目,还是企业的项目,现在基本上都是用固件库了,其可读性强,方便维护,编写难度较小,从这里开始就要向固件库过渡了。

2.在之前的传统点灯程序中,其各个引脚的寄存器映射,是从头文件中编辑的,类似于这样

#define GPIOF_MODER				*(unsigned int*)(GPIOF_BASE+0x00)//GPIO_BASE也是地址,数值为0x4002 1400

但是随着外设越来越多,所需要编写的寄存器映射也会越来越多,所以这种单纯的宏定义去编辑映射代码量很大,这时候,我们就可以借助结构体,帮我们去省略很多代码,下面介绍一下。

        实际上和普通的寄存器映射一样,原理都是互通的,通过把地址声明成一个指针,让编译器认为咱们写进去了一个地址而不是一个数,再对这个地址内部的数据进行调整,就可以编辑其硬件的电平状态,利用结构体的步骤如下

A.声明基地址

typedef unsigned int 			 uint32_t;
typedef unsigned short int uint16_t;
#define GPIOA_BASE 0x40020000
#define GPIOB_BASE 0x40020400
#define GPIOC_BASE 0x40020800
#define GPIOD_BASE 0x40020C00
#define GPIOE_BASE 0x40021000
#define GPIOF_BASE 0x40021400
#define GPIOG_BASE 0x40021800
#define GPIOH_BASE 0x40021C00
#define GPIOI_BASE 0x40022000

#define RCC_BASE 	 0x40023800

B.创建结构体类型(成员是GPIO的各种外设,能这么写的原因是,各个外设分别都是32位控制,每个成员都是四个字节)

typedef struct{
	uint32_t GPIOx_MODER;
	uint32_t GPIOx_OTYPER;
	uint32_t GPIOx_OSPEEDR;
	uint32_t GPIOx_PUPDR;
	uint32_t GPIOx_IDR;
	uint32_t GPIOx_ODR;
	
	uint16_t GPIOx_BSRR_L;
	uint16_t GPIOx_BSRR_H;
	
	uint32_t GPIOx_LCKR;
	
	uint32_t GPIOx_AFRL;
	uint32_t GPIOx_AFRH;
}GPIO;

C.声明结构体指针,指向某个GPIO的首地址

​
#define GPIOA 		 ((GPIO*)(GPIOA_BASE))
#define GPIOB      ((GPIO*)(GPIOB_BASE))
#define GPIOC      ((GPIO*)(GPIOC_BASE))
#define GPIOD      ((GPIO*)(GPIOD_BASE))
#define GPIOE      ((GPIO*)(GPIOE_BASE))
#define GPIOF      ((GPIO*)(GPIOF_BASE))
#define GPIOG      ((GPIO*)(GPIOG_BASE))
#define GPIOH      ((GPIO*)(GPIOH_BASE))
#define GPIOI      ((GPIO*)(GPIOI_BASE))

​

以上内容都是写在头文件里的,用来在预编译处理阶段处理宏定义,实现咱们想要的功能。

总体代码如下:

#ifndef _STM32F4XX_H
#define _STM32F4XX_H //这两句和#endif的作用是防止出现重复宏定义


typedef unsigned int 			 uint32_t;
typedef unsigned short int uint16_t;
#define GPIOA_BASE 0x40020000
#define GPIOB_BASE 0x40020400
#define GPIOC_BASE 0x40020800
#define GPIOD_BASE 0x40020C00
#define GPIOE_BASE 0x40021000
#define GPIOF_BASE 0x40021400
#define GPIOG_BASE 0x40021800
#define GPIOH_BASE 0x40021C00
#define GPIOI_BASE 0x40022000

#define RCC_BASE 	 0x40023800

typedef struct{
	uint32_t GPIOx_MODER;
	uint32_t GPIOx_OTYPER;
	uint32_t GPIOx_OSPEEDR;
	uint32_t GPIOx_PUPDR;
	uint32_t GPIOx_IDR;
	uint32_t GPIOx_ODR;
	
	uint16_t GPIOx_BSRR_L;
	uint16_t GPIOx_BSRR_H;
	
	uint32_t GPIOx_LCKR;
	
	uint32_t GPIOx_AFRL;
	uint32_t GPIOx_AFRH;
}GPIO;

#define GPIOA 		 ((GPIO*)(GPIOA_BASE))
#define GPIOB      ((GPIO*)(GPIOB_BASE))
#define GPIOC      ((GPIO*)(GPIOC_BASE))
#define GPIOD      ((GPIO*)(GPIOD_BASE))
#define GPIOE      ((GPIO*)(GPIOE_BASE))
#define GPIOF      ((GPIO*)(GPIOF_BASE))
#define GPIOG      ((GPIO*)(GPIOG_BASE))
#define GPIOH      ((GPIO*)(GPIOH_BASE))
#define GPIOI      ((GPIO*)(GPIOI_BASE))
#endif

/*本文件一般用于添加寄存器地址及结构体定义*/

这个文件中的基地址都是GPIO,如果把基地址设定为最高一级的片上外设的话还有个牛逼的例子

/*片上外设基地址  */
#define PERIPH_BASE           ((unsigned int)0x40000000)                          

/*总线基地址 */
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000)	

/*GPIO外设基地址*/
#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE            (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE            (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE            (AHB1PERIPH_BASE + 0x0C00)
#define GPIOE_BASE            (AHB1PERIPH_BASE + 0x1000)
#define GPIOF_BASE            (AHB1PERIPH_BASE + 0x1400)
#define GPIOG_BASE            (AHB1PERIPH_BASE + 0x1800)
#define GPIOH_BASE            (AHB1PERIPH_BASE + 0x1C00)

/*RCC外设基地址*/
#define RCC_BASE              (AHB1PERIPH_BASE + 0x3800)


/* GPIOF寄存器地址,强制转换成指针 */
//#define GPIOF_MODER				*(unsigned int*)(GPIOF_BASE+0x00)
//#define GPIOF_OTYPER			*(unsigned int*)(GPIOF_BASE+0x04)
//#define GPIOF_OSPEEDR			*(unsigned int*)(GPIOF_BASE+0x08)
//#define GPIOF_PUPDR				*(unsigned int*)(GPIOF_BASE+0x0C)
//#define GPIOF_IDR					*(unsigned int*)(GPIOF_BASE+0x10)
//#define GPIOF_ODR					*(unsigned int*)(GPIOF_BASE+0x14)
//#define GPIOF_BSRR					*(unsigned int*)(GPIOF_BASE+0x18)
//#define GPIOF_LCKR					*(unsigned int*)(GPIOF_BASE+0x1C)
//#define GPIOF_AFRL					*(unsigned int*)(GPIOF_BASE+0x20)
//#define GPIOF_AFRH					*(unsigned int*)(GPIOF_BASE+0x24)

/*RCC的AHB1时钟使能寄存器地址,强制转换成指针*/
//此处不使用,用结构体来代替
//#define RCC_AHB1ENR				*(unsigned int*)(RCC_BASE+0x30)


//寄存器的值常常是芯片外设自动更改的,即使CPU没有执行程序,也有可能发生变化
//编译器有可能会对没有执行程序的变量进行优化

//volatile表示易变的变量,防止编译器优化,
#define     __IO    volatile
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;

/* GPIO寄存器列表 */
typedef struct
{
	__IO	uint32_t MODER;    /*GPIO模式寄存器						地址偏移: 0x00      */
	__IO	uint32_t OTYPER;   /*GPIO输出类型寄存器				地址偏移: 0x04      */
	__IO	uint32_t OSPEEDR;  /*GPIO输出速度寄存器				地址偏移: 0x08      */
	__IO	uint32_t PUPDR;    /*GPIO上拉/下拉寄存器			地址偏移: 0x0C      		*/
	__IO	uint32_t IDR;      /*GPIO输入数据寄存器				地址偏移: 0x10      		*/
	__IO	uint32_t ODR;      /*GPIO输出数据寄存器				地址偏移: 0x14      		*/
	__IO	uint16_t BSRRL;    /*GPIO置位/复位寄存器 低16位部分	地址偏移: 0x18 	*/
	__IO	uint16_t BSRRH;    /*GPIO置位/复位寄存器 高16位部分	地址偏移: 0x1A  */
	__IO	uint32_t LCKR;     /*GPIO配置锁定寄存器				地址偏移: 0x1C      */
	__IO	uint32_t AFR[2];   /*GPIO复用功能配置寄存器		地址偏移: 0x20-0x24 		*/
} GPIO_TypeDef;

/*RCC寄存器列表*/
typedef struct
{
__IO	uint32_t CR;            /*!< RCC 时钟控制寄存器,				地址偏移: 0x00 */
__IO	uint32_t PLLCFGR;       /*!< RCC PLL配置寄存器, 				地址偏移: 0x04 */
__IO	uint32_t CFGR;          /*!< RCC 时钟配置寄存器,   		地址偏移: 0x08 */
__IO	uint32_t CIR;           /*!< RCC 时钟中断寄存器,     		地址偏移: 0x0C */
__IO	uint32_t AHB1RSTR;      /*!< RCC AHB1 外设复位寄存器,	地址偏移: 0x10 */
__IO	uint32_t AHB2RSTR;      /*!< RCC AHB2 外设复位寄存器, 	地址偏移: 0x14 */
__IO	uint32_t AHB3RSTR;      /*!< RCC AHB3 外设复位寄存器, 	地址偏移: 0x18 */
__IO	uint32_t RESERVED0;     /*!< 保留, 										地址偏移:0x1C */
__IO	uint32_t APB1RSTR;      /*!< RCC APB1 外设复位寄存器,	地址偏移: 0x20 */
__IO	uint32_t APB2RSTR;      /*!< RCC APB2 外设复位寄存器,	地址偏移: 0x24 */
__IO	uint32_t RESERVED1[2];  /*!< 保留, 										地址偏移:0x28-0x2C*/
__IO	uint32_t AHB1ENR;       /*!< RCC AHB1 外设时钟寄存器,	地址偏移: 0x30 */
__IO	uint32_t AHB2ENR;       /*!< RCC AHB2 外设时钟寄存器,	地址偏移: 0x34 */
__IO	uint32_t AHB3ENR;       /*!< RCC AHB3 外设时钟寄存器,	地址偏移: 0x38 */
	/*RCC后面还有很多寄存器,此处省略*/
} RCC_TypeDef;


/*定义GPIOA-H 寄存器结构体指针*/
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH               ((GPIO_TypeDef *) GPIOH_BASE)

/*定义RCC外设 寄存器结构体指针*/
#define RCC                 ((RCC_TypeDef *) RCC_BASE)

(3)使用结构体指针构建的映射

        有两种使用方式,一般都是第二种。

1:(*结构体指针变量名).成员
2:结构体指针变量名‐>成员

例如:

GPIOA->GPIOx_MODER=0xfffff;

五_2.自己写库雏形

哇,好累,一开始自己尝试着写库的实际情况和我想得确实有点不一样,倒不是思路的问题,是一些细枝末节上容易出错,改错改了好久好久好久,待会一样通过简单的点灯程序来搞定写库的思路,顺便说一说我碰见的错误。

1.确定目的,构思对什么寄存器进行处理,为了方便处理寄存器,在头文件中用宏定义取别名的方法创建对应的寄存器映射,一般在这个过程中会建立结构体指针,更加方便咱们创建大量的寄存器映射。

        代码如下,头文件名为 stm32f4xx.h

/*本文件一般用于添加寄存器地址及结构体定义*/
#ifndef _STM32F4XX_H
#define _STM32F4XX_H //这两句和#endif的作用是防止出现重复宏定义

typedef unsigned int 			 uint32_t;
typedef unsigned short int uint16_t;
#define GPIOA_BASE 0x40020000
#define GPIOB_BASE 0x40020400
#define GPIOC_BASE 0x40020800
#define GPIOD_BASE 0x40020C00
#define GPIOE_BASE 0x40021000
#define GPIOF_BASE 0x40021400
#define GPIOG_BASE 0x40021800
#define GPIOH_BASE 0x40021C00
#define GPIOI_BASE 0x40022000

#define RCC_BASE 	 0x40023800

typedef struct{
	uint32_t GPIOx_MODER;
	uint32_t GPIOx_OTYPER;
	uint32_t GPIOx_OSPEEDR;
	uint32_t GPIOx_PUPDR;
	uint32_t GPIOx_IDR;
	uint32_t GPIOx_ODR;
	
	uint16_t GPIOx_BSRR_L;
	uint16_t GPIOx_BSRR_H;
	
	uint32_t GPIOx_LCKR;
	
	uint32_t GPIOx_AFRL;
	uint32_t GPIOx_AFRH;
}GPIO_TYPEDEF;

#define GPIOA 		 ((GPIO_TYPEDEF*)GPIOA_BASE)
#define GPIOB      ((GPIO_TYPEDEF*)GPIOB_BASE)
#define GPIOC      ((GPIO_TYPEDEF*)GPIOC_BASE)
#define GPIOD      ((GPIO_TYPEDEF*)GPIOD_BASE)
#define GPIOE      ((GPIO_TYPEDEF*)GPIOE_BASE)
#define GPIOF      ((GPIO_TYPEDEF*)GPIOF_BASE)
#define GPIOG      ((GPIO_TYPEDEF*)GPIOG_BASE)
#define GPIOH      ((GPIO_TYPEDEF*)GPIOH_BASE)
#define GPIOI      ((GPIO_TYPEDEF*)GPIOI_BASE)







#endif

/*本文件一般用于添加寄存器地址及结构体定义*/

  2.在写主程序的时候构思都要对哪些寄存器,寄存器的哪些位进行什么电平状态的处理,比如我想让LED灯亮红色,那么就需要给PF6脚输出低电平(看硬件原理图,前面讲过,不赘述了),这里需要的流程是:开启目标时钟-调整端口模式为输出模式-调整输出数据寄存器ODR/置位复位寄存器BSRR寄存器输出低电平。(注意,置位复位寄存器也可以让PF6输出电平状态哦,之前在GPIO的原理分析电路图的时候说过,其高16位置1为0,低16为置1为1,下面的程序就用置位复位寄存器来操作输出低电平,这次不用输出数据寄存器,BSRR的内容参数如图)

 3.一步一步的分析,看看哪些功能能够写入库中,供给多次使用

例如:置位位复位寄存器BSRR寄存器指定一位输出低电平

        这就是一个有趣的例子,我们可以在同一文件夹内另外创建源文件或者头文件,将这个功能封装在新创建的文件当中,下次再用的时候就可以直接在主函数中调用。下面是我如何实现这个想法的方式。

        按照上面的例子,我做出了一个功能模块,一个源文件(.c后缀文件)用于存放BSRR寄存器某一位输出低电平的函数。文件名为gpio_fun.c

void GPIOF_RESET(GPIO_TYPEDEF*GPIOX,uint16_t GPIOF_PIN)
{
	GPIOX->GPIOx_BSRR_H=GPIOF_PIN;

}//这个函数是置位/复位寄存器的高16位引脚选定一个位去置1

可以看到这个函数的第一个形参是一个GPIO_TYPEDEF类型的结构体指针变量,而第二个形参是我们要将电平状态赋予哪个位。通过参数分析,这个结构体指针变量好说,在本文件里包含一个#include "stm32f4xx.h"(上面我自己编写存放寄存器映射的头文件)就可以了,但是第二个GPIOF_PIN我们需要再次写一个头文件来包含到本文件中,我将这个新的头文件命名为GPIOx_BSRR_PIN.h   代码如下。

#include "stm32f4xx.h"

#ifndef _STM32F4XX_GPIO_H//这个和下面一句还有最后的endif是为了不重复宏定义
#define _STM32F4XX_GPIO_H



#define _pin0 (uint16_t)(1<<0)
#define _pin1 (uint16_t)(1<<1)
#define _pin2 (uint16_t)(1<<2)
#define _pin3 (uint16_t)(1<<3)
#define _pin4 (uint16_t)(1<<4)
#define _pin5 (uint16_t)(1<<5)
#define _pin6 (uint16_t)(1<<6)
#define _pin7 (uint16_t)(1<<7)
#define _pin8 (uint16_t)(1<<8)
#define _pin9 (uint16_t)(1<<9)
#define _pin10 (uint16_t)(1<<10)
#define _pin11 (uint16_t)(1<<11)
#define _pin12 (uint16_t)(1<<12)
#define _pin13 (uint16_t)(1<<13)
#define _pin14 (uint16_t)(1<<14)
#define _pin15 (uint16_t)(1<<15)



#endif

4.然后我们就可以开始将这些文件组合起来,边写主函数边看功能去调用就可以啦。

#include "stm32f4xx.h"
#include "GPIOx_BSRR_PIN.h"
void GPIOF_RESET(GPIO_TYPEDEF*GPIOX,uint16_t GPIOF_PIN);//在这里声明外部源文件的函数
void GPIOF_SET(GPIO_TYPEDEF*GPIOX,uint16_t GPIOF_PIN);
/**
  *   主函数
  */

int main(void)
{	
	//目标:给PF6置低电平
	//开启目标时钟
	*(unsigned int*)(RCC_BASE+0x30)|=(1<<5);
	//调整GPIOF端口模式
	GPIOF->GPIOx_MODER&=~(0x03<<2*6);
	GPIOF->GPIOx_MODER|=(1<<2*6);
	
		while(1)
 {
	 //使用置位复位寄存器输出低电平
	 GPIOF_RESET(GPIOF,_pin6);


 }


}

// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{	
}


至此,红灯亮起。我自己还编写了一个红灯闪的代码,大家有兴趣可以看看,正好展示一下大体整个程序是如何组合的。

main.c代码:


#include "stm32f4xx.h"
#include "GPIOx_BSRR_PIN.h"
void GPIOF_RESET(GPIO_TYPEDEF*GPIOX,uint16_t GPIOF_PIN);//在这里声明外部源文件的函数
void GPIOF_SET(GPIO_TYPEDEF*GPIOX,uint16_t GPIOF_PIN);
/**
  *   主函数
  */

int main(void)
{	
	//目标:给PF6置高低电平交替,中间插入延时函数
	//开启目标时钟
	*(unsigned int*)(RCC_BASE+0x30)|=(1<<5);
	//调整GPIOF端口模式
	GPIOF->GPIOx_MODER&=~(0x03<<2*6);
	GPIOF->GPIOx_MODER|=(1<<2*6);
	
		while(1)
 {
	 //使用置位复位寄存器交替高低电平
	 GPIOF_RESET(GPIOF,_pin6);
	 delay(0x0fffff);
	 GPIOF_SET(GPIOF,_pin6);
	 delay(0x0fffff);
 }


}

// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{	
}






/*********************************************END OF FILE**********************/

stm32f4xx.h代码:

/*本文件一般用于添加寄存器地址及结构体定义*/
#ifndef _STM32F4XX_H
#define _STM32F4XX_H //这两句和#endif的作用是防止出现重复宏定义

typedef unsigned int 			 uint32_t;
typedef unsigned short int uint16_t;
#define GPIOA_BASE 0x40020000
#define GPIOB_BASE 0x40020400
#define GPIOC_BASE 0x40020800
#define GPIOD_BASE 0x40020C00
#define GPIOE_BASE 0x40021000
#define GPIOF_BASE 0x40021400
#define GPIOG_BASE 0x40021800
#define GPIOH_BASE 0x40021C00
#define GPIOI_BASE 0x40022000

#define RCC_BASE 	 0x40023800

typedef struct{
	uint32_t GPIOx_MODER;
	uint32_t GPIOx_OTYPER;
	uint32_t GPIOx_OSPEEDR;
	uint32_t GPIOx_PUPDR;
	uint32_t GPIOx_IDR;
	uint32_t GPIOx_ODR;
	
	uint16_t GPIOx_BSRR_L;
	uint16_t GPIOx_BSRR_H;
	
	uint32_t GPIOx_LCKR;
	
	uint32_t GPIOx_AFRL;
	uint32_t GPIOx_AFRH;
}GPIO_TYPEDEF;

#define GPIOA 		 ((GPIO_TYPEDEF*)GPIOA_BASE)
#define GPIOB      ((GPIO_TYPEDEF*)GPIOB_BASE)
#define GPIOC      ((GPIO_TYPEDEF*)GPIOC_BASE)
#define GPIOD      ((GPIO_TYPEDEF*)GPIOD_BASE)
#define GPIOE      ((GPIO_TYPEDEF*)GPIOE_BASE)
#define GPIOF      ((GPIO_TYPEDEF*)GPIOF_BASE)
#define GPIOG      ((GPIO_TYPEDEF*)GPIOG_BASE)
#define GPIOH      ((GPIO_TYPEDEF*)GPIOH_BASE)
#define GPIOI      ((GPIO_TYPEDEF*)GPIOI_BASE)







#endif

/*本文件一般用于添加寄存器地址及结构体定义*/

GPIOx_BSRR_PIN.h代码:

#include "stm32f4xx.h"

#ifndef _STM32F4XX_GPIO_H
#define _STM32F4XX_GPIO_H



#define _pin0 (uint16_t)(1<<0)
#define _pin1 (uint16_t)(1<<1)
#define _pin2 (uint16_t)(1<<2)
#define _pin3 (uint16_t)(1<<3)
#define _pin4 (uint16_t)(1<<4)
#define _pin5 (uint16_t)(1<<5)
#define _pin6 (uint16_t)(1<<6)
#define _pin7 (uint16_t)(1<<7)
#define _pin8 (uint16_t)(1<<8)
#define _pin9 (uint16_t)(1<<9)
#define _pin10 (uint16_t)(1<<10)
#define _pin11 (uint16_t)(1<<11)
#define _pin12 (uint16_t)(1<<12)
#define _pin13 (uint16_t)(1<<13)
#define _pin14 (uint16_t)(1<<14)
#define _pin15 (uint16_t)(1<<15)



#endif

gpio_fun.c代码:

#include "stm32f4xx.h"
void delay(unsigned int count)
{
	for(;count!=0;count--)
	{
		//让它空转,达到延时目的
	
	}
}

void GPIOF_RESET(GPIO_TYPEDEF*GPIOX,uint16_t GPIOF_PIN)
{
	GPIOX->GPIOx_BSRR_H=GPIOF_PIN;

}
void GPIOF_SET(GPIO_TYPEDEF*GPIOX,uint16_t GPIOF_PIN)
{
	GPIOX->GPIOx_BSRR_L=GPIOF_PIN;

}

结束!

再小提一嘴我碰见的错误哈:

(1).宏定义最后不要加分号

(2).用其他源文件定义的函数一定要在最上面声明一下这个函数

(3).宏不能重复定义,要用ifndef  define  endif这个语句防止重复

(4).无返回值的函数调用方法为,函数名(参数);

(5).头文件是可以包含头文件的,但是按照代码规范来说是不建议这么写的

五_3.初步带入嵌入式工程师怎么干活的角度写代码(库函数雏形终章)

        一开始我个人把嵌入式这个东西想的十分复杂,我一开始认为嵌入式软件工程师的主要任务是自己编写代码去处理实际问题,实际上这个角度是错的,嵌入式这个行业已经较为成熟了,单片机方面更是如此。在大部分企业中,嵌入式软件工程师的主要工作内容并不是“写”程序,而是“调”程序,这个“调”包括调用函数,调试程序等主要操作。

        下面我来用一个较为复杂的点灯程序实例来说明一下怎样去“调”程序。

1.了解干活步骤

        自己写程序固然很稳妥,我并不反对自己写程序这件事,相反我十分倡导原创,更多的新想法才能改善当下这个严峻的就业形势。但现实情况是中小企业所给的任务多且繁杂,而总是耕耘一方领域大企业对于学历和竞赛经历以及项目经历的要求又多,很多开发者是很难一直在一个东西上面不断开发新东西的,对于我这个普通人来说,我更偏向于在一些程序现有的基础上作出调整将其用在我的开发过程中。

        我个人的思路是:

        明确目的---找到可以实现的方法(现有的)---修改方法,加上我的一些调整用来解决问题。

        而落实到实际上就是(以点灯为例,自己编写库的情况下):看电路图和手册,明确自己要给PF6端口输出低电平---上网找到可以简化或者适合多次使用的函数---根据函数的功能以及参数撰写程序的其他部分(主函数和各种头文件)

        我事先说明一下,接下来的这个点灯方法旨在了解库函数雏形和“调”程序的思路,实际上这个方法用来点灯是有点杀鸡用牛刀的意思,有点把问题复杂化了。

2.实例讲解

A.还是一样,通过思路讲问题,我们的最终目的是给PF6输出低电平,点亮红灯LED_REG,但是我们无法直接通过GPIOF的输出数据端口/置位复位端口去调整电平状态,事实上按照传统流程我们需要在这之前调整输出端口模式,调整上拉下拉端口为上拉模式,调整输出类型端口为推挽输出,再根据实际情况调整输出数据端口的输出速度。这样的流程是比较麻烦的,仅仅开个红灯还好说,但是我要是多开几个别的颜色的灯呢?或者多开几个别的外设呢?这种情况下这些操作就要多次重复,不仅影响代码的可读性,也大大提升了无意义的代码量,维护和差错的成本很高很高。所以我们就有大佬搞出了把这些之前的复杂流程(统称为初始化流程)简化的方法。(给大佬磕头!邦邦邦)

B.这个是野火讲解的一个函数,功能就是把咱们正常思维的管脚名字=操作实现了,下面我就介绍一下我是通过什么思路来引用这个函数撰写程序的。

(1)首先看一下这个初始化函数(文件名为stm32f4xx_gpio.h)

#include "stm32f4xx_gpio.h"

/**
  *函数功能:初始化引脚模式
  *参数说明:GPIOx,该参数为GPIO_TypeDef类型的指针,指向GPIO端口的地址
  * 			  GPIO_InitTypeDef:GPIO_InitTypeDef结构体指针,指向初始化变量
  */
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
	  uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;
	
	/*-- GPIO Mode Configuration --*/
  for (pinpos = 0x00; pinpos < 16; pinpos++)
  {
		/*以下运算是为了通过 GPIO_InitStruct->GPIO_Pin 算出引脚号0-15*/
		
		/*经过运算后pos的pinpos位为1,其余为0,与GPIO_Pin_x宏对应。pinpos变量每次循环加1,*/
		pos = ((uint32_t)0x01) << pinpos;
   
		/* pos与GPIO_InitStruct->GPIO_Pin做 & 运算,若运算结果currentpin == pos,
		则表示GPIO_InitStruct->GPIO_Pin的pinpos位也为1,
		从而可知pinpos就是GPIO_InitStruct->GPIO_Pin对应的引脚号:0-15*/
    currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

		/*currentpin == pos时执行初始化*/
    if (currentpin == pos)
		{		
			/*GPIOx端口,MODER寄存器的GPIO_InitStruct->GPIO_Pin对应的引脚,MODER位清空*/
			GPIOx->MODER  &= ~(3 << (2 *pinpos));
		
			/*GPIOx端口,MODER寄存器的GPIO_Pin引脚,MODER位设置"输入/输出/复用输出/模拟"模式*/
			GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (2 *pinpos));

			/*GPIOx端口,PUPDR寄存器的GPIO_Pin引脚,PUPDR位清空*/
			GPIOx->PUPDR &= ~(3 << ((2 *pinpos)));
		
			/*GPIOx端口,PUPDR寄存器的GPIO_Pin引脚,PUPDR位设置"上/下拉"模式*/
			GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (2 *pinpos));		
		
			/*若模式为"输出/复用输出"模式,则设置速度与输出类型*/
			if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
			{
				/*GPIOx端口,OSPEEDR寄存器的GPIO_Pin引脚,OSPEEDR位清空*/
				GPIOx->OSPEEDR &= ~(3 << (2 *pinpos));
				/*GPIOx端口,OSPEEDR寄存器的GPIO_Pin引脚,OSPEEDR位设置输出速度*/
				GPIOx->OSPEEDR |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (2 *pinpos));

				/*GPIOx端口,OTYPER寄存器的GPIO_Pin引脚,OTYPER位清空*/
				GPIOx->OTYPER  &= ~(1 << (pinpos)) ;
				/*GPIOx端口,OTYPER位寄存器的GPIO_Pin引脚,OTYPER位设置"推挽/开漏"输出类型*/
				GPIOx->OTYPER |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << (pinpos));
			}
		}
	}
}



/*********************************************END OF FILE**********************/

        其实如何看懂别人的函数是一个难度不小,在野火官方教学中也没有详细介绍这个函数,甚至对于函数的功能也是大概的提了一嘴,所以我们的首要目标就是找到看懂函数的步骤。

        首先先看函数的本体功能介绍(注释)和参数介绍,了解它大概实现了什么功能,再假定一一组参数带入其中,尝试在流程中走几步,找到内部的变量和外部的变量,分别标记,这时再带入两组简单的实际参数,走完全流程,反复的去理解函数。

        可以看到,函数的参数是(GPIO_TYPEDEF* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)。

        第一个参数比较简单,不需要带入函数中看,和它说明的一样,是结构体指针,指向一个结构体的首地址,也是指向了GPIO的某一片引脚的首地址。
        第二个参数可以在函数中看出其是一个GPIO_InitTypeDef的结构体,内部有GPIO_Pin,GPIO_Mode,GPIO_PuPd,GPIO_Speed,GPIO_OType这四个成员。这四个成员应该都被赋予了某个值,在函数中起到传递值的作用

        再来区分内部变量和外部变量:

        pinpos,pos,currentpin是在函数体内部作用的普通变量。

        文中的41行可以看出GPIO_Mode_OUT和GPIO_Mode_AF应该是两个函数的外部枚举变量,看名字其应该是代表着输出/复用输出的电平状态。而参数GPIOx是一个外部定义的结构体变量名,根据其说明可以看出其成员变量应该是GPIO的各个外设寄存器。

        这时候就可以带入两组简单参数,摸清它想实现什么功能,在这过程中如果作者自己做了注释,阅读起来的难度可以小不少,我将GPIO_InitStruct->GPIO_Pin 带入实际寄存器映射的PIN0=unsigned  short int (1<<0);和PIN1=unsigned short int (1<<1);

        最后发现对应PIN端口偏移地址的各个外设寄存器都进行了清零或者移位操作,最后可以推断出实际上外部有着一个可以决定端口状态的GPIO_InitTypeDef的结构体,并且把端口状态的值限制,并赋给了多组枚举类型的内部成员,其结构体内部的成员的类型应该是对应的枚举类型。

        到这里就大概推断出函数到底进行了什么操作了,其起到的作用是将外部的外设寄存器的端口模式进行移位操作,具体移位多少就是这个函数决定的,端口具体置什么模式就是外部决定的。

(2)再看头文件,一般宏和结构体/枚举都在这里定义(文件名为stm32f4xx_gpio.h)

#include "stm32f4xx.h"

/*GPIO引脚号定义*/
#define GPIO_Pin_0                 ((uint16_t)0x0001)  /*!< 选择Pin0 (1<<0) */
#define GPIO_Pin_1                 ((uint16_t)0x0002)  /*!< 选择Pin1 (1<<1)*/
#define GPIO_Pin_2                 ((uint16_t)0x0004)  /*!< 选择Pin2 (1<<2)*/
#define GPIO_Pin_3                 ((uint16_t)0x0008)  /*!< 选择Pin3 (1<<3)*/
#define GPIO_Pin_4                 ((uint16_t)0x0010)  /*!< 选择Pin4 */
#define GPIO_Pin_5                 ((uint16_t)0x0020)  /*!< 选择Pin5 */
#define GPIO_Pin_6                 ((uint16_t)0x0040)  /*!< 选择Pin6 */
#define GPIO_Pin_7                 ((uint16_t)0x0080)  /*!< 选择Pin7 */
#define GPIO_Pin_8                 ((uint16_t)0x0100)  /*!< 选择Pin8 */
#define GPIO_Pin_9                 ((uint16_t)0x0200)  /*!< 选择Pin9 */
#define GPIO_Pin_10                ((uint16_t)0x0400)  /*!< 选择Pin10 */
#define GPIO_Pin_11                ((uint16_t)0x0800)  /*!< 选择Pin11 */
#define GPIO_Pin_12                ((uint16_t)0x1000)  /*!< 选择Pin12 */
#define GPIO_Pin_13                ((uint16_t)0x2000)  /*!< 选择Pin13 */
#define GPIO_Pin_14                ((uint16_t)0x4000)  /*!< 选择Pin14 */
#define GPIO_Pin_15                ((uint16_t)0x8000)  /*!< 选择Pin15 */
#define GPIO_Pin_All               ((uint16_t)0xFFFF)  /*!< 选择全部引脚 */




/** 
  * GPIO端口配置模式的枚举定义
  */   
typedef enum
{ 
  GPIO_Mode_IN   = 0x00, /*!< 输入模式 */
  GPIO_Mode_OUT  = 0x01, /*!< 输出模式 */
  GPIO_Mode_AF   = 0x02, /*!< 复用模式 */
  GPIO_Mode_AN   = 0x03  /*!< 模拟模式 */
}GPIOMode_TypeDef;

/** 
  * GPIO输出类型枚举定义
  */  
typedef enum
{ 
  GPIO_OType_PP = 0x00,	/*!< 推挽模式 */
  GPIO_OType_OD = 0x01	/*!< 开漏模式 */
}GPIOOType_TypeDef;


/** 
  * GPIO输出速率枚举定义
  */  
typedef enum
{ 
  GPIO_Speed_2MHz   = 0x00, /*!< 2MHz   */
  GPIO_Speed_25MHz  = 0x01, /*!< 25MHz  */
  GPIO_Speed_50MHz  = 0x02, /*!< 50MHz  */
  GPIO_Speed_100MHz = 0x03  /*!<100MHz  */
}GPIOSpeed_TypeDef;


  

/** 
  *GPIO上/下拉配置枚举定义
  */ 
typedef enum
{ 
  GPIO_PuPd_NOPULL = 0x00,/*浮空*/
  GPIO_PuPd_UP     = 0x01, /*上拉*/
  GPIO_PuPd_DOWN   = 0x02  /*下拉*/
}GPIOPuPd_TypeDef;



/** 
  * GPIO初始化结构体类型定义
  */ 
typedef struct 
{
  uint32_t GPIO_Pin;              /*!< 选择要配置的GPIO引脚
                                        可输入 GPIO_Pin_ 定义的宏 */

  GPIOMode_TypeDef GPIO_Mode;     /*!< 选择GPIO引脚的工作模式
                                       可输入 GPIOMode_TypeDef 定义的枚举值*/

  GPIOSpeed_TypeDef GPIO_Speed;   /*!< 选择GPIO引脚的速率
                                       可输入 GPIOSpeed_TypeDef 定义的枚举值 */

  GPIOOType_TypeDef GPIO_OType;   /*!< 选择GPIO引脚输出类型
                                       可输入 GPIOOType_TypeDef 定义的枚举值*/

  GPIOPuPd_TypeDef GPIO_PuPd;     /*!<选择GPIO引脚的上/下拉模式
                                       可输入 GPIOPuPd_TypeDef 定义的枚举值*/
}GPIO_InitTypeDef;




void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
//声明函数,包含这个头文件就可以使用包含它的源文件内部的函数

(3)再看上一个头文件里面包含的头文件(此文件名为stm32f4xx.h)       

/*片上外设基地址  */
#define PERIPH_BASE           ((unsigned int)0x40000000)                          

/*总线基地址 */
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000)	

/*GPIO外设基地址*/
#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE            (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE            (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE            (AHB1PERIPH_BASE + 0x0C00)
#define GPIOE_BASE            (AHB1PERIPH_BASE + 0x1000)
#define GPIOF_BASE            (AHB1PERIPH_BASE + 0x1400)
#define GPIOG_BASE            (AHB1PERIPH_BASE + 0x1800)
#define GPIOH_BASE            (AHB1PERIPH_BASE + 0x1C00)

/*RCC外设基地址*/
#define RCC_BASE              (AHB1PERIPH_BASE + 0x3800)




//寄存器的值常常是芯片外设自动更改的,即使CPU没有执行程序,也有可能发生变化
//编译器有可能会对没有执行程序的变量进行优化

//volatile表示易变的变量,防止编译器优化,
#define     __IO    volatile
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;

/* GPIO寄存器列表 */
typedef struct
{
	__IO	uint32_t MODER;    /*GPIO模式寄存器						地址偏移: 0x00      */
	__IO	uint32_t OTYPER;   /*GPIO输出类型寄存器				地址偏移: 0x04      */
	__IO	uint32_t OSPEEDR;  /*GPIO输出速度寄存器				地址偏移: 0x08      */
	__IO	uint32_t PUPDR;    /*GPIO上拉/下拉寄存器			地址偏移: 0x0C      		*/
	__IO	uint32_t IDR;      /*GPIO输入数据寄存器				地址偏移: 0x10      		*/
	__IO	uint32_t ODR;      /*GPIO输出数据寄存器				地址偏移: 0x14      		*/
	__IO	uint16_t BSRRL;    /*GPIO置位/复位寄存器 低16位部分	地址偏移: 0x18 	*/
	__IO	uint16_t BSRRH;    /*GPIO置位/复位寄存器 高16位部分	地址偏移: 0x1A  */
	__IO	uint32_t LCKR;     /*GPIO配置锁定寄存器				地址偏移: 0x1C      */
	__IO	uint32_t AFR[2];   /*GPIO复用功能配置寄存器		地址偏移: 0x20-0x24 		*/
} GPIO_TypeDef;

/*RCC寄存器列表*/
typedef struct
{
__IO	uint32_t CR;            /*!< RCC 时钟控制寄存器,				地址偏移: 0x00 */
__IO	uint32_t PLLCFGR;       /*!< RCC PLL配置寄存器, 				地址偏移: 0x04 */
__IO	uint32_t CFGR;          /*!< RCC 时钟配置寄存器,   		地址偏移: 0x08 */
__IO	uint32_t CIR;           /*!< RCC 时钟中断寄存器,     		地址偏移: 0x0C */
__IO	uint32_t AHB1RSTR;      /*!< RCC AHB1 外设复位寄存器,	地址偏移: 0x10 */
__IO	uint32_t AHB2RSTR;      /*!< RCC AHB2 外设复位寄存器, 	地址偏移: 0x14 */
__IO	uint32_t AHB3RSTR;      /*!< RCC AHB3 外设复位寄存器, 	地址偏移: 0x18 */
__IO	uint32_t RESERVED0;     /*!< 保留, 										地址偏移:0x1C */
__IO	uint32_t APB1RSTR;      /*!< RCC APB1 外设复位寄存器,	地址偏移: 0x20 */
__IO	uint32_t APB2RSTR;      /*!< RCC APB2 外设复位寄存器,	地址偏移: 0x24 */
__IO	uint32_t RESERVED1[2];  /*!< 保留, 										地址偏移:0x28-0x2C*/
__IO	uint32_t AHB1ENR;       /*!< RCC AHB1 外设时钟寄存器,	地址偏移: 0x30 */
__IO	uint32_t AHB2ENR;       /*!< RCC AHB2 外设时钟寄存器,	地址偏移: 0x34 */
__IO	uint32_t AHB3ENR;       /*!< RCC AHB3 外设时钟寄存器,	地址偏移: 0x38 */
	/*RCC后面还有很多寄存器,此处省略*/
} RCC_TypeDef;


/*定义GPIOA-H 寄存器结构体指针*/
#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH               ((GPIO_TypeDef *) GPIOH_BASE)

/*定义RCC外设 寄存器结构体指针*/
#define RCC                 ((RCC_TypeDef *) RCC_BASE)

(4)根据上述的头文件和函数就可以写咱们的主函数了!

/*
	自己写库的方法点亮LED灯
  */
#include "stm32f4xx_gpio.h"  

void Delay( uint32_t nCount); 

/**
  *   主函数,使用封装好的函数来控制LED灯
  */
int main(void)
{	
	GPIO_InitTypeDef InitStruct;
	
	/*开启 GPIOF 时钟,使用外设时都要先开启它的时钟*/
	RCC->AHB1ENR |= (1<<5);

	/* LED 端口初始化 */
	
	/*初始化PF6引脚*/
	/*选择要控制的GPIO引脚*/															   
	InitStruct.GPIO_Pin = GPIO_Pin_6;
	/*设置引脚模式为输出模式*/
	InitStruct.GPIO_Mode = GPIO_Mode_OUT;
	/*设置引脚的输出类型为推挽输出*/
	InitStruct.GPIO_OType = GPIO_OType_PP;
	/*设置引脚为上拉模式*/
	InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
	/*设置引脚速率为2MHz */   
	InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	/*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
	GPIO_Init(GPIOF, &InitStruct);	
while(1){
	/*使引脚输出低电平,点亮LED1*/
	GPIO_ResetBits(GPIOF,GPIO_Pin_6);
}
// 函数为空,目的是为了骗过编译器不报错
void SystemInit(void)
{	
}

至此,红灯亮起。

六.库函数点亮LED——基于stm32f4xx固件库

        到这个章节开始,正式进入使用固件库的阶段,前面从过渡阶段了解了库是怎么写出来的,库函数如何分析使用。在使用现成的固件库时,比咱们自己写库,上网抄函数,编写寄存器都要简单,并且十分清晰。下面简单的介绍一下固件库如何使用。

1.新建工程(文件夹)

        一个库函数工程文件的根目录下,应该有这些文件夹,按照功能对他们进行介绍

(1)Libraries文件,翻译过来就是库,存放相关的库文件。

(2)Listing文件夹用于存放编译器编译时候产生的C语言、汇编、链接文件。

(3)Output文件夹用于存放中间文件。

  (4)  Project文件夹用于存放UV5的项目文件。

(5)User文件夹存放自己写的文件。

(6)keilkill.bat防止中间过程产生的文件大小过大,会删除一部分使用完毕的中间过程,减少工程项目的总大小。

2.新建工程(库)

(1)固件库的获取方法

A.官网下载,这个可以自己查.

B.论坛分享,CSDN,github,码云上有相应资源

C.开发板自带,第三方平台自己整理的例如野火,正点原子等(我的就是开发板自带的)

        库函数里面包含了各种驱动,相关外设函数,头文件,里面有很多的外设寄存器映射,结构体类型和枚举类型等大量的,取完名字的东西blablabla一堆。总之多熟悉库函数,标准库虽然现在用的稍微少了点,但是到工作岗位上还是有可能要用的。

3.在keil5内部说明创建工程

(1)将上面的文件夹创建完之后就可以打开keil5创建新项目了

点击new uvision project创建新的工程项目,保存在刚才创建的user根目录下,选择芯片类型

这个界面是问你要不要在线下载固件库,不推荐用,巨慢无比,最好是先下载下来,点击取消(cancel)

单击右上方的Target 1,鼠标悬浮在上方,稍等一下再点一下就可以更改其文件名,我们取名为固件库模板

右击source group,点击我选中的添加已有文件

进去找这些个文件,把他们添加进来,第一个startup文件夹要自己在keil5里面创建一下,开始文件自己找一下,其中user下的那个bsp_led.c不用管,这个是我后来添加进去的

再将头文件通过魔法棒里面c/c++选项的这个模块添加即可,在各文件中找到头文件,存放路径(一般都是在include这个名字的文件夹里)

同样是在c/c++这个界面,将这个宏定义进去(选定的蓝色文字)

至此,固件库模板创建完毕。(中间我省略了几步,不是很重要,不过尽量还是边看野火教程边搞,因为有的外设这个板子上并没有,但是库里存在它的映射和函数一类的东西,可能会报错)

2.使用固件库模板实现点亮LED(玩点花活,整个流水灯,实现7个颜色闪烁)

(1)明确目标,构思步骤

        首先明确目标是先搞一个流水灯,那么肯定是给对应端口低电平,延时,给对应端口高电平,延时,不断重复。点亮七个颜色就是按照:点PF6,延时,关PF6,延时,点PF7,延时,关PF7,延时,点PF8,延时,关PF8,点PF6和PF7,关上PF6和PF7...........一直到点亮 PF6,PF7,PF8,延时,关闭PF6,PF7,PF8,延时,做出一个循环就可以了。

        看图可能更直观一点

(2)第二步就开始正经编写程序了,在这之前我习惯把实现的功能搞成一个函数放在其他文件里,在主函数里直接使用这个函数。

        首先肯定是先打开对应时钟,既然是要用库,咱们就要调用时钟的库函数,其位于这个文件目录下

        点开这个库函数,点开这个头文件之后首先进入这个界面。

        brief就是描述这个函数的功能:这个文件包含了RCC固件库的所有函数原型。(很遗憾的是市面上大部分关于电子产品底层的东西都是英文,如果看不懂就干脆下个翻译软件)

        在这个文件找到打开对应时钟的函数,RCC_AHB1PeriphClockCmd,查看其函数功能和参数,一般情况下只要注释做的到位,咱们是不需要去详细分析的。

        

        可以看到这个函数的功能是打开挂载在AHB1外设的时钟,其第一个参数就是上面所定义的管脚,第二个参数就是选定使能和不使能。这些参数在对应的头文件中都已经定义完了,直接按照函数说明的参数调用即可。

//打开时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);

       

        其二就是完成初始化步骤,调用上节课的初始化函数,这里涉及到一个先后顺序的问题,既然要做流水灯,那么初始化结构体的成员就应该分别赋值(赋枚举变量),每一次赋完值都应该调用一次初始化函数,让指定的端口接收到改变电平状态的指令,这些函数,结构体,枚举也是在库里面找的,自己找一下吧,仔细阅读文档前面对整个文档的功能介绍就会十分好找。

        

//完成初始化配置PF6,7,8(定义初始化结构体变量,配置初始化结构体变量成员)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed=GPIO_Low_Speed;

	GPIO_Init(GPIOF, &GPIO_InitStruct);
	
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed=GPIO_Low_Speed;
	
	GPIO_Init(GPIOF, &GPIO_InitStruct);
	
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_OType=GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed=GPIO_Low_Speed;	
	
	GPIO_Init(GPIOF, &GPIO_InitStruct);

        

        其三,找到置位复位函数,处理对应端口输出高低电平,再设置一个延时函数(由于没学定时器,现在先搞一个粗糙的软件延时),插入电平状态改变的间隔中。最后做一个循环。

void software_timer(unsigned int count)
{
	for(;count!=0;count--)
	{}
}

        

while(1){


//设置置位复位寄存器
GPIO_ResetBits(GPIOF, GPIO_Pin_6);//置0
software_timer(0XFFFFFF);
GPIO_SetBits(GPIOF, GPIO_Pin_6);//置1

software_timer(0XFFFFFF);

GPIO_ResetBits(GPIOF, GPIO_Pin_7);//置0
software_timer(0XFFFFFF);
GPIO_SetBits(GPIOF, GPIO_Pin_7);//置1

software_timer(0XFFFFFF);

GPIO_ResetBits(GPIOF, GPIO_Pin_8);//置0
software_timer(0XFFFFFF);
GPIO_SetBits(GPIOF, GPIO_Pin_8);//置1

software_timer(0XFFFFFF);

GPIO_ResetBits(GPIOF, GPIO_Pin_6);//置0
GPIO_ResetBits(GPIOF, GPIO_Pin_7);//置0

software_timer(0XFFFFFF);

GPIO_SetBits(GPIOF, GPIO_Pin_6);//置1
GPIO_SetBits(GPIOF, GPIO_Pin_7);//置1

software_timer(0XFFFFFF);

GPIO_ResetBits(GPIOF, GPIO_Pin_7);//置0
GPIO_ResetBits(GPIOF, GPIO_Pin_8);//置0

software_timer(0XFFFFFF);

GPIO_SetBits(GPIOF, GPIO_Pin_7);//置1
GPIO_SetBits(GPIOF, GPIO_Pin_8);//置1

software_timer(0XFFFFFF);

GPIO_ResetBits(GPIOF, GPIO_Pin_6);//置0
GPIO_ResetBits(GPIOF, GPIO_Pin_8);//置0

software_timer(0XFFFFFF);

GPIO_SetBits(GPIOF, GPIO_Pin_6);//置1
GPIO_SetBits(GPIOF, GPIO_Pin_8);//置1

software_timer(0XFFFFFF);

GPIO_ResetBits(GPIOF, GPIO_Pin_6);//置0
GPIO_ResetBits(GPIOF, GPIO_Pin_7);//置0
GPIO_ResetBits(GPIOF, GPIO_Pin_8);//置0

software_timer(0XFFFFFF);

GPIO_SetBits(GPIOF, GPIO_Pin_6);//置1
GPIO_SetBits(GPIOF, GPIO_Pin_7);//置1
GPIO_SetBits(GPIOF, GPIO_Pin_8);//置1

software_timer(0XFFFFFF);

	}


        最后效果,七个颜色的灯来回闪烁。胜利!

七.按键检测

        废话不多说,先看图。

        可以看到开关按下,KEY1(上方)和KEY2(下方)所对应的GPIO口都应该识别出高电平,既然做按键检测,那么就要使用GPIO的输入功能,调整对应管脚的端口模式寄存器,再调整其输入数据寄存器,最后调整上下拉寄存器为无上下拉。

#include "bsp_key.h"
//目标,实现初始化按键(输入部分),检测PA0和PC13的电平状态,并用LED灯来显示检测结果
//若PA0为低电平,则按键没按下,灯不亮,若PA0为高电平,灯亮红灯
//若PC13为低电平,则按键没按下,灯不亮,若PC13为高电平,灯亮绿灯



void GPIO_KEY_CONFIG()
{
	/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef GPIO_InitStructure;
		/*开启相关的GPIO外设时钟*/
		RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA,ENABLE); 
		/*选择要控制的GPIO引脚*/															   
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;	
		/*设置引脚模式为输入模式*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;   
    /*设置引脚为上拉模式*/
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

		/*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
		GPIO_Init(GPIOA, &GPIO_InitStructure);	

/*开启相关的GPIO外设时钟*/
		RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOC,ENABLE); 
		/*选择要控制的GPIO引脚*/															   
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;	
		/*设置引脚模式为输入模式*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;   
    /*设置引脚为上拉模式*/
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

		/*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
		GPIO_Init(GPIOC, &GPIO_InitStructure);	
		
}		

uint8_t KeyScan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{			
	/*检测是否有按键按下 */
	if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == 1)  
	{	 
		/*等待按键释放 */
		while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == 1)
			
		{return 	1;}	 
	}
	else
		return 0;
}

这代码的文件名是bsp_key.c, 定义了一个初始化按键函数和按键扫描函数。

其中GPIO_InitTypeDef结构体类型,

RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA,ENABLE)时钟使能函数,

GPIO_Pin_0管脚寄存器映射,

GPIO_Mode_IN,GPIO_PuPd_NOPULL这两个枚举类型,

GPIO_Init(GPIOC, &GPIO_InitStructure);            GPIO端口初始化函数

GPIO_ReadInputDataBit(GPIOx,GPIO_Pin)        GPIO电平状态读写函数

这些都是库函数里定义的,其作用就如名字一样。

 ——————————————————————————————————————————-

#include"bsp_led.h"
void LED_GPIO_Config(void)
{		
		/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef GPIO_InitStructure;

		/*开启LED相关的GPIO外设时钟*/
		RCC_AHB1PeriphClockCmd ( LED1_GPIO_CLK|
	                           LED2_GPIO_CLK|
	                           LED3_GPIO_CLK, ENABLE); 

		/*选择要控制的GPIO引脚*/															   
		GPIO_InitStructure.GPIO_Pin = LED1_PIN;	

		/*设置引脚模式为输出模式*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;   
    
    /*设置引脚的输出类型为推挽输出*/
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    
    /*设置引脚为上拉模式*/
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

		/*设置引脚速率为2MHz */   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; 

		/*调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO*/
		GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);	
    
    /*选择要控制的GPIO引脚*/															   
		GPIO_InitStructure.GPIO_Pin = LED2_PIN;	
    GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);	
    
    /*选择要控制的GPIO引脚*/															   
		GPIO_InitStructure.GPIO_Pin = LED3_PIN;	
    GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);	
		
		/*关闭RGB灯*/
		LED_RGBOFF;		
}
/*********************************************END OF FILE**********************/

        这个代码的文件名为bsp_led.h,其定义了LED灯的初始化函数。

———————————————————————————————————————————        主函数如下:

#include "stm32f4xx.h"
#include "./led/bsp_led.h"
#include "./key/bsp_key.h" 




int main(void)
{
	GPIO_KEY_CONFIG();
	LED_GPIO_Config();
		while(1)                            
	{	   
		if( KeyScan(GPIOA,GPIO_Pin_0)== 1)
		{
			
					LED_RED;	
		}   
    
    if(KeyScan(GPIOC,GPIO_Pin_13) == 1)
		{
	
					LED_GREEN;	
		}   
	}
}
	

        展示一下LED_RED的代码:

#ifndef __LED_H
#define	__LED_H

#include "stm32f4xx.h"

//引脚定义
/*******************************************************/
//R 红色灯
#define LED1_PIN                  GPIO_Pin_6                 
#define LED1_GPIO_PORT            GPIOF                      
#define LED1_GPIO_CLK             RCC_AHB1Periph_GPIOF

//G 绿色灯
#define LED2_PIN                  GPIO_Pin_7                 
#define LED2_GPIO_PORT            GPIOF                      
#define LED2_GPIO_CLK             RCC_AHB1Periph_GPIOF

//B 蓝色灯
#define LED3_PIN                  GPIO_Pin_8                 
#define LED3_GPIO_PORT            GPIOF                       
#define LED3_GPIO_CLK             RCC_AHB1Periph_GPIOF
/************************************************************/


/** 控制LED灯亮灭的宏,
	* LED低电平亮,设置ON=0,OFF=1
	* 若LED高电平亮,把宏设置成ON=1 ,OFF=0 即可
	*/
#define ON  0
#define OFF 1

/* 带参宏,可以像内联函数一样使用 */
#define LED1(a)	if (a)	\
					GPIO_SetBits(LED1_GPIO_PORT,LED1_PIN);\
					else		\
					GPIO_ResetBits(LED1_GPIO_PORT,LED1_PIN)

#define LED2(a)	if (a)	\
					GPIO_SetBits(LED2_GPIO_PORT,LED2_PIN);\
					else		\
					GPIO_ResetBits(LED2_GPIO_PORT,LED2_PIN)

#define LED3(a)	if (a)	\
					GPIO_SetBits(LED3_GPIO_PORT,LED3_PIN);\
					else		\
					GPIO_ResetBits(LED3_GPIO_PORT,LED3_PIN)


/* 直接操作寄存器的方法控制IO */
#define	digitalHi(p,i)			 {p->BSRRL=i;}		//设置为高电平
#define digitalLo(p,i)			 {p->BSRRH=i;}		//输出低电平
#define digitalToggle(p,i)	 {p->ODR ^=i;}		//输出反转状态

/* 定义控制IO的宏 */
#define LED1_TOGGLE		digitalToggle(LED1_GPIO_PORT,LED1_PIN)
#define LED1_OFF			digitalHi(LED1_GPIO_PORT,LED1_PIN)
#define LED1_ON				digitalLo(LED1_GPIO_PORT,LED1_PIN)

#define LED2_TOGGLE		digitalToggle(LED2_GPIO_PORT,LED2_PIN)
#define LED2_OFF			digitalHi(LED2_GPIO_PORT,LED2_PIN)
#define LED2_ON				digitalLo(LED2_GPIO_PORT,LED2_PIN)

#define LED3_TOGGLE		digitalToggle(LED3_GPIO_PORT,LED3_PIN)
#define LED3_OFF			digitalHi(LED3_GPIO_PORT,LED3_PIN)
#define LED3_ON				digitalLo(LED3_GPIO_PORT,LED3_PIN)

/* 基本混色,后面高级用法使用PWM可混出全彩颜色,且效果更好 */

//红
#define LED_RED  \
					LED1_ON;\
					LED2_OFF;\
					LED3_OFF

//绿
#define LED_GREEN		\
					LED1_OFF;\
					LED2_ON;\
					LED3_OFF

//蓝
#define LED_BLUE	\
					LED1_OFF;\
					LED2_OFF;\
					LED3_ON

					
//黄(红+绿)					
#define LED_YELLOW	\
					LED1_ON;\
					LED2_ON;\
					LED3_OFF
//紫(红+蓝)
#define LED_PURPLE	\
					LED1_ON;\
					LED2_OFF;\
					LED3_ON

//青(绿+蓝)
#define LED_CYAN \
					LED1_OFF;\
					LED2_ON;\
					LED3_ON
					
//白(红+绿+蓝)
#define LED_WHITE	\
					LED1_ON;\
					LED2_ON;\
					LED3_ON
					
//黑(全部关闭)
#define LED_RGBOFF	\
					LED1_OFF;\
					LED2_OFF;\
					LED3_OFF		




void LED_GPIO_Config(void);
void LED_GPIO_OPEN_RED(void);
void LED_GPIO_OPEN_GREEN(void);
#endif /* __LED_H */

        最终实现效果KEY1按下,红灯常亮。KEY2按下,绿灯常亮。

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值