1. FPGA驱动DS18B20温度传感器实战解析
在嵌入式开发中,DS18B20可以说是最经典的单总线数字温度传感器之一。但要在FPGA上用Verilog实现稳定可靠的驱动,却需要与严格的时序要求"死磕"。本文将基于Vivado 2017.4开发环境,详细拆解DS18B20的Verilog驱动实现,分享我在实际项目中的调试经验和避坑指南。
这个驱动方案已经在Xilinx Artix-7 FPGA(Basys3开发板)上实测通过,温度读取周期约750ms,精度达到±0.5℃。无论你是刚开始接触FPGA外设驱动的新手,还是正在寻找可靠DS18B20解决方案的开发者,都能从本文获得可直接复用的技术细节。
1.1 单总线通信的核心挑战
DS18B20采用单总线(1-Wire)协议,这意味着数据收发都通过一根线完成。这种设计虽然节省了IO资源,但也带来了几个关键挑战:
-
严格的时序要求:从复位脉冲到读写时隙,每个操作都有精确到微秒级的时间窗口。例如复位脉冲需要持续至少480us的低电平,而写0时隙必须保持60-120us。
-
双向总线控制:同一根线在不同时刻需要作为输出或输入,FPGA端必须正确处理三态控制。
-
低位优先的数据格式:DS18B20传输数据时是低位(LSB)在前,这与我们通常的认知习惯相反,容易在数据处理时出错。
2. 驱动架构设计与关键模块实现
2.1 顶层模块设计
驱动代码的顶层模块定义了与外部交互的接口和基本功能:
verilog复制module ds18b20_driver (
input clk_1MHz, // 1MHz主时钟(时序基准)
output reg dq_out, // 控制总线输出
input dq_in, // 总线输入监测
output [15:0] temp_data, // 温度值输出
output data_valid // 数据有效标志
);
时钟选择考量:
- 使用1MHz时钟(周期1us)作为基准,因为DS18B20的典型操作时序都在微秒级
- 这样设计计数器时可以直接用计数值对应微秒数,例如480计数对应480us
- 在FPGA中可以通过MMCM或PLL将系统时钟分频得到1MHz时钟
三态总线处理:
- 实际部署时需要例化IOBUF原语实现双向总线
- 在XDC约束文件中需要添加正确的电平标准和上拉电阻设置:
tcl复制
set_property PULLUP true [get_ports dq_io] set_property IOSTANDARD LVCMOS33 [get_ports dq_io]
2.2 状态机设计
状态机是驱动控制的核心,完整的状态转移包括:
verilog复制localparam [3:0]
IDLE = 4'd0, // 空闲状态
RESET_PULSE = 4'd1, // 发送复位脉冲
WAIT_PRESENCE = 4'd2, // 等待存在脉冲
WRITE_SCRATCH = 4'd3, // 写暂存器命令
CONVERSION = 4'd4, // 启动温度转换
READ_LOOP = 4'd5; // 读取温度数据
每个状态对应传感器操作的一个阶段,状态转移通过计数器精确控制:
verilog复制always @(posedge clk_1MHz) begin
case(state)
RESET_PULSE:
if(cnt == 480) begin // 480us低电平复位
state <= WAIT_PRESENCE;
cnt <= 0;
end else begin
cnt <= cnt + 1;
dq_out <= 1'b0;
end
WAIT_PRESENCE:
if(cnt == 70) begin // 等待70us后检测存在脉冲
if(!dq_in) begin
state <= WRITE_SCRATCH;
end else begin
state <= IDLE; // 未检测到存在脉冲,返回空闲
end
cnt <= 0;
end else begin
cnt <= cnt + 1;
dq_out <= 1'b1; // 释放总线
end
// 其他状态转移逻辑...
endcase
end
调试经验:状态机最容易出现的问题是计数器清零时机不当。建议在每个状态转移点同时重置计数器,避免残留计数值影响下一个状态。
3. 关键操作时序实现细节
3.1 复位与存在脉冲检测
复位和存在脉冲检测是DS18B20通信的第一步,也是后续操作的基础:
- FPGA拉低总线至少480us(复位脉冲)
- 释放总线(上拉电阻将总线拉高)
- DS18B20在15-60us内拉低总线60-240us(存在脉冲)
- FPGA需要在释放总线后70us左右采样总线状态
verilog复制// 复位脉冲生成
if(state == RESET_PULSE) begin
dq_out <= 1'b0; // 拉低总线
if(cnt == 480) begin
state <= WAIT_PRESENCE;
cnt <= 0;
end
end
// 存在脉冲检测
if(state == WAIT_PRESENCE) begin
dq_out <= 1'b1; // 释放总线
if(cnt == 70) begin // 等待70us后采样
presence <= ~dq_in; // 低电平表示存在
state <= presence ? WRITE_SCRATCH : IDLE;
end
end
3.2 读写时隙实现
DS18B20的读写操作通过特定的时隙完成,每个时隙通常持续60-120us:
写时隙实现:
verilog复制// 写0时隙
if(write_zero) begin
dq_out <= 1'b0;
if(cnt == 60) begin // 保持60us低电平
dq_out <= 1'b1;
cnt <= 0;
write_done <= 1'b1;
end
end
// 写1时隙
if(write_one) begin
dq_out <= 1'b0;
if(cnt == 2) begin // 短暂拉低后释放
dq_out <= 1'b1;
if(cnt == 60) begin
write_done <= 1'b1;
cnt <= 0;
end
end
end
读时隙实现:
verilog复制// 读时隙生成
if(read_bit) begin
dq_out <= 1'b0; // 启动读时隙
if(cnt == 2) begin
dq_out <= 1'b1; // 释放总线
bit_sample <= dq_in; // 采样数据
end
if(cnt == 60) begin
read_done <= 1'b1;
cnt <= 0;
end
end
实测技巧:写0时隙采用60us的下限值,而读时隙在2us后采样,这样的设置在多个DS18B20传感器上表现出最好的兼容性。
3.3 温度数据读取与处理
DS18B20的温度数据为16位,包含符号位和小数部分:
verilog复制// 读取字节处理
always @(posedge bit_done) begin
if(bit_counter <= 7) begin
temp_buffer[bit_counter] <= dq_in_reg; // 低位在前存储
end
bit_counter <= bit_counter + 1;
end
// 温度数据整合
assign temp_data = {temp_buffer[1][7], // 符号位
temp_buffer[1][6:0], // 整数部分
temp_buffer[0][7:4]}; // 小数部分
数据格式注意事项:
- 数据是低位(LSB)在前传输,因此存储时需要反向索引
- 温度值格式:bit15为符号位,bit14-bit4为整数部分,bit3-bit0为小数部分
- 小数部分每bit代表0.0625℃,实际应用中可根据需要取舍
4. Vivado工程实现与调试技巧
4.1 工程设置与约束
在Vivado 2017.4中需要特别注意以下设置:
-
IO约束:
tcl复制
set_property PACKAGE_PIN P17 [get_ports dq_io] set_property IOSTANDARD LVCMOS33 [get_ports dq_io] set_property PULLUP true [get_ports dq_io] -
综合设置:
- 启用优化以保留信号层次结构,便于调试
- 对于低速设计可以关闭时序驱动的综合策略
-
实现策略:
- 选择面积优化策略而非性能优化
- 关闭物理优化中的某些激进选项
4.2 调试与验证方法
ILA逻辑分析仪的使用:
- 设置触发条件为状态机跳转或读写使能信号
- 捕获总线波形并与DS18B20时序图对比
- 特别检查:
- 复位脉冲宽度(480us±10%)
- 写时隙低电平持续时间(60-120us)
- 读时隙采样点位置(通常在时隙开始后15us)
常见问题排查:
-
总线始终为低:
- 检查上拉电阻是否启用(set_property PULLUP true)
- 确认没有多个驱动同时拉低总线
-
无法检测存在脉冲:
- 测量复位脉冲后的总线恢复时间
- 确认传感器供电正常(寄生供电时需要强上拉)
-
温度数据错误:
- 检查数据位的采样时序
- 确认字节存储顺序是否正确(LSB first)
- 验证CRC校验(如果实现)
4.3 性能优化建议
-
时钟选择:
- 1MHz时钟足够满足DS18B20时序要求
- 可以动态调整时钟频率,在空闲时降低功耗
-
状态机优化:
- 使用独热编码(one-hot)可能更适合简单状态机
- 将长时间等待状态(如温度转换)拆分为子状态
-
资源利用:
- 整个驱动占用不到100个LUT,适合资源受限设计
- 可以进一步优化计数器位宽节省资源
5. 完整操作流程与实测数据
5.1 完整温度读取流程
-
初始化:
- 发送复位脉冲(480us低电平)
- 检测存在脉冲(60-240us低电平)
-
ROM命令阶段:
- 发送Skip ROM命令(0xCC),直接访问传感器
-
功能命令阶段:
- 发送Convert T命令(0x44),启动温度转换
- 等待转换完成(典型750ms)
-
读取数据:
- 再次初始化总线
- 发送Read Scratchpad命令(0xBE)
- 读取9字节数据(温度值+CRC)
5.2 实测性能数据
在不同环境温度下的测试结果:
| 环境温度(℃) | 测量值(℃) | 误差(℃) |
|---|---|---|
| 15.0 | 15.25 | +0.25 |
| 25.0 | 24.81 | -0.19 |
| 30.5 | 30.94 | +0.44 |
| 75.0 | 74.69 | -0.31 |
测试条件:Basys3开发板,Vivado 2017.4,DS18B20在寄生供电模式下
5.3 不同实现方案对比
| 方案特点 | 本实现方案 | 典型微控制器实现 | 专用接口芯片 |
|---|---|---|---|
| 时序精度 | ±1us | ±5us | ±0.1us |
| 资源占用 | <100 LUTs | 软件实现 | 额外硬件 |
| 最大采样率 | 1Hz | 1Hz | 10Hz |
| 多传感器支持 | 需分时 | 需分时 | 并行支持 |
| 开发复杂度 | 中等 | 简单 | 最简单 |
6. 扩展应用与进阶优化
6.1 多传感器支持
在单总线上挂载多个DS18B20时,需要:
- 实现ROM搜索算法,识别每个传感器的唯一64位地址
- 分时复用总线访问不同传感器
- 增加温度转换的并行度(通过Match ROM命令)
verilog复制// ROM命令示例
localparam [7:0]
SEARCH_ROM = 8'hF0,
READ_ROM = 8'h33,
MATCH_ROM = 8'h55,
SKIP_ROM = 8'hCC;
6.2 低功耗优化
对于电池供电应用:
- 在温度转换期间关闭FPGA部分逻辑
- 使用时钟门控技术降低动态功耗
- 减少不必要的总线活动
6.3 精度提升技巧
- 实现CRC校验确保数据传输正确性
- 对多次测量结果进行数字滤波
- 校准传感器偏差(通过写TH/TL寄存器)
在Basys3开发板上的实测表明,这个驱动方案稳定可靠,温度读取成功率达到99%以上。虽然DS18B20是相对简单的传感器,但通过FPGA实现驱动仍然需要考虑诸多细节。希望本文的详细解析和实战经验能为你的项目开发提供有价值的参考。