Flipper Zero 1-Wire 温度计示例解析:在 Unleashed 固件中用 DS18B20 构建完整应用
【免费下载链接】unleashed-firmwareFlipper Zero Unleashed Firmware项目地址: https://gitcode.com/GitHub_Trending/un/unleashed-firmware
本篇技术指南以 Unleashed 固件仓库中的 example_thermo 示例应用 为骨架,完整讲解如何将 DS18B20 数字温度传感器通过 1-Wire 总线连接到 Flipper Zero 的外部 GPIO,并跑通一个集温度采集、屏幕绘制、按键响应、后台线程与本地化显示于一体的示例程序。读完本文,你将掌握 1-Wire 主机库的初始化与通信流程、DS18B20 温区寄存器解析方法、通过fbt编译启动 FAP 的完整命令,以及如何更换数据引脚并理解外置上拉电阻的硬件前提。
示例应用概览与源码入口
example_thermo是一个 FlipperAppType.EXTERNAL 类型的示例应用(参见 application.fam),应用标识为example_thermo,入口函数为example_thermo_main,需要依赖gui服务,栈大小为 1KB,属于Examples分类。它的核心作用有两个层面:
- 演示 DS18B20 的 1-Wire 驱动用法:包括复位、写命令、读暂存器(Scratchpad)、CRC 校验与温度值换算;
- 演示完整应用的四个基本功:GUI 绘制(ViewPort + Canvas)、输入事件处理(消息队列)、线程(FuriThread 标志量协作)、本地化(locale 公制/英制单位切换)。
主程序源码位于 example_thermo.c,执行流程为:example_thermo_main→ 分配上下文example_thermo_context_alloc→ 运行主循环example_thermo_run→ 释放资源example_thermo_context_free。这种「分配上下文 → 运行循环 → 释放资源」三段式结构是 Flipper 应用的标准范式。
电气连接:DS18B20 与 Flipper 外部 GPIO
在启动应用之前,需要按下表将传感器与 Flipper 的 GPIO 排针相连(对应引脚位置见 furi_hal_resources.h 中定义的引脚映射):
| DS18B20 | Flipper 引脚 |
|---|---|
| VDD | 9 |
| GND | 18 |
| DQ | 17 |
两条重要注意事项:
- NOTE 1:GND 也可以使用排针上的 8 号或 11 号引脚,三者电气等价;
- NOTE 2:默认引脚 17(即
gpio_ibutton,映射到 iButton 端口,见 furi_hal_resources.c)内部已带有上拉能力;一旦改用其他引脚,必须在 DQ 与 VDD(9 号脚)之间外接一个 4.7kΩ 上拉电阻。
从源码层面看,这一约束与 1-Wire 总线的电气特性直接相关:总线空闲时必须被上拉至高电平,时序操作依赖主机拉低与释放的组合。在 one_wire_host_start 中,引脚被初始化为GpioModeOutputOpenDrain(开漏输出),开漏结构本身不具备拉高能力,因此外部上拉电阻是总线正确工作的物理前提。
启动应用:fbt 命令行全流程
启动该演示应用需要依次完成以下步骤:
- 确保 Flipper 中已插入 SD 卡(示例以 FAP 形式运行,需要 SD 卡存放应用文件);
- 用 USB 数据线将 Flipper 连接到电脑;
- 在终端中运行编译并启动命令:
./fbt launch APPSRC=example_thermofbt是 Unleashed 固件的统一构建工具(位于仓库根目录,脚本入口 fbt),launch子命令会完成 FAP 的交叉编译、通过 USB 部署到设备并从 CLI 侧启动该应用。若需要先构建而不启动,可使用./fbt fap_example_thermo之类目标;开发调试的更多用法可参考 fbt.md 与 AppManifests.md。运行后屏幕会显示Thermometer Demo标题与「Connect thermometer to GPIO pin 17」提示,温度读数实时刷新,按 Back 键退出。
更换数据引脚:THERMO_GPIO_PIN 宏详解
示例允许通过修改宏来更换 1-Wire 数据引脚,可选值如下(源码注释与 furi_hal_resources.h 中均可查证):
/* Possible GPIO pin choices: - gpio_ext_pc0 - gpio_ext_pc1 - gpio_ext_pc3 - gpio_ext_pb2 - gpio_ext_pb3 - gpio_ext_pa4 - gpio_ext_pa6 - gpio_ext_pa7 - gpio_ibutton */ #define THERMO_GPIO_PIN (gpio_ibutton)修改要点:
- 这些符号均为
const GpioPin类型的外部引脚描述符,定义于 furi_hal_resources.c,例如gpio_ext_pa6对应GPIOA端口的LL_GPIO_PIN_6; - 除
gpio_ibutton外,其余引脚没有内置上拉电阻,更换后必须外接 4.7kΩ 上拉电阻到 9 号脚,否则总线无法工作; - 修改后需重新编译并启动应用,屏幕上的提示文字会自动更新为对应的引脚号——这是通过
furi_hal_resources_get_ext_pin_number(&THERMO_GPIO_PIN)动态获取引脚编号实现的(见 example_thermo.c)。
值得注意的是,部分可选引脚在系统其他功能中被复用,例如gpio_ext_pa6/gpio_ext_pa7同时被调试输出与 PWM 使用(参见 furi_hal_os.c 与 furi_hal_pwm.c),选择引脚时需避免与已启用的外设冲突。
1-Wire 主机库:底层实现与关键 API
示例依赖的one_wire库位于 lib/one_wire,由主机端(one_wire_host.h)、从机端(one_wire_slave)与 Maxim CRC(maxim_crc.h)组成,构建配置见 SConscript。
时序参数:软件模拟 1-Wire 协议
one_wire_host通过 GPIO 位操作软件模拟 1-Wire 时序,没有依赖硬件外设。在 one_wire_host.c 中定义了三种时序表(基于 Maxim Application Note 126):
- normal(标准模式,默认):写 1 低电平 9µs/高电平 64µs,写 0 低电平 64µs,复位脉冲 480µs,存在检测采样 70µs 后延迟 410µs;
- overdrive(超速模式):所有时序缩短到约 1/8,复位脉冲仅 70µs;
- tm01x(面向 TM01x 器件):复位脉冲 740µs,存在检测 140µs。
可通过onewire_host_set_overdrive/onewire_host_set_timings_default/onewire_host_set_timings_tm01x切换。示例使用默认时序,即可满足 DS18B20 标准速率通信。
常用 API 与调用关系
| API | 作用 | 底层实现要点 |
|---|---|---|
onewire_host_alloc(gpio_pin) | 按引脚分配主机实例,初始化搜索状态 | 校验指针、置默认时序,见 one_wire_host.c |
onewire_host_start | 初始化引脚为开漏输出 | GpioModeOutputOpenDrain,见 one_wire_host.c |
onewire_host_reset | 总线复位并检测存在脉冲 | 拉低 480µs 后释放,采样返回是否有设备应答,见 one_wire_host.c |
onewire_host_write | 写一个字节(LSB 先行) | 逐位调用onewire_host_write_bit |
onewire_host_read_bytes | 连续读取多个字节 | 逐字节调用onewire_host_read |
onewire_host_search | 总线设备 ROM 搜索(支持条件搜索模式) | 实现标准三态位搜索算法,见 one_wire_host.c |
onewire_host_stop | 释放引脚 | 恢复为GpioModeAnalog,见 one_wire_host.c |
示例场景只有单个传感器,因此在每次事务前调用onewire_host_reset后直接发送 Skip ROM(0xCC)即可,无需执行 ROM 搜索;若总线上挂接多个 1-Wire 器件,则需改用onewire_host_search获取 8 字节 ROM 地址后再按地址寻址。
CRC 校验:maxim_crc8
DS18B20 暂存器第 9 字节是 Maxim 校验和,示例用 maxim_crc8 对前 8 个字节计算并与设备返回的 CRC 比对,匹配才采信数据。该实现为逐位多项式(多项式0x8C)算法,初始值为MAXIM_CRC8_INIT(0)。
温度读取协议:从命令序列到数值换算
读取流程在example_thermo_reader_thread_callback(后台线程)中以 1000ms 周期循环执行(宏UPDATE_PERIOD_MS),分两阶段完成。
阶段一:请求转换(request)
example_thermo_request_temperature在临界区(FURI_CRITICAL_ENTER/EXIT)内完成三条命令:
onewire_host_reset—— 复位总线,若返回 false 说明无设备,直接放弃并置has_device = false;- 写
DS18B20_CMD_SKIP_ROM(0xCC)—— 跳过 ROM 寻址,针对单设备总线; - 写
DS18B20_CMD_CONVERT(0x44)—— 启动温度转换。
DS18B20 完成一次转换最长需要约 750ms,因此线程随后通过furi_thread_flags_wait(ReaderThreadFlagExit, FuriFlagWaitAny, UPDATE_PERIOD_MS)既等待转换完成,又等待退出信号;超时(FuriFlagErrorTimeout)说明 1 秒已到、转换就绪,继续读取;收到ReaderThreadFlagExit标志则退出线程。
阶段二:读取暂存器并解析(read)
example_thermo_read_temperature同样在临界区内执行,最多重试 10 次:
- 复位总线,发送 Skip ROM,再发送
DS18B20_CMD_READ_SCRATCHPAD(0xBE)准备读取; - 用
onewire_host_read_bytes读取 9 字节暂存器内容,结构体DS18B20Scratchpad依次为:temp_lsb、temp_msb、高/低报警寄存器、配置寄存器、3 字节保留、CRC; - 用
maxim_crc8校验前 8 字节与第 9 字节 CRC,不匹配则重试,10 次全失败则置has_device = false; - 校验通过后按 DS18B20 数据手册(示例源码引用了官方数据手册)换算温度。
温度数值换算详解
换算逻辑位于 example_thermo.c,核心步骤如下:
- 分辨率提取:
resolution_mode = (config >> 5) & 0x03,对应 9/10/11/12 位分辨率(12 位为默认上电值,步进 0.0625°C); - 小数位掩码:
decimal_mask = (0x0F << (3 - resolution_mode)) & 0x0F,用于屏蔽低分辨率时无效的低位; - 整数部分:
integer_part = (temp_msb << 4) | (temp_lsb >> 4); - 小数部分:
decimal_part = temp_lsb & decimal_mask,每 1/16 位(LSB)代表 0.0625°C,故temp_abs = integer_part + decimal_part / 16.f; - 符号判断:
(temp_msb & 0xF0) != 0表示负温,最终赋值为-temp_abs或temp_abs。
这段代码将原始寄存器字节还原为带符号浮点摄氏度,是整个示例最具教学价值的部分,可对照 DS18B20 数据手册第 6 页温度寄存器格式逐行核对。
GUI、输入与线程模型
屏幕绘制
绘制回调example_thermo_draw_callback在每次刷新时全量重绘:标题Thermometer Demo、分隔线、提示连接引脚号,随后根据has_device状态显示温度读数或-- No data --。温度显示通过snprintf格式化为%+.1f(保留一位小数并带符号),例如Temperature: +23.5C。
本地化单位切换
示例是 locale-aware 的:根据locale_get_measurement_unit()(见 locale.h)判断单位制:
LocaleMeasurementUnitsMetric:直接显示摄氏温度,单位C;LocaleMeasurementUnitsImperial:调用locale_celsius_to_fahrenheit换算为华氏,单位F;- 其他取值触发
furi_crash("Illegal measurement units")作为防御性断言。
用户可在 Flipper 的Settings → System → Units中切换单位制,重进应用即可看到温度显示在 °C/°F 间变化,直观验证本地化机制。
输入与主循环
输入回调example_thermo_input_callback由 GUI 线程触发,只做一件事:把InputEvent投入容量为 8 的消息队列(furi_message_queue_alloc(8, sizeof(InputEvent)))。主循环example_thermo_run阻塞在furi_message_queue_get上,仅响应InputTypeShort类型的短按,当按键为InputKeyBack时置退出标志并跳出循环。退出时依次执行:向读取线程置ReaderThreadFlagExit标志 →furi_thread_join等待线程结束 →onewire_host_stop释放总线 →power_enable_otg(power, false)关闭外引脚电源。
外部引脚供电
example_thermo_run在启动时调用power_enable_otg(context->power, true)为外部 GPIO 排针提供 5V 输出(API 声明见 power.h,实现位于 power_api.c),退出时再关闭。这解释了 DS18B20 的 VDD 可直接接 Flipper 引脚 9 供电的原因;若自行接线改用外部电源,则需相应调整供电配置。
从示例到实战:关键注意事项小结
- 上拉电阻是硬前提:除默认的
gpio_ibutton(引脚 17)外,其余数据引脚都必须外接 4.7kΩ 上拉至 VDD,否则复位检测与读写时序均不可靠; - 临界区保护:1-Wire 位时序对时序抖动敏感,所有总线事务(复位、读写)都应放在
FURI_CRITICAL_ENTER/EXIT临界区内执行,防止被其他线程打断导致时序错乱; - 转换延时匹配:请求转换后需等待至多 750ms 再读取,示例用
UPDATE_PERIOD_MS = 1000的线程标志等待同时兼顾退出响应,是「周期任务 + 优雅退出」的典型写法; - CRC 校验不可省:1-Wire 在长线上易受干扰,读取暂存器后务必校验第 9 字节 CRC,失败应重试而不是直接采信;
- 引脚复用冲突:更换数据引脚前先确认该引脚未被调试输出、PWM、红外等其他外设占用(可在 furi_hal_resources.c 与对应外设驱动中核对)。
在此基础上,你可以把示例的读取逻辑抽取为可复用模块,接入自己的温度监控、数据记录或 BadUSB 联动的应用场景。
【免费下载链接】unleashed-firmwareFlipper Zero Unleashed Firmware项目地址: https://gitcode.com/GitHub_Trending/un/unleashed-firmware
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考