尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA实战:MS7210 ADC芯片SPI配置与Testbench仿真验证

FPGA实战:MS7210 ADC芯片SPI配置与Testbench仿真验证 1. 先弄清楚 MS7210 是什么以及为什么要用 FPGA 配置它在数字电源、电池管理、工业采集这类场景里ADC 芯片的作用是把模拟电压或电流转换成数字量交给 MCU、DSP 或 FPGA 处理。MS7210 是一款多通道、低功耗的模数转换芯片常用于需要多路模拟量同步采集的板卡中。芯片本身功能不复杂但要在 FPGA 工程里跑起来难点不在“读数据”而在“怎么能让它稳定工作”——这就要靠正确的配置时序、寄存器写入和仿真验证来保证。很多人第一次接触 MS7210 时会有一个误区以为把 SPI 接上、数据手册里的时序图看懂就能直接读 ADC 结果。实际项目中MS7210 在上电后需要完成配置初始化包括设置工作模式、通道选择、采样速率、参考电压等参数。如果寄存器配置顺序不对、时序不满足建立保持时间或者片选信号控制不当读回来的数据就可能是全零、跳变甚至完全无响应。这篇教程围绕一条主线展开在 FPGA 中实现 MS7210 的配置模块并用 testbenchtb 仿真验证配置命令发送过程和响应时序。学完之后你可以把这套配置代码直接移植到自己的采集项目里也能自己修改配置参数来适配不同采样需求。适合阅读本文的读者正在用 FPGA 驱动 ADC 芯片的开发者。已经会写基础 Verilog但没跑过完整 SPI 配置仿真的新手。需要把 MS7210 接入 Zynq、Artix-7 等 FPGA 平台想先验证逻辑正确性的工程师。在开始写代码前先解释两个核心概念MS7210 的配置机制以及为什么 tb 仿真在 FPGA 开发里不是“可选步骤”。1.1 MS7210 配置的本质寄存器写入 时序控制MS7210 的配置接口本质是一个串行接口通常兼容 SPI 通信方式。主机FPGA通过片选 CS、时钟 SCLK、数据输入 DIN 和设备输出 DOUT 四条线完成交互。芯片内部维护一组配置寄存器。上电后寄存器处于默认状态不一定符合设计需求。FPGA 要做的事情是按照数据手册规定的帧格式把命令字和数据字逐位发送到 DIN同时产生正确的 SCLK 和 CS 信号。芯片在 CS 拉低后开始接收数据在 SCLK 上升沿或下降沿采样 DIN。配置完成后才能发起转换命令或读取转换结果。这里的关键点在于每一位数据必须在 SCLK 的有效沿之前稳定下来。CS 拉低到第一个 SCLK 有效沿之间必须满足 tsetup。CS 拉高表示一次传输结束芯片内部会解析本次收到的命令。如果连续发送多个寄存器配置CS 是否允许保持低电平取决于手册要求。有的芯片支持连续写有的要求每帧独立拉高拉低。MS7210 的配置代码实际上就是“状态机 移位寄存器 时钟分频”的组合。把这三件事写清楚配置模块就完成了一大半。1.2 MS7210 的常见配置参数实际项目中MS7210 通常需要配置以下内容参数含义典型值影响工作模式连续转换或单次转换连续转换决定 FPGA 控制流程通道选择选择采样哪一路模拟输入单通道或多通道扫描决定配置字内容采样速率ADC 转换速度与 SCLK 频率匹配决定数据更新频率参考电压ADC 量程基准内部参考或外部参考影响转换结果满量程输出格式二进制补码或偏移二进制由寄存器位决定影响数据后处理这些参数都以位域形式写入寄存器。写错一位可能只是数据偏移也可能是整片 ADC 不工作。所以配置代码设计时一定要把“寄存器配置值”集中管理方便查看和修改。1.3 为什么必须做 tb 仿真FPGA 开发和软件调试最大的区别是出问题后不能直接在芯片内部打日志。虽然能通过 ILA 逻辑分析仪抓信号但综合、布局布线、上板下载的周期较长。一个 SPI 时序配置错误在线调试可能要花几小时。tb 仿真的价值在于不需要硬件就能验证配置模块是否按预期产生 CS、SCLK、DIN 波形。可以模拟 MS7210 芯片对配置命令的响应验证后续读取逻辑。可以精确检查建立时间、保持时间、帧间隔是否满足手册要求。出问题后把波形放大到 ns 级观察定位速度远快于板级调试。所以这篇教程的工程结构会同时包含设计代码和仿真代码。先通过仿真确认配置时序正确再上板验证真实 ADC 数据。2. 环境准备和工程结构先搭好再写代码MS7210 配置模块不依赖复杂 IP 核用 Verilog 编写即可。下面是一套推荐的环境和工程结构适用性较广不只是针对某一款开发板。2.1 开发环境要求项目推荐配置说明FPGA 开发工具Vivado 或 Quartus本教程使用 Vivado 工程示例仿真工具Vivado Simulator 或 ModelSim使用 tb 文件仿真不依赖板卡FPGA 型号Artix-7 或 Zynq-7000 系列代码为通用逻辑可移植到其他型号板卡包含 MS7210 芯片的 ADC 板学习阶段可使用仿真替代如果你用的不是 Vivado只要支持 Verilog-2001 语法这段代码基本不需要改动。项目里的时钟频率按 50 MHz 示例编写实际工程要根据你的板卡晶振修改参数。2.2 工程文件规划项目文件划分如下ms7210_config_demo/ ├── rtl/ │ ├── ms7210_config.v // 配置主模块 │ ├── ms7210_spi_driver.v // SPI 时序驱动 │ └── ms7210_top.v // 顶层封装可选 ├── sim/ │ ├── tb_ms7210_config.v // 仿真测试文件 │ └── ms7210_model.v // 简化的 MS7210 仿真模型 └── README.md如果你只是验证配置逻辑ms7210_spi_driver.v和tb_ms7210_config.v是必须的两个文件。ms7210_model.v用于模拟芯片反馈能有效验证 FPGA 发送的配置命令是否被“芯片”正确接收。2.3 理解 MS7210 配置模块内部的三个角色要写出可维护的配置代码需要把模块拆成职责清晰的三层控制层决定当前要写哪个寄存器、写入什么值、什么时候开始下一次配置。时序层负责产生符合芯片时序的 CS、SCLK、DIN 波形。数据层把寄存器地址和配置数据按芯片协议拼接成完整发送帧。很多新手把这三层混写在一个 always 块里结果状态机越写越乱。这里推荐控制层和时序层分离后面调试时定位问题会快很多。3. MS7210 配置代码实现从状态机到 SPI 波形下面进入核心代码编写阶段。这一节会给出一个可仿真的 MS7210 配置模块并解释各个部分的作用。3.1 顶层配置参数定义在编写代码之前先用宏定义或参数把寄存器地址和配置值集中起来。这样修改芯片配置时不需要改动状态机逻辑。timescale 1ns / 1ps // MS7210 配置寄存器地址示例定义实际地址以手册为准 define MS7210_REG_MODE 8h01 define MS7210_REG_CHANNEL 8h02 define MS7210_REG_RATE 8h03 define MS7210_REG_REF 8h04 // 配置示例值 define MS7210_MODE_CONT 8h00 // 连续转换模式 define MS7210_CH_SINGLE 8h00 // 单通道 define MS7210_RATE_DEFAULT 8h10 // 默认采样率 define MS7210_REF_INTERNAL 8h00 // 内部参考注意上述寄存器地址和配置值只是示例不同批次、不同后缀的 MS7210 可能定义不同。落地前必须打开你所使用芯片的数据手册把实际寄存器地址和位定义替换进去。配置值集中管理的优势在于后续如果要切换通道或采样率只需要修改宏定义状态机代码完全不用动。3.2 状态机定义配置模块的核心是一个状态机。这里采用常见的五状态设计IDLE、START、WRITE_CMD、WRITE_DATA、FINISH。module ms7210_config ( input wire clk, input wire rst_n, input wire start_config, output reg spi_cs_n, output reg spi_sclk, output reg spi_din, input wire spi_dout, output reg config_done ); // 时钟分频参数50MHz 输入产生 5MHz SCLK // 实际值 50MHz / (5MHz * 2) 5 localparam CLK_DIV 5; // 状态编码 localparam IDLE 3d0; localparam START 3d1; localparam WRITE_CMD 3d2; localparam WRITE_DATA 3d3; localparam FINISH 3d4; reg [2:0] state; reg [2:0] next_state; reg [7:0] cmd_buf; reg [7:0] data_buf; reg [3:0] bit_cnt; reg [6:0] clk_cnt; reg sclk_en; reg sclk_reg; reg sclk_falling; reg sclk_rising; reg [7:0] config_index; // 状态机主逻辑 always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; end else begin state next_state; end end说明IDLE等待启动信号所有 SPI 信号置为默认值。START拉低 CS准备开始传输数据。WRITE_CMD发送寄存器地址字节。WRITE_DATA发送配置数据字节。FINISH拉高 CS置位 config_done等待下一次配置。config_index用来记录当前配置到第几个寄存器。如果芯片初始化需要写入 4 个寄存器那么每完成一组“CMD DATA”配置索引加一直到全部完成。3.3 SCLK 时钟生成SPI 时序的 SCLK 是由系统时钟分频产生的。这里使用一个分频计数器并产生上升沿和下降沿标记。// SCLK 分频产生逻辑 always (posedge clk or negedge rst_n) begin if (!rst_n) begin clk_cnt 0; sclk_reg 1b1; // 空闲时为高 end else if (sclk_en) begin if (clk_cnt CLK_DIV - 1) begin clk_cnt 0; sclk_reg ~sclk_reg; end else begin clk_cnt clk_cnt 1; end end else begin clk_cnt 0; sclk_reg 1b1; end end assign spi_sclk sclk_reg; // 产生 SCLK 边沿脉冲供数据移位使用 always (posedge clk or negedge rst_n) begin if (!rst_n) begin sclk_rising 1b0; sclk_falling 1b0; end else begin sclk_rising clk_cnt (CLK_DIV - 2) !sclk_reg; sclk_falling clk_cnt (CLK_DIV - 2) sclk_reg; end endSPI 模式说明空闲时 SCLK 为高。数据在 SCLK 下降沿发送、上升沿采样对应 SPI Mode 3。如果芯片使用 Mode 0需要把 sclk 默认置为低并将发送和采样沿对调。这里容易犯的错是把 SCLK 频率算错。50 MHz 主频下如果分频参数为 5实际 SCLK 是 5 MHz。在仿真中看到 SCLK 波形周期为 200 ns就说明分频正确。3.4 状态机跳转和 SPI 数据发送接下来是状态机跳转逻辑和数据移位逻辑。这是整个配置模块的重点。// 状态机组合逻辑 always (*) begin next_state state; case (state) IDLE: begin if (start_config) next_state START; end START: begin next_state WRITE_CMD; end WRITE_CMD: begin if (bit_cnt 8 sclk_rising) next_state WRITE_DATA; end WRITE_DATA: begin if (bit_cnt 8 sclk_rising) next_state FINISH; end FINISH: begin if (config_index 3) // 假设需要配置 4 个寄存器 next_state IDLE; // 全部完成回到空闲 else next_state START; // 继续下一个寄存器配置 end default: next_state IDLE; endcase end在 WRITE_CMD 状态中bit_cnt 从 0 递增到 7每计数到 8 表示一个字节发送完毕。发送数据时FPGA 在 SCLK 下降沿放置数据在上升沿由芯片采样。移位发送逻辑如下// 数据移位发送 always (posedge clk or negedge rst_n) begin if (!rst_n) begin spi_din 1b0; bit_cnt 0; cmd_buf 0; data_buf 0; end else begin case (state) START: begin spi_din 1b0; bit_cnt 0; // 将当前要配置的寄存器地址和值装入缓冲区 case (config_index) 0: begin cmd_buf MS7210_REG_MODE; data_buf MS7210_MODE_CONT; end 1: begin cmd_buf MS7210_REG_CHANNEL; data_buf MS7210_CH_SINGLE; end 2: begin cmd_buf MS7210_REG_RATE; data_buf MS7210_RATE_DEFAULT; end 3: begin cmd_buf MS7210_REG_REF; data_buf MS7210_REF_INTERNAL; end default: begin cmd_buf 0; data_buf 0; end endcase end WRITE_CMD: begin if (sclk_falling) begin spi_din cmd_buf[7]; cmd_buf {cmd_buf[6:0], 1b0}; bit_cnt bit_cnt 1; end end WRITE_DATA: begin if (sclk_falling) begin spi_din data_buf[7]; data_buf {data_buf[6:0], 1b0}; bit_cnt bit_cnt 1; end end FINISH: begin bit_cnt 0; end endcase end end这段代码的关键行为START 状态把寄存器地址和数据准备好不占用额外总线时间。WRITE_CMD 状态下每次 SCLK 下降沿把 cmd_buf[7] 放到 DIN 上左移一位。8 个下降沿后完整命令字节发送完成。WRITE_DATA 状态重复相同过程发送数据字节。这样设计的好处是状态跳转和数据移位互不干扰状态机负责流程控制移位逻辑负责数据输出。3.5 CS 信号和完成标志CS 信号和 config_done 标志用于控制整帧传输。// CS 片选控制 always (posedge clk or negedge rst_n) begin if (!rst_n) begin spi_cs_n 1b1; end else begin case (state) IDLE: spi_cs_n 1b1; START: spi_cs_n 1b0; WRITE_CMD: spi_cs_n 1b0; WRITE_DATA: spi_cs_n 1b0; FINISH: spi_cs_n 1b1; default: spi_cs_n 1b1; endcase end end // 配置完成标志 always (posedge clk or negedge rst_n) begin if (!rst_n) begin config_done 1b0; end else if (state FINISH config_index 3) begin config_done 1b1; end else if (start_config) begin config_done 1b0; end endCS 拉低时机在 START 状态拉高时机在 FINISH 状态。两个字节传输结束后CS 拉高芯片解析本次收到的地址和数据。config_done在全部寄存器配置完成后拉高供上层逻辑读取或启动 ADC 转换流程。需要注意如果数据手册要求每两个连续帧之间 CS 必须保持高电平至少一个 SCLK 周期当前状态机已经满足。如果手册要求 CS 低电平期间连续写入多组数据则需要把状态机改成 CS 保持模式。4. tb 仿真代码编写验证配置时序是否满足手册要求配置代码写完下一步是编写 testbench。仿真的目的不是看代码能不能跑而是验证波形是否符合 MS7210 数据手册的时序要求。4.1 最小 testbench 结构timescale 1ns / 1ps module tb_ms7210_config; reg clk; reg rst_n; reg start_config; wire spi_cs_n; wire spi_sclk; wire spi_din; reg spi_dout; wire config_done; // 实例化被测模块 ms7210_config uut ( .clk(clk), .rst_n(rst_n), .start_config(start_config), .spi_cs_n(spi_cs_n), .spi_sclk(spi_sclk), .spi_din(spi_din), .spi_dout(spi_dout), .config_done(config_done) ); // 50MHz 时钟 always #10 clk ~clk; // 测试流程 initial begin clk 0; rst_n 0; start_config 0; spi_dout 0; #100; rst_n 1; #50; start_config 1; #30; start_config 0; // 等待配置完成 (posedge config_done); $display(MS7210 config finished at time %0t, $time); #500; $finish; end // 监测 SPI 数据 integer i; reg [7:0] recv_cmd; reg [7:0] recv_data; always (negedge spi_cs_n) begin recv_cmd 0; recv_data 0; // 等待第一个字节发送完成每 16 个 SCLK 周期一个完整帧 end endmodule这段 testbench 的核心作用是产生 50 MHz 仿真时钟。复位后拉高 start_config触发配置流程。等待 config_done 信号确认配置状态机走完。打印完成时间方便检查总耗时。4.2 增加数据采样监测模块仅靠 $display 无法确认发送的数据内容是否正确。更好的做法是在 testbench 中实例化一个简单的“虚拟芯片”,它会采样 SPI 总线上发送的每一位并拼成字节。// 简单 SPI 从机响应模型 reg [7:0] received_bytes [0:7]; integer byte_index; always (posedge spi_sclk) begin if (!spi_cs_n) begin received_bytes[byte_index] {received_bytes[byte_index][6:0], spi_din}; end end always (negedge spi_cs_n) begin byte_index 0; end always (posedge spi_cs_n) begin byte_index byte_index 1; $display(Received byte %0d 0x%02h at time %0t, byte_index, received_bytes[byte_index], $time); end这段逻辑会在 SCLK 上升沿采样 DINCS 拉高后打印收到的字节。运行仿真后你可以在 Transcript 窗口看到类似输出Received byte 0 0x01 Received byte 1 0x00 Received byte 2 0x02 Received byte 3 0x00如果打印的字节和预期配置值一致说明 FPGA 发送数据正确。如果不一致检查位序有的芯片先发低位有的先发高位需要按手册调整。4.3 仿真结果分析和时序检查运行仿真后打开波形窗口重点检查以下内容检查项预期结果错误现象SCLK 频率50 MHz 分频后约 5 MHz周期 200 ns周期错误说明分频参数写错CS 拉低到第一个 SCLK 下降沿大于手册要求的 tsetupCS 和 SCLK 几乎同时变化可能不满足建立时间DIN 在 SCLK 下降沿稳定数据在下降沿后变化数据在上升沿附近变化采样会出现错误每帧 CS 高电平间隔大于手册要求的最短帧间隔连续两帧之间 CS 高电平太短config_done 拉高四个寄存器全部配置完成状态机提前退出或卡死注意仿真波形“看起来正常”并不代表真实芯片一定工作正常。仿真使用的虚拟芯片模型不会包含所有电气特性比如输入电容、信号抖动。上板之前仍要以数据手册的直流和交流参数为准。5. 加入 MS7210 仿真模型模拟芯片响应如果有条件建议为 MS7210 写一个简化的行为模型。模型不追求完全模拟内部模拟电路只需要模拟 SPI 从机行为接收配置字节、保存寄存器值、回读响应。5.1 模型目标仿真模型要完成三件事在 SPI 总线上正确采样 DIN。根据命令地址解析配置数据保存到内部寄存器数组。在读取模式下把寄存器值或转换结果通过 DOUT 返回给主机。学习阶段可以只做 1 和 2这样足以验证 FPGA 发送的配置命令是否被芯片正确解析。5.2 简化模型代码module ms7210_model ( input wire spi_cs_n, input wire spi_sclk, input wire spi_din, output reg spi_dout ); reg [7:0] spi_reg; reg [7:0] config_regs [0:15]; reg [4:0] bit_cnt; reg is_cmd_phase; always (posedge spi_sclk or negedge spi_cs_n) begin if (!spi_cs_n) begin spi_reg 0; bit_cnt 0; is_cmd_phase 1; end else begin spi_reg {spi_reg[6:0], spi_din}; bit_cnt bit_cnt 1; if (bit_cnt 7) begin if (is_cmd_phase) begin // 收到命令字节下一字节是数据 is_cmd_phase 0; end else begin // 收到数据字节保存到寄存器 config_regs[spi_reg] spi_reg; is_cmd_phase 1; end end end end always (posedge spi_cs_n) begin $display(MS7210 model: reg[0x%02h] 0x%02h, spi_reg, config_regs[spi_reg]); end endmodule这段模型代码的局限在于它假设命令字节和数据字节连续发送且每个命令后面紧跟一个数据字节。如果芯片的命令格式是“地址 读写标志 数据”连续一帧模型需要按实际协议调整。5.3 模型验证效果将上述模型接入 testbenchFPGA 配置模块发送的每一个寄存器配置都会被模型记录。在 Transcript 窗口中可以看到类似输出MS7210 model: reg[0x01] 0x00 MS7210 model: reg[0x02] 0x00 MS7210 model: reg[0x03] 0x10 MS7210 model: reg[0x04] 0x00如果这些值和ms7210_config.v中的宏定义一致说明 FPGA 发送链路、格式、位序都正确。之后再去检查模拟采样数据问题范围就小很多。6. 常见问题排查从波形异常倒推原因6.1 仿真中 config_done 一直不拉高可能原因检查方式解决方案start_config 脉冲太短查看 start_config 波形确保脉冲至少覆盖 1 个时钟周期状态机死循环查看 state 信号波形检查状态跳转条件中 bit_cnt 是否始终未到 8config_index 判断条件错误查看 config_index 数值确认全部寄存器数量与代码一致复位未释放检查 rst_n 是否为高延长复位时间确认复位释放这个问题的典型特征是状态机停在 WRITE_CMD 或 FINISH 状态不再跳转。用波形窗口观察state和bit_cnt就能定位。6.2 仿真波形中 SCLK 频率不对SCLK 周期取决于分频参数 CLK_DIV。如果你的输入时钟不是 50 MHz分频参数也要等比调整。常见错误包括把分频参数当成 SCLK 频率的一半直接填。实际晶振是 100 MHz但仍使用 50 MHz 的分频参数。仿真时钟生成代码里#10代表 10 ns 半周期对应 50 MHz如果写成#20实际是 25 MHz。如果你希望 SCLK 是 5 MHz输入时钟是 100 MHz分频参数计算方法是100 MHz / (5 MHz × 2) 10。注意分频参数过大会导致配置总时间变长。但不要为了提高速度无限提高 SCLK 频率必须保证不超过芯片手册规定的最大 SCLK 频率。6.3 数据移位移反或时序采样沿不对SPI 有 4 种模式区别在于CPOLSCLK 空闲电平是高还是低。CPHA数据在第一个边沿还是第二个边沿采样。MS7210 配置常见的是 Mode 3SCLK 空闲为高数据在上升沿采样。如果你的芯片使用其他模式需要反转 SCLK 默认电平同时调整发送和采样沿。错误现象的区分方式前几个寄存器配置成功后面的错乱通常是位序或字节数错误。第一个字节就错误通常是 SPI 模式配置错误。所有字节都错但波形看着规律可能是发送沿和采样沿用了同一个边沿。6.4 收发数据错位字节内容整体偏移如果仿真中收到的第一个字节不是 0x01 而是 0x02通常是发送数据时状态机从 START 跳到 WRITE_CMD 的时机和第一个 SCLK 下降沿没对齐。第一位数可能被丢掉结果整体左移了一位。推荐的解决方式在 START 状态就拉低 CS。在 WRITE_CMD 状态的第一个 SCLK 下降沿发送 cmd_buf[7]。保证 cmd_buf 在 CS 拉低时已经装载完毕。6.5 上板后 ADC 读不到数据但仿真正常这种情况最容易让人困惑。仿真环境没有实际的模拟输入、没有电气噪声、没有时序裕量问题。上板失败时按以下顺序排查检查顺序检查内容说明1MS7210 供电电压检查数字电源和模拟电源是否满足手册要求2参考电压引脚内部参考是否使能外部参考是否接入3SPI 引脚约束确认引脚分配和 BANK 电压正确4时序约束为 SCLK 添加约束避免过大的时钟偏斜5ILA 在线调试抓取 spi_cs_n、spi_sclk、spi_din 波形6寄存器回读如果芯片支持回读把配置寄存器读出来检查很多情况下仿真正常但上板失败是因为芯片默认引脚状态和 FPGA 输出有冲突比如 SPI 引脚被误配置为按键或 LED 引脚。7. 最佳实践和工程化建议7.1 配置值参数化避免硬编码不要在状态机里直接写死寄存器地址和数据。推荐做法是用宏定义或 localparam 统一管理这是最基本的工程化约束。改动通道、切换采样率时只改一行而不是在代码里搜索替换多处。7.2 增加回读功能提升可靠性如果 MS7210 支持读寄存器建议在配置完成后增加一个回读状态把刚写入的寄存器读出来与预期值比较。这能有效区分“配置未写入”和“配置写入但 ADC 异常”两种情况。回读状态的实现方式类似写寄存器只是把发送方向改成接收。FPGA 发送读命令后切换到输入模式在 SCLK 驱动下采样 DOUT。7.3 状态机复位要彻底复位时不仅要把状态置为 IDLE还要把 SPI 引脚恢复成默认电平。特别是 CS 必须拉高否则芯片会错误地认为 SPI 传输仍在进行。if (!rst_n) begin spi_cs_n 1b1; spi_sclk 1b1; spi_din 1b0; end7.4 仿真时增加时序断言学习性质的项目可以不开断言但在真实项目中建议在 testbench 中检查以下时序CS 拉低到第一个 SCLK 边沿的间隔是否大于 tsetup。每帧结束后 CS 高电平时间是否大于帧间间隔。数据是否在 SCLK 边沿处发生改变即建立时间是否可能被违反。这些时序断言用$error输出能在回归测试中自动发现时序问题。7.5 生产环境要做的额外处理项目建议时钟约束对 SPI SCLK 设置输出延迟约束引脚约束检查上拉、下拉、输出驱动强度电源滤波ADC 模拟电源和数字电源要分开滤波异常处理配置完成后增加状态回读和超时判断上电时序确认 MS7210 上电稳定后再启动配置如果项目中使用的是 Zynq 平台也可以考虑用 PS 端 SPI 控制器或 AXI SPI IP 替代纯 Verilog 配置模块。但使用 Verilog 配置的好处是灵活、不占用 CPU且逻辑固定后功耗可控。两种方案可根据项目资源情况选择。7.6 学习路径建议如果这是你第一次接触 FPGA 驱动 SPI 类芯片后续可以按以下顺序深入先跑通本文的 MS7210 配置仿真。修改寄存器配置值观察仿真波形变化。增加回读功能验证 DOUT 路径。把配置模块和 ADC 数据读取模块对接完成一个完整的采集链路。在真实板卡上用 ILA 验证配置波形对比仿真结果。最后再优化代码结构和时序约束进入工程化阶段。不要一开始就追求把所有功能全部做完。先把“配置写入”这条链路在仿真中彻底跑通再把工作重心转向数据读取和模拟前端设计。8. 完整配置代码汇总为了方便复制到自己的工程里这里给出精简版的完整配置模块。timescale 1ns / 1ps module ms7210_config ( input wire clk, input wire rst_n, input wire start_config, output reg spi_cs_n, output reg spi_sclk, output reg spi_din, input wire spi_dout, output reg config_done ); localparam CLK_DIV 5; localparam REG_NUM 4; localparam IDLE 3d0; localparam START 3d1; localparam WRITE_CMD 3d2; localparam WRITE_DATA 3d3; localparam FINISH 3d4; reg [2:0] state; reg [2:0] next_state; reg [7:0] cmd_buf; reg [7:0] data_buf; reg [3:0] bit_cnt; reg [6:0] clk_cnt; reg sclk_en; reg sclk_reg; reg sclk_rising; reg sclk_falling; reg [3:0] config_index; // 配置参数 reg [7:0] cfg_cmd [0:REG_NUM-1]; reg [7:0] cfg_data [0:REG_NUM-1]; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cfg_cmd[0] 0; cfg_data[0] 0; cfg_cmd[1] 0; cfg_data[1] 0; cfg_cmd[2] 0; cfg_data[2] 0; cfg_cmd[3] 0; cfg_data[3] 0; end else begin cfg_cmd[0] 8h01; cfg_data[0] 8h00; cfg_cmd[1] 8h02; cfg_data[1] 8h00; cfg_cmd[2] 8h03; cfg_data[2] 8h10; cfg_cmd[3] 8h04; cfg_data[3] 8h00; end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; end else begin state next_state; end end always (*) begin next_state state; case (state) IDLE: if (start_config) next_state START; START: next_state WRITE_CMD; WRITE_CMD: if (bit_cnt 4d8 sclk_rising) next_state WRITE_DATA; WRITE_DATA: if (bit_cnt 4d8 sclk_rising) next_state FINISH; FINISH: if (config_index REG_NUM - 1) next_state IDLE; else next_state START; default: next_state IDLE; endcase end always (posedge clk or negedge rst_n) begin if (!rst_n) begin sclk_en 1b0; end else begin case (state) IDLE: sclk_en 1b0; START: begin sclk_en 1b1; clk_cnt 0; sclk_reg 1b1; end WRITE_CMD, WRITE_DATA: sclk_en 1b1; FINISH: begin sclk_en 1b0; clk_cnt 0; sclk_reg 1b1; end default: sclk_en 1b0; endcase end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin clk_cnt 0; sclk_reg 1b1; end else if (sclk_en) begin if (clk_cnt CLK_DIV - 1) begin clk_cnt 0; sclk_reg ~sclk_reg; end else begin clk_cnt clk_cnt 1; end end else begin clk_cnt 0; sclk_reg 1b1; end end assign spi_sclk sclk_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin sclk_rising 0; sclk_falling 0; end else begin sclk_rising (clk_cnt CLK_DIV - 2) !sclk_reg; sclk_falling (clk_cnt CLK_DIV - 2) sclk_reg; end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin spi_cs_n 1b1; end else begin case (state) IDLE: spi_cs_n 1b1; START, WRITE_CMD, WRITE_DATA: spi_cs_n 1b0; FINISH: spi_cs_n 1b1; default: spi_cs_n 1b1; endcase end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin spi_din 1b0; bit_cnt 0; cmd_buf 0; data_buf 0; config_index 0; end else begin case (state) START: begin spi_din 1b0; bit_cnt 0; cmd_buf cfg_cmd[config_index]; data_buf cfg_data[config_index]; end WRITE_CMD: begin if (sclk_falling) begin spi_din cmd_buf[7]; cmd_buf {cmd_buf[6:0], 1b0}; bit_cnt bit_cnt 1; end end WRITE_DATA: begin if (sclk_falling) begin spi_din data_buf[7]; data_buf {data_buf[6:0], 1b0}; bit_cnt bit_cnt 1; end end FINISH: begin bit_cnt 0; if (config_index REG_NUM - 1) config_index config_index 1; else config_index 0; end default: bit_cnt 0; endcase end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin config_done 1b0; end else if (state FINISH config_index REG_NUM - 1) begin config_done 1b1; end else if (start_config) begin config_done 1b0; end end endmodule这段代码不是唯一写法但它体现了配置模块的核心思想状态机控制流程、移位寄存器输出数据、分频器产生 SCLK。拿到真实芯片数据手册后重点修改cfg_cmd和cfg_data的赋值部分即可适配。MS7210 的配置工作并不复杂真正考验工程能力的是把配置时序、仿真验证、硬件约束排在一起处理。先把这一条链路跑通整个采集系统就有了稳定可靠的第一步。
返回列表