正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-13-按键实验

 前言:

本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。

引用:

正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》

正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档

正文:

本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第13.1, 13.2, 13.3 讲” 的读书笔记。第13.1, 13.2, 13.3 讲介绍如何使用通过GPIO 输入模式(input)来获取按键的输入。本节的示例程序是一个最简单的例子,它使用轮询的方法,在循环中每隔 10ms 检查一次按键输入引脚是低电平还是高电平来判断按键是否按下。

使用轮询的方法来检测按键的输入时,处理器将会一直忙运行造成处理器资源的浪费,但是作为本节入门实验的最简单例子来说,学习如何检测按键是否被按下已经足够了,后续的课程中将会学习如何改进按键检测的机制。

1. 查看电路原理图中按键使用的GPIO引脚

参考正点原子I.MX6ULL Mini 核心开发板的电路原理图,找到按键 'KEY0' ,并找到按键 KEY0 接在了I.MX6ULL 处理器的 'UART1_CTS'  IO 引脚。

 I.MX6ULL 处理器的 'UART1_CTS' 引脚作为按键的输入引脚,需要将UART1_CTS IO接口复用为 'gpio’ 功能并作为 'gpio input' 接口。和之前几节‘LED灯驱动程序’中将I.MX6ULL处理器引脚作为 gpio output 模式使用类似,将 io 引脚作为 gpio output 模式使用需要如下几步:

  1. 设置 MUX_CTL_UART1_CTS_B 寄存器,复用为 GPIO 模式,GPIO1_IO18。
  2. 设置 MUX_CTL_UART1_CTS_B 寄存器,配置io接口电气特性(速率,上拉电阻,压摆率,等)
  3. 设置 GPIO1 寄存器组 DR,GDIR 寄存器配置,GPIO1_IO18 位 gpio input 模式

2. 编写 bsp_key 源码实现按键引脚 gpio input 高/低电平的读取

从电路原理图中可以看到按键 KEY0 接到I.MX6ULL处理器 gpio1_io18 引脚,gpio1_io18有一个 10K 的上拉电阻,默认情况下按键打开 gpio 引脚读取到高电平,当按下按键后 gpio 引脚读取到低电平。通过读取 gpio1_io18 的电平输入,当读取到低电平时可以判断出按键被按下。

2.1 按键消抖

理想型按键电压变换过程如图 15.3.1 所示:

在15.3.1中,按键没有按下的时候按键值为1,当按键在 t1 时刻按下以后按键值就变为0,这是最理性的状态。但是实际上按键是机械结构,加上刚按下去的一瞬间人手可能也有抖动,实际电压变换过程如图 15.3.2 所示

在图15.3.2 中,t1 时刻按键被按下,但是由于抖动原因,知道 t2 时刻才稳定下来,t1 到 t2 这段时间就是抖动。一般这段时间就是十几 ms 左右,从图 15.3.2 可以看出在抖动期间会有多次触发,如果不消除这段抖动的话软件就会误判,本来按键就按下了一次,结果软件读取IO值发现电平多次跳变以为按下多次。所以我们需要跳过这段抖动时间再去读取按键的 io 值,也就是至少要在 t2 时刻以后再去读取IO值。在示例源码中,就是延时了大约10ms 后再去读取 gpio1_io18的值,如果此时按键的值依然是0,那么就表示这是一次有效的触发。

2.2 bsp/bsp_key.c 源码

根据上面按键KEY0使用的的分析,已经知道本次按键实验使用 KEY0 GPIO1_IO18 引脚作为 input 输入,当读取到gpio1_io18 引脚低电平时按键被按下,读取到高电平时按键松开,因为物理按键不是理想型的按键,在按键按下后的十几 ms 内会有多次的电平高低跳变如果不对按键读取掉的电平进行软件消抖可能会把一次按键按下错误的判断为多次按键输入,本节实验使用时延函数 delay 10ms 后再次读取一次 gpio 引脚输入电平来实现按键的软件消抖。

参考正点原子视频教程和文档,bsp_key.c 源码如下:


#include "bsp_delay.h"
#include "bsp_key.h"
#include "bsp_gpio.h"/** @description 	: 按键初始化。* @param – base 	: 无* @return 			: 无*/
void key_init(void)
{gpio_pin_config_t config;/* 1. 初始化IO复用,复用为GPIO1_IO18 */IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);/** * bit[0]		0		SRE,低偏摆率* bit[2:1]		00		Reserved(未使用)* bit[5:3]		000		DSE(当gpio位output时,驱动能力),本节gpio为input模式所以选择DSE=0关闭output* bit[7:6]		10		SPEED,速率,选择100MHz* bit[10:8]	000		Reserved(未使用)* bit[11]		0		ODE,开路输出,本节gpio为input,开路输出关闭* bit[12]		1		PKE, Pull/Keeper (上拉/保持器 开关),这里使能* bit[13]		1		PUE, 选择是Keeper还是PULL,本节这里选择 1 (PULL)* bit[15:14]	11		PUS, 上拉电阻阻值,本节选择22K欧姆上拉电阻* bit[16]		0		HYS, 磁滞,本节不使用,选择0* bit[31-17]	0		Reserved(未使用)** 最终选择的电气特性寄存器值:* 1111 0000 1000 0000 = 0xf080*//* 2. 设置 UART1_CTS_B IO 的电气特性 */IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xf080);/* 3. 初始化 GPIO1_IO18 设置为输入 */config.directioin = kGPIO_DigitalInput;gpio_init(GPIO1, 18, &config);	
}int key_read(void)
{return gpio_pinread(GPIO1, 18);
}/** @description 	: 获取按键值。* @param – base 	: 无* @return 			: 0 没有按键按下,其它值:对应的按键值。*/
int key_getvalue(void)
{int ret = 0;static int release = 1;if((release == 1) && (gpio_pinread(GPIO1, 18) == 0)){ /* KEY0 按下 */release = 0;			/* 标记按键按下 */delay(10);				/* 时延消抖 */if(key_read() == 0){	/* 按键按下 */ret =  KEY0_VALUE;}}else if((gpio_pinread(GPIO1, 18) == 1)){			/* KEY0 释放 */release = 1;			/* 标记按键释放 */ret = 0;}return ret;
}

在复用 UART1_CTS_B IO 为 GPIO1_IO18,并且设置 UART1_CTS_B IO接口的电气特性时,这里设置的值为 ‘0xF080

    /* 2. 设置 UART1_CTS_B IO 的电气特性 */IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xf080);

0xF080’ 这个值是怎么确定的呢?和之前分析“LED灯驱动程序GPIO引脚 output 电气特性”寄存器值的方式一样,需要参考《I.MX6ULL参考手册》第32章中 UART1_CTS_B 寄存器中每一个bit的定义,根据 UART1_CTS_B 工作在 input 模式,选择低速率,上拉电阻阻值的选择等,确定每一个bit的值,最终确定此处应该选择的io接口电气特性寄存器值为‘0xF080’。

3.3 bsp/bsp_gpio.c 接口函数

在这些LED灯驱动程序,Beep蜂鸣器启动程序,和按键驱动程序中,对GPIOx->DR, GPIOx->GDIR 寄存器组的操作是相似的,本节实验中将会把对 gpio 操作的api接口函数抽象出来放到 bsp/bsp_gpio.c 中,实现代码的复用和封装,也方便后续的开发使用。这也是我们自己写的 BSP 接口函数。

bsp_gpio.h

#ifndef __BSP_GPIO_H__
#define __BSP_GPIO_H__#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "cc.h"typedef enum _gpio_pin_direction
{kGPIO_DigitalOutput = 0U,	/*输出*/kGPIO_DigitalInput  = 1U,	/*输入*/} gpio_pin_direction_t;typedef struct _gpio_pin_config 
{gpio_pin_direction_t directioin;	/* GPIO 方向:输入还是输出 */int outputLogic; 					/* 如果是输出的话,默认输出电平 */
} gpio_pin_config_t;/* 初始化函数 */
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
void gpio_pinwrite(GPIO_Type *base, int pin, int value);
int  gpio_pinread(GPIO_Type *base, int pin);#endif

bsp_gpio.c 

#include "bsp_gpio.h"/** @description 	: GPIO初始化。* @param - base 	: 要初始化的寄存器组。* @param - pin		: 要初始化的寄存器脚号。* @param - config	: GPIO 配置结构体。* @return 			: 无*/
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{if(config){if(config->directioin == kGPIO_DigitalOutput){base->GDIR |= (1<<pin);								/* 输出 */gpio_pinwrite(base, pin, config->outputLogic);		/* 默认输出电平 */}else if(config->directioin == kGPIO_DigitalInput){base->GDIR &= ~(1<<pin);							/* 输入 */}}
}/** @description 	: 指定 GPIO 输出高或者低电平。* @param – base 	: 要输出的 GPIO 组。* @param – pin 	: 要输出的 GPIO 脚号。* @param - value	: 要输出的电平, 1 输出高电平, 0 输出低低电平* @return 			: 无*/
void gpio_pinwrite(GPIO_Type *base, int pin, int value)
{if(value == 0)base->DR &= ~(1<<pin);elsebase->DR |= (1<<pin);
}/** @description 	: 读取指定 GPIO 的电平值。* @param – base 	: 要读取的 GPIO 组。* @param – pin 	: 要读取的 GPIO 脚号。* @return 			: 1 读取高电平, 0 读取低低电平。*/
int  gpio_pinread(GPIO_Type *base, int pin)
{return ((base->DR >> pin) & 0x1);
}

3. 编译按键驱动实验程序

正点原子第13.1,13.2,13.3 视频教程里,正点哥在做实验时遇到了一个有趣的错误,在第13讲的视频教程里,正点哥发现当在 imx6u.lds 链接脚本里带上 '.bss' 分区的时,编译出来的 .bin 镜像烧录到SD卡上LED灯和蜂鸣器不能正常工作,去掉链接脚本里的 '.bss' 分区时编译出来的 .bin 镜像烧录SD卡,LED灯和蜂鸣器工作正常。在视频教程里,正点原子哥发现是链接脚本里的 .bss 段没有按照4字节对齐的原因,在视频教程里,正点原子哥本地变异的 .elf 文件的反汇编里 __bss_start 和 __bss_end 的确没有按照4字节对齐。因为 I.MX6ULL 是 ARM Contre-A7 32位的处理器,32位处理器读写内存时地址需要按照4字节对齐,如果内存起始地址不是4字节对齐的可能就会造成内存中内容读写错误的问题。

这个问题不一定会发生。这个其实和编译出来的 .elf 文件中的 .data 数据段的长度有关系,因为在链接脚本中 .bss 段时紧挨着 .data 数据段的,.data 数据段的起始地址是4字节对齐的,如果 .data数据段的长度本身是一个奇数(不能被4整除),那么 .bss_start = .data_start + .data_len 计算得到的 .bss 的起始地址就是一个非4字节对齐的地址,这样就会遇到正点原子哥视频里的问题。

在正点哥的的视频例程里,正点哥修改了 imx6u.lds 链接脚本,在定义 __bss_start 之前让 “. 当前定位符”按照4字节对齐,这样就解决了问题。

4. 烧录SD卡验证按键驱动功能

烧录SD卡验证按键驱动功能,使用正点原子提供的 'imxdownload' 烧录SD卡,然后把SD卡查到正点原子 I.MX6U APLHA/Mini 开发板上,上电验证LED灯是否闪烁,按下开发板上的按键蜂鸣器是否鸣叫,再次按下按键蜂鸣器是否停止鸣叫。

我在本地实验时,遇到了好几个问题,不过参考正点原子的按键示例源码反复修正了4次代码最终实现了按键开关蜂鸣器和LED灯闪烁的功能。

5. 总结

按键实验中遇到的问题记录和分析:

问题1: 按下按键之后不松开,蜂鸣器快速的10ms进行发出一次鸣叫。
原因:   在我最开始写的 key_getvalue() 函数中错误的将 'static uint8_t released' 的标志置零,正确的做法应该是在检测到按键gpio input 引脚的电平为高电平时才将 'static uint8_t released' 的标志置零。

问题2: 参考正点原子哥的Makefile,开启从编译 .o 文件的' -O2 ' 优化后,短时延函数失效。

原因: 短时延函数里的 'delay_short()' 空循环函数被编译器优化掉,造成通过空循环忙等的短时延函数失效。

解决方法: 在正点原子的示例源码中,delay_short() 函数的参数被声明为了 'volatile int ' 类型,把参数变量为 volatile 就可以避免掉编译器的优化,让编译器生成的汇编指令老老实实的按照我们的源程序执行空循环。

把变量声明为 "Volatile" 指示编译器不要进行优化。

按照示例源码的将short_dealy()的形参声明为 volatile ,然后重新编译烧录SD卡验证下是否解决问题。从反汇编源码来看,使用 "volatile" 关键字之后已经让编译器不再优化掉 short_delay() 的源码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/670540.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

04-25 周四 FastBuild重构实践-TLS、全局捕获异常、一键配置

04-25 周四 FastBuild重构实践 时间版本修改人描述04-25V0.1宋全恒新建文档2024年5月6日14:33:16V1.0宋全恒完成文档撰写 简介 由于 04-22 周日 阿里云-瑶光上部署FastBuild过程(配置TLS、自定义辅助命令)描述了重新部署一个FastBuild实例的过程&#xff0c;通过阅读这个&…

pytest教程-36-钩子函数-pytest_collection_start

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_unconfigure钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_collection_start钩子函数的使用方法。 pytest_collection_start(session) 是一个 pytest 钩子函数&#xff0c;…

pytest教程-37-钩子函数-pytest_collection_finish

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_collection_start钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_collection_finish钩子函数的使用方法。 pytest_collection_finish(session) 是一个 pytest 钩子函数&…

BEV下统一的多传感器融合框架 - FUTR3D

BEV下统一的多传感器融合框架 - FUTR3D 引言 在自动驾驶汽车或者移动机器人上&#xff0c;通常会配备许多种传感器&#xff0c;比如&#xff1a;光学相机、激光雷达、毫米波雷达等。由于不同传感器的数据形式不同&#xff0c;如RGB图像&#xff0c;点云等&#xff0c;不同模态…

java-函数式编程-函数对象

定义 什么是合格的函数&#xff1f;无论多少次执行函数&#xff0c;只要输入一样&#xff0c;输出就不会改变 对象方法的简写 其实在类中&#xff0c;我们很多参数中都有一个this&#xff0c;被隐藏传入了 函数也可以作为对象传递&#xff0c;lambda就是很好的例子 函数式接口中…

微搭低代码入门05文件的上传和下载

目录 1 创建数据源2 创建应用3 创建页面4 设置导航功能5 文件上传6 文件下载总结 小程序中&#xff0c;我们通常会有文件的上传和下载的需&#xff0c;在微搭中&#xff0c;文件是存放在云存储中&#xff0c;每一个文件都会有一个唯一的fileid&#xff0c;我们本篇就介绍如何通…

如何将视频转换成gif表情包?超简单的方法分享

把视频中的片段截取制作成gif动画表情包是现在网络中常见的制作图片的一种方法。Gif表情包能够调节聊天中的氛围&#xff0c;快速有趣的传递信息。也因为gif动图兼容性高、体积小便于分享所以在现在的网络中非常的收欢迎。接下来&#xff0c;小编就给大家分享一下怎么把视频转g…

供应链|经典论文解读:(s,S) 策略在动态库存下的最优性

文章考虑了具有订购成本&#xff08;由单位成本加上重新订购成本组成&#xff09;的动态库存问题。具体而言&#xff0c;对于每个时期&#xff0c;系统在中期开始是做出一系列采购决策——这些采购有助于库存的积累&#xff0c;并在随后的周期被需求所消耗。每时期系统会产生各…

什么是PXE

文章目录 在局域网内搭建PXE服务器PXE 启动组件PXE的优点实验一、搭建PXE服务器&#xff0c;实现远程部署CentOS系统环境准备server关闭防火墙安装组件准备 Linux 内核、初始化镜像文件及PXE引导文件配置启用TFTP 服务配置启动DHCP服务准备CentOS 7 安装源配置启动菜单文件 Cli…

面试笔记——JVM组成

基本介绍 JVM: Java Virtual Machine Java程序的运行环境&#xff08;java二进制字节码的运行环境&#xff09; 使用JVM的好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收机制 JVM的组成及运行流程&#xff1a; 程序计数器 程序计数器&a…

手动交互式选点提取三维点云轮廓边界线 附python代码

一种新的三维点云轮廓边界提取方案: 1 手动选择一个边界或者其附近的点 2 自动搜索临近区域,并找到附近的平面和进行平面分割 3 提取平面的交点 4 该交点就是点云的轮廓边界点,把它往两边延展,就是完整的点云轮廓边界 import open3d as o3d import numpy as np import …

[激光原理与应用-92]:振镜的光路图原理

目录 一、振镜的光路 二、振镜的工作原理 2.1 概述 2.2 焊接头 2.3 准直聚焦头-直吹头 2.4 准直聚焦头分类——按应用分 2.4.1 准直聚焦头分类——功能分类 2.4.2 准直聚焦头镜片 2.4.3 振镜焊接头 2.4.4 振镜分类&#xff1a; 2.4.5 动态聚焦系统演示&#xff08;素…