linux 下驱动开发 _ 红外线解码驱动
- 2022-10-18 重庆
本文字数:4881 字
阅读完需:约 1 分钟
一、前言
现在很多手机都支持红外线发送了,支持家电控制。红外线协议有很多,当前介绍的是 NEC 协议(红外线传输协议中的一种),也是常说的万能遥控器的协议。
对于接收端而言,NEC 协议最先收到的是 9ms 高电平时间,然后时 4.5 毫秒低电平时间。这表示引导码。随后会发送 32 位数据码,也就是 4 个字节。 接收数据码的时候:前面都会有一个 0.56 毫秒的低电平,随后就是数据。如果是 0.56ms 高电平就是 0,如果是 1.68ms 高电平就是 1。在单片机里可以使用中断+定时器来判断时间长短,从而判断数据值。其中重复码由 9ms 高电平和 2.25ms 的低电平以及 560us 的高电平组成。
红外线 NEC 协议总结: 引导码(9ms 低电平+4.5ms 高电平)+用户码+用户反码+按键码+按键反码
使用红外线作为传输协议,成本比较低,在家电领域还是很常见的。比如:电视机、投影仪等等。 NEC 协议是红外线协议中的一种,也可以自己定义协议,自己发送端和接收端一致能解析数据即可。
二、编写红外线解码驱动
编写驱动之前要先搞清楚原理,红外线的数据接收是通过红外线接收头对红外光的感应后裔输出高低电平来通知单片机。
红外线接收头特性: 收到 38KHZ 的方波(光),就输出低电平,相反就为高。
在 NEC 协议里要弄清楚收到什么样的电平表示数据 1 和 0 非常关键。
数据 0 是怎么表示? 0.56ms(低电平)+.056ms(高电平)
数据 1 是怎么表示? 0.56ms(低电平)+1.68ms(高电平)
红外线解码驱动编写的思路---Linux 下:
(1) 配置中断,将红外线接收头的一个引脚接在开发板的可以产生中断的 IO 上。
(2)工作队列,用来处理实际的解码过程。
(3)时间判断,需要获取高精度的时间 ktime_get。
封装获取高电平的时间函数,获取低电平的时间函数。
下面是例子代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/workqueue.h>
#include <linux/ktime.h>
#include <linux/delay.h>
/*
红外接收头,三个引脚
三个引脚向下, 突出的半圆面向自己,从左往右引脚分别是: 数据脚(接IO口), 地线(gnd), 电源线(vcc, 3.3v)
测试使用M3开发板的红外线测试。
连接线:
将开发板的PB9与Tiny4412的按键1接口相连接!
*/
// 红外接收头的数据脚接的是PL11
#define INFRARED_GPIO EXYNOS4_GPX3(2) //按键1的引脚
/*中断编号*/
static u32 infrared_irq;
/*
函数功能:获取高电平持续的时间
返 回 值:高电平持续的时间
*/
u32 GetInfraredRxH(void)
{
ktime_t time_val;
unsigned int tim_cnt1=0,tim_cnt2=0;
time_val=ktime_get(); //获取当前时间
tim_cnt1=ktime_to_us(time_val); //转us
while(gpio_get_value(INFRARED_GPIO)){}
time_val=ktime_get(); //获取当前时间
tim_cnt2=ktime_to_us(time_val); //转us
return tim_cnt2-tim_cnt1;
}
/*
函数功能:获取低电平持续的时间
返 回 值:低电平持续的时间
*/
u32 GetInfraredRxL(void)
{
ktime_t time_val;
unsigned int tim_cnt1=0,tim_cnt2=0;
time_val=ktime_get(); //获取当前时间
tim_cnt1=ktime_to_us(time_val); //转us
while(!gpio_get_value(INFRARED_GPIO)){}
time_val=ktime_get(); //获取当前时间
tim_cnt2=ktime_to_us(time_val); //转us
return tim_cnt2-tim_cnt1;
}
/*
函数功能:工作列队处理函数
NEC协议解码原理:
1. 先接收引导码:9ms低电平+4.5ms高电平
2. 引导码之后,是连续的32位数据。用户码+用户反码+按键码+按键反码
3. 数据‘0’ :560us低电平+560us高电平
4. 数据‘1’ :560us低电平+1680us高电平
*/
u8 InfraredRxBuff[5]={0}; //存放红外线接收的数据值,其中[4]表示标志位。=0失败,=1成功
void infrared_work_func(struct work_struct * dat)
{
u32 time,j,i;
u8 data=0;
/*1. 判断引导码*/
time=GetInfraredRxL(); //获取低电平的时间 9000us
if(time<5000||time>11000)goto out;
time=GetInfraredRxH(); //4500us
if(time<2500||time>5500)goto out;
/*2. 接收用户码和按键码*/
for(i=0;i<4;i++)
{
for(j=0;j<8;j++)
{
time=GetInfraredRxL(); //获取低电平的时间 560us
if(time<360||time>660)goto out;
time=GetInfraredRxH(); //获取高电平的时间
//560us高电平 0 、 1680us高电平 1
if(time>360&&time<660) //接到数据0
{
data>>=1;
}
else if(time>1480&&time<1880) //接收到数据1
{
data>>=1;
data|=0x80; //1000 0000
}
else
{
goto out;
}
}
InfraredRxBuff[i]=data;
}
InfraredRxBuff[4]=1; //标志红外线解码成功
//打印解码数据
if(InfraredRxBuff[4])
{
InfraredRxBuff[4]=0; //清除接收成功标志
printk("USER=0x%x\r\n",InfraredRxBuff[0]);
printk("KEY=0x%x\r\n",InfraredRxBuff[2]);
}
out:
//使能中断
enable_irq(infrared_irq);
}
/*静态初始化工作队列*/
DECLARE_WORK(infrared_work,infrared_work_func);
/*中断服务函数*/
static irqreturn_t infrared_irq_handler(int irq, void *dev)
{
/*关闭中断*/
disable_irq_nosync(infrared_irq);
/*工作队列调度函数*/
schedule_work(&infrared_work);
return IRQ_HANDLED;
}
static int __init infrared_drv_init(void)
{
/*1. 获取GPIO口中断编号*/
infrared_irq=gpio_to_irq(INFRARED_GPIO);
/*2. 注册中断,下降沿触发中断*/
int stat=request_irq(infrared_irq,infrared_irq_handler,IRQF_TRIGGER_FALLING,"infrared",NULL);
if(stat!=0)printk("infrared中断注册失败!\n");
printk("infrared—irq中断号:%d\n",infrared_irq);
return 0;
}
static void __exit infrared_drv_exit(void)
{
free_irq(infrared_irq,NULL);
}
module_init(infrared_drv_init);
module_exit(infrared_drv_exit);
MODULE_LICENSE("GPL");
三、编写红外线编码发送驱动
如果要完成红外线的发送,可以通过开发板的 PWM 引脚产生 38KHZ 的载波。
这是开发板底板上的 PWM 波形输出的引脚:
下面是例子代码:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/io.h> /*使用IO端口映射*/
#include <linux/pwm.h>
#include <linux/err.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
/*设备名称*/
#define DEVICE_NAME "pwm1"
/*最大频率*/
#define NS_IN_1HZ (1000000000UL)
/*PWM ID设置*/
#define BUZZER_PWM_ID 1
#define BUZZER_PMW_GPIO EXYNOS4_GPD0(1)
static struct pwm_device *pwm4buzzer;
static void pwm_statr(unsigned long freq)
{
int period_ns = NS_IN_1HZ / freq;
/*配置PWM频率与占空比*/
pwm_config(pwm4buzzer, period_ns / 2, period_ns);
/*PWM使能*/
pwm_enable(pwm4buzzer);
/*配置模式为PWM模式*/
s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_SFN(2));
}
static void pwm_stop(void)
{
/*配置PWM模式为输出模式*/
s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);
/*配置展频率与占空比*/
pwm_config(pwm4buzzer, 0, NS_IN_1HZ / 100);
/*关闭PWM输出*/
pwm_disable(pwm4buzzer);
}
/*
函数功能: 发送38KHZ载波
函数形参:
u8 flag : 代表发送0还是发送1
u32 time: 载波持续的时间
让红外线发送二极管以38KHZ频率闪烁: 亮13us然后灭13us
*/
static void Send38KHZ(u8 flag,u32 time) /*9ms*/
{
u32 i;
int period_ns;
if(flag) //发送38KHZ载波------对方接收到是低电平
{
period_ns= NS_IN_1HZ/38000; // 9ms低电平 发送方要持续产生9m的方波(38KHZ)
/*配置PWM频率与占空比*/
pwm_config(pwm4buzzer, period_ns/2,period_ns);
udelay(time);
}
else //不发送------对方接收到是高电平
{
/*配置PWM频率与占空比*/
pwm_config(pwm4buzzer,0,NS_IN_1HZ/38000); //输出电平持续为0
udelay(time);
}
}
/*
PWM功能初始化
*/
static void pwm_init(void)
{
int ret;
ret = gpio_request(BUZZER_PMW_GPIO, DEVICE_NAME);
if (ret)
{
printk("请求pwm GPIO %d 失败\n", BUZZER_PMW_GPIO);
}
gpio_set_value(BUZZER_PMW_GPIO, 0);
s3c_gpio_cfgpin(BUZZER_PMW_GPIO, S3C_GPIO_OUTPUT);
pwm4buzzer = pwm_request(BUZZER_PWM_ID, DEVICE_NAME);
if(IS_ERR(pwm4buzzer))
{
printk("请求pwm %d,%s失败\n", BUZZER_PWM_ID, DEVICE_NAME);
}
pwm_statr(38000);
}
/*
退出时,释放PWM相关引脚
*/
static void pwm_exit(void)
{
pwm_stop();
gpio_free(BUZZER_PMW_GPIO);
}
/*
函数功能: 编码NEC红外线协议
函数形参:
用户码: user_val
按键码: key_val
接收方: 用户码+用户反码+按键码+按键反码
8 + 8 + 8 + 8
buff[0] buff[1] buff[2] buff[3]
发送方: 按键反码+按键码+用户反码+用户码
8 + 8 + 8 + 8
*/
static void SendInfradData(u8 user_val,u8 key_val)
{
u32 i,data;
data=((~key_val&0xFF)<<24)|((key_val&0xFF)<<16)|((~user_val&0xFF)<<8)|((user_val&0xFF)<<0);
/*1. 发送9ms的38KHZ载波*/
Send38KHZ(1,9000);
/*2. 发送4.5ms高电平*/
Send38KHZ(0,4500);
/*3. 发送32位的数据*/
for(i=0;i<32;i++)
{
Send38KHZ(1,560); //先发送0.56ms的间隔38KHZ载波
if(data&0x01) //先发低位
{
Send38KHZ(0,1680); //先发送1.68ms的间隔低电平
}
else
{
Send38KHZ(0,560); //先发送0.56ms的间隔低电平
}
data>>=1; //先发低位
}
Send38KHZ(1,560); //先发送0.56ms的间隔38KHZ载波
/*配置PWM频率与占空比*/
pwm_config(pwm4buzzer,0,NS_IN_1HZ/38000); //输出电平持续为0
mdelay(10); //为了防止干扰
}
static int open_key (struct inode *my_inode, struct file *my_file)
{
printk("open_key调用成功!\n");
return 0;
}
static ssize_t read_key(struct file *my_file, char __user *buff, size_t cnt, loff_t *loff)
{
/*发送一次红外线数据*/
SendInfradData(6,8);
printk("read_key ok\n");
return 0;
}
static ssize_t write_key(struct file *my_file, const char __user *buff, size_t cnt, loff_t *loff)
{
printk("write_key调用成功!\n");
return cnt;
}
static int release_key(struct inode *my_inode, struct file *my_file)
{
printk("release_key调用成功!\n");
return 0;
}
static struct file_operations fops_key=
{
.open=open_key,
.write=write_key,
.read=read_key,
.release=release_key,
};
static struct miscdevice misc_key=
{
.minor=MISC_DYNAMIC_MINOR, /*自动分配次设备号*/
.name="tiny4412_key", /*设备节点的名称 open("/dev/tiny4412_key",2)*/
.fops=&fops_key /*文件操作集合*/
};
static int __init tiny4412_time_init(void)
{
misc_register(&misc_key); //杂项设备注册函数
/*PWM初始化*/
pwm_init();
return 0;
}
static void __exit tiny4412_time_exit(void)
{
misc_deregister(&misc_key); //杂项设备注销函数
pwm_exit();
printk("提示: 驱动卸载成功!\n");
}
module_init(tiny4412_time_init); /*指定驱动的入口函数*/
module_exit(tiny4412_time_exit); /*指定驱动的出口函数*/
MODULE_LICENSE("GPL"); /*指定驱动许可证*/
版权声明: 本文为 InfoQ 作者【DS小龙哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/2bc31ea026deec4b62ead81c5】。文章转载请联系作者。
DS小龙哥
之所以觉得累,是因为说的比做的多。 2022-01-06 加入
熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域
评论