Android 红外功能实现与红外协议开发笔记:ConsumerIrManager、NEC、PWM、曼彻斯特编码
Android 从 API 19 开始提供 ConsumerIrManager,带有消费级红外发射器的设备可以直接通过系统 API 发送红外信号。Android 红外功能本身并不负责识别家电型号或解析遥控协议,核心能力可以概括为:载波频率 + 红外脉冲序列 → ConsumerIrManager → 红外发射器。
ini
consumerIrManager.transmit(carrierFrequency, pattern);
实际开发涉及的完整链路是:设备类型 + 厂商型号 + 红外协议 + 命令数据 + 协议编码 + Mark/Space 波形 + ConsumerIrManager + 红外发射。
一、红外信号基本结构
红外遥控并不是简单地点亮一次红外 LED,而是通过载波、脉冲和协议共同传递数据。完整过程可以记录为:遥控命令 → 协议编码 → Mark/Space 时序 → 载波调制 → 红外 LED → 家电接收头 → 协议解码。
常见消费级红外载波在几十 kHz 范围内,NEC 常见载波为 38kHz。
ini
int carrierFrequency = 38000;
这里的 38000 单位为 Hz。载波负责红外 LED 在 Mark 阶段的高速闪烁,协议负责控制 Mark 和 Space 持续多长时间。
二、Mark、Space 与 Android pattern
红外协议描述中通常使用 Mark 和 Space。Mark = 发射载波,Space = 停止发射载波。
例如:
css
9000μs Mark + 4500μs Space + 560μs Mark + 560μs Space
对应 Android:
ini
int[] pattern = {
9000, 4500,
560, 560
};
Android 的 pattern 按 Mark + Space + Mark + Space... 交替排列,单位为微秒 μs。因此 9000, 4500, 560, 560 表示:发射 9000μs + 停止 4500μs + 发射 560μs + 停止 560μs。
需要注意"高电平、低电平"的说法依赖波形观察位置以及接收头是否反相。协议分析时使用 Mark / Space 更准确。下面为了方便观察波形,在约定 Mark = 高、Space = 低 的情况下描述高低电平。
三、常见红外编码方式
红外遥控中经常遇到的编码方式包括:脉冲距离编码 PDM + 脉宽编码 PWM + 曼彻斯特编码 Manchester/Biphase。
NEC:脉冲距离编码
NEC 使用 Pulse Distance Modulation,也就是脉冲距离编码。每个数据位的 Mark 长度基本固定,区别主要体现在后面的 Space 时间。
css
逻辑 0:560μs Mark + 560μs Space
逻辑 1:560μs Mark + 1690μs Space
在约定 Mark = 高电平 的逻辑波形中可以记录为:NEC 无论发送 0 还是 1,高电平持续时间基本固定,区别在于后面的低电平时间长短。
因此在逻辑分析仪或已经解调后的波形中,如果看到:一串宽度基本一致的短脉冲 + 脉冲之间的间隔一短一长,很符合 NEC/PDM 一类协议的特征。
NEC 的逻辑 0 总时长约 1.12ms,逻辑 1 总时长约 2.25ms,常见载波为 38kHz。
PWM:脉宽编码
PWM 即 Pulse Width Modulation,数据通过脉冲本身的宽度变化表达。
在约定 有效脉冲 = 高电平 的波形中,可以记录为:低电平间隔时间固定,区别在于高电平持续时间长短。
波形看起来更像:不同宽度的实心方块 + 基本固定的间隔。
三种编码观察特征可以先记录为:NEC/PDM:Mark 固定 + Space 变化,PWM:Space 固定 + Mark 变化。
曼彻斯特编码
曼彻斯特编码不依赖简单的"长脉冲"和"短脉冲"表达 0、1,而是把每一个 bit 划分成两个半 bit,并要求 bit 中间发生一次电平翻转。
可以记录为:一个 Bit → 前半周期 + 后半周期 → 中间必须发生一次翻转。
逻辑 0 和逻辑 1 通过翻转方向区分。究竟是 高→低 = 0 还是 低→高 = 0 取决于协议定义。
RC5 就属于典型的 Manchester/Biphase 编码协议。
波形特征可以记录为:Bit 周期固定 + 每个 bit 中间都有翻转 + 连续 bit 边界可能再次发生翻转。
三类编码可以压缩为:NEC/PDM:Mark 固定 + Space 变化,PWM:Space 固定 + Mark 变化,Manchester:Bit 周期固定 + Bit 中点必须翻转。
四、NEC 标准协议
NEC 是消费级红外遥控中很常见的一类协议,大量遥控器以及厂商衍生协议可以看到 NEC/PDM 的结构特征,但不能只根据 TCL、创维、海尔、美的等品牌直接断定某个具体型号一定使用标准 NEC。
标准 NEC 的核心参数可以记录为:38kHz 载波 + 9ms 引导 Mark + 4.5ms 引导 Space + 32bit 数据 + 560μs 结束 Mark。
完整标准帧:
css
9ms Mark + 4.5ms Space + Address 8bit + ~Address 8bit + Command 8bit + ~Command 8bit + 560μs Mark
也可以写成:引导码 + 地址码 + 地址反码 + 命令码 + 命令反码 + 结束脉冲。
其中真正的数据部分是:Address 8bit + ~Address 8bit + Command 8bit + ~Command 8bit = 32bit。
五、标准 NEC 数据包结构
以:
0x00FF02FD
为例,32 位数据可以拆成:
00 FF 02 FD
完整 NEC 帧可以表示为:
css
9000μs Mark + 4500μs Space + 0x00 + 0xFF + 0x02 + 0xFD + 560μs Mark
1. 引导码
9000μs Mark + 4500μs Space,用于标识一帧数据开始,同时给接收端留出增益调整和准备接收数据的时间。
2. 地址码
0x00
8 位地址,也经常叫 Address / Customer Code,用于区分设备、系统或者一组遥控目标。
0x00 本身不能固定解释成电视机,不同厂家和设备可以使用不同地址。
3. 地址反码
0xFF
标准 NEC 会将地址取反后再次发送:
0x00 → 0xFF
接收端可以检查:
ini
address ^ addressInverse == 0xFF
如果结果不满足,可以认为数据发生错误。
4. 命令码
0x02
8 位命令数据,对应当前遥控器定义的具体动作,例如电源、音量、频道等。
0x02 具体代表什么由设备协议决定,不能脱离对应设备码表解释。
5. 命令反码
0xFD
命令同样发送反码:
0x02 → 0xFD
可以检查:
ini
command ^ commandInverse == 0xFF
因此:
ini
00 ^ FF = FF
02 ^ FD = FF
标准 NEC 通过:原始数据 + 反码 的方式提供基础校验。
六、NEC Bit 顺序
NEC 每个字节采用 LSB First,也就是最低位优先发送。
例如:
ini
0x02 = 00000010
通常书写顺序:
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
0 0 0 0 0 0 1 0
实际发送顺序:
bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7
0 1 0 0 0 0 0 0
Java 编码时直接从最低位开始读取:
arduino
for (int bit = 0; bit < 8; bit++) {
boolean one = (value & (1 << bit)) != 0;
}
七、NEC 数据转换成 Android pattern
NEC 基本时序:
arduino
private static final int HEADER_MARK = 9000;
private static final int HEADER_SPACE = 4500;
private static final int BIT_MARK = 560;
private static final int ZERO_SPACE = 560;
private static final int ONE_SPACE = 1690;
private static final int END_MARK = 560;
完整转换代码:
ini
int address = 0x00;
int command = 0x02;
int[] data = {
address,
address ^ 0xFF,
command,
command ^ 0xFF
};
List<Integer> values = new ArrayList<>();
values.add(9000);
values.add(4500);
for (int value : data) {
for (int bit = 0; bit < 8; bit++) {
boolean one = (value & (1 << bit)) != 0;
values.add(560);
values.add(one ? 1690 : 560);
}
}
values.add(560);
int[] pattern = new int[values.size()];
for (int i = 0; i < values.size(); i++) {
pattern[i] = values.get(i);
}
生成的数据结构为:9000,4500 + 32 × (560,560/1690) + 560。
八、NEC 重复码
NEC 在按键持续按住时,可以使用 Repeat Code,而不是反复发送完整 32bit 数据。
标准重复码:
css
9000μs Mark + 2250μs Space + 560μs Mark
Android 波形:
ini
int[] repeatPattern = {
9000,
2250,
560
};
长按音量、频道等按键时,需要同时考虑:首次完整帧 + Repeat Code + 重复间隔。
九、Android 红外权限
AndroidManifest.xml:
ini
<uses-permission android:name="android.permission.TRANSMIT_IR" />
<uses-feature
android:name="android.hardware.consumerir"
android:required="false" />
TRANSMIT_IR 属于普通权限,不需要运行时弹窗申请。
android.hardware.consumerir 设置为 required="false" 后,没有红外发射器的设备仍然可以安装应用。
十、检测红外硬件
ini
boolean hasFeature = getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CONSUMER_IR
);
ConsumerIrManager consumerIrManager =
(ConsumerIrManager) getSystemService(
Context.CONSUMER_IR_SERVICE
);
boolean supported =
hasFeature
&& consumerIrManager != null
&& consumerIrManager.hasIrEmitter();
设备检测可以保持:FEATURE_CONSUMER_IR + ConsumerIrManager + hasIrEmitter()。
十一、直接发送红外
已有 pattern 后直接调用:
ini
ConsumerIrManager consumerIrManager =
(ConsumerIrManager) getSystemService(
Context.CONSUMER_IR_SERVICE
);
int carrierFrequency = 38000;
int[] pattern = {
9000, 4500,
560, 560,
560, 1690,
560, 560
};
if (consumerIrManager != null && consumerIrManager.hasIrEmitter()) {
consumerIrManager.transmit(
carrierFrequency,
pattern
);
}
NEC 数据完整链路为:Address + Command → NEC 编码 → Mark/Space pattern → 38kHz → ConsumerIrManager.transmit()。
十二、载波范围
Android 可以读取当前设备红外硬件支持的载波范围:
scss
ConsumerIrManager consumerIrManager =
(ConsumerIrManager) getSystemService(
Context.CONSUMER_IR_SERVICE
);
ConsumerIrManager.CarrierFrequencyRange[] ranges =
consumerIrManager.getCarrierFrequencies();
if (ranges != null) {
for (ConsumerIrManager.CarrierFrequencyRange range : ranges) {
Log.d(
"IR",
range.getMinFrequency()
+ "Hz ~ "
+ range.getMaxFrequency()
+ "Hz"
);
}
}
码库至少需要保存:协议 + 载波频率 + 数据或波形。
十三、红外数据类型
实际收集码库时,经常会遇到:协议地址/命令 + HEX 数据 + Raw Timings + Pronto Hex + 载波周期计数。
Android ConsumerIrManager.transmit() 最终需要的是:
ini
int carrierFrequency;
int[] pattern;
例如:
ini
int carrierFrequency = 38000;
int[] pattern = {
9000, 4500,
560, 560,
560, 1690,
560, 560
};
数据处理链路可以记录为:原始红外数据 → 协议识别 → 格式转换 → Mark/Space → Android pattern。
十四、空调状态编码
电视、机顶盒一类遥控器经常使用离散命令:电源 + 音量+ + 音量- + 频道+ + 频道-。
空调协议中大量设备使用完整状态帧,调整温度时可能重新发送:开关状态 + 工作模式 + 温度 + 风速 + 扫风 + 定时 + 其他状态。
因此空调红外开发的重点不只是按键码,而是:当前状态 → 厂商协议编码 → 完整状态帧 → Mark/Space → ConsumerIrManager。
例如当前状态可以是:
开机 + 制冷 + 26℃ + 自动风 + 扫风关闭
修改为 27℃ 后,实际发送的可能是:
开机 + 制冷 + 27℃ + 自动风 + 扫风关闭
而不是单独发送一条"温度 +1"。