IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 【WB32库开发】第4章 GPIO的输出与输入(下) -> 正文阅读

[嵌入式]【WB32库开发】第4章 GPIO的输出与输入(下)

在进入本章内容之前,我们先回顾一下前两节提出的一些问题:
1)如何找到固件库自带函数位置?
2)#include 的作用是什么?
3)将函数模块化的优与劣。
如果你已经完成了前两节的例程,那么就带着疑问与笔者一起走进这一节的内容吧。

4.5按键检测点亮LED例程详解

4.5.1工程主体构成

在这里插入图片描述
在这里我们可以看到工程目录下有五个文件夹,这里简单介绍一下其中内容与作用,想要更深入了解的同学也不要心急,跟着后续教程学习你的水平必然水涨船高。
我们还需要知道三件事:
第一,是用C语言编写程序时,编写的内容被储存在文本文件中,该文件被称为源代码文件(source code file),大部分C系统都要求文 件名以.c结尾,在文件名中,点号(.)前面 的部分称为基本名(basename),点号后面的部分称为扩展名(extension)。基本名与扩展名的组合就是文件名。
第二,为了方便管理.c文件中定义的各种函数,我们一般会将 .c 文件中定义的函数名在 .h 文件中进行声明,这样在外部需要调用某部分函数时只需使用 #include 将 .h 文件包含进去即可,不需再次声明。
第三,#include这行代码是一条C预处理器指令(preprocessor directive)。通常,C编译器在编译前会对源代码做一些准备工作,即预处理 (preprocessing)。
例,#include "bsp_key.h"的作用相当于把bsp_key.h文件的所有内容都输入该行所在的位置,实际上,这是一种“拷贝-粘贴”的操作(注,此处及上述部分内容引用修改《C Prime Plus》第二章中部分基础知识内容)。

STARTUP:这里放着WB32的启动文件,这是系统上电复位后执行的第一个程序,启动代码充当着程序和操 作系统之间的接口,不需要人为修改。
CMSIS:这里存放着WB32的系统配置文件,主要管理着WB32的时钟频率,除需要修改系统工作频率外不需要修改。
FWLIB:这里存放着WB32的外设固件库函数,我们想要使用WB32,就必须会使用好存放在这里的.c文件。
例,在按键检测点亮LED例程中,我们会使用到核心开发板的GPIO功能,对应的就需要找到 “wb32f10x_gpio.c” 文件。//笔者上面提到过,如果在 .c 文件中定义的函数一般会在 .h 文件中进行声明,所以我们可以直接使用 #include 指令对 .h 文件进行包含即可在外部调用对应的函数了。
USER:此处存放main函数,wb32f10x_it.c 文件以及其他由用户自己编写的函数模块。(wb32f10x_it.c 一般用来存放中断相关函数,后续课程会进行补充)
DOC:这里一般存放说明性文档 (readme.txt),可以记录开发过程中的细节问题等。

4.5.2用户代码分析

1.LED驱动程序
以下分别为bsp_led.c与bsp_led.h文件

#include "bsp_led.h"
void LED_GPIO_Config(void)
{
	//LED端口引脚时钟初始化
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 | LED_GPIO_CLK, ENABLE );
	//LED端口引脚设置初始化
	GPIO_Init(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_MODE_OUT |GPIO_OTYPE_PP |GPIO_PUPD_NOPULL |GPIO_SPEED_HIGH);		
	//LED端口引脚置位,处于关闭状态
	GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN);
}
#ifndef __BSP_LED_H
#define __BSP_LED_H

#include "wb32f10x.h"

//LED GPIO宏定义
#define LED_GPIO_PIN				GPIO_Pin_13						
#define LED_GPIO_PORT				GPIOC
#define LED_GPIO_CLK				RCC_APB1Periph_GPIOC

//定义控制IO的宏
#define	LED_Toggle			    GPIO_ToggleBits(LED_GPIO_PORT,LED_GPIO_PIN)
#define	LED_OFF					GPIO_SetBits(LED_GPIO_PORT,LED_GPIO_PIN)
#define	LED_ON					GPIO_ResetBits(LED_GPIO_PORT,LED_GPIO_PIN)

//声明LED配置函数
void LED_GPIO_Config(void);

#endif /* __BSP_LED_H */

——————————————————————————————————————————————————————————————————————————————————————
我们首先来了解bsp_led.h部分代码:
1)

#ifndef __BSP_LED_H
#define __BSP_LED_H

#include "wb32f10x.h"

#endif /* __BSP_LED_H */

往往在编写头文件时都需要编写这些内容,这里先不做过多讲解,具体如何修改格式请跟随下面的内容一起学习。
——————————————————————————————————————————————————————————————————————————————————————
2)

//LED GPIO宏定义
#define LED_GPIO_PIN				GPIO_Pin_13						
#define LED_GPIO_PORT				GPIOC
#define LED_GPIO_CLK				RCC_APB1Periph_GPIOC

看到注释,大家已经知道了这是对于LED使用到的GPIO的宏定义,为什么要使用宏定义呢?我们在前面的内容大致了解了宏定义是为了提高代码的可维护性。
在这里大家还注意到如“GPIO_Pin_13”、“GPIOC”、“RCC_APB1Periph_GPIOC”这样代码,这些代码底层原理我们暂不深究,初学者只需知道这样的代码是WB32官方在固件库中规定好的,我们要做的只是根据实际使用需求更改成对应的GPIO端口(GPIO_PORT)和引脚(GPIO_PIN)即可。
其中,RCC_APB1Periph_GPIOC,请注意这个函数,RCC代表时钟外设,APB1Periph代表总线位置,GPIOC代表我们要开启时钟的端口,连在一起代表的含义就是:控制LED所使用的GPIO端口的时钟,这个函数我们可以在 wb32f10x_rcc.h 这个头文件中找到。
问题:如果使用GPIOB的引脚12作为LED的控制引脚,请根据下图将此部分代码进行修改。(关于系统结构会在以后的教学中详解)
在这里插入图片描述

——————————————————————————————————————————————————————————————————————————————————————
3)

//定义控制IO的宏
#define	LED_Toggle			GPIO_ToggleBits(LED_GPIO_PORT,LED_GPIO_PIN)  //GPIO状态翻转
#define	LED_OFF				GPIO_SetBits(LED_GPIO_PORT,LED_GPIO_PIN)	 //GPIO置位为1
#define	LED_ON				GPIO_ResetBits(LED_GPIO_PORT,LED_GPIO_PIN)	 //GPIO复位为0

在这部分代码里,我们根据实际使用需求,定义了控制LED亮、灭和翻转三个状态的宏,三个宏中对应使用的函数GPIO_ToggleBits、GPIO_SetBits、GPIO_ResetBits可以在 wb32f10x_gpio.h 中找到函数声明,那么这些函数该如何使用呢?
例:
在这里插入图片描述

首先可以看到,上方英文注释,这里标注着函数功能,形参可以传入的范围(请注意观察自己使用的开发板端口,由于引脚数量限制,不是所有的开发板都有A、B、C、D…等端口)。GPIO_ToggleBits函数需要传入两个形参,分别是 GPIO_TypeDef* GPIOx 和 uint16_t GPIO_Pin ,传入形参进入函数后,通过GPIOx->ODR ^= GPIO_Pin;进行运算,进而使函数完成对GPIO端口在0与1之间进行状态翻转的过程。
这里涉及到的寄存器操作的知识不需要深究,在目前我们初学时只需要了解库函数中声明的函数的作用,学会调用即可,化繁为简正是库函数开发要做的事。
——————————————————————————————————————————————————————————————————————————————————————
4)

//声明LED配置函数
void LED_GPIO_Config(void);

这个就是LED配置函数的声明,一般会在完成 .c 文件后统一在 .h 文件中统一声明,上面已经讲过声明的作用,这里不过多赘述。
——————————————————————————————————————————————————————————————————————————————————————
5)

#include "bsp_led.h"

现在开始讲bsp_led.c文件,代码开始首先包含了"bsp_led.h"文件,这是因为在 .c 文件中编写函数时需要使用到我们在 .h 文件中定义好的宏,如果不包含 .h 文件而直接引用,编译器就会报未定义错误。
——————————————————————————————————————————————————————————————————————————————————————
6)

void LED_GPIO_Config(void)
{
	//LED端口引脚时钟初始化
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 | LED_GPIO_CLK, ENABLE );
	//LED端口引脚设置初始化
	GPIO_Init(LED_GPIO_PORT, LED_GPIO_PIN, GPIO_MODE_OUT |GPIO_OTYPE_PP |GPIO_PUPD_NOPULL |GPIO_SPEED_HIGH);		
	//LED端口引脚置位,处于关闭状态
	GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN);
}

在WB32中,初始化一个GPIO引脚只需要两个函数,第一个便是开启时钟。
时钟就是心脏,心脏跳动才能工作。那么如何找到时钟使能函数所在位置,如何确定使用哪一条函数、如何传入参数就是我们必须要理解的事。
对于初学者,请在KEIL中将鼠标放在RCC_APB1PeriphClockCmd上,右击进入定义,可以在wb32f10x_rcc.c中看到
在这里插入图片描述
(此处向下翻阅即可看到RCC_APB2PeriphClockCmd)
注释说明此函数的功能及应当传入何种形参,根据我们的需求,我们需要打开C端口的13引脚的时钟,根据前面给出的系统框图可以看到,GPIOC端口在APB1总线上,所以我们应当向此函数中传入RCC_APB1Periph_GPIOC,而我们在头文件中将RCC_APB1Periph_GPIOC宏定义为LED_GPIO_CLK。所以,传入的参数应当为LED_GPIO_CLK,但实际传入的参数为RCC_APB1Periph_GPIOC。(由于WB32特有的保护机制,我们在使能时钟时应同时传入RCC_APB1Periph_BMX1,其中“|”即为“位或”)
问题:若想打开端口A的时钟,传入哪些参数?请你在评论区写一写。

接下来进行GPIO初始化,进入GPIO_Init函数(请猜一猜这个函数在哪个文件里)
在这里插入图片描述
请看注释,本函数功能为根据引脚设置对GPIO引脚进行初始化,传入的参数除了GPIO端口、GPIO引脚外还有GPIO设置,但注释里并没有给出此形参建议。
本段配置GPIO输出的代码为(GPIO_MODE_OUT |GPIO_OTYPE_PP |GPIO_PUPD_NOPULL |GPIO_SPEED_HIGH),分别代表的意思为 ( 输出模式| 推挽输出|无上下拉|高速模式 )。
此处定义可在"wb32f10x_gpio.h"中找到
在这里插入图片描述
(未来会结合案例详解GPIO配置模式,此处不做过多解释)

在开启端口时钟和初始化GPIO后,为了使开发板上LED处于上电关闭的状态,我们使用GPIO_SetBits函数将对应端口引脚置位1。(不理解为何这样配置可以使LED关闭的小伙伴可以查看WB32开发板原理图的LED部分)

——————————————————————————————————————————————————————————————————————————————————————
2.按键驱动程序
以下分别为bsp_key.c与bsp_key.h文件

#include "bsp_key.h"
#include "bsp_delay.h"

void KEY_GPIO_Config(void)
{
	//初始化按键GPIO时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 | KEY1_GPIO_CLK, ENABLE );
	//KEY GPIO初始化为无上下拉输入模式
	GPIO_Init(KEY1_GPIO_PORT,KEY1_GPIO_PIN, GPIO_MODE_IN | GPIO_PUPD_NOPULL);
}

uint16_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	if(GPIO_ReadInputDataBit( GPIOx,GPIO_Pin ) == KEY_ON)
	{
		//此处使用到延时函数,请看第三部分delay代码分析。
		SOFT_DELAY;
		while( GPIO_ReadInputDataBit( GPIOx,GPIO_Pin ) == KEY_ON);
		return KEY_ON;
	}
	else return KEY_OFF;
}

#ifndef __BSP_KEY_H
#define __BSP_KEY_H

#include "wb32f10x.h"

//宏定义输入端口检测到的两种状态
#define KEY_ON      1
#define KEY_OFF     0

//KEY1
#define KEY1_GPIO_PIN       		GPIO_Pin_12
#define KEY1_GPIO_PORT       		GPIOB
#define KEY1_GPIO_CLK      			RCC_APB1Periph_GPIOB


void KEY_GPIO_Config(void);
uint16_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin);

#endif /* __BSP_KEY_H */

——————————————————————————————————————————————————————————————————————————————————————
首先查看"bsp_key.h"部分
1)

#ifndef __BSP_KEY_H
#define __BSP_KEY_H

#include "wb32f10x.h"

硬件宏定义语句

函数声明语句

#endif /* __BSP_KEY_H */

根据第一部分LED的代码分析,我们可以观察.h头文件部分的框架,大致是相同的,不同点有“__BSP_KEY_H”、“__BSP_KEY_H”还有硬件宏定义语句和函数声明语句。
关于

#ifndef xx
#define xx

#endif /* xx */

这个部分的主要含义是防止编译头文件时出错,“xx”其实并无具体的格式要求,开发者亦可按照自己的编写方式去命名,但最好还是遵循这个格式书写,若无特殊需求不必深究此部分代码。
例:有如下头文件key.h,编写此部分内容

#ifndef __KEY_H
#define __KEY_H

#endif /* __KEY_H */

而宏定义语句根据所涉及到的硬件不同和设计的功能不同也会不同,函数声明亦是如此。
——————————————————————————————————————————————————————————————————————————————————————
2)

//宏定义输入端口检测到的两种状态
#define KEY_ON      1
#define KEY_OFF     0

//KEY1
#define KEY1_GPIO_PIN       		GPIO_Pin_12
#define KEY1_GPIO_PORT       		GPIOB
#define KEY1_GPIO_CLK      			RCC_APB1Periph_GPIOB

关于GPIO端口引脚宏定义大家已经了解了,不再赘述,还不懂的可以参考LED代码分析内容。
关于宏定义输入端口检测到的两种状态,在按键检测电路中,我们往往使用高电平触发的方式,即按键的一脚接在控制器的VCC,另一脚接在控制器引脚处用来检测按键的接通状态,当按键按下时,GPIO引脚与VCC相当于直接导通,即为高电平状态1,当按键松开时,GPIO引脚与VCC断路,即为低电平状态0。所以将这两种状态通过宏定义为方便理解的名称。
——————————————————————————————————————————————————————————————————————————————————————
接下来看"bsp_key.c"部分
3)

#include "bsp_key.h"
#include "bsp_delay.h"

与分析LED代码时不同,本部分代码多包含了一个"bsp_delay.h"头文件。根据学过的知识我们可以了解到,但凡包含,必定会使用到被包含的头文件里的函数,我们在后续内容分析。
——————————————————————————————————————————————————————————————————————————————————————
4)

void KEY_GPIO_Config(void)
{
	//初始化按键GPIO时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BMX1 | KEY1_GPIO_CLK, ENABLE );
	//KEY GPIO初始化为无上下拉输入模式
	GPIO_Init(KEY1_GPIO_PORT,KEY1_GPIO_PIN, GPIO_MODE_IN | GPIO_PUPD_NOPULL);
}

按键GPIO初始化函数,时钟开启部分参考代码分析,主要看GPIO初始化函数里的GPIO配置部分。
在LED代码分析中,需要控制器端口输出来控制LED的亮灭,而在按键检测函数中,则需要检测GPIO端口的状态来判断按键是否按下,因此在初始化函数中的GPIO配置部分有所不同,需要将GPIO配置为无上下拉输入(输入模式 | 无上下拉)。
——————————————————————————————————————————————————————————————————————————————————————
5)

uint16_t Key_Scan(GPIO_TypeDef *GPIOx,uint16_t GPIO_Pin)
{
	if(GPIO_ReadInputDataBit( GPIOx,GPIO_Pin ) == KEY_ON)
	{
		//延时函数
		SOFT_DELAY;
		while( GPIO_ReadInputDataBit( GPIOx,GPIO_Pin ) == KEY_ON);
		return KEY_ON;
	}
	else return KEY_OFF;
}

定义一个按键扫描函数,通过GPIO_ReadInputDataBit函数检测GPIO位的电平,为了防止按键抖动造成的不良影响,编写一个软件防抖函数来保证按键功能正常实现。
首先判断按键是否按下,若第一次判断按键按下则延迟一定时间再次判断是否真的按下,若按下则返回按键打开(KEY_ON),否则返回按键关闭(KEY_OFF)。(在软件消抖中使用到了延时函数,这就是本部分开头包含"bsp_delay.h"的原因,关于延时函数会在下面内容分析)
——————————————————————————————————————————————————————————————————————————————————————
3.延时函数代码分析
以下分别为bsp_delay.c与bsp_delay.h文件

#include "bsp_delay.h"

void Delay( __IO uint32_t nCount)
{
	for( ;nCount!=0;nCount--);
}

#ifndef __BSP_DELAY_H
#define __BSP_DELAY_H

#include "wb32f10x.h"

#define SOFT_DELAY Delay(0x0000FF)

void Delay( __IO uint32_t nCount);

#endif

先看bsp_delay.c
1)
就是一个简单延时函数,相信大家都懂。
——————————————————————————————————————————————————————————————————————————————————————
2)

#define SOFT_DELAY Delay(0x0000FF)

void Delay( __IO uint32_t nCount);

在bsp_delay.h中,对延时函数声明,然后对延时函数赋值并宏定义为一个方便定义的名称,这样外部函数在调用时仅需使用宏定义的名称即可达到延时的目的了,方便漂亮。
——————————————————————————————————————————————————————————————————————————————————————
3)为什么要这么麻烦?连一个延时函数都要单独封装。这是因为大家现在刚刚接触WB32库开发,编写的还是一些简单的程序,等到后续接触的程序越来越复杂,各个函数之间需要相互调用时,你就会了解这样做的好处了。
——————————————————————————————————————————————————————————————————————————————————————
4.main函数

#include "wb32f10x.h"
#include "bsp_led.h"
#include "bsp_key.h"

int main(void)
{	
	LED_GPIO_Config();  //LED配置初始化
	KEY_GPIO_Config();	//按键配置初始化
	
  while (1)						
  {
		if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)   //判断按键扫描得到的返回值是否等于KEY_ON
		{
			LED_Toggle;											//如果按键按下,则翻转LED的GPIO引脚的状态
  	}
  }
}

最后来讲一讲main函数,在进入main 函数后,首先要将要使用到的LED配置和按键配置初始化(调用这些函数进行初始化时不必再带上void 和形参)。
为了让按键每次按下后都能翻转LED的状态,需要进入while循环,在循环中判断按键扫描得到的返回值是否等于KEY_ON,若等于,即按键按下,则调用LED_Toggle翻转LED的状态,如果按键没有按下则继续判断,直到按下为止。
——————————————————————————————————————————————————————————————————————————————————————

4.5.3总结

本章内容到此结束,本节对第二节例程的代码进行了详细的分析,相信用户对WB32库开发已经有了一个初步的认识,在教程中你还遇到过哪些问题呢?欢迎留言讨论。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-12-10 11:13:10  更:2021-12-10 11:13:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/9 1:30:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码