前言
由于想要加入物联网实验室的嵌入式部门,按照学长给我们的考核要求我们通过江科大的视频对STM32进行了一个初步的学习,考核的内容是LED流水灯和用按键控制LED灯,下面分享一些我的学习过程中遇到的一些问题及我的一些心得
简介
我们使用的是keil5这个软件,使用的芯片是STM32F103C8T6,主要的学习内容有两个
1、LED流水灯
我们需要用到LED灯和面包板等材料。
LED:发光二极管,正向通电点亮,反向通电不亮。
那么我先来讲一下比较简单的流水灯吧,流水灯的代码主要由三个部分构成,RCC时钟控制函数,GPIO初始化函数,GPIO口输出函数。
(1)启用RCC开启GPIO的时钟
最常用的时钟控制函数有以下三个
我们采用下面这个函数来开启我们的时钟(所有外设使用之前都必须开启时钟)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟;
(2)使用GPIO_Init函数初始化GPIO
GPIO_Init函数的作用是:用结构体的参数来初始化GPIO口,需要先定义一个结构体变量,然后再给结构体赋值,最后调用这个函数,则函数内部会自动读取结构体的值,然后自动把外设的各个参数配置好。
//GPIO的初始化
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//输出模式为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//GPIO引脚,赋值为第0号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//GPIO速度,赋值为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);//将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
//实现GPIOA的初始化
(3)使用输出或者输入的函数控制GPIO口
可以用的四个输出函数
- GPIO_SetBits是将对应的标志口置为高电平;GPIO_ResetBits是将对应的标志口置为低电平
- GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);等价于GPIO_ResetBits;GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET);等价于GPIO_setBits;
- LED长脚插到PA0口,短脚插到负极为高电平点亮方式。
由于需要一次设置多个端口,所以在这里选择GPIO_Write #在GPIO_Write 的第二个参数中,指定写到数据寄存器的值所以可以写各个端口对应的值 eg. 0x0001对应GPIO_Pin_0
由于我们采用的是低电平点亮的方式所以我们需要在 0x0001前面加一个按位取反的符号 即 “ ~ ”。
由于我们想要的效果是轮流亮,所以我们得在每个灯的输出函数前后加上延时函数来确保能够出现一个轮流亮的效果。
GPIO_Write(GPIOA, ~0x0001); //0000 0000 0000 0001,PA0引脚为低电平,其他引脚均为高电平,注意数据有按位取反
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0002);
Delay_ms(100);
GPIO_Write(GPIOA, ~0x0004);
Delay_ms(100);
GPIO_Write(GPIOA, ~0x0008);
Delay_ms(100);
GPIO_Write(GPIOA, ~0x0010);
Delay_ms(100);
GPIO_Write(GPIOA, ~0x0020);
Delay_ms(100);
GPIO_Write(GPIOA, ~0x0040);
Delay_ms(100);
GPIO_Write(GPIOA, ~0x0080);
Delay_ms(100);
此时我们第一个任务LED流水灯的代码就完成了。
总结:在这个LED流水灯的学习过程中,主要学习了对于GPIO_Init函数的初始化,RCC时钟控制,还有对于一些比较基本的输入输出控制函数,同时也了解到了延时函数的作用以及如何使用延时函数。当然对于初学者而言我觉得最有用的还是新建工程这一小节,让我们对keil5这一软件的使用有了一个最基本的了解。
2、按键控制LED
在这个代码里面,我们需要同时写LED和按键的代码,由于若把LED和按键的两部分驱动代码混在主函数里,代码会很乱,不方便管理和移植,所以我们一般情况下会把它封装起来单独放在另外的.c和.h文件里面,这种方法叫做模块化编程
2.按键控制LED灯
- 按键:常见的输入设备,按下导通,松手断开。
- 按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴有一连串的抖动(通常会加入一个20ms的延时函数来进行消抖处理)
- 传感器模块介绍:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出。
按键控制LED接线图
(1)我们第一步可以先将LED.c和LED.h还有Key.c和Key.h这几个文件先建好,并放在用于存放硬件驱动的文件夹Hardware中。
LED.c和Key.c用于存放驱动程序的主体代码
LED.h和Key.h用于存放这个驱动程序可以对外提供的函数或变量的声明
#.h文件中要添加一个防止头文件重复包含的代码
#ifndef __XXX_H
#define __XXX_H
#endif
#ifndef和#endif构成了一个类似于括号一样的东西,函数和变量声明放在里面,其中#endif后面必须是空行。我们写好的函数需要放里面声明然后主函数就可以直接使用
(2)我们需要在LED.c文件里面写好LED_Init的初始化函数,类似于GPIO_Init,我们需要写打开时钟,配置端口模式这些东西
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//GPIO_Init初始化
GPIO_SetBits(GPIOA,GPIO_Pin_1 | GPIO_Pin_2);//由于我们采用低电平点亮方式所以我们将初始电平设置为高电平这样如果不进行操作LED就不会亮
}
初始化完成后我们需要对这个模块进行外部声明,即在.h文件中将上述代码的第一行复制过去
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
#endif
然后在main.c 文件中添加#include”LED.h”包含LED模块的头文件,之后我们就能在主函数中直接调用LED_Init,即完成了LED的初始化。
(3)初始化之后我们需要点亮和熄灭LED的函数
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为低电平
}
**void LED1_OFF(void)
{
GPIO_setBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为高电平
}
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为低电平
}
void LED1_OFF(void)
{
GPIO_setBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为高电平
}**
同样我们也需要将这些文件放入.h文件进行声明
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
void LED1_ON(void);
void LED1_OFF(void);
void LED2_ON(void);
void LED2_OFF(void);
#endif
(4)对Key.c文件进行初始化并加上一个读取按键值的函数,并将其放入.h文件进行声明
#include "stm32f10x.h" // Device header
#include "Delay.h"
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0,如果没有按键按下,就返回0;
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 1; //置键码为1
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 2; //置键码为2
}
return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
#ifndef __KEY_H
#define __KEY_H
void Key_Init(void);
uint8_t Key_GetNum(void);
#endif
四个GPIO的读取函数
(5)
经过上面的四个步骤,我们已经可以实现用按键控制LED的开与关,但是与我们所要求的按下按键LED状态取反还有一定的差别,所以我们还得对我们的代码进行进一步的改进。
我们可以在LED.c文件中再加入一个函数来实现。
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为低电平
}
}
上述代码为LED1的举例,实现逻辑为通过GPIO_ReadOutputDataBit函数来读取当前端口的输出状态,再由if else条件语句对端口状态进行调整,从而实现端口电平的翻转。
(6)最后我们可以得到一个简洁但是功能十分强大的main.c函数
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
uint8_t KeyNum; //定义用于接收按键键码的变量
int main(void)
{
LED_Init();
Key_Init();
while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键1按下
{
LED1_Turn(); //LED1翻转
}
if (KeyNum == 2) //按键2按下
{
LED2_Turn(); //LED2翻转
}
}
}