Linux驱动开发基础(IRDA 红外遥控模块)

所学来自百问网

目录

1.红外遥控简介

2.硬件设计

3.软件设计

[4. 示例代码](#4. 示例代码)

[4.1 驱动代码](#4.1 驱动代码)

[4.2 Makefile](#4.2 Makefile)

[4.3 实验效果](#4.3 实验效果)


1.红外遥控简介

红外遥控被广泛应用于家用电器、工业控制和智能仪器系统中,像我们熟知的有电视机盒子遥控器、空调遥控器。红外遥控器系统分为发送端和接收端,如下图所示。

发送端就是红外遥控器,上面有许多按键,当我们按下遥控器按键时,遥控器内部电路会进行编码和调制,再通过红外发射头,将信号以肉眼不可见的红外线发射出去。红外线线虽然肉眼不可见,但可以通过手机摄像头看到,常用该方法检查遥控器是否正常工作。

接收端是一个红外接收头,收到红外信号后,内部电路会进行信号放大和解调,再将数据传给板子上的 GPIO,板子收到数据后再解码才能确定是哪个按键被按下。

2.硬件设计

IRDA 红外接收头,只需要一个GPIO即可实现数据的传输,这种传输协议叫做"1-Wire单总线"。顾名思义,即只有一根数据线,系统中的数据交换、控制都由这根线完成。

原理图中的U1(HS0038)即为IRDA红外接收头,1脚VDD接到了3V3,2 脚GND接到了GND,3脚IRD外接GPIO。

3.软件设计

我们按下遥控器按键的时候,遥控器自动发送某个红外信号,接收头接收到红外信号,然后把红外信号转换成电平信号,通过IRD这根线,传给SOC。整个传输,只涉及单向传输,由HS0038向主芯片传送。

红外协议有:NEC、SONY、RC5、RC6等,常用的就是NEC格式,因此我们主要对NEC进行讲解。

NEC 协议的开始是一段引导码:

这个引导码由一个9ms的低脉冲加上一个4.5ms的高脉冲组成,它用来通知接收方我要开始传输数据了。

然后接着的是数据,数据由4字节组成:地址、地址(取反)、数据、数据(取反),取反是用来校验用的。

地址是指遥控器的ID,每一类遥控器的ID都不一样,这样就可以防止操控电视的遥控器影响空调。数据就是遥控器上的不同按键值。

从前面的图可以知道,NEC每次要发32位(地址、地址取反、数据、数据取反,每个8位)的数据。数据的1和0,开始都是0.56ms的低脉冲,对于数据1,后面的高脉冲比较长,对于数据0,后面的高脉冲比较短。

第一次按下按键时,它会发出引导码,地址,地址取反,数据,数据取反。

但当我们一直按着按键不松的时候,会触发连发码(重复码),这个连发码由9ms的低脉冲,2.25ms 的高脉冲组成, 表示现在按的还是上次一样的按键

4. 示例代码

4.1 驱动代码

复制代码
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <asm/current.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/version.h>



static int major;
static struct class* hs0038_class;
static struct gpio_desc *gpio_hs0038_pin;
static int irq;
static wait_queue_head_t hs0038_wq;
static u64 hs0038_edge_time[100];
static int hs0038_edge_cnt = 0;
static unsigned int hs0038_data = 0;  

int hs0038_parse_data(unsigned int *val)
{
	u64 tmp;
	unsigned char data[4];
	int i, j, m;
	// 判断是否是连发码(重复码)
	if(hs0038_edge_cnt == 4)
	{
		tmp = hs0038_edge_time[1] - hs0038_edge_time[0];
		if(tmp > 8000000 && tmp < 10000000)
		{
			tmp = hs0038_edge_time[2] - hs0038_edge_time[1];
			if(tmp < 3000000)
			{
				*val = hs0038_data;
				return 0;
			}
		}
	}
    // m表示中断数
 	m = 3;
	if(hs0038_edge_cnt >= 68) // 68是4 + 64 64表示接收一个数据产生两次中断,4是两次的引导码
	{
		for(i = 0; i < 4; i++) // 4个字节的数据
		{
			data[i] = 0; // 清空数组
			for(j = 0; j < 8; j++) 
			{
				if(hs0038_edge_time[m+1] - hs0038_edge_time[m] > 1000000) // 高电平持续超过1ms表示数据1
					data[i] |= (1 << j);
				m += 2;// 中断次数加2
			}
		}
		// 校验数据
		data[1] = ~data[1];
		if(data[0] != data[1])
		{
			return -2;
		}

		data[3] = ~data[3];
		if(data[2] != data[3])
		{
			return -2;
		}

		hs0038_data = (data[0] << 8) | data[2];
		*val = hs0038_data;
		return 0;
		}
		else{
			return -1;
		}

}

static irqreturn_t hs0038_isr(int irq, void * dev_id)
{
	unsigned int val;
	int ret;
	
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0))
	hs0038_edge_time[hs0038_edge_cnt++] = ktime_get_boottime_ns();
#else
	hs0038_edge_time[hs0038_edge_cnt++] = ktime_get_boot_ns();
#endif
	// 判断超时
	if(hs0038_edge_cnt >= 2)
	{
		if(hs0038_edge_time[hs0038_edge_cnt-1] - hs0038_edge_time[hs0038_edge_cnt-2] > 6000000)
		{
			hs0038_edge_time[0] = hs0038_edge_time[hs0038_edge_cnt-1];
			hs0038_edge_cnt = 1;
			return IRQ_HANDLED; // IRQ_WAKE_THREAD;
		}

	}

	ret = hs0038_parse_data(&val);
	if (!ret)
	{
		/* 解析成功 */
		hs0038_edge_cnt = 0;
		printk("get ir code = 0x%x\n", val);		
	}
	else if (ret == -2)
	{
		/* 解析失败 */
		hs0038_edge_cnt = 0;
	}
	
	return IRQ_HANDLED;
}



static ssize_t hs0038_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{

	return 0;
}

static unsigned int hs0038_poll (struct file *file, struct poll_table_struct *wait)
{

	return 0;
}
static struct file_operations hs0038_opes = {
	.owner = THIS_MODULE,
	.read = hs0038_read,
	.poll = hs0038_poll,
};


static int hs0038_probe(struct platform_device *pdev)
{
	int err;
	gpio_hs0038_pin = gpiod_get(&pdev->dev,NULL,0); 

	irq = gpiod_to_irq(gpio_hs0038_pin);
	err = request_irq(irq, hs0038_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "hs0038", NULL);
	
	device_create(hs0038_class, NULL, MKDEV(major, 0), NULL,"myhs0038");
	return 0;
}
static int hs0038_remove(struct platform_device *pdev)
{
	device_destroy(hs0038_class, MKDEV(major, 0));
	gpiod_put(gpio_hs0038_pin);
	free_irq(irq,NULL);
	return 0;
}


static struct of_device_id  ask100_hs0038[] = {
	{ .compatible = "100ask,hs0038" },
	{},

};

static struct platform_driver hs0038_dri = {
	.probe = hs0038_probe,
	.remove = hs0038_remove,
	.driver = {
		.name ="100ask_hs0038",
		.of_match_table = ask100_hs0038,
	},
};

static int __init hs0038_init(void)
{
	int err;
	major =register_chrdev(0, "hs0038", &hs0038_opes);
	hs0038_class = class_create(THIS_MODULE, "hs0038_class");

	init_waitqueue_head(&hs0038_wq);

	err = platform_driver_register(&hs0038_dri);
	return err;
}


static void __exit hs0038_exit(void)
{
	platform_driver_unregister(&hs0038_dri);
	unregister_chrdev(major, "hs0038");
	class_destroy(hs0038_class);
	
}

module_init(hs0038_init);
module_exit(hs0038_exit);
MODULE_LICENSE("GPL");

4.2 Makefile

复制代码
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

#KERN_DIR =  /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4
KERN_DIR =  /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o hs0038_test hs0038_test.c
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order  hs0038_test

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o


obj-m += hs0038_drv.o

4.3 实验效果

相关推荐
WoY20205 小时前
opencv-python在ubuntu系统中缺少依赖
python·opencv·ubuntu
ICscholar6 小时前
ExaDigiT/RAPS
linux·服务器·ubuntu·系统架构·运维开发
sim20206 小时前
systemctl isolate graphical.target命令不能随便敲
linux·mysql
米高梅狮子7 小时前
4. Linux 进程调度管理
linux·运维·服务器
再创世纪8 小时前
让USB打印机变网络打印机,秀才USB打印服务器
linux·运维·网络
fengyehongWorld9 小时前
Linux ssh端口转发
linux·ssh
知识分享小能手10 小时前
Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04中的Shell编程详细知识点(含案例代码)(17)
linux·学习·ubuntu
Xの哲學11 小时前
深入解析 Linux systemd: 现代初始化系统的设计与实现
linux·服务器·网络·算法·边缘计算
龙月11 小时前
journalctl命令以及参数详解
linux·运维
EndingCoder12 小时前
TypeScript 的基本类型:数字、字符串和布尔
linux·ubuntu·typescript