TagTinker 红外 ESL 引擎深度解析:PP4 微秒时序 + RLE 流式编码如何驱动电子价签
【免费下载链接】TagTinkerFlipper Zero app for ESL research using IR. All based on https://www.furrtek.org/?a=esl项目地址: https://gitcode.com/gh_mirrors/ta/TagTinker
TagTinker 是一款运行在 Flipper Zero 上的红外 ESL(电子货架标签)研究应用,其核心是一套RLE 流式红外引擎:通过 PP4 微秒级时序调制红外载波,把 RLE 压缩后的位图逐帧流式发送,让价签显示文本、甚至红黄双色(Accent)图片。本文将从时序到编码,带你完整看懂这套引擎的运转原理。
上图正是 TagTinker 的输出效果:黑色曲线 + 红色填充面积图,红色部分就是由"双平面 Accent 色编码"实现的。
一、为什么红外 ESL 协议值得研究
超市货架上的电子价签大多靠红外(IR)信号更新——和电视遥控器同一类信号。Flipper Zero 官方也点明过:
"这就是纯红外信号,和你家遥控器一样。整个安全性依赖于协议的保密性。"
TagTinker 就是基于对这类协议逆向研究的工具:它用 Flipper 的 IR LED 复现价签基站的下行信号,支持文本与自定义位图写入,兼容单色与红/黄 Accent 色图形价签。整个协议栈分三层,各有所分工:
| 层次 | 文件 | 职责 |
|---|---|---|
| 协议层 | tagtinker_proto.c | 组帧、RLE 压缩、像素流编码 |
| 驱动层 | tagtinker_ir.c | PP4 微秒时序、载波调制、重复发送 |
| 场景层 | scenes/ | 目标管理、图像选择、传输界面 |
二、PP4 时序引擎:把字节"雕刻"进微秒里
2.1 载波与符号:2 比特一个符号
红外传输的第一步是用 PWM 生成约 38 kHz 的载波。驱动层用 STM32 的 TIM1 CH3N 通道驱动 IR LED,预计算好自动重装载与比较值(ARR=50、CCR=25),开启即载波、关闭即停,见 tagtinker_ir.c 中的CARRIER_ARR/CARRIER_CCR与carrier_on()/carrier_off()。
PP4 编码的关键在于:每个符号只传 2 比特,符号值不靠电平,而靠"突发后的间隙长度"来表达。也就是说,红外灯先亮一段固定长度的突发,再熄灭一段可变长度的间隙——间隙长短就是符号本身。四个符号对应的间隙由一张预计算表给出(64 MHz 下的 CPU 周期数),见 tagtinker_ir.c:
#define PP4_BURST_CYCLES 2581 // 突发 ≈ 40 µs static const uint32_t pp4_gap_cycles[4] = { 3871, /* symbol 0 ~60 us */ 15483, /* symbol 3 ~242 us */ 7741, /* symbol 2 ~121 us */ 11612, /* symbol 1 ~181 us */ };2.2 微秒级发送循环:DWT 计数器当秒表
要精确到微秒,普通delay不够用。TagTinker 直接读 Cortex-M 的DWT->CYCCNT 硬件周期计数器做忙等延时(delay_cycles,tagtinker_ir.c),再用临界区把整个帧的发送包起来,防止系统调用打断微秒级时序:
FURI_CRITICAL_ENTER(); send_frame_pp4(data, len); // 每字节拆成 4 个 2-bit 符号,突发+间隙交替 FURI_CRITICAL_EXIT();发送节奏是:取 2 比特符号 → 载波突发 ≈40 µs → 关断并按符号值插入 60~242 µs 间隙,整帧结束前再补一次收尾突发,完整逻辑在 send_frame_pp4()。
2.3 重复与让出的平衡
价签射频链路不可靠,所以帧要连发多遍。tagtinker_ir_transmit()支持指定重复次数与帧间延迟(以 500 µs 为单位),每发满 5 遍就让出一次 OS(furi_delay_ms(1)),避免看门狗饿死——这个细节在 tagtinker_ir.c 中体现得很清楚。
三、RLE 流式压缩:像素不落地,边读边压
3.1 位级 RLE:颜色 1 比特 + 游程变长编码
Flipper 内存极小,传统做法(先把整幅图像压缩完再发)会撑爆 RAM。TagTinker 的解法是流式:图像不整体驻留,而是通过一个"逐像素回调"边取边压。
压缩格式是位级 RLE:
- 先写入 1 个像素颜色位(0/1);
- 紧接着写入一段游程长度,采用变长编码:占
2×bitlen(count) - 1个比特; - 颜色变化则重复 1-2,直到所有像素走完。
核心打包函数 tagtinker_pack_fn_rle() 只有 12 行,但配合预扫描函数 tagtinker_rle_fn_bit_length() 先算出压缩后总长度,即可判断"压了到底划不划算"。
3.2 像素流:双平面也能流式读取
TagTinkerPixelAtFn这个函数指针是整个流式设计的灵魂(定义于 tagtinker_proto.h):RLE 编码器只认识"第 i 个像素是什么",完全不知道图像从哪来——BMP 文件、文字渲染、双平面色卡对它都是同一种东西。
编码入口tagtinker_encode_fn_payload()在 Auto 模式下自动比较:RLE 结果更短才用压缩(comp_type=2),否则退回原始位流(comp_type=0),并且总长会向上补齐到帧容量整数倍,见 tagtinker_proto.c。
四、红黄双平面 Accent 色编码原理
单色价签只需 1 个位平面;而红/黄 Accent 标签要表达"黑前景 + 彩色前景"两种墨迹。TagTinker 用双平面实现(tagtinker_encode_planes_payload,tagtinker_proto.c):
- 平面 1(primary):普通黑色前景位图;
- 平面 2(secondary):Accent 色前景位图,两个平面首尾相接组成一条像素流,
plane_pixel_at()按下标自动切换到对应平面(tagtinker_proto.c); - 标签收到后,把两平面叠加渲染:平面 1 点亮为黑色,平面 2 点亮为红/黄——于是"红色面积图 + 黑色曲线"这类双色画面就出来了(文章开头那张价签照片)。
对纯黑图tagtinker_encode_image_payload()还会传入color_clear参数补一个全亮的第二平面,保证协议格式统一,见 tagtinker_proto.c。
五、完整传输链路:从一张 BMP 到价签刷屏
把所有环节串起来,一次图片写入的 IR 序列是:
唤醒帧(wake) → 参数帧(param) → 数据帧×N(data 0..N) → 刷新帧(refresh)- 唤醒帧:命令 0x17 + 22 个 0x01 尾字节,把休眠的标签叫醒(tagtinker_make_wake_frame());
- 参数帧:声明字节数、压缩类型、页码、宽高与贴图坐标(tagtinker_make_image_param_frame());
- 数据帧:每帧固定 20 字节图像数据(
TAGTINKER_IMAGE_DATA_BYTES_PER_FRAME),流式 RLE 比特流正好按这个粒度切分逐帧发送; - 刷新帧:命令 0x01,通知 MCU 把缓冲内容刷上屏(tagtinker_make_refresh_frame());
- 每一帧末尾都追加CRC16(初值 0x8408,tagtinker_crc16())保证完整性。
最终由 tagtinker_build_image_sequence() 组装整条序列,交给第二节的 PP4 引擎发射出去。
上图两块 Pricer 标签均由 Flipper Zero 红外驱动:上方为双平面红黑人像图,下方为纯黑底白字文本。
六、上手体验与延伸阅读
想亲手试试?把 web-image-prep/ 中的浏览器端图像预处理器打开,选中标签型号、丢入图片、导出 1-bit 或双平面 BMP,放进 Flipper SD 卡的apps_data/tagtinker/dropped/,然后在Targeted Payloads → <标签> → Set Image中发送即可。
关键模块速查📌
- 红外驱动(PP4 时序):ir/tagtinker_ir.c
- 协议与 RLE 流式编码:protocol/tagtinker_proto.c
- 图像选项场景:scenes/tagtinker_scene_image_options.c
- 传输界面:scenes/tagtinker_scene_transmit.c
- 浏览器端图像预处理:web-image-prep/index.html
⚠️ 安全提示:只测试你自己拥有或被授权测试的标签。很多 ESL 标签把固件和显示数据存在易失性 RAM 里,断电就可能"变砖",且可能无法恢复。本项目仅限教育研究与数字艺术展示用途,严禁用于篡改商用价签。
【免费下载链接】TagTinkerFlipper Zero app for ESL research using IR. All based on https://www.furrtek.org/?a=esl项目地址: https://gitcode.com/gh_mirrors/ta/TagTinker
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考