看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动EPIT定时器中断《按键消抖》) 2023.5.10

2023-05-16

前言

首先我们在使用开发板进行开发时,自然而然会使用到定时器这个外设,因为我们需要它来完成精准的定时功能,但是说到精准,我会在下一篇文章中使用其他的定时器来完成这个功能即GPT定时器。在本文章中我们会利用定时器中断来解决按键消抖功能,并且解决上一讲GPIO中断中的问题。

整体文件结构

在这里插入图片描述

源码分析(保姆级讲解)

带有消抖功能的按键初始化部分

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);		

	filtertimer_init(66000000/100);	
}

好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!

gpio_pin_config_t key_config;

声明了一个gpio_pin_config_t 类型,并且名称为key_config。那我们可以看下这个结构体中声明了什么?

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

其中声明了三个变量,分别是directionoutputLogicinterruptMode

其中gpio_pin_direction_t 结构体如下所示:

typedef enum _gpio_pin_direction
{
    kGPIO_DigitalInput = 0U,  		/* 输入 */
    kGPIO_DigitalOutput = 1U, 		/* 输出 */
} gpio_pin_direction_t;

其中gpio_interrupt_mode_t 结构体如下所示:

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;	
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);	

初始化IO复用功能为用为GPIO1_IO18

IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);

配置GPIO1_IO18的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 低转换率

key_config.direction = kGPIO_DigitalInput;

设置GPIO1_IO18方向为输入。

key_config.interruptMode = kGPIO_IntFallingEdge;

设置GPIO1_IO18为下降沿触发。

key_config.outputLogic = 1;

设置GPIO1_IO18初始电平为1,即高电平。

gpio_init(GPIO1, 18, &key_config);

因为要产生GPIO中断,所以需要配置中断号等其他设置。

GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); 

使能GIC中对应的中断

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

注册中断服务函数,并且名称为gpio1_16_31_irqhandler,即产生GPIO中断后,会自动进入该中断服务函数。

gpio_enableint(GPIO1, 18);

使能GPIO1_IO18的中断功能

filtertimer_init(66000000/100);

初始化EPIT定时器,10ms

带有消抖功能的EPIT定时器初始化部分

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);	
}

好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!

EPIT1->CR = 0;	

//先清零

EPIT1->CR = (1<<24 | 1<<3 | 1<<2 | 1<<1);

该寄存器具体配置如下:

在这里插入图片描述
在讲为什么会这样配置该寄存器之前,我们先了解下EPIT定时器的工作原理。

在这里插入图片描述

由上图所示,我们分别说以下6点:

①、这是个多路选择器,用来选择 EPIT 定时器的时钟源,EPIT 共有 3 个时钟源可选择,ipg_clk、ipg_clk_32k 和 ipg_clk_highfreq

②、这是一个 12 位的分频器,负责对时钟源进行分频,12 位对应的值是 0~ 4095,对应着1~4096 分频

③、经过分频的时钟进入到 EPIT 内部,在 EPIT 内部有三个重要的寄存器:计数寄存器(EPIT_CNR)、加载寄存器(EPIT_LR)和比较寄存器(EPIT_CMPR),这三个寄存器都是 32 位的。

EPIT 是一个向下计数器,也就是说给它一个初值,它就会从这个给定的初值开始递减,直到减为 0,计数寄存器里面保存的就是当前的计数值。如果 EPIT 工作在 set-and-forget 模式下,当计数寄存器里面的值减少到 0EPIT 就会重新从加载寄存器读取数值到计数寄存器里面,重新开始向下计数。比较寄存器里面保存的数值用于和计数寄存器里面的计数值比较,如果相等的话就会产生一个比较事件。

④、比较器。

⑤、EPIT 可以设置引脚输出,如果设置了的话就会通过指定的引脚输出信号。

⑥、产生比较中断,也就是定时中断。

EPIT 定时器有两种工作模式:set-and-forgetfree-running,这两个工作模式的区别如下:

set-and-forget 模式:EPITx_CR(x=1,2)寄存器的 RLD 位置 1 的时候 EPIT 工作在此模式下,在此模式下 EPIT 的计数器从加载寄存器 EPITx_LR 中获取初始值,不能直接向计数器寄存器写入数据。不管什么时候,只要计数器计数到 0,那么就会从加载寄存器 EPITx_LR 中重新加载数据到计数器中,周而复始。

free-running 模式:EPITx_CR 寄存器的 RLD 位清零的时候 EPIT 工作在此模式下,当计数器计数到0以后会重新从0XFFFFFFFF开始计数,并不是从加载寄存器EPITx_LR中获取数据。

所以通过了解了上述功能后,我们来看下这行代码都做了些什么

EPIT1->CR = (1<<24 | 1<<3 | 1<<2 | 1<<1);
  1. 1<<1:当计数器在每次变为0之后,会从加载寄存器读取下一轮计数的初始值。
  2. 1<<2 :当为1时,代表使能比较中断,即当计数器的值现在和我们设定的比较值相等时,会触发定时器中断。
  3. 1<<3:为 1 的时候工作在 set-and-forget 模式,即会从会从加载寄存器读取下一轮计数的初始值。
  4. 1<<24:选择定时器的时钟源为Peripheral 时钟(ipg_clk),即66MHz。
EPIT1->LR = value;

EPIT1->LR 时加载寄存器。

EPIT1->CMPR	= 0;	

EPIT1->CMPR是比较值,意味着当加载寄存器从value减少到0之后,会触发定时器中断。

GIC_EnableIRQ(EPIT1_IRQn);	

使能GIC中对应的中断

system_register_irqhandler(EPIT1_IRQn, (system_irq_handler_t)filtertimer_irqhandler, NULL);	

注册中断服务函数filtertimer_irqhandler

gpio中断服务函数部分

void gpio1_16_31_irqhandler(void)
{ 
	/* 开启定时器 */
	filtertimer_restart(66000000/100);

	/* 清除中断标志位 */
	gpio_clearintflags(GPIO1, 18);
}
void filtertimer_restart(unsigned int value)
{
	EPIT1->CR &= ~(1<<0);	/* 先关闭定时器 */
	EPIT1->LR = value;		/* 计数值 			*/
	EPIT1->CR |= (1<<0);	/* 打开定时器 		*/
}

按键消抖其实就是在按键按下以后延时一段时间再去读取按键值,如果此时按键值还有效那就表示这是一次有效的按键,中间的延时就是消抖的。因为中断服务函数最基本的要求就是快进快出!

当按键按下以后触发按键中断,在按键中断中开启一个定时器,定时周期为 10ms,当定时时间到了以后就会触发定时器中断,最后在定时器中断处理函数中读取按键的值,如果按键值还是按下状态那就表示这是一次有效的按键。

那我们如果想让其10ms触发一次定时器中断,我们应该设置多大的value

计算公式如下

Tout = ((frac +1 )* value) / Tclk;

其中:

Tclk:EPIT1 的输入时钟频率(单位 Hz)
Tout:EPIT1 的溢出时间(单位 S)。
frac:分频值,默认是0,代表是1分频

那么1ms = ((0+1)* 66000000/100)/66000000 = 1/100s=10ms

gpio_clearintflags(GPIO1, 18);

每次完成一次gpio中断响应后,我们需要手动清除中断标志位,方便下一次进入中断函数。

定时器中断服务函数部分

void filtertimer_irqhandler(void)
{ 
	static unsigned char state = OFF;

	if(EPIT1->SR & (1<<0)) 					/* 判断比较事件是否发生			*/
	{
		filtertimer_stop();					/* 关闭定时器 				*/
		if(gpio_pinread(GPIO1, 18) == 0)	/* KEY0 				*/
		{
			state = !state;
			beep_switch(state);				/* 反转蜂鸣器 				*/
		}
	}
		
	EPIT1->SR |= 1<<0; 						/* 清除中断标志位 				*/
}
if(EPIT1->SR & (1<<0)) 					/* 判断比较事件是否发生			*/
{
	filtertimer_stop();					/* 关闭定时器 				*/
	if(gpio_pinread(GPIO1, 18) == 0)	/* KEY0 				*/
	{
		state = !state;
		beep_switch(state);				/* 反转蜂鸣器 				*/
	}
}

此函数先读取 EPIT1_SR 寄存器,判断当前的中断是否为比较事件,如果是的话,并且此时gpio状态还是低电平状态,则代表此时按键按下,即我们将蜂鸣器翻转即可。

EPIT1->SR |= 1<<0; 						/* 清除中断标志位 				*/

每次完成一次定时器中断响应后,我们需要手动清除中断标志位,方便下一次进入中断函数。

while循环部分

while(1)			
	{	
		state = !state;
		led_switch(LED0, state);
		delay(500);
	}

每隔500msled灯亮灭。

最终编译验证

按下 KEY 就会控制蜂鸣器的开关,并且 LED0 不断的闪烁

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动EPIT定时器中断《按键消抖》) 2023.5.10 的相关文章

  • Linux| |对于UDP的学习

    UDP 前序 UDP xff08 用户数据报协议 xff09 没有连接的 xff0c 是面向数据报的 xff0c 是不可靠 套接字 就是IP地址 43 端口号 IP地址 xff1a 4字节 端口号 xff1a 2字节 xff0c 也就是说范
  • 数据结构| |各类排序的时间复杂度以及稳定性

    各类排序的时间复杂度以及稳定性 插入排序 xff1a 直接插入排序 xff1a O N 2 稳定 希尔排序 xff1a O N 1 3 不稳定 选择排序 xff1a 选择排序 xff1a O N 2 不稳定 堆排序 xff1a O Nlog
  • 安装Nvidia显卡驱动和CUDA

    原链接 http community bwbot org topic 152 网上看到的 xff0c 但是原链接 不过这里安装的是CUDA7 5 xff0c 现在最新的是8 0 可以到官网进行下载 xff0c 记住一定不要选择deb方式 x
  • Linux| |IP地址的三类私有地址

    IP地址的三类私有地址 对于IP地址来说有着三种私有地址 三种私有地址如下 xff1a 10 0 0 0 10 255 255 255 172 16 0 0 172 16 255 255 192 168 0 0 192 168 255 25
  • Linux| |如何高效切换目录

    Linxu如何高效切换目录 前言 Linux下对于目录的切换 xff0c 大家肯定会想到一个命令 xff1a cd命令 cd命令确实方便 xff0c 但是当需要频繁的切换目录的时候 xff0c cd命令可能比较麻烦了 比如 xff1a ho
  • C++| |四种强制类型转化(剖析)

    四种强制类型转换 1 出现的原因 C语言的强制类型转换 xff0c 有着两种 隐式类型转换 显示的强制类型转换 举例 xff1a int main int i 61 1 double d 61 i 隐式类型转换 int p 61 amp i
  • 数据结构| |快速排序,二路快排,三路快排

    快速排序 二路快排 三路快排 1 快速排序 1 概念 快速排序采用分治的思想对数据进行排序 选择一个基准值 将比基准值小的放在基准值的左边 xff0c 其余的 xff08 大于或者等于 xff09 放在右边 然后再对左边和右边继续进行划分
  • socket编程中write、read和send、recv

    write xff08 xff09 与read xff08 xff09 函数send xff08 xff09 与recv xff08 xff09 函数 一旦 xff0c 我们建立好了tcp连接之后 xff0c 我们就可以把得到的fd当作文件
  • Ubuntu上火狐浏览器无法上网的解决方法

    网上有的方法是在浏览器中选择更新 xff0c 后来找到了更加直接好用的方法 xff0c 只需要几行命令就可以 1 在终端中输入sudo apt get update 如果在这一步出现错误 xff0c 显示暂时不能解析域名的情况 xff0c
  • 实现字符串连接函数(strcat)

    在字符串的操作中strcat函数的使用是频繁的 xff0c 那么下面我们来自己实现strcat函数的功能 自定义一个函数将要连接的两个字符串作为参数传入 xff0c 然后将str1赋值给临时变量p 然后p一直向后指 xff0c 直到str1
  • C#开发简单的串口上位机

    采用C 开发上位机非常方便 xff0c 具体步骤如下 xff1a 1 绘制一个上位机的界面 xff0c 如下图所示 xff1a 不要忘记还有下面的串口模块serialPort1 2 初始化部分 xff1a 波特率编辑框中加入需要的波特率 实
  • STM32读取匿名光流数据——与Guidance的光流和超声波做对比测试

    使用两个串口同时读取匿名光流和Guidance数据 xff1a 用以比较两个光流的效果 Github链接 xff1a https github com W yt YuTian Pro tree master Guidance 26Ano R
  • UDP编程笔记

    1 字节序 1 1 概念 是指多字节数据的存储顺序 1 2 分类 小端格式 xff1a 将低位字节数据存储在低地址 大端格式 xff1a 将高位字节数据存储在低地址 1 3 注意 LSB xff1a 低地址 MSB xff1a 高地址 2
  • rviz的简单使用

    原链接 xff1a http community bwbot org 运行测试平台 小强ROS机器人 rviz是ros自带的一个图形化工具 xff0c 可以方便的对ros的程序进行图形化操作 其使用也是比较简单 整体界面如下图所示 界面主要
  • c语言_结构体封装寄存器的用法,以及typedef、 volatile、static、 inline关键字用法

    define span class token constant ELFIN TIMER BASE span span class token number 0xE2500000 span span class token comment
  • Ros—RPLIDAR A2激光雷达安装(hector_mapping算法建图同cartographer_ros建图对比)

    Ros RPLIDAR A2激光雷达安装 hector mapping算法建图同cartographer ros建图对比 因为大部分教程复杂繁琐 xff0c 而且容易失败 便整理总结了一下网上的资料 xff0c 感谢 Cayla和 口袋里的
  • 海康设备xml透传以及DS-K1F100-D8E 设备下发卡 ,读卡

    对接海康5604设备 设置温度上限及下限 设备较多通过demo手动透传不可取 故采用代码方式进行透传 代码记录 方便后续开发找方便 public void setThermal Map lt String Object gt params
  • ROS学习之error解决记录

    目录 虚拟机系统版本 xff1a Ubuntu 20 04 ROS版本 xff1a Noetic 1 15 14 虚拟机系统版本 xff1a Ubuntu 18 04 ROS版本 xff1a Melodic 1 14 13 整理一下平时遇到
  • C语言 带参数的#define中#和##的基本用法

    1 单 的作用是把参数变成字符串 xff1b 2 的作用是连接组合参数名字 xff1b 废话不多说 xff0c 看个简洁的例子就明白了 span class token macro property span class token dir
  • 类的封装--- 2021.10.19

    封装是什么 xff1f 我们都知道C 43 43 有三大特性 xff0c 分别是继承 多态和封装 至于继承和多态我会在之后的文章中进行讲述 xff0c 在本讲中我们只讲类的封装 在上一讲中 xff0c 我们论述了类是什么 xff0c 那么我

随机推荐