前言
书接上文,为进一步验证所设计的Verilog功能模块——SPI主机在实际硬件环境中的功能,本文对MAX31865芯片进行实测。测试证明此SPI主机模块在实际硬件场景中工作正常。
本文仅为验证SPI主机,故仅进行了读写配置寄存器与故障阈值寄存器的试验,并非是完整的MAX31865驱动实验。
本文最后开源了所有Verilog源码。
一、MAX31865简介
MAX31865 是一款易于使用的电阻-数字转换器,专为铂电阻温度检测器(RTD)优化设计。通过一个外部电阻设置所用RTD的灵敏度,精密的Δ-Σ ADC将RTD电阻与参考电阻的比值转换为数字形式。MAX31865的输入端可防护高达±45V的过压故障。该器件还集成了可编程的RTD及电缆开路、短路检测功能。
MAX31865特点:
-
集成降低了系统成本,简化了设计工作,并缩短了设计周期。 -
简单地将铂电阻温度计的电阻转换为数字值。 -
处理100Ω至1kΩ(在0°C时)的铂电阻温度计(PT100到PT1000) -
兼容2线、3线和4线传感器连接 -
SPI兼容接口 -
20引脚TQFN和SSOP封装
-
-
高精度便于满足误差预算 -
15位ADC分辨率;名义温度分辨率为0.03125℃(由于RTD非线性而变化) -
所有操作条件下的总精度:0.5℃(满量程的0.05%)最大值 -
完全差分VREF输入 -
转换时间为21毫秒(最大值)
-
-
集成故障检测提高了系统的可靠性 -
±45V输入保护 -
故障检测(开路RTD元件、RTD短接到超出范围电压或RTD元件短路)
-
二、MAX31865驱动设计思路
-
1.MAX31865通过SPI写入/读取寄存器来完成对芯片的配置和数据读取
-
2.MAX31865需要先进行配置, 再进行数据读取
-
3.配置分为两种类型, 一为配置寄存器(读地址8’h00, 写地址8’h80), 包括:
-
启用或禁用偏置VBIAS
-
设置转换模式:自动或常态关闭
-
设置是否开启单次触发
-
选择 RTD 连接方式:三线或四线
-
清除故障状态寄存器
-
选择滤波器频率
-
-
4.二为配置故障阈值寄存器,包括:
高故障阈值寄存器MSB (读地址8’h03, 写地址8’h83)
高故障阈值寄存器LSB (读地址8’h04, 写地址8’h84)
低故障阈值寄存器MSB (读地址8’h05, 写地址8’h85)
低故障阈值寄存器LSB (读地址8’h06, 写地址8’h86)
-
5.芯片工作时如果有故障会更新故障状态寄存器的值, 上层模块在遇到芯片故障时需读取故障寄存器值, 判断故障类型并对外输出
-
% 驱动错误
* drive_error_code[2:0]: 表示发生了某项驱动错误
* 2’b00: 表示配置寄存器读出数据不等于前一步骤写入的数据, 可能是SPI时序问题, 或者芯片本身的响应问题
* 2’b01: 表示故障阈值寄存器读出数据不等于前一步骤写入的数据, 可能原因同上
三、MAX31865的SPI时序分析
-
1.时序特性参考: 数据手册–交流电气特性:SPI接口 -
2.MAX31865的SCLK频率最大为5MHz, 实际使用建议最大fSCLK设定为4MHz, 占空比固定为50% -
3.SCLK空闲时为高电平, 对应 CPOL=1 -
4.MAX31865在SCLK的上升沿采样SDI引脚上的数据, 结合SCLK空闲高电平, 即在第二个时钟边沿采样, 对应CPHA = 1 -
5.综合来看 SPI_MODE = {CPOL, CPHA} = 2’b11 = 3 -
6.tCC: CS_N下降沿到SCLK的第一个下降沿, 最小值为400ns -
7.tCCH: 最后一个SCLK上升沿到CS_N上升沿, 最小值100ns -
8.tCWH: CS_N无效时间, CS_N低电平有效到下一次低电平有效的时间, 最小值为400ns -
9.tCDZ: CS_N上升沿到SDO引脚高阻的时间, 最大值40ns -
10.SPI的读写时序是确定的, 芯片SDI先接收8位数据, 根据地址值决定是读还是写: -
如果是读, 则芯片SDO输出16位数据 -
如果是写, 则芯片SDI接收16位数据 -
单次通讯结束
四、MAX31865驱动模块
4.1 模块框图
4.2 信号接口
4.2.1 参数列表
参数名 | 类型 | 默认值 | 说明 |
---|---|---|---|
SCLK_PERIOD_CLK_NUM | integer | 100 | fSCLK, SCLK周期对应CLK数, 必须为偶数, 最小为2 |
CS_EDGE_TO_SCLK_EDGE_CLK_NUM | integer | 50 | TCC, CS_N下降沿到SCLK的第一个边沿对应CLK数, 最小为1 |
SCLK_EDGE_TO_CS_EDGE_CLK_NUM | integer | 20 | TCCH, 最后一个SCLK边沿到CS_N上升沿对应CLK数, 最小为3 |
CS_KEEP_HIGH_CLK_NUM | integer | 50 | TCWH, CS_N低电平后保持高电平的时间对应CLK数, 最小为2 |
CLK_FREQ_MHZ | integer | 100 | 模块工作时钟, 常用100/120,100MHz对应单周期10ns |
4.2.2 信号列表
信号分组 | 信号名 | 方向 | 说明 |
---|---|---|---|
外部控制信号 | MAX31865_begin | input | 上升沿有效, 进行一次配置寄存器和故障阈值寄存器的读写 |
MAX31865_is_busy | output | 高电平指示芯片正在工作, 此时不响应begin信号 | |
max31865_config_data[7:0] | input | 待写入配置寄存器的值, 在有效的begin上升沿锁存 | |
max31865_rd_config_data[7:0] | output | 读出的配置寄存器的值 | |
max31865_rd_config_data_valid | output | 指示读出的配置寄存器的值有效, 高电平有效, 只会持续一个时钟周期的高电平 | |
max31865_high_threshold[15:0] | input | 写入的高故障阈值 | |
max31865_low_threshold[15:0] | input | 写入的低故障阈值 | |
max31865_rd_high_threshold[15:0] | output | 读取的高故障阈值 | |
max31865_rd_low_threshold[15:0] | output | 读取的低故障阈值 | |
max31865_rd_threshold_valid | output | 读取的故障阈值有效信号, 高电平有效, 只会持续一个时钟的高电平 | |
SPI硬线链接 | spi_cs_n | output | 片选, 低电平有效 |
spi_sclk | output | SPI时钟, 主机提供 | |
spi_mosi | output | 主机输出从机输入 | |
spi_miso | input | 主机输入从机输出 | |
时钟与复位 | clk | input | 模块工作时钟 |
rstn | input | 模块复位, 低电平有效 |
4.3 驱动部分代码
4.3.1 寄存器读写地址
//++ 寄存器读写地址 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/* 配置寄存器 读写*/
localparam [7:0] CONFIG_REG_RADDR = 8'h00; // 配置寄存器读地址
localparam [7:0] CONFIG_REG_WADDR = CONFIG_REG_RADDR + 8'h80; // 配置寄存器写地址
/* 故障阈值寄存器 读写*/
localparam [7:0] HIGH_FAULT_THRESHOLD_MSB_REG_RADDR = 8'h03; // 高故障阈值MSB读地址
localparam [7:0] HIGH_FAULT_THRESHOLD_LSB_REG_RADDR = 8'h04; // 高故障阈值LSB读地址
localparam [7:0] LOW_FAULT_THRESHOLD_MSB_REG_RADDR = 8'h05; // 低故障阈值MSB读地址
localparam [7:0] LOW_FAULT_THRESHOLD_LSB_REG_RADDR = 8'h06; // 低故障阈值LSB读地址
localparam [7:0] HIGH_FAULT_THRESHOLD_MSB_REG_WADDR = HIGH_FAULT_THRESHOLD_MSB_REG_RADDR
+ 8'h80; // 高故障阈值MSB写地址
localparam [7:0] HIGH_FAULT_THRESHOLD_LSB_REG_WADDR = HIGH_FAULT_THRESHOLD_LSB_REG_RADDR
+ 8'h80; // 高故障阈值LSB写地址
localparam [7:0] LOW_FAULT_THRESHOLD_MSB_REG_WADDR = LOW_FAULT_THRESHOLD_MSB_REG_RADDR
+ 8'h80; // 低故障阈值MSB写地址
localparam [7:0] LOW_FAULT_THRESHOLD_LSB_REG_WADDR = LOW_FAULT_THRESHOLD_LSB_REG_RADDR
+ 8'h80; // 低故障阈值LSB写地址
//-- 寄存器读写地址 ------------------------------------------------------------
4.3.2 读写配置与故障阈值寄存器
//++ 读写配置与故障阈值寄存器 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(* mark_debug *)reg [3:0] spi_end_cnt; // spi_end计数, 作为下一次spi_begin的开始
always @(posedge clk) begin
if (~rstn)
spi_end_cnt <= 'd0;
else if (max31865_is_busy)
if (spi_end)
spi_end_cnt <= spi_end_cnt + 1'b1;
else
spi_end_cnt <= spi_end_cnt;
else
spi_end_cnt <= 'd0;
end
localparam SPI_END_CNT_MAX = 9;
assign max31865_end = spi_end && spi_end_cnt == SPI_END_CNT_MAX;
always @(posedge clk) begin
if (~rstn)
spi_begin <= 1'b0;
else if (max31865_begin_pedge && ~max31865_is_busy)
spi_begin <= 1'b1;
else if (spi_end && spi_end_cnt < SPI_END_CNT_MAX)
spi_begin <= 1'b1;
else
spi_begin <= 1'b0;
end
// spi写入
always @(*) begin
if (~rstn)
spi_master_tx_data = 'd0;
else
case (spi_end_cnt)
0: spi_master_tx_data = {CONFIG_REG_WADDR, max31865_config_data};
1: spi_master_tx_data = {CONFIG_REG_RADDR, max31865_config_data};
2: spi_master_tx_data = {HIGH_FAULT_THRESHOLD_MSB_REG_WADDR, max31865_high_threshold[15:8]};
3: spi_master_tx_data = {HIGH_FAULT_THRESHOLD_MSB_REG_RADDR, max31865_high_threshold[15:8]};
4: spi_master_tx_data = {HIGH_FAULT_THRESHOLD_LSB_REG_WADDR, max31865_high_threshold[7:0]};
5: spi_master_tx_data = {HIGH_FAULT_THRESHOLD_LSB_REG_RADDR, max31865_high_threshold[7:0]};
6: spi_master_tx_data = {LOW_FAULT_THRESHOLD_MSB_REG_WADDR, max31865_low_threshold[15:8]};
7: spi_master_tx_data = {LOW_FAULT_THRESHOLD_MSB_REG_RADDR, max31865_low_threshold[15:8]};
8: spi_master_tx_data = {LOW_FAULT_THRESHOLD_LSB_REG_WADDR, max31865_low_threshold[7:0]};
9: spi_master_tx_data = {LOW_FAULT_THRESHOLD_LSB_REG_RADDR, max31865_low_threshold[7:0]};
default: ;
endcase
end
// 读取配置数据
always @(posedge clk) begin
if (~rstn)
max31865_rd_config_data <= 'd0;
else if (spi_end)
case (spi_end_cnt)
1: max31865_rd_config_data <= spi_master_rx_data[7:0];
default: ;
endcase
else
max31865_rd_config_data <= max31865_rd_config_data;
end
assign max31865_rd_config_data_valid = spi_end && spi_end_cnt == 'd1;
// 读取高故障阈值
always @(posedge clk) begin
if (~rstn)
max31865_rd_high_threshold <= 'd0;
else if (spi_end)
case (spi_end_cnt)
3: max31865_rd_high_threshold[15:8] <= spi_master_rx_data[7:0];
5: max31865_rd_high_threshold[7:0] <= spi_master_rx_data[7:0];
default: ;
endcase
else
max31865_rd_high_threshold <= max31865_rd_high_threshold;
end
// 读取低故障阈值
always @(posedge clk) begin
if (~rstn)
max31865_rd_low_threshold <= 'd0;
else if (spi_end)
case (spi_end_cnt)
7: max31865_rd_low_threshold[15:8] <= spi_master_rx_data[7:0];
9: max31865_rd_low_threshold[7:0] <= spi_master_rx_data[7:0];
default: ;
endcase
else
max31865_rd_low_threshold <= max31865_rd_low_threshold;
end
assign max31865_rd_threshold_valid = spi_end && spi_end_cnt == 'd9;
//-- 读写配置与故障阈值寄存器 ------------------------------------------------------------
五、MAX31865驱动顶层模块
在顶层模块功能:
-
实例化PLL -
生成复位信号 -
实例化MAX31865驱动 -
设定配置与故障阈值寄存器值 -
提供驱动begin信号
部分代码如下:
配置寄存器设置:
//++ 设置配置寄存器值 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/*
* 配置寄存器位定义:
* D7: V_BIAS - 偏置电压(1:开启, 0:关闭)
* D6: CONV_MODE - 转换模式(1:自动, 0:正常关闭)
* D5: SINGLE_SHOT - 单次触发(1:1-shot, 0:无)
* D4: WIRE_MODE - 线制选择(1:三线, 0:四线)
* D3: FAULT_DETECT_H - 故障检测周期高位(与D2组合)
* D2: FAULT_DETECT_L - 故障检测周期低位(见表3)
* D1: FAULT_CLR - 故障清除(1:清除状态, 0:无操作)
* D0: FILTER_SEL - 工频滤波(1:60Hz, 0:50Hz)
*/
/*
* 故障检测周期控制 (D3-D2):
* 00: 无操作 | 读取: 故障检测完成
* 01: 带自动延迟的故障检测 | 读取: 自动故障检测仍在运行
* 10: 带手动延迟的故障检测(周期 1) | 读取: 手动周期1仍在运行;等待用户写入11
* 11: 带手动延迟的故障检测(周期 2) | 读取: 手动周期2仍在运行
*/
localparam [0: 0] V_BIAS = 1'b1;
localparam [0: 0] CONV_MODE = 1'b0;
localparam [0: 0] SINGLE_SHOT = 1'b1;
localparam [0: 0] WIRE_MODE = 1'b1;
localparam [0: 0] FAULT_DETECT_H = 1'b0;
localparam [0: 0] FAULT_DETECT_L = 1'b0;
localparam [0: 0] FAULT_CLR = 1'b0;
localparam [0: 0] FILTER_SEL = 1'b0;
assign max31865_config_data = {
V_BIAS,
CONV_MODE,
SINGLE_SHOT,
WIRE_MODE,
FAULT_DETECT_H,
FAULT_DETECT_L,
FAULT_CLR,
FILTER_SEL
};
//-- 设置配置寄存器值 ------------------------------------------------------------
生成begin信号:
//++ 生成MAX31865驱动demo控制信号 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam MAX31865_SAMPLE_FREQ_HZ = 10;
localparam MAX31865_BEGIN_CLK_CNT_MAX = CLK_FREQ_MHZ * 1000 * 1000 / MAX31865_SAMPLE_FREQ_HZ;
reg [$clog2(MAX31865_BEGIN_CLK_CNT_MAX+1)-1 : 0] max31865_begin_clk_cnt;
always @(posedge clk) begin
if (~rstn)
max31865_begin_clk_cnt <= 'd0;
else if (max31865_begin_clk_cnt < MAX31865_BEGIN_CLK_CNT_MAX)
max31865_begin_clk_cnt <= max31865_begin_clk_cnt + 1'b1;
else
max31865_begin_clk_cnt <= 'd0;
end
always @(posedge clk) begin
if (~rstn)
max31865_begin <= 1'b0;
else if (max31865_begin_clk_cnt == MAX31865_BEGIN_CLK_CNT_MAX && ~max31865_is_busy)
max31865_begin <= 1'b1;
else
max31865_begin <= 1'b0;
end
//-- 生成MAX31865驱动demo控制信号 ------------------------------------------------------------
六、MAX31865驱动的ILA实测波形
一次SPI写入同时读取16bit数据,前8位为写/读寄存器地址,后8位为写/读寄存器的值。
实测表明MAX31865驱动功能正常,进一步验证了Verilog功能模块–SPI主机的正确性。
七、分享
源码在Gitee与Github开源,两平台同步:
仓库中包含MAX31865数据手册及其中文翻译版。
测试工程分享,BX71-MAX31865Driver_demo Vivado 2024.2 20250810.7z。
欢迎大家关注我的微信公众号:徐晓康的博客,回复以下6位数字获取网盘链接。
451078
建议复制过去不会码错字!
如果本文对你有所帮助,欢迎点赞、转发、收藏、评论让更多人看到,赞赏支持就更好了。
如果对文章内容有疑问,请务必清楚描述问题,留言评论或私信告知我,我看到会回复。

徐晓康的博客持续分享高质量硬件、FPGA与嵌入式知识,软件,工具等内容,欢迎大家关注。