目录
关于固件库的应用,我们从输入和输出两个角度来进行练习,外设都以常见的GPIO口为例。
一、用固件库点亮LED灯——输出
(1)点亮灯
我们在USER文件夹中,每用到一个模块,就建立一个相关模块的文件夹。建立led文件夹,在该文件夹中添加.c和.h文件,同时,别忘了给新的.h文件添加编译路径(前面文章介绍过)。
为防止该文件被多个文件引用而产生重定义的情况,我们在每个.h文件的开头都加上条件编译的宏。
#ifndef __BSP_LED_H
#define __BSP_LED_H
#endif /*__BSP_LED_H*/
1.观察外设(GPIO)端口结构体的成员:引脚、速度、输出/入方式,我们把常用到的端口、引脚这些宏定义(与硬件相关的)用我们熟悉的、好理解的名字在bsp_led.h文件里面重新define宏定义一下。
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOC//时钟
#define LED1_GPIO_PORT GPIOC //端口
#define LED1_GPIO_PIN GPIO_Pin_2//pin 引脚
#define LED2_GPIO_CLK RCC_APB2Periph_GPIOC//时钟
#define LED2_GPIO_PORT GPIOC //端口
#define LED2_GPIO_PIN GPIO_Pin_3//pin
2.在新建立的led文件夹中,添加控制led的.c文件:bsp_led.c,在里面添加关于led的函数:初始化两个灯的程序(两个led灯用到两个不同的端口,所以要分别配置两个不同的初始化函数)。根据前面文章介绍的初始化I/O口的步骤:
1、打开外设(GPIO)的时钟
2、选定I/O口
3、配置输入还是输出(配置CRL/CRH寄存器)
4、如果是输出,配置输出速率(也是在CRL/CRH寄存器)
5、调用外设(GPIO)初始化函数,把配置好的结构体成员写到寄存器里面
void LED_GPIO_Config(void)//初始化相关的GPIO 第2个灯
{
GPIO_InitTypeDef GPIO_InitStruct;
/*第一步:打开外设的时钟(RCC寄存器控制)*/
RCC_APB2PeriphClockCmd(LED1_GPIO_CLK|LED2_GPIO_CLK,ENABLE);
/*第二步:配置外设初始化结构体*/
GPIO_InitStruct.GPIO_Pin = LED1_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
/*第三步:调用外设初始化函数,把配置好的结构体成员写到寄存器里面*/
GPIO_Init(LED1_GPIO_PORT,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = LED2_GPIO_PIN;
GPIO_Init(LED2_GPIO_PORT,&GPIO_InitStruct);
}
初始化函数放在主函数while循环之前,并且写一个软件延时更好的判断实验现象。
void delay(uint32_t count);
int main(void)
{
/*在程序来到main函数这里的时候,系统时钟已经配置成72M*/
LED_GPIO_Config();//初始化函数
while(1)
{
GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
delay(0xfffff);
GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);
delay(0xfffff);
GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
delay(0xfffff);
GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);
delay(0xfffff);
}
}
void delay(uint32_t count)
{
for(;count!=0;count--);
}
(2)移植到其他板子
如果我们把代码移植到其他板子上时,寄存器会发生改变,如果将代码通过宏来进行控制,将会做到与硬件无关,只需修改简单的宏,增加可移植性。
在led的.h文件里里面:添加板子的宏定义,增加if判断语句。
#define YHMINI 1
#define YHIOT 0
#if YHMINI
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOC//时钟
#define LED1_GPIO_PORT GPIOC //端口
#define LED1_GPIO_PIN GPIO_Pin_2//pin 引脚
#define LED2_GPIO_CLK RCC_APB2Periph_GPIOC//时钟
#define LED2_GPIO_PORT GPIOC //端口
#define LED2_GPIO_PIN GPIO_Pin_3//pin
#elif YHIOT
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOB//时钟
#define LED1_GPIO_PORT GPIOB //端口
#define LED1_GPIO_PIN GPIO_Pin_0//pin
#define LED2_GPIO_CLK RCC_APB2Periph_GPIOB//时钟
#define LED2_GPIO_PORT GPIOB //端口
#define LED2_GPIO_PIN GPIO_Pin_1//pin
#endif
二、按键检测——输入
用到GPIOA、C端口,并且需要配置输入数据寄存器(IDR)高16位不动,只配置低16位(第0位和第13位)
1.按键检测函数:
与51不同的是,这里采取的是按下按键返回数据的方式,按下且松手之后返回按下的指令,没按下返回未被按下的指令。且按键按下之后是高电平,松手是低电平。我们只需要自己写一个按键检测函数即可,且这个函数是要带返回值的。
uint8_t KEY_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)//按键检测函数
{
if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON)//如果按键被按下
//该函数是读出某个寄存器某一位的数值
{
while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);//松手检测
//如果while循环的条件为真,也就说明按键一直被按下,先不返回指令
return KEY_ON;//松手之后,才返回按键被按下的指令
}
else return KEY_OFF;//按下没被按下,返回按键未被按下的指令
}
//要求:按键按下不出现反应,当按键按下且松手之后再出现指令
2.宏定义闪烁:按键按下一次:灯亮;再按下一次:灯灭。如此循环,相关解释如下:
//按键按下一次:灯亮;再按下一次:灯灭。如此循环
//两个数^ :若两数相同为0,不同为1
#define digitalTOGGLE(p,i) {p->ODR ^= i;}
#define LED1_TOGGLE digitalTOGGLE(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED2_TOGGLE digitalTOGGLE(LED2_GPIO_PORT,LED2_GPIO_PIN)
/* p->ODR ^= i : p->ODR = p->ODR ^ i
0000 0000 0000 0000 ^ 0000 0000 0000 0010 = 0000 0000 0000 0010
0000 0000 0000 0010 ^ 0000 0000 0000 0010 = 0000 0000 0000 0000
0000 0000 0000 0000 ^ 0000 0000 0000 0010 = 0000 0000 0000 0010
......*/
这样在main函数里面就会写的非常简单:
int main(void)
{
LED_GPIO_Config();
KEY_GPIO_Config();
while(1)
{
if(KEY_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
{
/*如果检测到按键1被按下,LED1闪烁一次*/
LED1_TOGGLE;
}
if(KEY_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON)
{
/*如果检测到按键2被按下,LED2闪烁一次*/
LED2_TOGGLE;
}
}
}