Linux红外驱动重点解析

嵌入式Linux

共 21840字,需浏览 44分钟

 · 2021-05-11

红外遥控是我们经常见到的一种无线收发设备,比如电视遥控,空调遥控,现在电视遥控有些慢慢变成了蓝牙装置。昨天是在知识星球里面看到有人提问,今天来解析一份网友写的驱动程序。


调试红外需要注意几个细节

1、我们发射的遥控器用肉眼是看不到的,需要拿相机来观察。

2、红外接收管和普通的二极管不同,如果用错物料也是不行的。



1.NEC协议无线传输数据原理


NEC协议的特征: 


1、8位地址和8位指令长度; 

2、地址和命令两次传输;(确保可靠性) 

3、PWM脉冲宽度调制,以发射红外载波的占空比代表“0”和“1”; 

4、载波频率为38KHz 

5、位时间为1.125ms和2.25ms 


NEC码位的定义:一个脉冲对应560us的连续载波,一个逻辑1传输需要2.25ms(560us脉冲+1680us低电平),一个逻辑0的 传输需要1.125ms(560us脉冲+560us低电平)。


而遥控接收头在收到脉冲时为低电平,在没有收到脉冲时为高电平,因此, 我们在接收头端收到的信号为:逻辑1应该是560us低+1680us高,逻辑0应该是560us低+560us高。


如下图:




硬件




2. Linux下的驱动接收程序


参考原文:


https://blog.csdn.net/wllw7176/article/details/110506677


两个驱动文件


gpio-ir-recv.c

/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */


#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
#include <media/rc-core.h>
#include <media/gpio-ir-recv.h>

#define GPIO_IR_DRIVER_NAME "gpio-rc-recv"
#define GPIO_IR_DEVICE_NAME "gpio_ir_recv"

struct gpio_rc_dev {
 struct rc_dev *rcdev;
 int gpio_nr;
 bool active_low;
};

#ifdef CONFIG_OF
/*
 * Translate OpenFirmware node properties into platform_data
 */

static int gpio_ir_recv_get_devtree_pdata(struct device *dev,
      struct gpio_ir_recv_platform_data *pdata)

{
 struct device_node *np = dev->of_node;
 enum of_gpio_flags flags;
 int gpio;

 gpio = of_get_gpio_flags(np, 0, &flags);
 if (gpio < 0) {
  if (gpio != -EPROBE_DEFER)
   dev_err(dev, "Failed to get gpio flags (%d)\n", gpio);
  return gpio;
 }

 pdata->gpio_nr = gpio;
 pdata->active_low = (flags & OF_GPIO_ACTIVE_LOW);
 /* probe() takes care of map_name == NULL or allowed_protos == 0 */
 pdata->map_name = of_get_property(np, "linux,rc-map-name"NULL);
 pdata->allowed_protos = 0;

 return 0;
}

static const struct of_device_id gpio_ir_recv_of_match[] = {
 { .compatible = "gpio-ir-receiver", },
 { },
};
MODULE_DEVICE_TABLE(of, gpio_ir_recv_of_match);

#else /* !CONFIG_OF */

#define gpio_ir_recv_get_devtree_pdata(dev, pdata) (-ENOSYS)

#endif

static irqreturn_t gpio_ir_recv_irq(int irq, void *dev_id)
{
 struct gpio_rc_dev *gpio_dev = dev_id;
 int gval;
 int rc = 0;
 enum raw_event_type type = IR_SPACE;

 gval = gpio_get_value(gpio_dev->gpio_nr);

 if (gval < 0)
  goto err_get_value;

 if (gpio_dev->active_low)
  gval = !gval;

 if (gval == 1)
  type = IR_PULSE;

 rc = ir_raw_event_store_edge(gpio_dev->rcdev, type);
 if (rc < 0)
  goto err_get_value;

 ir_raw_event_handle(gpio_dev->rcdev);

err_get_value:
 return IRQ_HANDLED;
}

static int gpio_ir_recv_probe(struct platform_device *pdev)
{
 struct gpio_rc_dev *gpio_dev;
 struct rc_dev *rcdev;
 const struct gpio_ir_recv_platform_data *pdata =
     pdev->dev.platform_data;

 int rc;

 if (pdev->dev.of_node) {
  struct gpio_ir_recv_platform_data *dtpdata =
   devm_kzalloc(&pdev->devsizeof(*dtpdata), GFP_KERNEL);

  if (!dtpdata)
   return -ENOMEM;
  rc = gpio_ir_recv_get_devtree_pdata(&pdev->dev, dtpdata);
  if (rc)
   return rc;
  pdata = dtpdata;
 }

 if (!pdata)
  return -EINVAL;

 if (pdata->gpio_nr < 0)
  return -EINVAL;

 gpio_dev = kzalloc(sizeof(struct gpio_rc_dev), GFP_KERNEL);
 if (!gpio_dev)
  return -ENOMEM;

 rcdev = rc_allocate_device();
 if (!rcdev) {
  rc = -ENOMEM;
  goto err_allocate_device;
 }

 rcdev->priv = gpio_dev;
 rcdev->driver_type = RC_DRIVER_IR_RAW;
 rcdev->input_name = GPIO_IR_DEVICE_NAME;
 rcdev->input_phys = GPIO_IR_DEVICE_NAME "/input0";
 rcdev->input_id.bustype = BUS_HOST;
 rcdev->input_id.vendor = 0x0001;
 rcdev->input_id.product = 0x0001;
 rcdev->input_id.version = 0x0100;
 rcdev->dev.parent = &pdev->dev;
 rcdev->driver_name = GPIO_IR_DRIVER_NAME;
 if (pdata->allowed_protos)
  rcdev->allowed_protocols = pdata->allowed_protos;
 else
  rcdev->allowed_protocols = RC_BIT_ALL;
 rcdev->map_name = pdata->map_name ?: RC_MAP_EMPTY;

 gpio_dev->rcdev = rcdev;
 gpio_dev->gpio_nr = pdata->gpio_nr;
 gpio_dev->active_low = pdata->active_low;

 rc = gpio_request(pdata->gpio_nr, "gpio-ir-recv");
 if (rc < 0)
  goto err_gpio_request;
 rc  = gpio_direction_input(pdata->gpio_nr);
 if (rc < 0)
  goto err_gpio_direction_input;

 rc = rc_register_device(rcdev);
 if (rc < 0) {
  dev_err(&pdev->dev, "failed to register rc device\n");
  goto err_register_rc_device;
 }

 platform_set_drvdata(pdev, gpio_dev);

 rc = request_any_context_irq(gpio_to_irq(pdata->gpio_nr),
    gpio_ir_recv_irq,
   IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
     "gpio-ir-recv-irq", gpio_dev);
 if (rc < 0)
  goto err_request_irq;

 return 0;

err_request_irq:
 rc_unregister_device(rcdev);
 rcdev = NULL;
err_register_rc_device:
err_gpio_direction_input:
 gpio_free(pdata->gpio_nr);
err_gpio_request:
 rc_free_device(rcdev);
err_allocate_device:
 kfree(gpio_dev);
 return rc;
}

static int gpio_ir_recv_remove(struct platform_device *pdev)
{
 struct gpio_rc_dev *gpio_dev = platform_get_drvdata(pdev);

 free_irq(gpio_to_irq(gpio_dev->gpio_nr), gpio_dev);
 rc_unregister_device(gpio_dev->rcdev);
 gpio_free(gpio_dev->gpio_nr);
 kfree(gpio_dev);
 return 0;
}

#ifdef CONFIG_PM
static int gpio_ir_recv_suspend(struct device *dev)
{
 struct platform_device *pdev = to_platform_device(dev);
 struct gpio_rc_dev *gpio_dev = platform_get_drvdata(pdev);

 if (device_may_wakeup(dev))
  enable_irq_wake(gpio_to_irq(gpio_dev->gpio_nr));
 else
  disable_irq(gpio_to_irq(gpio_dev->gpio_nr));

 return 0;
}

static int gpio_ir_recv_resume(struct device *dev)
{
 struct platform_device *pdev = to_platform_device(dev);
 struct gpio_rc_dev *gpio_dev = platform_get_drvdata(pdev);

 if (device_may_wakeup(dev))
  disable_irq_wake(gpio_to_irq(gpio_dev->gpio_nr));
 else
  enable_irq(gpio_to_irq(gpio_dev->gpio_nr));

 return 0;
}

static const struct dev_pm_ops gpio_ir_recv_pm_ops = {
 .suspend        = gpio_ir_recv_suspend,
 .resume         = gpio_ir_recv_resume,
};
#endif

static struct platform_driver gpio_ir_recv_driver = {
 .probe  = gpio_ir_recv_probe,
 .remove = gpio_ir_recv_remove,
 .driver = {
  .name   = GPIO_IR_DRIVER_NAME,
  .of_match_table = of_match_ptr(gpio_ir_recv_of_match),
#ifdef CONFIG_PM
  .pm = &gpio_ir_recv_pm_ops,
#endif
 },
};
module_platform_driver(gpio_ir_recv_driver);

MODULE_DESCRIPTION("GPIO IR Receiver driver");
MODULE_LICENSE("GPL v2");


ir-nec-decoder.c


/* ir-nec-decoder.c - handle NEC IR Pulse/Space protocol
 *
 * Copyright (C) 2010 by Mauro Carvalho Chehab
 *
 * This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 */


#include <linux/bitrev.h>
#include <linux/module.h>
#include "rc-core-priv.h"

#define NEC_NBITS  32
#define NEC_UNIT  562500  /* ns */
#define NEC_HEADER_PULSE (16 * NEC_UNIT)
#define NECX_HEADER_PULSE (8  * NEC_UNIT) /* Less common NEC variant */
#define NEC_HEADER_SPACE (8  * NEC_UNIT)
#define NEC_REPEAT_SPACE (4  * NEC_UNIT)
#define NEC_BIT_PULSE  (1  * NEC_UNIT)
#define NEC_BIT_0_SPACE  (1  * NEC_UNIT)
#define NEC_BIT_1_SPACE  (3  * NEC_UNIT)
#define NEC_TRAILER_PULSE (1  * NEC_UNIT)
#define NEC_TRAILER_SPACE (10 * NEC_UNIT) /* even longer in reality */
#define NECX_REPEAT_BITS 1

enum nec_state {
 STATE_INACTIVE,
 STATE_HEADER_SPACE,
 STATE_BIT_PULSE,
 STATE_BIT_SPACE,
 STATE_TRAILER_PULSE,
 STATE_TRAILER_SPACE,
};

/**
 * ir_nec_decode() - Decode one NEC pulse or space
 * @dev: the struct rc_dev descriptor of the device
 * @duration: the struct ir_raw_event descriptor of the pulse/space
 *
 * This function returns -EINVAL if the pulse violates the state machine
 */

static int ir_nec_decode(struct rc_dev *dev, struct ir_raw_event ev)
{
 struct nec_dec *data = &dev->raw->nec;
 u32 scancode;
 u8 address, not_address, command, not_command;
 bool send_32bits = false;

 if (!(dev->enabled_protocols & RC_BIT_NEC))
  return 0;

 if (!is_timing_event(ev)) {
  if (ev.reset)
   data->state = STATE_INACTIVE;
  return 0;
 }

 IR_dprintk(2"NEC decode started at state %d (%uus %s)\n",
     data->state, TO_US(ev.duration), TO_STR(ev.pulse));

 switch (data->state) {

 case STATE_INACTIVE:
  if (!ev.pulse)
   break;

  if (eq_margin(ev.duration, NEC_HEADER_PULSE, NEC_UNIT * 2)) {
   data->is_nec_x = false;
   data->necx_repeat = false;
  } else if (eq_margin(ev.duration, NECX_HEADER_PULSE, NEC_UNIT / 2))
   data->is_nec_x = true;
  else
   break;

  data->count = 0;
  data->state = STATE_HEADER_SPACE;
  return 0;

 case STATE_HEADER_SPACE:
  if (ev.pulse)
   break;

  if (eq_margin(ev.duration, NEC_HEADER_SPACE, NEC_UNIT)) {
   data->state = STATE_BIT_PULSE;
   return 0;
  } else if (eq_margin(ev.duration, NEC_REPEAT_SPACE, NEC_UNIT / 2)) {
   if (!dev->keypressed) {
    IR_dprintk(1"Discarding last key repeat: event after key up\n");
   } else {
    rc_repeat(dev);
    IR_dprintk(1"Repeat last key\n");
    data->state = STATE_TRAILER_PULSE;
   }
   return 0;
  }

  break;

 case STATE_BIT_PULSE:
  if (!ev.pulse)
   break;

  if (!eq_margin(ev.duration, NEC_BIT_PULSE, NEC_UNIT / 2))
   break;

  data->state = STATE_BIT_SPACE;
  return 0;

 case STATE_BIT_SPACE:
  if (ev.pulse)
   break;

  if (data->necx_repeat && data->count == NECX_REPEAT_BITS &&
   geq_margin(ev.duration,
   NEC_TRAILER_SPACE, NEC_UNIT / 2)) {
    IR_dprintk(1"Repeat last key\n");
    rc_repeat(dev);
    data->state = STATE_INACTIVE;
    return 0;

  } else if (data->count > NECX_REPEAT_BITS)
   data->necx_repeat = false;

  data->bits <<= 1;
  if (eq_margin(ev.duration, NEC_BIT_1_SPACE, NEC_UNIT / 2))
   data->bits |= 1;
  else if (!eq_margin(ev.duration, NEC_BIT_0_SPACE, NEC_UNIT / 2))
   break;
  data->count++;

  if (data->count == NEC_NBITS)
   data->state = STATE_TRAILER_PULSE;
  else
   data->state = STATE_BIT_PULSE;

  return 0;

 case STATE_TRAILER_PULSE:
  if (!ev.pulse)
   break;

  if (!eq_margin(ev.duration, NEC_TRAILER_PULSE, NEC_UNIT / 2))
   break;

  data->state = STATE_TRAILER_SPACE;
  return 0;

 case STATE_TRAILER_SPACE:
  if (ev.pulse)
   break;

  if (!geq_margin(ev.duration, NEC_TRAILER_SPACE, NEC_UNIT / 2))
   break;

  address     = bitrev8((data->bits >> 24) & 0xff);
  not_address = bitrev8((data->bits >> 16) & 0xff);
  command     = bitrev8((data->bits >>  8) & 0xff);
  not_command = bitrev8((data->bits >>  0) & 0xff);

  if ((command ^ not_command) != 0xff) {
   IR_dprintk(1"NEC checksum error: received 0x%08x\n",
       data->bits);
   send_32bits = true;
  }

  if (send_32bits) {
   /* NEC transport, but modified protocol, used by at
    * least Apple and TiVo remotes */

   scancode = data->bits;
   IR_dprintk(1"NEC (modified) scancode 0x%08x\n", scancode);
  } else if ((address ^ not_address) != 0xff) {
   /* Extended NEC */
   scancode = address     << 16 |
       not_address <<  8 |
       command;
   IR_dprintk(1"NEC (Ext) scancode 0x%06x\n", scancode);
  } else {
   /* Normal NEC */
   scancode = address << 8 | command;
   IR_dprintk(1"NEC scancode 0x%04x\n", scancode);
  }

  if (data->is_nec_x)
   data->necx_repeat = true;

  rc_keydown(dev, RC_TYPE_NEC, scancode, 0);
  data->state = STATE_INACTIVE;
  return 0;
 }

 IR_dprintk(1"NEC decode failed at count %d state %d (%uus %s)\n",
     data->count, data->state, TO_US(ev.duration), TO_STR(ev.pulse));
 data->state = STATE_INACTIVE;
 return -EINVAL;
}

static struct ir_raw_handler nec_handler = {
 .protocols = RC_BIT_NEC,
 .decode  = ir_nec_decode,
};

static int __init ir_nec_decode_init(void)
{
 ir_raw_handler_register(&nec_handler);

 printk(KERN_INFO "IR NEC protocol handler initialized\n");
 return 0;
}

static void __exit ir_nec_decode_exit(void)
{
 ir_raw_handler_unregister(&nec_handler);
}

module_init(ir_nec_decode_init);
module_exit(ir_nec_decode_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mauro Carvalho Chehab");
MODULE_AUTHOR("Red Hat Inc. (http://www.redhat.com)");
MODULE_DESCRIPTION("NEC IR protocol decoder");



参考文章中的dts文件:


gpio-ir-receiver {
    compatible = "gpio-ir-receiver";
    gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>; //连接红外的中断引脚
    active_low = <1>;                     //红外接收器是否将信号取反,有些红外接收器会将接收到的高低电平信号反向输出,比如我使用的hx1838红外接收器
    linux,rc-map-name = "rc-hx18380-carmp3"//红外scancode与实际input_evnent code映射表名称,要在rc_register_device注册,具体见gpio-ir-recv.c
    allowed_protos = <0x100>; /*NEC protocol*/ //保留,驱动中并未使用
};


另一个文件里面调用的


ir-nec-decoder.c


这个函数是Linux内核中的红外处理申请函数




这里主要是注册一个解码的结构体


ir-nec-decoder.c




3.中断处理程序解析


gpio-ir-recv.c



ir_raw_event_store_edge() 这个函数用来计算电平的持续时间。

ir_raw_event_handle() 用来处理这个电平表示什么含义。




驱动程序里面,首先是判断当前GPIO电平,如果是低电平,就进入红外解析,如果不是,或者获取失败,就退出程序。


4.红外数据处理程序解析


内核专门开了一个线程来处理数据解析


rc-ir-raw.c





处理函数其实就是处理电平时间长短来决定数字信号


ir-nec-decoder.c



这里是判断头,这个时间和9ms进行比较



9ms 从哪里来的,可以看看这里


ir-nec-decoder.c




拿到头后,这个switch函数就继续往下跑


ir-nec-decoder.c




然后就是判断 1 和 0  的时候了


ir-nec-decoder.c



上面那个就是1,下面那个就是0。


4.然后数据怎么上报呢?


ir-nec-decoder.c



这里是在另一个模块中注册的映射

不同的红外键值对应不同的上报按键键值


rc-trekstor.c



6.参考

https://cloud.tencent.com/developer/article/1354479

https://blog.csdn.net/wllw7176/article/details/110506677





推荐阅读:
专辑|Linux文章汇总
专辑|程序人生
专辑|C语言
我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~



浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报