红外遥控及Android手机红外遥控器开发

一、红外遥控

红外遥控技术是通过红外技术、红外通信技术和遥控技术的结合实现的一种无线控制技术。由于红外线的波长较短,对障碍物的衍射能力较差,无法穿透墙壁,所以红外遥控术更适合应用在短距离直线控制的场合,也正是这样,放置在不同房间的家用电器可使用通用的遥控器而不会产生相互干扰。

红外遥控所需传输的数据量较小,一般仅为几个至几十个字节的控制码,传输距离一般小于 10 米,因其功耗小、成本低、易实现等诸多优点,被广泛应用于电视机、机顶盒、DVD 播放器、功放、空调等家用电器的遥控。

二、手机红外遥控功能

部分智能手机都配置了是红外遥控功能(即安装红了外发射器)。那么,安装了红外发射器的智能手机,可以拿来当遥控器使用,还能一部手机遥控许多家电。具有红外功能的智能手机的顶部,有的镶嵌一个或多个小灯泡,有的是一小片黑色盖子,这个黑盖子对红外线来说可是透明的,只是人的肉眼看不穿它。红外遥控带着灯泡就像一支手电筒,红外光照到哪里,哪里的电器才会接收响应,这决定了红外遥控的三个特性:

  1. 遥控器要对准电器才有反应。
  2. 遥控器不能距离电器太远,最好是五米之内。
  3. 遥控器与电器之间不能有障碍物。

安装了红外发射器的手机,可以拿来当遥控器使用,还能一部手机遥控许多家电,这就需要破解电器的信号编码了。

三、红外发射原理简介

通用红外遥控系统主要由发射和接收两大部分组成。发射部分包括单片机芯片或红外遥控发射专用芯片实现编码和调制,红外发射电路实现发射;接收部分包括一体化红外接收头电路实现接收和解调,单片机芯片实现解码。红外遥控发射专用芯片非常多,编码及调制频率也不完全一样。手机实现红外遥控功能,主要就是发射红外信号部分,这就需要了解下红外信号的编码和调制原理。

3.1.红外遥控二进制信号的编码

红外遥控器发射的信号由一串「0」和「1」的二进制代码组成,不同的芯片对「0」和「1」的编码有所不同,通常有曼彻斯特 (Manchester) 编码和脉冲宽度编码 (PWM)。家用电器使用的红外遥控器绝大部分都是脉冲宽度编码,如下图所示:
红外遥控及Android手机红外遥控器开发_第1张图片

3.2.红外遥控二进制信号的调制

二进制信号的调制由发送单片机芯片或红外遥控发射专用芯片来完成,把编码后的二进制信号调制成频率为 38kHz 的间断脉冲串,相当于用二进制信号的编码乘以频率为38kHz 的脉冲信号得到的间断脉冲串,即是调制后用于红外发射二极管发送的信号。通用红外遥控器里面常用的红外遥控发射专用芯片载波频率为 38kHz,这是由发射端所使用的 455kHz 陶瓷晶振来决定的。在发射端对晶振进行整数分频,分频系数一般取 12 所以 455kHz ÷ 12 ≈ 37.9kHz ≈ 38kHz。

四、 NEC编码协议

在红外开发中,可能最重要的就是发送二进制信号的编码协议了,各个厂家所使用的编码协议不同,所以遥控器也不能相互控制,而即使编码协议相同,使用的用户码不同,也不能被接收端接受。所以类似万能遥控器这种应用,第一个要解决的问题就是各类家用电器,各类厂家所使用的编码协议以及所使用的用户码等。但各类电器众多,这种应用是很难兼容全的,有的大厂家会将自家产品的编码及对应功能键的数据序列公布在网上,方便其他开发者开发,至于其他没有公布的,可能就要使用红外解码仪来破解它所使用的协议以及各功能键对应的数据码等。

在日常家用电器中,NEC 编码是比较常见的一种编码协议, 通用红外遥控器发出的一串二进制代码按功能可以分为分「引导码、用户码 16 位、数据码 8 位、数据反码 8 位和结束位」,编码共占 32 位,如下图2所示:
红外遥控及Android手机红外遥控器开发_第2张图片
其中引导码由一个 9ms 的 38kHz 载波起始码和一个 4.5ms 的无载波低电平结果码组成。用户码由低 8 位和高 8 位组成 (用户码高八位和低八位可采用原码与反码的方式,可用于纠错,但也可直接是 16 位的原码方式),不同的遥控器有不同的用户码,避免不同设备产生干扰,用户码又称为地址码或系统码。数据码采用原码和反码方式重复发送,编码时用于对数据的纠错,遥控器发射编码时,低位在前,高位在后。结束位是 0.56ms 的 38kHz 载波。而其中的「0」码由 0.56ms 的 38kHz 载波和 0.56ms 的无载波低电平组合而成,脉冲宽度为 1.125ms,「1」码由 0.56ms 的 38kHz 载波和 1.69ms 的无载波低电平组合而成,脉冲宽度为 2.25ms,如下图所示:
红外遥控及Android手机红外遥控器开发_第3张图片

五、红外遥控器解码

红外遥控器的解码仪能够分析常见家电的红外遥控信号,下面两种除外:
1、空调遥控器,空调的控制比较复杂,光光温度就可能调节十几次,难以破解。
2、灯光遥控器,灯本身发光发热,同时也会散发大量红外线,势必对外部的红外信号造成严重干扰;所以灯只能采取射频遥控器。

红外解码仪是家电维修人员的必备仪器,常用于检测遥控器能否正常工作,开发者为了让手机实现遥控功能,也要利用解码仪捕捉每个按键对应的红外信号,红外信号由三部分组成,分别是:

  1. 用户码,表示厂商代号,每个厂家都有自己的唯一代号;
  2. 数据码,表示按键的编号,不同的数据码代表不同的按键;
  3. 电路格式,表示红外信号的编码协议,每种协议都有专门的指令格式。

比如说电路61212表示的是NEC6121协议,该协议的红外信号编码格式为:引导码+用户码+数据码+数据反码+结束码,其中引导码和结束码都是固定的,数据反码由数据码按位取反得来,真正变化的只有用户码和数据码。

六、 Android红外遥控功能开发

1、红外权限配置

在App工程的AndroidManifest.xml中补充红外权限配置

	
   <uses-permission android:name="android.permission.TRANSMIT_IR" />
   
   <uses-feature android:name="android.hardware.ConsumerIrManager" android:required="false" />	

2、红外功能API

红外遥控功能从Android4.4之后才开始支持,对应的管理类名叫ConsumerIrManager,常用的三个方法分别是:

  • hasIrEmitter (): 用于检查设备是否具有红外发射器。返回true表示有,返回false表示没有。

  • getCarrierFrequencies() : 获得红外发射器可用的载波频率范围。

  • transmit (int carrierFrequency, int[] pattern) : 用于发射红外信号。
    第一个参数为信号频率,单位赫兹(Hz),家用电器的红外频率通常使用38000Hz;第二个参数为整型数组形式的信号格式。

下边是一个封装好的红外遥控管理类

	import android.annotation.TargetApi;
	import android.content.Context;
	import android.os.Build;

	@TargetApi(Build.VERSION_CODES.KITKAT)
	public class ConsumerIrManagerApi {

	    private static ConsumerIrManagerApi instance;
	    private static android.hardware.ConsumerIrManager service;
	
	    private ConsumerIrManagerApi(Context context) {
	        //Android4.4才开始支持红外功能
	        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
	            // 获取系统的红外遥控服务
	            service = (android.hardware.ConsumerIrManager) context.getApplicationContext().getSystemService(Context.CONSUMER_IR_SERVICE);
	        }
	    }
	
	    public static ConsumerIrManagerApi getConsumerIrManager(Context context){
	        if(instance == null){
	            instance = new ConsumerIrManagerApi(context);
	        }
	        return instance;
	    }
	
	    /**
	     * 手机是否有红外功能
	     * @return
	     */
	    public static boolean hasIrEmitter() {
	        //android4.4及以上版本&有红外功能
	        if(service!=null){
	            return service.hasIrEmitter();
	        }
	        //android4.4以下及4.4以上没红外功能
	        return false;
	    }
	
	    /**
	     * 发射红外信号
	     * @param carrierFrequency 红外频率
	     * @param pattern
	     */
	    public static void transmit(int carrierFrequency, int[] pattern) {
	        if(service!=null){
	            service.transmit(carrierFrequency, pattern);
	        }
	    }
	
	    /**
	     * 获取可支持的红外信号频率
	     * @return
	     */
	    public static android.hardware.ConsumerIrManager.CarrierFrequencyRange[] getCarrierFrequencies() {
	        if(service!=null){
	            return service.getCarrierFrequencies();
	        }
	        return null;
	    }
	}

3、码值编码

上文提到过,一般家用电器遥控器的频率是38000,核心就在transmit()方法的信号编码参数上,上文第5部分提到过,编码由“引导码(9ms+4.5ms)+用户编码(高八位)+用户编码(低八位)+键数据码+键数据反码+结束码”组成,然后按照一定的编码规则,合成数组的形式。以NEC6122协议举例,引导码都是固定的(9000+4500),结束码即停止位可以按照(560,2000),不同遥控器差别主要在于用户码和数据码,同一个遥控器的用户码是一样的,不同按键有不同的码值,码值可以转换出对应的数据码和数据反码,以下面的遥控器码值举例。
红外遥控及Android手机红外遥控器开发_第4张图片
用户码是0X08E6,按键2对应的码值是0X41,码值都是十六进制表示的,转化为二进制,用户码高八位08为00001000,用户码低八位E6为11100110,数据码41为01000001,数据反码为10111110。

由于手机与遥控器的信号编码有区别,需要逆序编码。逆序编码后,用户码高八位为00010000,用户码低八位为01100111,数据码为10000010,数据反码为01111101。

编码转换完成,但是transmit方法,参数要传递整型数组形式的信号,并不是二进制数而是电平信号数据。电平是电路中某一点电压的高低状态,在数字电路中常用高电平表示“1”,用低电平表示“0”。根据上文第四部分图三可以看出,遥控器发射红外信号之时,通过“560us低电平+1690us高电平”代表“1”,通过“560us低电平+565us低电平”代表“0”。于是编写Android代码的时候,使用“560,1690”表示二进制的1,使用“560,565”表示二进制的0,具体数组值如下所示:

int[] pattern = {9000,4500, // 开头两个数字表示引导码
	    // 下面两行表示用户码
	    560,565, 560,565, 560,565, 560,1690, 560,565, 560,565, 560,565, 560,565,
	    560,565, 560,1690, 560,1690, 560,565, 560,565, 560,1690, 560,1690, 560,1690,
	    // 下面一行表示数据码
	    560,1690, 560,565, 560,565, 560,565, 560,565, 560,565, 560,1690, 560,565,
	    // 下面一行表示数据反码
	    560,565, 560,1690, 560,1690, 560,1690, 560,1690, 560,1690, 560,565, 560,1690,
	    560,20000}; // 末尾两个数字表示结束码

具体这些转换工作用代码写出,如下:

	import java.util.ArrayList;
	import java.util.List;
	
	/**
	 * Copyright  : huangr
	 * Created by huangr on 2019/8/8.
	 * ClassName  : NecPattern
	 * Description  : 构造NEC协议的pattern编码
	 */
	
	public class NecPattern {
	    //引导码
	    private static final int startH = 9000;
	    private static final int startL = 4500;
	
	    //结束码
	    private static final int endL = 560;
	    private static final int endH = 2000;
	
	    //高电平
	    private static final int high8 = 560;
	    //低电平0:1125
	    private static final int low0 = 565;
	    //低电平1:2250
	    private static final int low1 = 1690;
	
	    private static int[] pattern;
	    private static List<Integer> list = new ArrayList<>();
	
	
	    /**
	     * 正常发码:引导码(9ms+4.5ms)+用户编码(高八位)+用户编码(低八位)+键数据码+键数据反码+结束码
	     */
	    public static int[] buildPattern(int userCodeH, int userCodeL, int keyCode) {
	        //用户编码高八位00
	        String userH = constructBinaryCode(userCodeH);
	        //用户编码低八位DF
	        String userL = constructBinaryCode(userCodeL);
	        //数字码
	        String key = constructBinaryCode(keyCode);
	        //数字反码
	        String keyReverse = constructBinaryCode(~keyCode);
	
	        list.clear();
	        //引导码
	        list.add(startH);
	        list.add(startL);
	        //用户编码
	        changeAdd(userH);
	        changeAdd(userL);
	        //键数据码
	        changeAdd(key);
	        //键数据反码
	        changeAdd(keyReverse);
	        //结束码
	        list.add(endL);
	        list.add(endH);
	
	        int size = list.size();
	        pattern = new int[size];
	        for (int i = 0; i < size; i++) {
	            pattern[i] = list.get(i);
	        }
	        return pattern;
	    }
	
	    /**
	     * 十六进制键值转化为二进制串,并逆转编码
	     * @param keyCode
	     * @return
	     */
	    private static String constructBinaryCode(int keyCode) {
	        String binaryStr = convertToBinary(keyCode);
	        char[] chars = binaryStr.toCharArray();
	        StringBuffer sb = new StringBuffer();
	        for (int i = 7; i >= 4; i--) {
	            sb.append(chars[i]);
	        }
	
	        for (int i = 3; i >= 0; i--) {
	            sb.append(chars[i]);
	        }
	        return sb.toString();
	    }
	
	    /**
	     * 数字转换为长度为8位的二进制字符串
	     * @return
	     */
	    private static String convertToBinary(int num) {
	        String binary = Integer.toBinaryString(num);
	        StringBuffer sb8 = new StringBuffer();
	        //每个元素长度为8位,不够前面补充0
	        if (binary.length() < 8) {
	            for (int i = 0; i < 8 - binary.length(); i++) {
	                sb8.append("0");
	            }
	            String binaryStr8 = sb8.append(binary).toString();
	            return binaryStr8;
	        }else{
	            String binaryStr8 = binary.substring(binary.length() - 8);
	            return binaryStr8;
	        }
	    }
	
	    /**
	     * 二进制转成电平
	     *
	     * @param code
	     */
	    public static void changeAdd(String code) {
	        int len = code.length();
	        String part;
	        for (int i = 0; i < len; i++) {
	            list.add(high8);
	            part = code.substring(i, i + 1);
	            if (part.equals("0"))
	                list.add(low0);
	            else
	                list.add(low1);
	        }
	    }
	}

4、使用

在需要发射红外信号处调用如下:

	/**
     * 按下按键发射红外信号
     */
    public void transmit() {
         ConsumerIrManagerApi.transmit(38000,NecPattern.buildPattern(NecPattern.buildPattern(0X08,0XE6, 0X41));
    }

你可能感兴趣的:(Android开发)