前言
前文已经进行了SPI主从机回环试验的功能仿真,验证了主从机基本通信功能的正确性。然而在实际应用中,绝大部分场景FPGA均是作为SPI主机使用,PCB板上的其它器件(如传感器、ADC、DAC、存储器等)则作为SPI从机。
为进一步验证所设计的Verilog功能模块——SPI主机在实际硬件环境中的功能,本系列后续文章将在实际的FPGA板卡上运行该SPI主机模块,驱动多个实际SPI从机芯片。选用的测试从机包括:
-
ADX112: analogysemi(类比半导体)推出的16位、4路单端/2路差分、8~860SPS的ADC,内部集成晶振、参考电压源与PGA,数据接口为SPI接口; -
MAX31865: ADI公司推出的用于铂电阻温度检测(RTD)的专用芯片,数据接口为SPI接口;
本文对ADX112芯片进行实测。
测试证明此SPI主机模块在实际硬件场景中工作正常。
本文最后开源了所有Verilog源码。
一、ADX112简介
ADX112(Q)超小尺寸、超低功耗SPI接口16位高精度ΔΣ ADC(内置基准电压源和振荡器)。
-
AEC-Q100认证(仅ADX112Q具备)
-
超小尺寸QFN封装:2mm×1.5mm×0.4mm
-
小型3mm×3mm MSOP封装
-
宽供电电压范围:2V至5.5V
-
低电流消耗:
连续模式:仅145μA
单脉冲模式:自动断电
-
可编程数据速率:8SPS至860SPS
-
单周期建立时间
-
内部低漂移电压基准
-
内部振荡器
-
兼容SPI接口
-
内部可编程增益放大器(PGA)
-
4个单端输入或2个差分输入
-
工作温度范围:-40°C至125°C
二、ADX112驱动设计思路
思路:
-
1.此芯片数字接口为SPI接口, 故只需编写驱动SPI通用主机模块的外层逻辑即可
-
2.此芯片的SPI有两种读取方式:
-
一次读32位, 这时dout的前16位为转换结果, 后16位为配置寄存器数据
-
一次读16位, 这时dout只输出转换结果
-
-
3.din用于设置配置寄存器, 如果不需要更改配置寄存器, 那么保持din=全0或者全1即可
三、ADX112 SPI时序分析
% 此芯片SPI时序
-
1.SCLK时钟周期最小为250ns, 对应最大频率为4MHz -
2.CS下降沿到第一个SCLK上升沿的延迟, 最小为100ns -
3.SCLK空闲为低电平,即CPOL=0 -
4.最后一个SCLK下降沿到CS上升沿的延迟, 最小为100ns -
5.CS高电平持续时间, 最小为200ns -
6.SCLK低电平持续28ms后, 芯片SPI接口将被复位 -
7.din有效到sclk下降沿的时间, 最小为50ns, 此为建立时间, 意味着芯片在sclk下降沿采样 -
8.sclk下降沿是时钟的第二个边沿, 故CPHA=1, 所以SPI_MODE={CPOL, CPHA}=2‘b01=1 -
9.sclk下降沿之后, din保持有效的时间, 最小值为50ns, 此为保持时间 -
10.CS下降沿到DOUT输出有效的延迟, 最大为100ns -
11.SCLK上升沿到new dout有效的延迟, 最大为50ns -
12.CS上升沿到DOUT高阻态的延迟, 最大为100ns
四、ADX112驱动模块
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 | 20 | 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 | 40 | TCWH, CS_N低电平后保持高电平的时间对应CLK数, 最小为2 |
CLK_FREQ_MHZ | integer | 100 | 模块工作时钟, 常用100/120,100MHz对应单周期10ns |
4.2.2 信号列表
信号分组 | 信号名 | 方向 | 说明 |
---|---|---|---|
外部控制信号 | adx112_begin | input | 上升沿有效,使芯片读取一次数据 |
adx112_is_busy | output | 高电平指示芯片正在工作, 此时不响应begin信号 | |
adx112_config_value[15:0] | input | 待写入配置寄存器的值, 在有效的begin上升沿锁存 | |
adx112_dout[15:0] | output | 芯片输出, 16bit | |
adx112_dout_valid | output | 芯片输出有效指示, 高电平有效, 只会持续一个时钟周期的高电平 | |
adx112_rd_config_value | output | 回读的配置寄存器值 | |
SPI硬线链接 | spi_cs_n | output | 片选, 低电平有效 |
spi_sclk | output | SPI时钟, 主机提供 | |
spi_mosi | output | 主机输出从机输入 | |
spi_miso | input | 主机输入从机输出 | |
时钟与复位 | clk | input | 模块工作时钟 |
rstn | input | 模块复位, 低电平有效 |
4.3 驱动部分代码
//++ SPI读写控制 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
reg adx112_begin_r1;
always @(posedge clk) begin
adx112_begin_r1 <= adx112_begin;
end
wire adx112_begin_pedge = adx112_begin && ~adx112_begin_r1;
localparam integer DATA_WIDTH = 32; // 单次通信发送或接收数据的位宽, 16/32(默认)
wire spi_begin;
// wire spi_end;
wire spi_is_busy;
wire [DATA_WIDTH-1:0] spi_master_tx_data;
wire [DATA_WIDTH-1:0] spi_master_rx_data;
wire spi_master_rx_data_valid;
assign spi_begin = adx112_begin_pedge && ~spi_is_busy;
assign adx112_is_busy = spi_is_busy;
assign adx112_dout = spi_master_rx_data[DATA_WIDTH-1:DATA_WIDTH-16];
assign adx112_dout_valid = spi_master_rx_data_valid;
assign spi_master_tx_data = {adx112_config_value, 16'b0};
assign adx112_rd_config_value = spi_master_rx_data[DATA_WIDTH-17:DATA_WIDTH-32];
//-- SPI读写控制 ------------------------------------------------------------
//++ 实例化SPI主机模块 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
mySPI_4Wire_Master #(
.SPI_MODE (1 ),
.DATA_WIDTH (DATA_WIDTH ),
.SCLK_PERIOD_CLK_NUM (SCLK_PERIOD_CLK_NUM ),
.CS_EDGE_TO_SCLK_EDGE_CLK_NUM (CS_EDGE_TO_SCLK_EDGE_CLK_NUM),
.SCLK_EDGE_TO_CS_EDGE_CLK_NUM (SCLK_EDGE_TO_CS_EDGE_CLK_NUM),
.CS_KEEP_HIGH_CLK_NUM (CS_KEEP_HIGH_CLK_NUM ),
.CLK_FREQ_MHZ (CLK_FREQ_MHZ )
) mySPI_4Wire_Master_u0 (
.spi_begin (spi_begin ),
.spi_end ( ),
.spi_is_busy (spi_is_busy ),
.spi_master_tx_data (spi_master_tx_data ),
.spi_master_rx_data (spi_master_rx_data ),
.spi_master_rx_data_valid (spi_master_rx_data_valid),
.spi_cs_n (spi_cs_n ),
.spi_sclk (spi_sclk ),
.spi_mosi (spi_mosi ),
.spi_miso (spi_miso ),
.clk (clk ),
.rstn (rstn )
);
//-- 实例化SPI主机模块 ------------------------------------------------------------
五、ADX112驱动顶层模块
在顶层模块功能:
-
实例化PLL -
生成复位信号 -
实例化ADX112驱动 -
设定配置寄存器值 -
提供驱动begin信号
部分代码如下:
配置寄存器设置:
//++ 配置寄存器设置 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/*
* 工作状态或单次转换启动
* 该位决定器件的工作状态。
* 仅在掉电状态下可写入工作状态,在转换进行时写入无效。
* 写入时:
* 0 = 无效果
* 1 = 启动单次转换(在掉电状态下)
* 读取时:
* 0 = 器件当前正在执行转换
* 1 = 器件当前未执行转换(默认)
*/
localparam [0:0] SS = 1'b1; //* 对配置寄存器的设置值只关注写入, 下同
/*
* 输入多路复用器配置
* 这些位配置输入多路复用器。
* 000 = 正输入端为AIN0,负输入端为AIN1(默认)
* 001 = 正输入端为AIN0,负输入端为AIN3
* 010 = 正输入端为AIN1,负输入端为AIN3
* 011 = 正输入端为AIN2,负输入端为AIN3
* 100 = 正输入端为AIN0,负输入端为地
* 101 = 正输入端为AIN1,负输入端为地
* 110 = 正输入端为AIN2,负输入端为地
* 111 = 正输入端为AIN3,负输入端为地
*/
localparam [2:0] MUX = 3'b100;
/*
* 可编程增益放大器配置
* 这些位配置可编程增益放大器。
* 000 = 满量程为±6.144V(1)
* 001 = 满量程为±4.096V(1)
* 010 = 满量程为±2.048V(默认)
* 011 = 满量程为±1.024V
* 100 = 满量程为±0.512V
* 101 = 满量程为±0.256V
* 110 = 满量程为±0.256V
* 111 = 满量程为±0.256V
*/
localparam [2:0] PGA = 3'b001;
/*
* 器件工作模式
* 该位控制ADX112(Q)的工作模式。
* 0 = 连续转换模式
* 1 = 掉电和单次模式(默认)
*/
localparam [0:0] MODE = 1'b1;
/*
* 数据速率
* 这些位控制数据速率设置。
* 000 = 8SPS
* 001 = 16SPS
* 010 = 32SPS
* 011 = 64SPS
* 100 = 128SPS(默认)
* 101 = 250SPS
* 110 = 475SPS
* 111 = 860SPS
*/
localparam [2:0] DR = 3'b100;
/*
* 温度传感器模式
* 该位配置ADC是转换温度信号还是输入信号。
* 0 = ADC模式(默认)
* 1 = 温度传感器模式
*/
localparam [0:0] TS_MODE = 1'b0;
/*
* 上拉使能
* 仅当CS为高电平时,该位使能DOUT/DRDY引脚上的弱内部上拉电阻。
* 使能时,一个内部400kΩ电阻将总线连接到电源;禁用时,DOUT/DRDY引脚浮空。
* 0 = DOUT/DRDY引脚上拉电阻禁用
* 1 = DOUT/DRDY引脚上拉电阻使能(默认)
*/
localparam [0:0] PULL_UP_EN = 1'b1;
/*
* 无操作
* NOP[1:0]位控制是否向配置寄存器写入数据。
* 要向配置寄存器写入数据,NOP[1:0]位必须为“01”;其他任何值都将产生NOP命令。
* 在SCLK脉冲期间,DIN可保持高电平或低电平,且不会有数据写入配置寄存器。
* 00 = 无效数据,不更新配置寄存器内容
* 01 = 有效数据,更新配置寄存器(默认)
* 10 = 无效数据,不更新配置寄存器内容
* 11 = 无效数据,不更新配置寄存器内容
*/
localparam [1:0] NOP = 2'b01;
/*
* 保留位
* 向该位写入0或1均无效果,读取时始终为1
*/
localparam [0:0] RESERVED = 1'b1;
assign adx112_config_value = {
SS,
MUX,
PGA,
MODE,
DR,
TS_MODE,
PULL_UP_EN,
NOP,
RESERVED
};
/*
* 如果不设置仅读取配置寄存器则会读到其默认值 = 16'h858B
* 手册里写的是058B, 经分析是错误的, 最高位SS应读到1, 表示器件当前未执行转换(默认)
*/
// assign adx112_config_value = 'd0;
//-- 配置寄存器设置 ------------------------------------------------------------
生成begin信号:
//++ ADX112控制 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam ADX112_BEGIN_DELAY_CNT_MAX = CLK_FREQ_MHZ * 1000 * 1000 / 128;
reg [$clog2(ADX112_BEGIN_DELAY_CNT_MAX+1)-1 : 0] adx112_begin_delay_cnt;
always @(posedge clk) begin
if (~rstn)
adx112_begin_delay_cnt <= 'd0;
else if (adx112_begin_delay_cnt < ADX112_BEGIN_DELAY_CNT_MAX)
adx112_begin_delay_cnt <= adx112_begin_delay_cnt + 1'b1;
else
adx112_begin_delay_cnt <= 'd0;
end
always @(posedge clk) begin
if (~rstn)
adx112_begin <= 1'b0;
else if (~adx112_is_busy && adx112_begin_delay_cnt == ADX112_BEGIN_DELAY_CNT_MAX)
adx112_begin <= 1'b1;
else
adx112_begin <= 1'b0;
end
//-- ADX112控制 ------------------------------------------------------------
六、ADX112驱动的ILA实测波形
一次SPI读取32bit数据,前16位为ADC码,后16位为配置寄存器读取值。
写入配置寄存器的值为16’hc38b,最高位SS写入1表示开始一次转换。
读出配置寄存器的值为16’h438b,最高位SS读出0表示芯片转换已经完成。
实测表明ADX112驱动功能正常,进一步验证了Verilog功能模块–SPI主机的正确性。
七、分享
源码在Gitee与Github开源,两平台同步:
仓库中包含ADX112数据手册及其中文翻译版。
测试工程分享,BX71-ADX112Driver Vivado 2024.2 20250810.7z。
欢迎大家关注我的微信公众号:徐晓康的博客,回复以下6位数字获取网盘链接。
451078
建议复制过去不会码错字!
如果本文对你有所帮助,欢迎点赞、转发、收藏、评论让更多人看到,赞赏支持就更好了。
如果对文章内容有疑问,请务必清楚描述问题,留言评论或私信告知我,我看到会回复。

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