由于期末考试需要考实验代码,为了自己能够印象深刻,考试能够顺利通过,特意写个博客记录一下实验的过程以及其中一些细节。下面将通过一个例题一步一步进行讲解。
软件配置:Keil uVision5
开发板芯片型号:STM32F103ZE
基于C语言的GPIO操作
已知:战舰开发板连接按键(输入设备)KEY0、KEY1、KEY2,和发光二极管(输出设备)LED0、LED2.
要求:用户任意按下某个按键,开发板根据用户按下的不同的按键,采用不同的方式使二极管闪烁。
硬件电路如下:
由于实验要求需要我们进行输入输出操作,那么就需要使用到GPIO,需要电亮二极管,因为硬件的响应速度是十分迅速的,如果仅仅在程序中输出一下高电平,那现实中LED只是在极短的时间闪一下,以至于我们根本观察不到它亮过,因此这里需要使用延时,由于我们只需要看到灯亮灯灭的效果即可,那么使用软件延时即可,即用while循环来达到延时的效果,虽然精度和效率都不高,但是实现起来是十分方便的,也符合我们的实验要求。
由于LED0和LED1分别接在PB5和PE5上,因此我们只需要操控GPIOB和GPIOE的Pin5的输出即可实现实验需要的效果了。
下面的实验将使用CMSIS标准库版。CMSIS全称Cortex MicroController Software Interface Standard由ARM 与芯片厂商建立的软件接口标准。芯片商意法半导体公司(ST公司)推出的STM32 标准库函数就是遵循CMSIS标准。
因为配置文件比较多,但是配置起来还是比较简单的,就是c语言开头导入配置文件即可,因此以下的代码内容可运行默认的前提是已经配置好了需要的头文件。
整个实验的工程目录如上图所示,这个工程我会原封不动的传到资源中去。因为这是我们老师给我们的工程模板,因此会比较规范,其中需要我们编写代码的文件只有sw_delay.c、key_driver.c、led_driver.c、main.c这四个文件。
其中
sw_delay.c需要写入软件延时的函数,是我们能够清晰的看到LED灯亮和灯灭的实验效果。
key_driver.c需要对应GPIO输入脚的初始化,以达到按下按键能够实现灯亮灯灭的控制效果。
led_driver.c需要写入对应GPIO驱动LED的函数以及初始化。
main,c则是将上述三个文件的函数整合,完成整个实验的内容。
这里使用软件延时,while循环指定次数达到延时的效果。
#include "sw_delay.h"
void SoftwareDelay(uint32_t time )
{
while(time>0)
{time--;
}
}
在使用一个外设前,必须先配置和激活启动该外设的时钟,如GPIO端口B,那么就要激活GPIOB的时钟。只有启动了时钟外设才变得可用,而不需要使用的外设无需使能时钟,这样做的好处是可以降低STM32芯片的内部功耗。
外设也区分高速外设和低速外设,这就需要对应不同工作频率的时钟来使能。
可见GPIO属于高速外设,需要使用APB2来连接使能。
然后我们可以参考STM32固件库使用手册,查看对应时钟使能函数的用法:
//代表使能GPIOA使得其能够正常工作和使用
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)
GPIO一共有16个引脚,8种工作模式,3种最大翻转速度(2MHz、10MHz、50MHz)。
上拉输入是指在没有信号输入的情况下可以稳定在高电平。
工作模式可以参考:深刻理解GPIO(上拉输入、下拉输入、模拟输入、浮空输入,开漏输出,推挽输出的区别,以STM32为例)感觉讲的十分不错和详细。
库函数中已经封装好了GPIO的初始化结构体
其中三个参数的取值分别如下:
因此我们能够对GPIO进行快速的初始化:
//定义一个GPIO结构体
GPIO_InitTypeDef GPIO_InitStructure;
//使用引脚2、3、4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
//工作模式为上拉输入,即无信号时处于高电平状态
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
//初始化GPIOE
GPIO_Init(GPIOE, &GPIO_InitStructure);
最终我们的key_driver.c如下所示:
#include "stm32f10x_conf.h"
void SoftwareDelay(uint32_t time );
void KEY_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
使能GPIOE,然后使用GPIOE的2、3、4号引脚上拉输入作为我们控制LED的开关。
此函数能够设置GPIO引脚的电平。
此函数能够重置GPIO引脚的电平。
由于PB5和PE5分别连接着LED0和LED1,因此我们设置PB5和PE5为推挽输出用于控制LED灯的亮灭,然后通过对PE2、3、4引脚的输入来控制PB5和PE5的状态(高电平或者低电平)从而控制LED0和LED1的状态,达到我们需要的效果。
#include "stm32f10x_conf.h"
void LED_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
//推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
}
void LED0_LightUp(void)//点亮LED0
{
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
}
void LED1_LightUp(void)//点亮LED1
{
GPIO_ResetBits(GPIOE, GPIO_Pin_5);
}
void LED0_Flash(void){//LED0闪烁4次
uint8_t i;
for(i=1; i<=4; i++){
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
SoftwareDelay(100000);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
SoftwareDelay(100000);
}
}
void LED1_Flash(void){
uint8_t i;
for(i=1; i<=4; i++){
GPIO_ResetBits(GPIOE, GPIO_Pin_5);
SoftwareDelay(100000);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
SoftwareDelay(100000);
}
}
void LED01_Flash(void){
uint8_t i;
for(i=1; i<=4; i++){
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
GPIO_ResetBits(GPIOE, GPIO_Pin_5);
SoftwareDelay(100000);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
SoftwareDelay(100000);
}
}
main函数只需要将整体架构拼接起来就可以了。整体思路就是,初始化LED0和LED1都是熄灭状态,按下KEY0、1、2两个LED都会一直亮;按下KEY1、2 LED0闪烁;按下KEY0、2 LED1闪烁; 按下KEY0、1 LED01一起闪烁。
#include"stm32f10x_conf.h"
void LED_GPIO_Config(void);//LED GPIO口配置函数申明
void LED0_LightUp(void);//LED0亮
void LED1_LightUp(void);//LED1灭
void LED0_Flash(void);
void LED1_Flash(void);
void LED01_Flash(void);
#define KEY0 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)//读取键0
#define KEY1 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)//读取键1
#define KEY2 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_2)//读取键2
void KEY_GPIO_Config(void);//KRY GPIO初始化申明
int main(void)
{
KEY_GPIO_Config();
LED_GPIO_Config();
while(1){
if((KEY0 == 1)&&(KEY1 == 1)&&(KEY2 == 1)){
LED0_LightUp();//LED0点亮
LED1_LightUp();//LED1点亮
}
if((KEY0 == 0)&&(KEY1 == 1)&&(KEY2 == 1)){
LED0_Flash;//LED0闪烁
}
if((KEY0 == 1)&&(KEY1 == 0)&&(KEY2 == 1)){
LED1_Flash();//LED1闪烁
}
if((KEY0 == 1)&&(KEY1 == 1)&&(KEY2 == 0)){
LED01_Flash();//LED01一起闪烁
}
}
}
点击魔术棒
然后Debug选择Use Simulator即可。
点击调试按钮进入调试界面
首先可以在main函数中设置几个断点如下所示:
然后需要打开GPIOB和GPIOE的对应详细信息的界面:
打开之后如下所示,因为还没初始化,所以看不到初始化后的结果
然后点击左上角运行到断点处,运行完初始化函数就能够看到GPIOE和GPIOB的设置发生了变化:PB5和PE5是推挽输出,PE2、3、4是上拉输入。
当将PE4、3(分别对应的是KEY0、1)点击模拟开关按下时,程序能够运行到LED01闪烁的代码语句中,此时PE5和PB5都会发生较快的闪动信号,模拟LED的闪亮,由于闪动太快整个过程可能不足一秒钟,因此可以再增加一些延时,但是还是看的出来信号在闪动的。
写了一遍,确实也遇到了一些问题,让我发现原来老师以前的代码有些可能并不是必要的,并且理解了初始化以及LED被控制的操作,让我更加熟悉了整个流程。毕竟学习,不能学了就忘,总得留下点东西才好。