正点原子[第二期]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.mzph.cn/news/836199.shtml

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

相关文章

xhci 寄存器学习

xhci 寄存器介绍 查看linux 代码&#xff1a; 1733 /* There is one xhci_hcd structure per controller */ 1734 struct xhci_hcd { 1735 struct usb_hcd *main_hcd; 1736 struct usb_hcd *shared_hcd; 1737 /* glue to PCI and HCD framework */ 1738 stru…

哈希表第5/9题--两数之和

题目描述&#xff1a; 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。…

实操专区-第11周-课堂练习专区-图的标记线和标记点

下载安装ECharts&#xff0c;完成如下样式图形。 代码和截图上传 完成 3.1.3.5 图的标记线和标记点 中的任务点 在一些折线图或柱状图当中&#xff0c;可以经常看到图中对最高值和最低值进行了标记。 在ECharts中&#xff0c;标记点&#xff08;markPoint&#xff09;常用于表示…

自定义实现 Java17+SpringBoot3+OpenAPI+Knife4j Starter

文章目录 前言正文1 创建starter项目1.1 依赖文件1.2 配置信息 2 自定义starer代码开发2.1 配置字段的定义2.2 自动配置类 3 验证starter3.1 测试项目的配置3.2 功能配置 application.yml3.3 测试代码3.3.1 实体类3.3.2 控制器13.3.2 控制器2 4 效果展示4.1 主页4.2 实体类列表…

构造二叉树

推断二叉树 P1827 [USACO3.4] 美国血统 American Heritage - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 先序遍历 : 根 左 右 中序遍历 : 左 根 右 后序遍历 : 左 右 跟 由前序遍历和中序遍历推后序遍历 // 由中序遍历和先序遍历 --> 后序遍历 void dfs(string InOr…

数据结构之----线性表顺序表

线性表分为 顺序存储结构 和 链式存储结构 线性表的顺序存储结构&#xff1a; 线性表的顺序存储结构&#xff0c;指的是用一段地址连续的存储单元依次存储线性表的数据元素。 1&#xff0c;顺序表的结构&#xff1a; #define MAXSIZE 20 typedef int El…

计算机组成与结构 计算机基本原理 软设刷题

计算机组成与结构 1-9 1-9 1 在&#xff08;&#xff09;校验方法中&#xff0c;采用模二运算来构造校验位。 A 水平奇偶 B 垂直奇偶 C 海明码 D 循环冗余 Cache与主存之间的映射由硬件实现&#xff0c;主存与辅存之间的交互是硬件与软件结合起来实现的。 D 2 采用n位补码&…

JS中手写函数实现数据的深度拷贝以及异常的抓取

1、手写深度拷贝函数 function deepCopy(data:any) {if (typeof data ! object || data null) {return data; // 非对象直接返回};let copied Array.isArray(data) ? [] : {};for (let key in data) {if (data.hasOwnProperty(key)) {//ts-ignorecopied[key] deepCopy(da…

Windows快速部署DCNv4(成功版)

文章目录 一、介绍二、编译DCNv42.1 下载源码2.2 编译DCNv4 三、报错提示3.1 Cuda is not available3.2 需要Microsoft Visual C 14.0 一、介绍 论文链接&#xff1a;[https://arxiv.org/pdf/2401.06197.pdf] (https://arxiv.org/pdf/2401.06197.pdf)   在这篇文章中介绍了一…

MySQL Undo Log、Redo Log、bin Log

Undo Log 回滚日志&#xff0c;用于将数据回滚到之前的状态。 MySQL在进行数据的增、删、改时&#xff0c;会将数据写入到Undo Log日志中。 对于Undo Log存在着insert和update两种类型的数据。插入语句对应的是insert类型&#xff0c;修改、删除语句对应的是update类型。 U…

【嵌入式开发 Linux 常用命令系列 7.6 -- sed 替换指定字符串】

请阅读【嵌入式开发学习必备专栏】 文章目录 sed 替换指定字符串 sed 替换指定字符串 背景&#xff1a; 找到当前目录下所有的.h 和 .c 文件 将他们中的字符 print_log替换为 demo_log 可以使用find命令结合sed命令在Linux环境下完成这项任务。下面是一个命令行示例&#xff…

【Android】Kotlin学习之Kotlin方法的声明和传参

方法声明 普通类的方法 静态类的方法 不需要构建实例对象, 可以通过类名直接访问静态方法 : NumUtil.double(1) companion object 伴生类的方法 使用companion object 在普通类里定义静态方法 参数 括号内传入方法 : 当参数是方法时, 并且是最后一个参数 , 可以使用括号外…

Linux -- > vim

vi和vim是什么 vi和vim是两款流行的文本编辑器&#xff0c;广泛用于Unix和类Unix系统中。它们以其强大的功能和灵活的编辑能力而闻名&#xff0c;特别是在编程和系统管理中非常受欢迎。 vi&#xff08;Visual Interface&#xff09; vi是最初的文本编辑器之一&#xff0c;由…

React 之 lazy(延迟加载)(十七)

lazy 能够让你在组件第一次被渲染之前延迟加载组件的代码。 在组件外部调用 lazy&#xff0c;以声明一个懒加载的 React 组件: import { lazy } from react;const MarkdownPreview lazy(() > import(./MarkdownPreview.js)); 配合 Suspense 实现懒加载组件 //App.js imp…

外观模式详解

外观模式 1 概述 有些人可能炒过股票&#xff0c;但其实大部分人都不太懂&#xff0c;这种没有足够了解证券知识的情况下做股票是很容易亏钱的&#xff0c;刚开始炒股肯定都会想&#xff0c;如果有个懂行的帮帮手就好&#xff0c;其实基金就是个好帮手&#xff0c;支付宝里就…

课时122:awk实践_进阶知识_赋值运算

1.2.1 赋值运算 学习目标 这一节&#xff0c;我们从 基础知识、简单实践、小结 三个方面来学习 基础知识 简介 awk本质上属于一种编程语言&#xff0c;所以它具有编程语言的一般功能&#xff0c;表达式、流程控制等基本上都在awk中具有想当程度的使用。这一节我们学习awk进…

【智能算法】最优捕食算法(OFA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2017年&#xff0c;GY Zhu受到动物行为生态学理论启发&#xff0c;提出了最优捕食算法&#xff08;Optimal Foraging Algorithm, OFA&#xff09;。 2.算法原理 2.1算法思想 OFA灵感来源…

【C++风云录】跨越语音壁垒:口语识别与方言分析

解码语音&#xff1a;语音识别新篇章 前言 本文将探讨C在口语识别与方言分析中的应用&#xff0c;简述其重要性和挑战&#xff0c;并详细介绍Kaldi, ProsodyLab-Aligner, PocketSphinx, HTK (HMM Toolkit), 和 OpenFst等语音识别和处理工具包和库的特点、主要功能以及实际应用…

常用的命令技巧总结

java命令执行 如下编码网站&#xff1a; Runtime.exec Payload Generater | AresXs Blogjava.lang.Runtime.exec() Payload Workarounds - Jackson_Thttps://www.bugku.net/runtime-exec-payloads/ 手动编码操作 bash -c {echo,cGluZyAxMjcuMC4wLjE7ZWNobyAxID50ZXN0LnR4dA}|…

Lab4: traps

RISC-V assembly Which registers contain arguments to functions? For example, which register holds 13 in mains call to printf? 根据RISC-V函数调用规范&#xff0c;函数的前8个参数使用a0-a7寄存器传递。 当main函数调用printf函数时&#xff0c;a2寄存器保存13 …