使用IMX6UL定时器EPTI实现延时

        上一节,我们讲解了如何使用Imx6uL上面的定时器EPTI,这一节我们将使用EPTI进行实战,也就是使用定时器的效果来使用延时

        在之前的实验中我们都使用到了按键,用到按键就要处理因为机械结构带来的按键抖动问题,也就是按键消抖。前面的实验中都是直接使用了延时函数来实现消抖,因为简单,但是直接用延时函数来实现消抖会浪费 CPU 性能,因为在延时函数里面 CPU 什么都做不了。如果按键使用中断的话更不能在中断里面使用延时函数,因为中断服务函数要快进快出
        使用定时器可以实现按键消抖,使用定时器既可以实现按键消抖,而且也不会浪费CPU性能。

        

        按键消抖的原理之前我们已经讲解过了,这里简单讲解一下,在按键按下以后延时 一段时间再去读取按键值,如果此时按键值还有效那就表明这一次有效的按键,中间的延时就是消抖的。但是延时函数会浪费CPU的性能。如果按键是用中断方式实现的,那就更不能在中断函数中使用延时函数,因为中断都要求快进快出
        所以在我们了解到定时器的基础上,定时器设置好时间,CPU就可以去做其他的事情了,定时时间到了过后就会触发中断然后在中断中做相应的处理即可。因此,我们可以借助定时器来实现消抖,按键采用中断驱动方式,当按键按下以后触发按键中断,在按键中断中开启一个定时器,定时周期为 10ms,当定时时间到了以后就会触发定时器中断,最后在定时器中断处理函数中读取按键的值,如果按键值还是按下状态那就表示这是一次有效的按键。

        如上图, t1~t3 这一段时间就是按键抖动,是需要消除的。设置按键为下降沿触发,因此会在 t1、t2 和 t3 这三个时刻会触发按键中断,每次进入中断处理函数都会重新开器定时器中
断,所以会在 t1、t2 和 t3 这三个时刻开器定时器中断。但是 t1~t2 和 t2~t3 这两个时间段是小于
我们设置的定时器中断周期(也就是消抖时间,比如 10ms),所以虽然 t1 开启了定时器,但是定时器定时时间还没到呢 t2 时刻就重置了定时器,最终只有 t3 时刻开启的定时器能完整的完成整个定时周期并触发中断,我们就可以在中断处理函数里面做按键处理了,这就是定时器实现按键防抖的原理

        下面梳理一下我们具体需要完成的思路,这个章节没有学习新的外设和内设,所以只需要对之前的知识点进行一个复习与总结
        1.配置按键所使用的IO,因为要使用到中断驱动按键,所以需要配置IO的中断模式

        2.初始化消抖用的定时器
        消抖需要使用定时器来完成,所以需要初始化一个定时器,这里使用我们之前使用过的EPTI1定时器的一次巩固。定时器的定时周期为10ms,大概预测的按键可能会抖动的时间

        3.编写中断处理函数
        需要编写两个中断处理函数,按键对应的GPIO中断处理函数和EPIT1定时器的中断处理函数。在按键的中断处理函数中主要用于开启EPIT1定时器,EPIT1的中断处理函数才是重点,按键要做的具体任务都是在定时器EPIT1的中断处理服务函数中完成的,例如控制蜂鸣器或者关闭

        这一讲的效果就是按下按键,蜂鸣器会打开,再次按下则关闭,灯会一直闪烁,实验相比于之前都更简单,这里我们就直接开始编写代码
        首先是创建一个文件夹,名字叫做Keyfilter,然后在文件夹的下方创建一个.c和.h文件,名字都是bsp_keyfilter,首先就是按键的初始化

#include "bsp_keyfilter.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_epittimer.h"void filterkey_init(void)
{gpio_pin_config_t key_config;IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);key_config.direction = kGPIO_DigitalInput;key_config.interruptMode = kGPIO_IntFallingEdge;key_config.outputLogic = 1;gpio_init(GPIO1, 18,&key_config);GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);system_register_irqhandler(GPIO1_Combined_16_31_IRQn,(system_irq_handler_t)gpio1_16_31_irqhandler,NULL);gpio_enableint(GPIO1,18);           /*使能GPIO1_IO18的中断功能*/filtertimer_init(66000000/100);     /*初始化定时器,10ms*/
}

        在之间定义了结构体gpio_pin_config_t,定义了一个结构体变量,它的作用就是存储和表示一个GPIO引脚的配置信息。
        因为这一节是对前面知识的复习,所以这里我们再用通俗的语言,来实际理解一下结构体变量(实例)与结构体成员的关系
        首先,假如我们有一个工具箱,这个工具箱中有很多不同种类的工具,例如螺丝刀、扳手、锤子等。每种工具都有它自己的用途和特点。现在我们要将这些工具的信息(例如工具的名称、用途、存放位置等)整理起来,方便之后的查找和使用
       这个时候,我们就可以去创建一个工具信息表,这个表就类似于我们的结构体,在其中它定义了每种工具需要记录的信息类型,加入我们的工具信息表有以下字段 ,工具名称(如“螺丝刀”),工具用途(如“拧紧螺丝”),存放位置(如“第一层,第二个格子”)
        现在,我们有一个具体的螺丝刀,你想把它的信息记录在这个“工作信息表”里。于是,你创建一个“工作信息表的实例”,在C语言就是结构体变量,也就是我们上面代码的key_config
        在这个例子中,工作信息表就是结构体(struct)的定义,它告诉你每种工具需要记录哪种信息。螺丝刀的信息卡片就是结构体变量(例子中是key_config),它包含了螺丝刀的具体信息,工具名称、工具用途和存放位置就是结构体中的成员(或字段),分别对应了螺丝刀的名称、用途和存放位置等信息。
        那么回到我们的key_config变量里,它就类似一个专门为GPIO引脚填写的“信息卡片”。这个卡片上记录GPIO引脚的所有重要配置信息,比如它是输入还是输出、中断触发模式以及输出逻辑电平是高还是低等,这些信息都是通过修改key_config变量中的成员(或字段)来设置的
        因此,结构体变量key_config的作用就是存储和表示一个GPIO引脚的配置信息,而它与结构体中成员的关系就像是“信息卡片”与卡片上填写的具体内容之间的关系。


        上面我们对于结构体进行了一个通俗的讲解,所以继续讲解我们剩下的代码,首先IOMUXC_SetPinMux是在复用GPIO1_IO18引脚,然后 IOMUXC_SetPinConfig是在配置引脚具体的电气属性这两步,都是在初始化IO口

#include "bsp_keyfilter.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_epittimer.h"void filterkey_init(void)
{gpio_pin_config_t key_config;IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);key_config.direction = kGPIO_DigitalInput;key_config.interruptMode = kGPIO_IntFallingEdge;key_config.outputLogic = 1;gpio_init(GPIO1, 18,&key_config);GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);system_register_irqhandler(GPIO1_Combined_16_31_IRQn,(system_irq_handler_t)gpio1_16_31_irqhandler,NULL);gpio_enableint(GPIO1,18);           /*使能GPIO1_IO18的中断功能*/filtertimer_init(66000000/100);     /*初始化定时器,10ms*/
}

        下一步就是将我们的IO初始化为中断,上面也讲解了结构体,我们的结构体如下,所以这里将GPIO输出方向设置为输入

typedef struct _gpio_pin_config
{gpio_pin_direction_t direction; 		/* GPIO方向:输入还是输出 */uint8_t outputLogic;            		/* 如果是输出的话,默认输出电平 */gpio_interrupt_mode_t interruptMode;	/* 中断方式 */
} gpio_pin_config_t;
typedef enum _gpio_pin_direction
{kGPIO_DigitalInput = 0U,  		/* 输入 */kGPIO_DigitalOutput = 1U, 		/* 输出 */
} gpio_pin_direction_t;

        要将GPIO设置为中断,按键按下则触发中断,所以将中断触发类型设置为下降沿触发

typedef enum _gpio_interrupt_mode
{kGPIO_NoIntmode = 0U, 				/* 无中断功能 */kGPIO_IntLowLevel = 1U, 			/* 低电平触发	*/kGPIO_IntHighLevel = 2U, 			/* 高电平触发 */kGPIO_IntRisingEdge = 3U, 			/* 上升沿触发	*/kGPIO_IntFallingEdge = 4U, 			/* 下降沿触发 */kGPIO_IntRisingOrFallingEdge = 5U, 	/* 上升沿和下降沿都触发 */
} gpio_interrupt_mode_t;	

        这里我们将引脚设置为了输入,所以outputLogic的值通常不会影响到引脚的行为,毕竟输入模式下的引脚是用来读取外部信号的,而不是输出信号

GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);system_register_irqhandler(GPIO1_Combined_16_31_IRQn,(system_irq_handler_t)gpio1_16_31_irqhandler,NULL);gpio_enableint(GPIO1,18);           /*使能GPIO1_IO18的中断功能*/filtertimer_init(66000000/100);     /*初始化定时器,10ms*/

        接下来这部分就是使能GPIO中断,以及注册中断处理函数,按键引脚是GPIO1_IO18,对应的是如下中断号,所以使能此中断号,

GPIO1_Combined_16_31_IRQn    = 99,               /**< Combined interrupt indication for GPIO1 signals 16 - 31. */

        GIC_EnableIRQ();也是编写好的初始化使能函数

FORCEDINLINE __STATIC_INLINE void GIC_EnableIRQ(IRQn_Type IRQn){GIC_Type *gic = (GIC_Type *)(__get_CBAR() & 0xFFFF0000UL);gic->D_ISENABLER[((uint32_t)(int32_t)IRQn) >> 5] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));}

        下一步给指定的中断号注册中断服务函数

 system_register_irqhandler(GPIO1_Combined_16_31_IRQn,(system_irq_handler_t)gpio1_16_31_irqhandler,NULL);

        编写的函数如下,这些前面都讲解过,而且了理解起来也不复杂,就不多赘述,主要目的就是注册GPIO1)_Combined_16_31_IRQn这个中断信号,然后待会儿编写它的中断处理函数的时候名字叫做,gpio1_16_31_irqhandler

void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) 
{irqTable[irq].irqHandler = handler;irqTable[irq].userParam = userParam;
}

        使能对应的按键引脚GPIO1_18的中断功能,最后再初始化定时器,设置定时器的周期, filtertimer_init将在下面的代码中编写

/*初始化消抖定时器*/
void filtertimer_init(unsigned int value)
{EPIT1->CR = 0;      /*还是先将寄存器清零再配置*/EPIT1->CR = (1 << 24 | 1 <<3 | 1<<2 | 1<<1);EPIT1->LR = value;  /*计数值*/EPIT1->CMPR = 0;    /*比较寄存器*/GIC_EnableIRQ(EPIT1_IRQn);system_register_irqhandler(EPIT1_IRQn,(system_irq_handler_t)filtertimer_irqhandler,NULL);
}

        编写的定时器消抖函数和之前EPIT的初始化函数很相似,这里也可以直接使用之前EPIT初始化函数,中间的CR寄存器配置,因为设置为1分频,所以可以直接默认,所以这里就没有设置分频值

/*关闭定时器函数*/
void filtertimer_stop(void)
{EPIT1-> &= ~(1<<0); /*关闭定时器*/
}void filtertimer_start(unsigned int value)
{EPIT1->CR &= ~(1<<0);  /*先关闭定时器*/EPIT1->LR = value;   /*计数值*/EPIT1->CR |= (1<<0); /*打开定时器*/
}

            编写如下两个关闭和重启定时器的函数,重启定时器的时候第一段代码是关闭定时器也是为了严谨起见,其次就是需要重新加载计数值,然后再打开寄存器,接下来就是编写两个中断服务函数

        首先第一个是定时器中断处理函数,定时一个静态的无符号字符变量state,并初始化为OFF。由于是静态,所以值在函数调用之间保持不变
        下面的判断语句,检查EPIT1的状态寄存器SR是否被设置,也就是比较事件是否发生,若发生则进行下一步代码,也就是关闭定时器,因为事件已经发生自然就不需要定时器再运行,直到下一次的启动
        然后下一个if判断,就是判断引脚GPIO1的18引脚的KEY0按键的状态。如果按键被按下就执行下面的蜂鸣器的状态反转,也就是使蜂鸣器工作,最后如果比较事件的判断没有成功,或者蜂鸣器反转完成,最后都需要清除中断标志位,准备下一次的中断事件

/*定时器中断处理服务函数*/
void filtertimer_irqhandler(void)
{static unsigned char state = OFF;if(EPIT1->SR & (1<<0)) /*判断比较事件是否发生*/{filtertimer_stop(); /*关闭定时器*/if(gpio_pinread(GPIO1,18) == 0){state =!state;beep_switch(state);}}EPIT1->SR |= 1<<0;      /*清除中断标志位*/
}

        也就是如果在按键按下的时候产生相关中断,中断产生后执行这个中断处理函数,这个函数的作用就是初始化定时器,然后设置周期过后开启定时器,也就是上面我们编写的那个函数,最后还是在完成这个中断后需要清除标志位,以用来准备下一次的中断产生


/*GPIO处理函数*/
void gpio1_16_31_irqhandler(void)
{filtertimer_start(66000000/100); /*开启定时器*/gpio_clearintflags(GPIO1,18);
}

        到这里这完成了所有的有关蜂鸣器的操作,因为我们在实验目的当中还有一个就是需要LED灯一直以500ms的时间持续闪烁,所有最后我们还需要编写一下灯的相关代码,这个代码我们直接在main.c文件当中进行编写就可以了

        灯的逻辑很简单,主函数中只需要引用相关的头文件,且将编写的好的外设进行初始化就可以了

#include "bsp_clk.h"#include "bsp_delay.h"#include "bsp_led.h"#include "bsp_beep.h"#include "bsp_key.h"#include "bsp_int.h"#include "bsp_exit.h"#include "bsp_epittimer.h"#include "bsp_keyfilter.h"int main(void){unsigned char state =OFF;int_init();imx6u_clkinit();clk_enable();led_init();beep_init();filterkey_init();while(1)			{	state = !state;led_switch(LED0,state);delay(500);}return 0;}

        最后修改一下Makefile的文件,然后将代码烧写到开发板中,就可以看到预期的实验现象了,这一讲主要就是对之前的定时器,以及代码的运用,这一节实验就讲解到这里,欢迎大家的指正与讨论

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

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

相关文章

01 为什么要学习数据结构与算法

为什么要学习数据结构与算法 一、问题提出 ​ 最早计算机的设计初衷主要用于军事上枪炮的弹道计算和火力表的测试&#xff0c;后来更多的用于科学计算&#xff0c;即数值类的计算&#xff0c;而现在&#xff0c;计算机深入到日常生活的各个方面&#xff0c;其计算的数据早已从…

毕业设计选题:基于php+vue+uniapp的新闻资讯小程序

开发语言&#xff1a;PHP框架&#xff1a;phpuniapp数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;PhpStorm 系统展示 管理员登录界面 管理员功能界面 新闻类别管理 新闻信息管理 用户管理 管理员管…

基于SpringBoot+Uniapp的家庭记账本微信小程序系统设计与实现

项目运行截图 展示效果图 展示效果图 展示效果图 展示效果图 展示效果图 5. 技术框架 5.1 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更…

MicroFlow:一种高效的基于Rust的TinyML推理引擎

英文论文标题&#xff1a;MICROFLOW: AN EFFICIENT RUST-BASED INFERENCE ENGINE FOR TINYML 中文论文标题&#xff1a;MicroFlow&#xff1a;一种高效的基于Rust的TinyML推理引擎 作者信息&#xff1a; Matteo Carnelos&#xff0c;意大利帕多瓦大学&#xff0c;Grepit AB,…

潜水打捞系统助力,破解汽车打捞难题

随着人类活动的不断扩展&#xff0c;汽车落水事故频发&#xff0c;成为救援工作中的一大难题。汽车因其重量和结构特性&#xff0c;一旦沉入水体&#xff0c;打捞工作将面临巨大挑战。传统的打捞方法往往效率低下&#xff0c;且在操作过程中可能会对汽车造成进一步的损害&#…

Excel中使用SQL语句的四种方法

总结在 Excel 中使用 SQL 语句的四种方法&#xff0c;各种方法都有各自的适用场景&#xff0c;可以选择自己熟悉的或喜欢方式。本文以在 Excel 中操作 MS SQL 数据库的数据为例进行说明。MS SQL 的数据如下&#xff0c;使用微软 SQLExpress 版本。 方法 1&#xff1a; Excel 现…

RSA - 共模攻击

概念 RSA 共模攻击&#xff08;Common Modulus Attack&#xff09;是一种在特定条件下可以利用的 RSA 加密算法的攻击方法。它利用了当多个密文使用同一个 RSA 公钥&#xff08;即相同的模数 n&#xff09;进行加密时&#xff0c;可能可以通过密文之间的关系来获取明文信息&am…

spring揭秘25-springmvc04-servlet容器与springmvc容器总结

文章目录 【README】【1】DelegatingFilterProxy回顾【1.1】DelegatingFilterProxy初始化过滤器bean 【2】从servlet容器获取springmvc顶级web容器【2.1】从Servlet容器中获取springmvc容器总结【2.2】ContextLoaderListener加载springmvc顶级web容器并将其添加到servlet容器【…

Android开发视频预览效果

Android开发视频预览效果 视频播放不是一个简单的事情&#xff0c;得有暂停&#xff0c;继续播放等功能&#xff0c;屏幕的适配也是头疼的事情 一、思路&#xff1a; 引用的是腾讯播放器TXVodPlayer 二、效果图&#xff1a; 图片不是很直观&#xff0c;也可以看下视频 And…

(Linux和数据库)1.Linux操作系统和常用命令

了解Linux操作系统介绍 除了办公和玩游戏之外不用Linux&#xff0c;其他地方都要使用Linux&#xff08;it相关&#xff09; iOS的本质是unix&#xff08;unix是付费版本的操作系统&#xff09; unix和Linux之间很相似 Linux文件系统和目录 bin目录--放工具使用的 操作Linux远程…

消费者Rebalance机制

优质博文&#xff1a;IT-BLOG-CN 一、消费者Rebalance机制 在Apache Kafka中&#xff0c;消费者组 Consumer Group会在以下几种情况下发生重新平衡Rebalance&#xff1a; 【1】消费者加入或离开消费者组&#xff1a; 当一个新的消费者加入消费者组或一个现有的消费者离开消费…

ROS基础入门——实操教程

ROS基础入门——实操教程 前言 本教程实操为主&#xff0c;少说书。可供参考的文档中详细的记录了ROS的实操和理论&#xff0c;只是过于详细繁杂了&#xff0c;看得脑壳疼&#xff0c;于是做了这个笔记。 Ruby Rose&#xff0c;放在这里相当合理 本文初编辑于2024年10月4日 C…

MySQL--事务(详解)

目录 一、前言二、本文章目标三、什么是事务&#xff1f;四、事务的ACID特性五、为什么要使用事务六、如何使用事务6.1 查看支持使用事务的引擎6.2语法6.3 开启⼀个事务&#xff0c;执行更新后回滚6.4 开启一个事务更新后提交6.5 保存点6.6 自动/手动提交事务 七、事务的隔离性…

Spring异步线程池的问题

今天看一视频&#xff0c;提到说 Spring默认的异步线程池比较简单&#xff0c;每次执行异步任务&#xff0c;都会新建一个线程进行处理&#xff0c;不会重复利用&#xff0c;所以在用Spring框架开发的时候&#xff0c;需要自定义异步线程池。第一次听到这个说法。遂开始百度。 …

Word页眉内容自动填充为章节标题

Word页眉内容自动填充为章节标题 在写毕业论文的过程中&#xff0c;通常要求将页眉设置为章节标题&#xff0c;例如这样 通常&#xff0c;页眉内容我们都是手敲上去的&#xff0c;其实在Word中可以设置为自动引用章节标题&#xff0c;以下为设置方法&#xff0c;仅供参考&…

jmeter入门:脚本录制

1.设置代理。 网络连接-》代理-》手动设置代理. ip&#xff1a; 127.0.0.1&#xff0c; port&#xff1a;8888 2. add thread group 3. add HTTP(s) test script recorder, target controller chooses Test plan-> thread Group 4. click start. then open the browser …

利用 Python 爬虫采集 1688商品详情

1688是中国的一个大型B2B电子商务平台&#xff0c;主要用于批发和采购各种商品。对于需要从1688上获取商品详情数据、工程数据或店铺数据的用户来说&#xff0c;可以采用以下几种常见的方法&#xff1a; 官方API接口&#xff1a;如果1688提供了官方的API接口&#xff0c;那么可…

猎板PCB设计中的HDI板技术革新与实践

在设计工业控制器的HDI板时&#xff0c;需要注意以下几个关键方面&#xff1a; 布线设计&#xff1a;由于HDI板布线密度高&#xff0c;合理规划走线非常关键&#xff0c;避免交叉和重叠&#xff0c;确保信号传输的稳定性和可靠性。需要控制线宽和线距&#xff0c;根据电路板的…

C语言 | Leetcode C语言题解之第454题四数相加II

题目&#xff1a; 题解&#xff1a; struct hashTable {int key;int val;UT_hash_handle hh; };int fourSumCount(int* A, int ASize, int* B, int BSize, int* C, int CSize, int* D, int DSize) {struct hashTable* hashtable NULL;for (int i 0; i < ASize; i) {for (…

日语学习零基础生活日语口语柯桥外语学校|股票用日语怎么说?

在日语中&#xff0c;“股票”可以说&#xff1a; • 株&#xff08;かぶ&#xff09; 这是最常用的表达方式&#xff0c;直接表示“股票”。 例如&#xff1a; 株を買う - 买股票 株を売る - 卖股票 • 株式&#xff08;かぶしき&#xff09; 这个词也是“股票”的意…