
Flipper Zero I2C 调试工具集详解flipperzero-i2ctools 的扫描、嗅探与发送实战指南【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipperflipperzero-i2ctools 是运行在 Flipper Zero 上的 I2C 总线调试工具集合包含 Scanner地址扫描、Sniffer总线嗅探与 Sender命令发送三大功能可帮助开发者在没有独立逻辑分析仪的情况下快速定位 I2C 外设地址、窃听总线通信并主动读写寄存器。阅读本文后你将掌握该工具的接线方式、三个子工具的完整操作流程、底层实现原理GPIO 中断采样与furi_hal_i2c调用链以及当前版本的功能边界与后续规划。本工具位于当前仓库的 flipperzero-i2ctools-main 目录README 中官方提供如下预览项目概览三个工具一套总线根据 README.md本项目是一组面向 Flipper Zero 的 I2C 工具Set of i2c tools for Flipper Zero核心功能分为三块工具作用Scanner扫描总线上所有 I2C 外设的 7 位地址Look for i2c peripherals addressesSniffer监听并记录总线上的 I2C 通信流量Spy i2c trafficSender向指定 I2C 外设发送命令并读取返回结果Send command to i2c peripherals and read result从工程形态看这是一个标准的 Flipper 外部应用FAP。其 application.fam 文件给出了构建所需的全部元信息应用标识i2cTools、入口函数i2ctools_app、依赖gui模块、栈大小 2KB、应用分类为GPIO并指定了应用图标i2ctools.png与图标资源目录images。这意味着它被打包为 GPIO 类别的.fap独立应用可直接通过固件的应用商店机制分发与安装。接线与硬件准备C0/C1 与 3.3V 电平约束接线是使用本工具的第一步README.md 给出的接法非常明确C0 - SCL C1 - SDA GND - GND其中 C0、C1 是 Flipper Zero 顶部 GPIO 排针上的引脚。源码中对此有更精确的印证i2csniffer.h 中定义了// I2C Pins #define pinSCL gpio_ext_pc0 #define pinSDA gpio_ext_pc1即 SCL 对应gpio_ext_pc0PC0 引脚SDA 对应gpio_ext_pc1PC1 引脚与 README 的接线说明完全一致。电平兼容性是硬性要求README 用醒目字体提示/!\ Target must use 3v3 logic levels. If you not sure use an i2c isolator like ISO1541即目标设备必须使用 3.3V 逻辑电平。如果目标板电平不明确例如工作在 5V 的旧式传感器模块强烈建议在 Flipper Zero 与目标设备之间串接一个 I2C 隔离器官方示例型号为ISO1541以避免电平不匹配损坏引脚或设备。此外I2C 属于开漏总线实际使用时还需依赖目标板上的上拉电阻才能正常通信。构建与安装以 FAP 形式接入固件本项目以外部应用FlipperAppType.EXTERNAL的形式组织构建所需信息全部声明在 application.fam 中appidi2cTools应用唯一标识namei2c Tools界面中显示的应用名称entry_pointi2ctools_app应用入口函数对应 i2ctools.c 中的int32_t i2ctools_app(void* p)requires[gui]依赖 GUI 子系统stack_size2 * 1024应用线程栈大小为 2KBfap_categoryGPIO在应用菜单中归类于 GPIO 分类。由于仓库本身未附带构建脚本使用时需要将其纳入 Flipper Zero 固件的 FAP 构建流程例如放入固件应用的合适目录后随固件工具链一并编译编译产物即为可安装到设备上的.fap文件。工具一Scanner——扫描总线上的外设地址Scanner 的目标是快速回答这条总线上挂了哪些设备的问题。在 i2ctools.c 的事件循环中当处于 SCAN_VIEW 且按下 OK 键时会调用scan_i2c_bus(i2ctools-scanner)触发一次扫描。其底层实现位于 i2cscanner.cvoid scan_i2c_bus(i2cScanner* i2c_scanner) { i2c_scanner-nb_found 0; i2c_scanner-scanned true; // Get the bus furi_hal_i2c_acquire(I2C_BUS); // scan for(uint8_t addr 0x01; addr MAX_I2C_ADDR 1; addr) { // Check for peripherals if(furi_hal_i2c_is_device_ready(I2C_BUS, addr, I2C_TIMEOUT)) { // skip even 8-bit addr if(addr % 2 ! 0) { continue; } // convert addr to 7-bits i2c_scanner-addresses[i2c_scanner-nb_found] addr 1; i2c_scanner-nb_found; } } furi_hal_i2c_release(I2C_BUS); }实现要点可以归纳为总线句柄使用 Flipper 固件提供的furi_hal_i2c_handle_external外部 I2C 总线定义在 i2cscanner.h 中的#define I2C_BUS furi_hal_i2c_handle_external访问前后通过furi_hal_i2c_acquire/furi_hal_i2c_release成对加锁避免与其他外设竞争总线扫描范围7 位地址从0x01到MAX_I2C_ADDR即0x7F对应完整 7 位地址空间设备就绪判定通过furi_hal_i2c_is_device_ready(I2C_BUS, addr, I2C_TIMEOUT)探测超时阈值I2C_TIMEOUT为 3单位毫秒见 i2cscanner.h地址归一化固件探测函数以 8 位地址形式调用因此循环中先跳过奇数addr % 2 ! 0对应 R/W 位再通过addr 1将命中的 8 位地址换算回标准 7 位地址存入addresses[]数组。扫描结果保存在i2cScanner结构体i2cscanner.h中addresses[]存放命中的地址列表nb_found记录数量scanned标记是否已完成过扫描menu_index用于界面分页浏览结果按每屏 3 个地址分页。值得注意的是Scanner 扫描到的地址列表会被后续的 Sender 直接复用见下文形成先扫描、后定点通信的完整工作流。工具二Sniffer——被动监听总线流量Sniffer 是最具逻辑分析仪色彩的工具它不向总线发起任何通信而是通过 GPIO 中断被动采样 SCL/SDA 的电平变化还原总线上的 I2C 帧。启动与停止在 Sniffer 界面按下 OK 键可切换监听状态逻辑见 i2ctools.c未启动时调用start_interrupts(i2ctools-sniffer)已启动时调用stop_interrupts()并将状态复位为I2C_BUS_FREE。退出 Sniffer 视图返回主菜单时同样会强制停止中断采集。中断采样原理start_interrupts与stop_interrupts的实现位于 i2csniffer.cvoid start_interrupts(i2cSniffer* i2c_sniffer) { furi_hal_gpio_init(pinSCL, GpioModeInterruptRise, GpioPullNo, GpioSpeedHigh); furi_hal_gpio_add_int_callback(pinSCL, SCLcallback, i2c_sniffer); // Add Rise and Fall Interrupt on SDA pin furi_hal_gpio_init(pinSDA, GpioModeInterruptRiseFall, GpioPullNo, GpioSpeedHigh); furi_hal_gpio_add_int_callback(pinSDA, SDAcallback, i2c_sniffer); }SCL 引脚注册上升沿中断GpioModeInterruptRise回调SCLcallbackSDA 引脚注册上升下降沿中断GpioModeInterruptRiseFall回调SDAcallback停止时移除回调并将两个引脚复位为模拟模式GpioModeAnalog。SDAcallback负责判定起始/停止条件若 SCL 为低直接返回可能是时钟拉伸 clock stretching 期间的电平变化不构成有效条件若当前处于I2C_BUS_STARTED状态且 SDA 上升判定为STOP 条件总线回到I2C_BUS_FREE若总线空闲且 SDA 下降判定为START 条件进入I2C_BUS_STARTED并递增帧计数frame_index首次检测到的 START 仅用于初始化不计入帧。SCLcallback负责逐位采样数据当总线处于 STARTED 状态时每个 SCL 上升沿采样一次 SDA前 8 个位拼成一个字节存入data[data_idx]左移一位再并入 SDA 电平第 9 个位被解释为 ACK 位ack[data_idx] !SDASDA 为低即 ACK随后data_index并复位位计数继续采集下一个字节。数据容量与界面缓冲区上限定义在 i2csniffer.hMAX_MESSAGE_SIZE 128单帧最多 128 字节MAX_RECORDS 128最多缓存 128 帧帧存满后会自动调用clear_sniffer_buffers清空重录见 i2csniffer.c 的SDAcallback。界面渲染逻辑在 views/sniffer_view.c 中顶部显示当前帧序号Frame: x/y、目标地址取首字节data[0] 1显示为0xXX、读写方向首字节最低位为 0 显示 Write为 1 显示 Read以及首个 ACK 状态下方以两行布局逐字节显示数据每个字节后标注 AACK或 NNACK。未捕获到任何数据时显示 Nothing Recorded界面下方有 Start/Stop 切换按钮。没有捕获到任何数据时显示 Nothing Recorded。工具三Sender——向外设发送命令并读取响应Sender 允许用户向指定地址的外设写入一个字节命令并读取至多 2 字节的返回数据用于主动盘问外设例如读取寄存器值或触发特定指令。地址来源Sender 的地址直接复用 Scanner 的扫描结果。在 i2ctools.c 的初始化阶段有一行关键代码// Share scanner with sender i2ctools-sender-scanner i2ctools-scanner;因此在 Sender 界面中切换目标地址左右键调整address_idx时实际上是依次指向scanner-addresses[]中扫描到的设备。这解释了为什么用 Sender 前最好先跑一次 Scanner没有扫描结果Sender 就没有可选的地址列表。发送实现发送逻辑位于 i2csender.cvoid i2c_send(i2cSender* i2c_sender) { furi_hal_i2c_acquire(I2C_BUS); uint8_t adress i2c_sender-scanner-addresses[i2c_sender-address_idx] 1; i2c_sender-error furi_hal_i2c_trx( I2C_BUS, adress, i2c_sender-value, sizeof(i2c_sender-value), i2c_sender-recv, sizeof(i2c_sender-recv), I2C_TIMEOUT); furi_hal_i2c_release(I2C_BUS); i2c_sender-must_send false; i2c_sender-sended true; }调用链上的关键点目标地址取addresses[address_idx] 1将 7 位地址换算为 8 位写地址使用furi_hal_i2c_trx执行一次写后读事务写入value1 字节随后读取最多 2 字节到recv[]i2csender.h 中uint8_t recv[2]返回值存入error标志供界面提示通信是否成功整个事务同样被furi_hal_i2c_acquire/furi_hal_i2c_release保护。界面操作Sender 的按键逻辑i2ctools.c上/下键增减待发送的字节值value0x00~0xFF长按/连按时每次步进 5左/右键在已扫描到的地址列表中前后切换目标OK 键置位must_send触发发送。每调整一次值或地址sended都会被复位为false界面据此刷新已发送/未发送状态确保用户能区分当前显示的是上次结果还是待发送的新值。当前版本仅支持单字节发送与 2 字节回读README 的 TODO 中也明确列出Sender 模式读取超过 2 字节待实现。源码架构纵览事件驱动主循环与视图分离除了三个功能模块理解 i2ctools.c 中的主框架对二次开发很有帮助消息队列驱动应用创建FuriMessageQueue容量 8元素为InputEvent输入回调i2ctools_input_callback将按键事件压入队列主循环furi_message_queue_get阻塞取事件并分发处理互斥锁保护共享数据i2cTools结构体i2ctools_i.h通过ValueMutex保护绘制回调i2ctools_draw_callback在访问数据前先acquire_mutex渲染完成后释放视图状态机main_view-current_view在MAIN_VIEW / SCAN_VIEW / SNIFF_VIEW / SEND_VIEW四态间切换绘制回调根据当前视图分别调用draw_main_view、draw_scanner_view、draw_sniffer_view、draw_sender_view各视图实现位于 views 目录全屏直绘应用注册GuiLayerFullscreen层的 ViewPort自绘 128×64 屏幕内容不依赖标准 GUI 菜单组件返回键语义主菜单按返回退出应用在任意子视图按返回则回到主菜单若当前在 Sniffer 视图还会顺带stop_interrupts()并复位状态防止中断泄漏。这种事件队列 互斥锁 多视图分发的结构与 Flipper 官方应用的典型模式一致值得作为参考模板。当前限制与后续规划README.md 末尾明确列出了 TODO 清单结合源码可以梳理出当前版本的已知边界Sender 仅支持读写少量字节发送固定 1 字节、回读最多 2 字节recv[2]不支持超过 2 字节的批量读取TODO 第 1 条仅支持 7 位地址扫描与发送均未实现 10 位地址支持TODO 第 2 条i2cscanner.h 的MAX_I2C_ADDR 0x7F即 7 位地址上限高速率尚未验证TODO 第 3 条明确测试 100kHz 的速率意味着超过标准 100kHz 模式的时钟频率400kHz 快速模式等尚未经过验证使用时建议保持常规速率无数据持久化嗅探记录不支持保存到 SD 卡也不支持从文件回放TODO 第 4、5 条缓冲区硬上限单帧 128 字节、最多 128 帧MAX_MESSAGE_SIZE/MAX_RECORDSTODO 中计划移除最大数据大小与移除最大帧读取大小硬件与体验类规划还包括 KiCad 模块便于自制转接板、UI 改进、事件管理代码重构、补充文档等TODO 第 6~8 条。延伸阅读若想深入源码细节可重点关注以下文件README.md官方说明接线、工具简介、TODOi2ctools.c应用入口、事件循环与全部按键逻辑i2ctools_i.h顶层数据结构i2cscanner.c / i2cscanner.h地址扫描实现与总线定义i2csniffer.c / i2csniffer.hGPIO 中断采样与帧缓冲i2csender.c / i2csender.h写后读事务实现views四个视图的绘制逻辑application.famFAP 构建元数据。顺带一提本仓库还收录了同项目的另一份副本 kyhwana/flipperzero-i2ctools-main内容与 grnch 版本同源可作为交叉对照的参考。动手之前请务必再次确认目标板电平为 3.3V 且共地连接正确这是安全可靠使用本工具的前提。【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考