最近入手了一块显示屏,又惊喜的发现移动咪咕盒子一直落灰,(反正盒子没破解也不能看电视),那给我的讯为4412开发板刷个安卓系统,写个红外遥控驱动烧进去,这样就能用咪咕盒子的遥控器看电视了。说干就干,开动啦
首先,了解下红外遥控器接收头引脚 ,从左往右,引脚依次是OUT,GND,VCC
红外遥控接收的整个过程是你拿着一个红外遥控器对准红外接收头按时,红外接收头的OUT引脚将会如何变化?这个是由你按下了遥控器哪个键决定的。因为它只是起到一个解码的作用。在当下,我国基本上是用的遥控器都是遵循NEC的编码方式。所以,我们就需要知道NEC红外遥控的编码方式了,然后才能在驱动程序里解码遥控器的信号。
遥控器发送的数据码由以下部分组成:引导码,8位的用户码,8位用户码的补码,8位的按键值,8位按键值的补码;
引导码及数据的定义如下图所示,当一直按住一个按钮的时候,会隔110ms左右发一次引导码(重复),并不带任何数据
解码的过程如下:遥控器的解码是在中断处理函数中完成的,当MCU 的中断引脚发生电平变化时,会引发中断;
void interrupt NEC_IR(void)
{
NEC遥控器中断处理
}
NEC遥控器中断里面处理红外接收头解码的代码如下:
#define UP 1
#define DOWN 2
unsigned char IR_STATE = 0;//信号处理的状态
unsigned char IR_CAPTURE_VAL = DOWN;//默认起始为低电平
unsigned char IR_BIT_POSITION = 0;
unsigned char IR_DATA[4];
long long IR_CAPTURE_TIME = 0;
time_t last_time;//上次的时间单位秒
suseconds_t last_time_us;//上次的时间单位微秒
#if 1
do_gettimeofday(&tv_time);//linux获得系统时间的函数
IR_CAPTURE_TIME = ((tv_time.tv_sec - last_time) * 1000 * 1000) + (tv_time.tv_usec - last_time_us);//捕获高低电平的时间
last_time = tv_time.tv_sec;//把捕获触发的时间设置为上一次的时间
last_time_us = tv_time.tv_usec;
if (IR_STATE == 5)
return IRQ_HANDLED;
//printk("%d %d(%lld)\n", IR_CAPTURE_VAL, IR_STATE, IR_CAPTURE_TIME);
if (IR_CAPTURE_VAL == UP) //如果捕捉到上升沿
{
//printf("L:%d\n",IR_CAPTURE_TIME);
//如果低电平时间有9000us IR_STATE=1 引导码低电平9ms
if (7000 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 9500)
{
IR_STATE = 1;
}
else if (IR_STATE == 2)
{
if (300 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 700)
{
IR_STATE = 3;
}
else
{
IR_STATE = 0;
}
}
//如果低电平没有9ms.IR_STATE=4
else if (IR_STATE == 4)
{
IR_STATE = 5;
spend();
}
IR_CAPTURE_VAL = DOWN;
}
else
{
if (IR_STATE == 1)
{
//引导码9ms低电平之后如果是4.5ms的高电平则完成的读取
if (3000 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 6000)
{
IR_BIT_POSITION = 0;
IR_DATA[0] = 0;
IR_DATA[1] = 0;
IR_DATA[2] = 0;
IR_DATA[3] = 0;
IR_STATE = 2;
}
//如果引导码后是2.xms的高电平则是重复码,则直接标记读取完成
else if (1000 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 3000)
{
IR_STATE = 5;
//既然按下-松开才是一个完整的事件,那么就不需要判断是否重复了
spend();
}
}
else if (IR_STATE == 3)
{//0.56ms
if (300 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 700)
{
IR_DATA[IR_BIT_POSITION / 8] |= (0 << (IR_BIT_POSITION % 8));
}//1.69ms
else if (700 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 1900)
{
IR_DATA[IR_BIT_POSITION / 8] |= (1 << (IR_BIT_POSITION % 8));
}
if (IR_BIT_POSITION == 31)
{
//全部读取完成
IR_STATE = 4;
}
else
{
IR_STATE = 2;
IR_BIT_POSITION++;
}
}
IR_CAPTURE_VAL = UP;
}
#endif
好了,现在已经把接收到的遥控红外信号解码了,然后我们进行测试,分别按下遥控器的每个按键,串口控制台会输出对应的键值。现在我们要对收到的解码后的编码进行处理了代码如下图:
static void spend(void)
{
int i;
//如果数据位不等于数据位的反码,则接收错误
if (IR_DATA[2] != (IR_DATA[3] ^ 0xff))
{
//不一致可能是错误数据 直接丢弃
printk("verified failed,drop data 0x%02X 0x%02X(%d%d%d%d%d%d%d%d %d%d%d%d%d%d%d%d)\n",
(IR_DATA[2]),
(IR_DATA[3]),
((IR_DATA[2] >> 7) & 0x01),
((IR_DATA[2] >> 6) & 0x01),
((IR_DATA[2] >> 5) & 0x01),
((IR_DATA[2] >> 4) & 0x01),
((IR_DATA[2] >> 3) & 0x01),
((IR_DATA[2] >> 2) & 0x01),
((IR_DATA[2] >> 1) & 0x01),
((IR_DATA[2] >> 0) & 0x01),
((IR_DATA[3] >> 7) & 0x01),
((IR_DATA[3] >> 6) & 0x01),
((IR_DATA[3] >> 5) & 0x01),
((IR_DATA[3] >> 4) & 0x01),
((IR_DATA[3] >> 3) & 0x01),
((IR_DATA[3] >> 2) & 0x01),
((IR_DATA[3] >> 1) & 0x01),
((IR_DATA[3] >> 0) & 0x01));
}
else//正确就进行上报系统
{
printk("[0x%02X] ", IR_DATA[2]);
for (i = 0; i < ARRAY_SIZE(key_event); i++)
{//如果键值等于数据码,就上报
if (key_event[i].ir_code == IR_DATA[2])
{
//上报按下事件
input_event(buttons_dev, EV_KEY, key_event[i].key_code, 1);
input_sync(buttons_dev);
//------------------------------------------------------
del_timer(&ir_timer); //删除上次的超时函数
ir_timer.expires = jiffies + 25; //设定超时时间,250毫秒
ir_timer.data = key_event[i].key_code; //传递给定时器超时函数的值
ir_timer.function = ir_timer_function; //设置定时器超时函数
add_timer(&ir_timer); //添加定时器,定时器开始生效
// input_event(buttons_dev, EV_KEY, key_event[i].key_code, 0);
// input_sync(buttons_dev);
break;
}
}
}
IR_STATE = 0;
}
/**
* 超时函数
*
* 如果执行到此函数说明规定时间内没有新的按键信号
* 则上报给系统松开按键的事件
*/
static void ir_timer_function(unsigned long key_code)
{
//------------------------------------------------------
//上报一个松开事件
input_event(buttons_dev, EV_KEY, key_code, 0);
input_sync(buttons_dev);
}
好了,现在主要程序已经讲完了,接下来连接硬件,首先查找原理图,找到一个有中断的GPIO口,对应找出核心板的控制引脚,如下图所示。然后连接接收头到开发板上。
然后连接好开发板,进行驱动设备注册,使用命令“vim arch/arm/mach-exynos/mach-itop4412.c”,打开平台 文件,添加注册
接着到 menuconfig 中将其配置上,使用命令“make menuconfig”,进入“Device Drivers —>”→“Character devices —>”→“Enable IR config”,如下图所 示,配置上宏定义“IR_CTL”。
配置后保存退出。这样就确认了宏定义“IR_CTL”已经出现。 接着再次打开“arch/arm/mach-exynos/mach-itop4412.c”平台文件,再添加设备注册。
保存退出,重新编译内核,烧写到开发板。 开发板启动之后,使用命令“ls /sys/devices/platform/”可以查看到新注册的 IR 设 备。最后加载驱动模块“insmod /mnt/sdcard/ir.ko”.按遥控器按键,惊喜的发现可以控制安卓啦,安卓里面安个当贝桌面和电视家APP,这样子就可以看用遥控器遥控选择电视频道啦!美滋滋~
完整代码见下图:
/*以后写驱动可以讲头文件一股脑的加载代码前面*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*中断函数头文件*/
#include
#include
#include //jiffies在此头文件中定义
#include //超时休眠的相关功能再这里定义
#define DEBUG
#ifndef DEBUG
#define Logd( fmt,... )
#else
#define Logd( fmt,... ) do{printk("[ IR ]" fmt "\r\n",##__VA_ARGS__);}while(0)
#endif
#define DRIVER_NAME "ir"
#define GPIO_IR EXYNOS4_GPX3(0)
#define IR gpio_get_value(GPIO_IR)
#define UP 1
#define DOWN 2
//宏定义 按键被按下
#define KEY_PRESSED 1
//宏定义 按键已松开
#define KEY_RELEASED 0
unsigned char IR_STATE = 0;
unsigned char IR_DATA[4];
unsigned char ir_bit_position = 0;
unsigned char ir_capture_val = DOWN;
unsigned char last_key_code = 0;
long long IR_CAPTURE_TIME = 0;
time_t last_time;
suseconds_t last_time_us;
struct timeval tv_time;
static struct timer_list ir_timer;
static void spend(void);
static int find_key_code(unsigned char ir_code); //根据红外编码查找键值
static void ir_tasklet_function(unsigned long data);
static void ir_timer_function(unsigned long key_code); //超时函数 用于模拟松开事件
static void input_event_report(unsigned char key_code,unsigned char event); //按键上报
DECLARE_TASKLET(tasklet, ir_tasklet_function,(unsigned long)0);
struct KEY_EVENT
{
int ir_code; //红外的编码
int key_code; //对于的key键值
};
struct KEY_EVENT key_event[] = {
{.ir_code = 0xca,
.key_code = KEY_UP},
{.ir_code = 0xd2,
.key_code = KEY_DOWN},
{.ir_code = 0x99,
.key_code = KEY_LEFT},
{.ir_code = 0xc1,
.key_code = KEY_RIGHT},
{.ir_code = 0xce,
.key_code = KEY_ENTER},
{.ir_code = 0x95,
.key_code = KEY_BACK},
{.ir_code = 0x82,
.key_code = KEY_MENU},
{.ir_code = 0x80,
.key_code = KEY_VOLUMEUP},
{.ir_code = 0x81,
.key_code = KEY_VOLUMEDOWN},
{.ir_code = 0xf1,
.key_code = KEY_POWER},
{.ir_code = 0x9c,
.key_code = KEY_MUTE},
{.ir_code = 0x8d,
.key_code = KEY_SELECT},
{.ir_code = 0x88,
.key_code = KEY_HOME},
{.ir_code = 0xd9,
.key_code = KEY_PAUSE},
{.ir_code = 0xdd,
.key_code = KEY_PAGEUP},
{.ir_code = 0x8c,
.key_code = KEY_PAGEDOWN},
{.ir_code = 0x92,
.key_code = KEY_1},
{.ir_code = 0x93,
.key_code = KEY_2},
{.ir_code = 0xcc,
.key_code = KEY_3},
{.ir_code = 0x8e,
.key_code = KEY_4},
{.ir_code = 0x8f,
.key_code = KEY_5},
{.ir_code = 0xc8,
.key_code = KEY_6},
{.ir_code = 0x8a,
.key_code = KEY_7},
{.ir_code = 0x8b,
.key_code = KEY_8},
{.ir_code = 0xc4,
.key_code = KEY_9},
{.ir_code = 0x87,
.key_code = KEY_0},
};
struct input_dev *buttons_dev; // 定义一个input_dev结构体
static void ir_tasklet_function(unsigned long data)
{
//用户识别码
unsigned char user_code_h = IR_DATA[0];
unsigned char user_code_l = IR_DATA[1];
//操作码和操作反码
unsigned char option_data = IR_DATA[2];
unsigned char option_inverse = IR_DATA[3];
IR_STATE = 0;
//验证操作码是否正确
if (option_data != (option_inverse ^ 0xff))
{
//不一致可能是错误数据 直接丢弃
Logd("verified failed,drop data 0x%02X 0x%02X(%d%d%d%d%d%d%d%d %d%d%d%d%d%d%d%d)",
(option_data),
(option_inverse),
((option_data >> 7) & 0x01),
((option_data >> 6) & 0x01),
((option_data >> 5) & 0x01),
((option_data >> 4) & 0x01),
((option_data >> 3) & 0x01),
((option_data >> 2) & 0x01),
((option_data >> 1) & 0x01),
((option_data >> 0) & 0x01),
((option_inverse >> 7) & 0x01),
((option_inverse >> 6) & 0x01),
((option_inverse >> 5) & 0x01),
((option_inverse >> 4) & 0x01),
((option_inverse >> 3) & 0x01),
((option_inverse >> 2) & 0x01),
((option_inverse >> 1) & 0x01),
((option_inverse >> 0) & 0x01));
return;
}
//判断last_key_code是否为0,不为0 说明是重复码,省去查找的步骤
if(last_key_code == 0)
{
//查找对应的KEY键值
if((last_key_code = find_key_code(option_data)) == 0)
{
//如果结果为0说明没有查找到
Logd("This key event is not support!!");
return;
}
Logd("[0x%02X] ", option_data);
//上报按下事件
input_event_report(last_key_code,KEY_PRESSED);
}
//------------------------------------------------------
del_timer(&ir_timer); //删除上次的超时函数
ir_timer.expires = jiffies + 25; //设定超时时间,250毫秒
ir_timer.data = last_key_code; //传递给定时器超时函数的值
ir_timer.function = ir_timer_function; //设置定时器超时函数
add_timer(&ir_timer); //添加定时器,定时器开始生效
return;
}
static void input_event_report(unsigned char key_code,unsigned char event)
{
//上报按下事件
input_event(buttons_dev, EV_KEY,key_code, event);
input_sync(buttons_dev);
}
/**
* 超时函数
*
* 如果执行到此函数说明规定时间内没有新的按键信号
* 则上报给系统松开按键的事件
*/
static void ir_timer_function(unsigned long key_code)
{
Logd("松开");
//上报一个松开事件
input_event_report(key_code,KEY_RELEASED);
}
static int find_key_code(unsigned char ir_code)
{
int i;
for (i = 0; i < ARRAY_SIZE(key_event); i++)
{
if (key_event[i].ir_code == ir_code)
{
return key_event[i].key_code;
}
}
return 0;
}
static void spend(void)
{
//ir_tasklet_function(123);
tasklet_schedule(&tasklet);
}
/**
* 红外接收的中断处理函数
*
* 需要特别注意的是红外对时间要求较为严格,切勿在解码过程中使用printk
* printk 消耗时间可能过长 影响到红外的正常解码
*/
static irqreturn_t interrupt_callback(int ir, void *dev_id)
{
//读取当前引脚的高低电平
ir_capture_val = IR;
#if 1
do_gettimeofday(&tv_time);
IR_CAPTURE_TIME = ((tv_time.tv_sec - last_time) * 1000 * 1000) + (tv_time.tv_usec - last_time_us);
last_time = tv_time.tv_sec;
last_time_us = tv_time.tv_usec;
if (IR_STATE == 5)
return IRQ_HANDLED;
//printk("%d %d(%lld)\n", ir_capture_val, IR_STATE, IR_CAPTURE_TIME);
if (ir_capture_val == UP) //捕捉到上升沿
{
//printf("L:%d\n",IR_CAPTURE_TIME);
//9ms
if (7000 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 10000)
{
IR_STATE = 1;
}
else if (IR_STATE == 2)
{
if (300 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 700)
{
IR_STATE = 3;
}
else
{
IR_STATE = 0;
}
}
else if (IR_STATE == 4)
{
IR_STATE = 5;
spend();
}
//ir_capture_val = DOWN;
}
else
{
if (IR_STATE == 1)
{
//引导码9ms低电平之后如果是4.5ms的高电平则完成的读取
if (3000 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 6000)
{
//重置读取的位数
ir_bit_position = 0;
//清空数据
memset(IR_DATA, 0, sizeof(IR_DATA));
IR_STATE = 2;
last_key_code = 0;
}
//如果引导码后是2.xms的高电平则是重复码,则直接标记读取完成
else if (1000 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 3000)
{
IR_STATE = 5;
spend();
}
}
else if (IR_STATE == 3)
{
if (300 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 700)
{
IR_DATA[ir_bit_position / 8] |= (0 << (ir_bit_position % 8));
}
else if (700 < IR_CAPTURE_TIME && IR_CAPTURE_TIME < 1900)
{
IR_DATA[ir_bit_position / 8] |= (1 << (ir_bit_position % 8));
}
if (ir_bit_position == 31)
{
//全部读取完成
IR_STATE = 4;
goto irq_exit;
}
IR_STATE = 2;
ir_bit_position++;
}
//ir_capture_val = UP;
}
#endif
irq_exit:
return IRQ_HANDLED;
}
static int ir_probe(struct platform_device *pdev)
{
int ret;
int i;
Logd("ir_probe");
//gpio_free(GPIO_IR);
ret = gpio_request(GPIO_IR, "GPIO");
if (ret < 0)
{
Logd("%s: request GPIO %d for GPIO failed, ret = %d\n", DRIVER_NAME,
GPIO_IR, ret);
return ret;
}
Logd("request GPIO success!");
//注册中断
s3c_gpio_cfgpin(GPIO_IR, S3C_GPIO_SFN(0xF));
s3c_gpio_setpull(GPIO_IR, S3C_GPIO_PULL_UP);
ret = request_irq(gpio_to_irq(GPIO_IR), interrupt_callback, IRQ_TYPE_EDGE_BOTH | IRQF_DISABLED, "irint", NULL);
if (ret < 0)
{
Logd("Request IRQ %d failed, %d\n", gpio_to_irq(GPIO_IR), ret);
return ret;
}
Logd("request IRQ success!");
//------------------------------------------------------
//申请一个定时器
init_timer(&ir_timer); //初始化定时器
//1.向内核 申请input_dev结构体
buttons_dev = input_allocate_device();
buttons_dev->name = "irdev";
/*2.设置input_dev , */
set_bit(EV_KEY, buttons_dev->evbit); //支持键盘事件
set_bit(EV_REP, buttons_dev->evbit); //支持键盘重复按事件
for (i = 0; i < ARRAY_SIZE(key_event); i++)
{
set_bit(key_event[i].key_code, buttons_dev->keybit);
}
ret = input_register_device(buttons_dev);
if(ret < 0)
{
Logd("input_register_device failed:%d",ret);
return ret;
}
Logd("input_register_device success!");
return 0;
}
static int ir_remove(struct platform_device *pdev)
{
Logd("ir_remove");
//释放中断
free_irq(gpio_to_irq(GPIO_IR), NULL);
//删除定时器
del_timer(&ir_timer);
gpio_free(GPIO_IR);
tasklet_kill(&tasklet);
input_unregister_device(buttons_dev);
input_free_device(buttons_dev);
return 0;
}
static int ir_suspend(struct platform_device *pdev, pm_message_t state)
{
Logd("ir suspend:power off!\n");
return 0;
}
static int ir_resume(struct platform_device *pdev)
{
Logd("ir resume:power on!\n");
return 0;
}
static struct platform_driver ir_driver = {
.probe = ir_probe,
.remove = ir_remove,
.suspend = ir_suspend,
.resume = ir_resume,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
};
static int __init ir_init(void)
{
Logd("ir initialized");
return platform_driver_register(&ir_driver);
}
static void __exit ir_exit(void)
{
Logd("ir_exit");
platform_driver_unregister(&ir_driver);
}
module_init(ir_init);
module_exit(ir_exit);
MODULE_AUTHOR("Liliping && Donggua");
MODULE_DESCRIPTION("ir devices");
MODULE_LICENSE("Dual BSD/GPL");