用IMX6UL开发板编写按键输入实验

        在之前我们都是讲解如何使用IMX6UL的GPIO输出控制等功能,IMX6U的IO不仅能作为输出,而且也可以作为输入,而我们开发板上具有一个按键,按键肯定是连接了一个IO口的额,我们在这一节将会把IO配置成输入功能,读取这个IO的值,即可获取按键的状态,然后可以利用这个特性来控制蜂鸣器

        那么我们对这个实验做一个简单的分析,按键就只有两个状态,按下或者弹起,将按键连接在一个IO口上,读取这个IO口就知道按键是按下的还是弹起的。后面需要根据实际电路图来判断按键按下的时候是产生高电平还是低电平
        下面就开始硬件的分析,按键KEY0连接到UART_CTS这个引脚的IO上,KEY0上面接了一个10K的上拉电阻,因此KEY0在没有被按下的时候是一个高电平的状态,当KEY0按下以后就改变了引脚的状态,也就是低电平

        那么我们创建一个文件夹作为本次实验,可以是07_key,然后将前面蜂鸣器的代码复制过来,我们在此基础上添加代码,这一节实验除了要编写按键实验以外,我们还有个任务要求是,将我们的GPIO编写一个函数集合,也可以理解为封装一下,类似于STM32中的库,这样在之后每次编写的时候就不用再去反复操作寄存器了
        首先创建bsp_gpio.h和bsp_gpio.c,然后先编写如下代码

typedef enum _gpio_pin_direction
{kGPIO_DigitalInput = 0U,kGPIO_DigitalOutput = 1U,
}gpio_pin_direction_t;

        这里我们定义了一个名为_gpio_pin_direction的枚举类型,并通过类型别名,也就是起了另一个名字叫做gpio_pin_direction_t,也就是在之后引用这个名字的时候就变相的引用了上面的枚举类型,使用 gpio_pin_direction_t 会比直接使用 _gpio_pin_direction 更简洁
        kGPIO_DigitalInput = 0U是枚举的第一个成员,也被称为枚举常量表示GPIO引脚被配置为数字输入模式。0U表示这个成员的值是0,U后缀表示这是一个无符号整形(unsigned integer)字面量,保证涉及枚举值的计算时保持一致,避免隐式类型转换,且枚举成员主要用于表示一组固定的、命名的常量值
         然后定义一个结构体,

#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#include "imx6ul.h"typedef enum _gpio_pin_direction
{kGPIO_DigitalInput = 0U,kGPIO_DigitalOutput = 1U,
}gpio_pin_direction_t;typedef struct _gpio_pin_config
{gpio_pin_direction_t direction; /*GPIO方向,输入还是输出*/uint8_t outputLogic;            /*如果是输出的话,默认输出电平*/
}gpio_pin_config_t;

        在结构体的这行代码中,gpio_pin_direction_t意思是声明了一个名为direction的枚举变量,它的类型是direction_t。也就是上面的枚举类型(gpio_pin_direction_t diretion)。这个变量可以存储该枚举类型中定义的任何一个常量(也称为枚举成员)
        这里的意思是,在结构体中的枚举变量direction可以被赋值为  kGPIO_DigitalInput = 0U或者kGPIO_DigitalOutput = 1U,中的任何一个。这些常量是枚举类型gpio_pin_direction_t的成员
        outputLogic是创建一个这个uint8_t的变量,被用作了结构体的一部分

#include "bsp_gpio.h"void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{if(config->direction == kGPIO_DigitalInput){base->GDIR &= ~(1 << pin);}else{base->GDIR |= 1 << pin;gpio_pinwrite(base,pin, config->outputLogic);}
}

        然后我们在bsp_gpio.c当中这么写,首先这段代码的作用是用于初始化GPIO引脚的函数,它接受三个返回参数,一个指向GPIO基地址的指针base,一个整数pin表示要操作的引脚编号,以及一个指向gpio_pin_config_t结构体的指针config,这个函数当中有一个if判断语句,如果方向是输入,那么就执行if语句当中的对应引脚给初始化
        那么反之如何不是输入,而是输出,那么就通过下面一个自定义的函数,老控制要输出的电平,所以接下来还要继续编写gpio_pinwrite


        

        那么现在就开始编写引脚输出电平的函数,代码如下,这个代码不需要怎么解释,也就是0对应输出低电平,然后操作GPIOx_DR寄存器,如果不是0那么就输出高电平,同样也是控制GPIOx_DR寄存器,来输出高电平

void gpio_pinwrite(GPIO_Type *base ,int pin, int value)
{if(value == 0u){base->DR &= ~(1U << pin); //输出低电平}else{base->DR |= 1U << pin;  //输出高电平}
}

        那么有了输出电平的控制函数,那么我们设计到按键,所以我们也需要一个读取GPIO电平的状态读取函数,所以下一个我们编写这个函数,然后代码如下,主要会解释这个return函数的含义
        一个括号一个括号来解释,首先,通过指针base访问GPIO的寄存器(DR)的当前值
        ((base->DR) >> pin ),然后将这个值右移pin位,目的就是将我们需要读取的引脚移动到最低位进行读取,因为前面提到过,如果需要读取寄存器当中某个位的值,必须要将其移动到最右边。
        然后下一步就是将移动到最右边的最低位进行保留,然后将其他的位全部清零
        结合return就是将,最后引脚的结果返回,返回的是一个整数,要么是0也就是低电平,要么就是高电平也就是数据位1
        简单来描述就是,gpio_pinread 函数通过读取GPIO模块的数据寄存器(DR),并将指定引脚的状态移动到最低位,然后通过与 0x1 进行按位与操作来提取这个状态,最后返回这个状态。

int gpio_pinread(GPIO_Type *base , int pin)
{return (((base->DR) >> pin ) &0x1);
}

        最后需要完成GPIO函数的封装就是将上面我们描写的函数然后在.h文件当中去申明一下,下面将展示一个完整gpio.c和gpio.h文件

//bsp_gpio.c#include "bsp_gpio.h"void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{if(config->direction == kGPIO_DigitalInput){base->GDIR &= ~(1 << pin);}else{base->GDIR |= 1 << pin;gpio_pinwrite(base,pin, config->outputLogic);}
}int gpio_pinread(GPIO_Type *base , int pin)
{return (((base->DR) >> pin ) &0x1);
}void gpio_pinwrite(GPIO_Type *base ,int pin, int value)
{if(value == 0u){base->DR &= ~(1U << pin); //输出低电平}else{base->DR |= 1U << pin;}
}
//_bsp_gpio.h#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#include "imx6ul.h"typedef enum _gpio_pin_direction
{kGPIO_DigitalInput = 0U,kGPIO_DigitalOutput = 1U,
}gpio_pin_direction_t;typedef struct _gpio_pin_config
{gpio_pin_direction_t direction; /*GPIO方向,输入还是输出*/uint8_t outputLogic;            /*如果是输出的话,默认输出电平*/
}gpio_pin_config_t;void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
int gpio_pinread(GPIO_Type *base, int pin);
void gpio_pinwrite(GPIO_Type *base, int pin, int value);#endif           

        在完成了GPIO的封装后,下一步就要开始编写按键的初始化了,创建一个文件夹命名为key,下面创建两个文件分别是bsp_key.c和bap_key.h,然后我们先看原理图,key引脚接在UART1_CTS这个引脚上面

        首先编写.h文件,创建一个枚举类型keyvalue,因为我们的开发板上只有一个按键,但是考虑到代码的移植性,所以这里枚举了三个成员变量分别为,KEY0,1,2,

#ifndef _BSP_KEY_H
#define _BSP_KEY_H
#include "imx6ul.h"enum keyvalue{KEY_NONE =0,Key0_Value,Key1_Value,key2_value,
};#endif

        然后开始编写.c文件,代码如下

/*初始化按键*/
void key_init()
{gpio_pin_config_t key_config;IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); //IO引脚复用,将引脚复用为GPIO1_IO18/* 2、、配置UART1_CTS_B的IO属性	*bit 16:0 HYS关闭*bit [15:14]: 11 默认22K上拉*bit [13]: 1 pull功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 000 关闭输出*bit [0]: 0 低转换率*/IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080); /*1111 0000 1000 0000*//*初始化GPIO*/key_config.direction = kGPIO_DigitalInput;gpio_init(GPIO1,18,&key_config);    
}

        首先IOMUXC_SetPinMux这个函数依然是为了设置IO引脚的复用,然后IOMUXC_SetPinConfig是为了设置对应引脚的电气属性,为什么是0xF080我已经注释出来了,直接参考就可以了
        首先我们需要了解的就是下面的gpio_init这个函数的参数,主要是最后一个参数gpio_pin_config_t* config,意味着这个函数期望接受一个指向该结构体实例的指针作为参数,这个指针通常指向一个已经初始化好的gpio_pin_config_t实例
        所以我们就需要在代码中创建一个结构体实例,这个实例也是一个具体的变量,包含了结构体定义的所有成员变量。所以代码为gpio_pin_config_t key_config;
        然后接下里就要设置,结构体实例的成员变量,为结构体中的各个成员变量赋予适当的值,创建了实例过后,通过代码 key_config.direction = kGPIO_DigitalInput;访问key_config结构体中名为direction的成员方式,然后将kGPIO_DigitalInput 的值赋给 key_config.direction,那么按键的初始化就完成了

        那么接下就要开始编写按键电平读取的函数了,这里定义一个ret变量将其初始化为0,用于存储函数的返回值,然后再定义一个静态的unsigned char类型的变量release,将其初始化为1.为什么使用静态,因为可以保证在函数调用期间保持其值不变,在之后我们会使用这个变量来防止按键抖动,因为暂时没有涉及到寄存器,所以这里统一使用延时消抖的方法来避免

int getvalue()
{int ret = 0;static unsigned char release = 1;if((release == 1)&&(gpio_pinread(GPIO1,18)==0)){delay(10);release = 0;if(gpio_pinread(GPIO1,18) == 0){ret =Key0_Value;}}else if(gpio_pinread(GPIO1,18) == 1){ret = 0;release = 1;}return ret;
}

         首先,先检查release以及GPIO1的IO18是否为低电平,如果为低电平就说明按键被按下了,如果条件满足条件则,先执行去除抖动,然后将release设置为0,表示现在处于去抖动的状态,然后再去检查对应的GPIO1的IO18引脚是否为低电平,如果为低电平则则将ret设置为Key0_Value这个值,最终将ret的值返回
        但是如果在刚开始的时候,if的条件判断错误,release 不为1,或者 GPIO1 的第18引脚为高电平(1),则将ret设置为0,表示按键未被按下,并且将release设置重置为1,为下一次检测做准备。最后的操作同样是将ret的值返回
        然后最后就是在bsp_key.h申明一下我们在这里编写的函数,驱动以及初始化函数全部都已经编写完了,接下来就可以开始准备编写主函数

#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"int main()
{int keyvalue = 0;unsigned char led_state = 0;unsigned char beep_state = 0;clk_enable();led_init();beep_init();key_init();while(1){keyvalue = key_getvalue();if(key_getvalue){switch ((keyvalue)){case Key0_Value:beep_state = !beep_state;beep_switch(beep_switch);break;}}led_state =!led_state;led_switch(LED0,led_state);delay(10);}}return 0;
}

        上面的代码是主函数的代码,主要目的就是同过按键控制BSP蜂鸣器,通过key_getvalue,来判断按键是否按下,如果按下就直接通过switch语句,就可以使得蜂鸣器使能了
        然后keyvalue循环执行完后,就开始执行下面的LED常亮的代码,通过反转前面的变量,再通过switch语句来控制LED0的亮状态,然后返回值,主函数的代码就执行完成了,最后将我们的代码烧写到板子当中,然后我们的这项实验就完成了

        感谢各位的阅读,如果有任何问题的欢迎在评论区指出,或者与作者讨论
        

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

codetop标签动态规划大全C++讲解(三)!!动态规划刷穿地心!!学吐了家人们o(╥﹏╥)o

每天复习一篇&#xff0c;只有十题左右 1.买卖股票的最佳时机2.买卖股票的最佳时机含手续费3.买卖股票的最佳时机III4.买卖股票的最佳时机IV5.打家劫舍6.打家劫舍II7.不同路径8.不同路径II9.最小路径和10.三角形的最小路径和11.两个字符串的删除操作12.编辑距离13.一和零 1.买卖…

强化学习笔记之【DDPG算法】

强化学习笔记之【DDPG算法】 文章目录 强化学习笔记之【DDPG算法】前言&#xff1a;原论文伪代码DDPG算法DDPG 中的四个网络代码核心更新公式 前言&#xff1a; 本文为强化学习笔记第二篇&#xff0c;第一篇讲的是Q-learning和DQN 就是因为DDPG引入了Actor-Critic模型&#x…

Ubuntu22.04 Docker 国内安装最靠谱教程

目前docker在国内安装常存在众所周知的网络问题&#xff0c;如果安装过程如果从官网地址安装以及安装之后从官网要拉取镜像都存在问题。这篇文章主要针对这两个问题总结最靠谱的docker安装教程。 1. docker安装 1.1 系统环境概述 Ubuntu 22.04linux内核版本 6.8&#xff08;…

重学SpringBoot3-集成Redis(四)之Redisson

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;四&#xff09;之Redisson 1. 添加 Redisson 依赖2. 配置 Redisson 客户端3. 使用 Redisson 实现分布式锁4. 调用分布式锁5. 为什…

二进制的神奇操作——拆位法和贡献思想

拆位的引入 我们来思考这么一个问题&#xff0c;如果给你一个数组&#xff0c;让你去求一个数组里面所有连续子串的异或和的和&#xff0c;问你该怎么求&#xff1f; 我们该如何去处理&#xff0c;首先肯定是会想到暴力的思路&#xff0c;第一层循环遍历左端点&#xff0c;第…

SpringBoot在线教育平台:设计与实现的深度解析

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

654、最大二叉树

1、题目描述 . - 力扣&#xff08;LeetCode&#xff09; 其实就是给定了一个所谓"最大二叉树"的规则&#xff0c;让我们去构建二叉树。 以 nums [3,2,1,6,0,5] 为例&#xff0c;规则如下&#xff1a; (1)找出其中的最大值6将其作为根节点&#xff0c;6前面的是左子…

程序传入单片机的过程,以Avrdude为例分析

在市场上有各式各样的单片机&#xff0c;例如Arduino&#xff0c;51单片机&#xff0c;STM等。通常&#xff0c;我们都用其对应的IDE软件进行单片机的编程。这些软件既负责将程序代码转写成二进制代码&#xff0c;即机器语言&#xff0c;也负责将该二进制代码导入单片机。与此同…

C++ 算法学习——7.4.1 优化算法——双指针

双指针法&#xff08;Two Pointers&#xff09;是一种常用的算法技巧&#xff0c;通常用于解决数组或链表中的问题。这种技巧通过维护两个指针&#xff0c;通常分别指向数组或链表的不同位置&#xff0c;来协同解决问题。双指针法一般有两种类型&#xff1a;快慢指针和左右指针…

什么是transformer大模型,答案就在这里

Transformer大模型是一种在自然语言处理&#xff08;NLP&#xff09;领域中广泛使用的模型&#xff0c;其详细数据与分析可以从以下几个方面进行阐述&#xff1a; 1. 模型架构 Transformer模型本质上是一个Encoder-Decoder架构。编码组件由多层编码器&#xff08;Encoder&…

(笔记)第三期书生·浦语大模型实战营(十一卷王场)–书生基础岛第3关---浦语提示词工程实践

学员闯关手册&#xff1a;https://aicarrier.feishu.cn/wiki/ZcgkwqteZi9s4ZkYr0Gcayg1n1g?open_in_browsertrue 课程视频&#xff1a;https://www.bilibili.com/video/BV1cU411S7iV/ 课程文档&#xff1a; https://github.com/InternLM/Tutorial/tree/camp3/docs/L1/Prompt 关…

还在“卷”长度?长文本模型真的基于上下文进行回复吗?

近年来&#xff0c;随着长文本模型&#xff08;Long-context Model, LCM&#xff09;技术的突飞猛进&#xff0c;处理长上下文的能力已成为各大语言模型&#xff08;Large Language Model, LLM&#xff09;的核心竞争力&#xff0c;也是各大技术厂商争夺的焦点。截至2023年12月…

RAG再总结之如何使大模型更好使用外部数据:四个不同层级及查询-文档对齐策略

我们来看看RAG进展。《Retrieval Augmented Generation (RAG) and Beyond: A Comprehensive Survey on How to Make your LLMs use External Data More Wisely》(https://arxiv.org/abs/2409.14924)&#xff0c;主要讨论了如何使大型语言模型&#xff08;LLMs&#xff09;更明智…

Redis中BitMap实现签到与统计连续签到功能

服务层代码 //签到Overridepublic Result sign() {//1.获取当前登录的用户Long userId UserHolder.getUser().getId();//获取日期LocalDateTime now LocalDateTime.now();//拼接keyString keySuffix now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String …

实例分割、语义分割和 SAM(Segment Anything Model)

实例分割、语义分割和 SAM&#xff08;Segment Anything Model&#xff09; 都是图像处理中的重要技术&#xff0c;它们的目标是通过分割图像中的不同对象或区域来帮助识别和分析图像&#xff0c;但它们的工作方式和适用场景各有不同。 1. 语义分割&#xff08;Semantic Segme…

一款基于 Java 的可视化 HTTP API 接口快速开发框架,干掉 CRUD,效率爆炸(带私活源码)

平常我们经常需要编写 API&#xff0c;但其实常常只是一些简单的增删改查&#xff0c;写这些代码非常枯燥无趣。 今天给大家带来的是一款基于 Java 的可视化 HTTP API 接口快速开发框架&#xff0c;通过 UI 界面编写接口&#xff0c;无需定义 Controller、Service、Dao 等 Jav…

Bolt.new:终极自动化编程工具

兄弟们&#xff0c;终极写代码工具来了—— Bolt.new&#xff01;全方位的编程支持&#xff1a; StackBlitz 推出了 Bolt․new&#xff0c;这是一款结合了 AI 与 WebContainers 技术的强大开发平台&#xff0c;允许用户快速搭建并开发各种类型的全栈应用。 它的主要特点是无需…

内网靶场 | 渗透攻击红队内网域渗透靶场-1(Metasploit)零基础入门到精通,收藏这一篇就够了

“ 和昨天的文章同一套靶场&#xff0c;这次主要使用的是Kali Linux以及Metasploit来打靶场&#xff0c;熟悉一下MSF在内网渗透中的使用&#xff0c;仅供学习参考&#xff0c;大佬勿喷。本期文章靶场来自公众号&#xff1a;渗透攻击红队。” 靶场下载地址&#xff1a;https://…

展锐平台WIFI国家码信道总结

展锐平台WIFI国家码信道总结 1.下载wireless-regdb wireless-regdb是一个开源的工程,编译它会生成regulatory.bin文件,这实际上是一个加密后的数据库,它记录各个国家可用的无线频段。 可从下面的网站上下载最新的regdb库: https://git.kernel.org/pub/scm/linux/kernel…

【无人水面艇路径跟随控制2】(C++)USV代码阅读: SetOfLos 类的从路径点和里程计信息中计算期望航向

【无人水面艇路径跟随控制2】&#xff08;C&#xff09;USV代码阅读&#xff1a; SetOfLos 类的从路径点和里程计信息中计算期望航向 写在最前面set_of_los.cpp小结详细解释头文件包含命名空间构造函数和析构函数设置参数函数获取航向函数 &#x1f308;你好呀&#xff01;我是…