使用IMX6UL定时器EPTI实现延时

news2024/10/12 3:31:58

        上一节,我们讲解了如何使用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.coloradmin.cn/o/2206751.html

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

相关文章

ELK:Elasticsearch、Logstash、Kibana Spring Cloud Sleuth和Spring Cloud Zipkin

〇、虚拟机中docker安装elasticsearch 、Kibana、Logstash elasticsearch导入中文分词器 Logstash修改es数据库ip及创建索引名配置 一、elasticsearch数据库的结构 和mysql作比较&#xff0c;mysql中的数据库的二维表相当于es数据库的index索引结构&#xff1b;mysql数据库的二…

Java_EE ( IO 流技术)

什么是IO输入(Input)指的是&#xff1a;可以让程序从外部系统获得数据&#xff08;核心含义是“读”&#xff0c;读取外部数据&#xff09;。输出(Output)指的是&#xff1a;程序输出数据给外部系统从而可以操作外部系统&#xff08;核心含义是“写”&#xff0c;将数据写出到外…

七言-重阳寄友

题 记 九九重阳节&#xff0c;岁岁嵌在仲秋季节里&#xff0c;人们的心情也便随着气温的下降而逐渐地沉静下来。 丰收的喜悦洋溢在山水和田野之间。金黄的稻穗低垂着头&#xff0c;仿佛在向大地母亲致以最深情的敬意。 果园里&#xff0c;沉甸甸的苹果、梨子挂满枝头&#…

职场上的人情世故你知多少

1.发微信找人帮忙&#xff0c;半天不回&#xff0c;那基本没戏了&#xff0c;不要再打扰了&#xff0c;懂得都懂。 2.能力越大&#xff0c;事情越多&#xff0c;要懂得张弛有度&#xff0c;不要把自己全抛出去&#xff0c;给自己留点余地&#xff0c;毕竟你不知道别人如何暗地…

关于电动车新国标要求的北斗定位功能资料

一、安装及接口 电动自行车应内置北斗定位模块&#xff0c;安装于电动自行车不易损坏的固定部件中。定位装置所采用的芯片或模组应支持UAR或SPI接口至少支持接收处理北斗 B1C和B2a信号 二、定位检测环境 应具备接收实际卫星信号并进行电动自行车整车运行状态下检测的实验室条件…

【Axure安装包与汉化包附带授权证书】

一、下载Axure安装包与汉化包附带授权证书 1.下载汉化包 【快传】: 点击链接即可保存 2.解压安装包 解压下载好的压缩包&#xff0c;能看到有lang也就是汉化包&#xff0c;AxureRP-Setup-RC.exe 也就是Axure9的安装程序&#xff0c;以及汉化说明和授权码。 二、安装Axure9…

机器人末端的负载辨识

关节处的摩擦力变小了&#xff0c;导致系统的参数辨识精度会变高&#xff0c;因为动力学方程中的摩擦力项占的比例会变小。 为什么要有一个负载的参数辨识&#xff0c;因为对于整个系统来说&#xff0c;除了负载哈&#xff0c;其他关节都是不变的&#xff0c;出厂时都设置好了&…

用YOLOv5跑口罩佩戴识别时的一些问题解决

文章目录 1. 问题12. 问题23. 问题3 1. 问题1 1. 报错&#xff1a;TypeError: No loop matching the specified signature and casting was found for ufunc greater. 2. 原因&#xff1a;版本不兼容。 3. 解决&#xff1a; 先查看当前的 numpy 版本&#xff1a;pip list卸载当…

【动手学深度学习】5.2 参数管理(个人向笔记+代码注释)

之前的课程中&#xff0c;我们只是通过深度学习框架完成训练的工作&#xff0c;而忽略了操作参数的具体细节。所以我们我们介绍的内容有&#xff1a; 访问参数&#xff0c;用于调试&#xff0c;诊断和可视化参数初始化在不同的模型组件间共享参数 下面是一个有单隐藏层的多层感…

基于Python flask的豆瓣电影可视化系统,豆瓣电影爬虫系统

博主介绍&#xff1a;✌Java徐师兄、7年大厂程序员经历。全网粉丝13w、csdn博客专家、掘金/华为云等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不…

猿人学 — 第1届第17题(解题思路附源码)

猿人学 — 第1届第17题 根据题目“天杀的Http2.0”大概知道&#xff0c;请求的协议应该遵照的是Http2.0协议&#xff0c;并且目标网站专门对此进行了检测&#xff0c;在Network面板中右键表头&#xff0c;勾选Protocol 果不其然&#xff0c;一堆请求都是遵照Http2.0协议。而u…

2023年4月自考《数据库系统原理》04735试题

目录 一:选择题 二:填空题 三:设计题 四:简答题 五:综合题 一:选择题 1.在数据库系统中&#xff0c;专门用户建立和管理数据的软件是 (书中)P28页 A.DBS B.DB C.DBA D.DBMS 2.通常所说的数据库系统容不包括 (书中)P29页 A.应用程序 B.数据库管理员 C.用户 D.网络环境 …

Datawhale 组队学习 文生图 Prompt攻防 task01

文生图大模型面临着巨大的滥用风险&#xff0c;如生成虚假、违法违规、血腥恐怖或歧视仇恨的图片&#xff0c;评估此类模型和系统的安全防范能力至关重要。 赛题背景&#xff1a;从产业应用需求出发&#xff0c;体验为大模型注入生成式“风险疫苗”&#xff0c;透视大模型生图潜…

Java SE vs Java EE 与 JVM vs JDK vs JRE

Java SE&#xff08;Java Platform&#xff0c;Standard Edition&#xff09;: Java 平台标准版&#xff0c;Java 编程语言的基础&#xff0c;它包含了支持 Java 应用程序开发和运行的核心类库以及虚拟机等核心组件。Java SE 可以用于构建桌面应用程序或简单的服务器应用程序。…

聚类分析 | IPOA优化FCM模糊C均值聚类优化算法

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 (多图聚类)IPOA优化FCM模糊C均值聚类优化算法&#xff0c;matlab代码&#xff0c;超多图 基于改进的鹈鹕优化算法&#xff08;IPOA&#xff09;优化FCM模糊C均值聚类优化&#xff0c;matlab代码&#xff0c;直接运行…

万字长文解读预测市场的历史发展与未来前景

撰文&#xff1a;Alex Nardi&#xff0c;Shoal Research 编译&#xff1a;Yangz&#xff0c;Techub News 文章要点&#xff1a; 预测市场的发展和影响&#xff1a;预测市场利用集体智慧预测未来事件的能力日益得到认可&#xff0c;对政治、金融、娱乐和流行文化等领域产生了重…

HiRT | 异步控制策略,告别VLA时延问题

论文&#xff1a;HiRT: Enhancing Robotic Control with Hierarchical Robot Transformers 前言&#xff1a;HiRT 通过异步处理的策略&#xff0c;将 VLM 作为低频慢思考过程&#xff0c;将轻量的动作策略模型作为高频快响应过程 &#xff0c;以此解决 VLA 驱动带来的控制时延问…

Ubuntu22.04环境下源码安装OpenCV 4.8.1

因为项目需要用OpenCV对yolov8模型进行推理&#xff0c;通过DNN模块&#xff0c;之前本地的OpenCV版本是4.5.4&#xff08;好像安装完ROS2 humble之后系统就自带了opencv&#xff09;&#xff0c;加载onnx模型一直报错&#xff0c;网上查询到需要4.7以上&#xff0c;干脆直接升…

vue3之依赖注入provide(提供)/inject(注入)

通常情况下&#xff0c;需要从父组件传值到子组件使用props足以&#xff0c;但是如果是深层嵌套的组件&#xff0c;如果某个深层的组件想要得到祖先组件的部分数据&#xff0c;使用props的话需要沿着各个嵌套的组件着层传递数据&#xff0c;而在传递过程中的组件压根就不需要使…

你了解 SpringBoot 在一次 http 请求中耗费了多少内存吗?

在实际工作中&#xff0c;经常会需要进行在全链路压测&#xff0c;优化 GC参数&#xff0c;优化 JVM 内存分配。 当知道 1 次 RPC 请求和 Http 请求需要的堆内存大小后&#xff0c;你可以精确地计算&#xff1a;指定的并发量之下&#xff0c;系统需申请多少堆内存。同时结合 J…