Android 红外功能实现与红外协议开发笔记:ConsumerIrManager、NEC、PWM、曼彻斯特编码

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

红外协议描述中通常使用 MarkSpaceMark = 发射载波Space = 停止发射载波

例如:

css 复制代码
9000μs Mark + 4500μs Space + 560μs Mark + 560μs Space

对应 Android:

ini 复制代码
int[] pattern = {
    9000, 4500,
    560, 560
};

Android 的 patternMark + 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"。

相关推荐
mmsx2 小时前
MapLibre 统一图层模型:sealed class + 声明式图层 + XML 持久化
android·源码·地图·maplibre
YUJIANYUE2 小时前
查立得万用查分安卓版(web环境+查询系统免安装单文件一键运行包)
android·前端
天空之城--3 小时前
Android Launcher 性能优化完全指南:从启动到滑动,构建极致流畅的桌面体验
android·性能优化
hunterandroid3 小时前
Android ANR 排查实战:从线上告警到主线程卡点定位
android·前端
TimeFine3 小时前
智能眼镜开发:图片翻译与EXIF的重要性
android
律宏阔3 小时前
Android 车载蓝牙开发笔记:HFP、A2DP、AVRCP、PBAP、MAP、BLE 与系统 API
android·蓝牙
@Adzc3 小时前
Android 16 省电模式 Tile 点击导致状态栏收起
android
杉氧4 小时前
打破边界(二):在 React Native 中嵌入原生 UI 组件 (Native UI Components)
android·前端·react native
古法安卓4 小时前
Android-切换白天黑夜内存不足问题排查
android·java·android studio