FPGA初级项目9——基于SPI的ADC芯片进行模数转换
ADC芯片介绍
ADC(Analog-to-Digital Converter)芯片是一种将连续变化的模拟信号转换为离散数字信号的电子器件,广泛应用于电子系统中,是连接现实世界与数字世界的桥梁。可将电压、电流等模拟量转换为二进制数字信号,供计算机、微控制器等数字设备处理。
关键参数
分辨率:以位数表示(如 12 位、16 位)表示将一个模拟量转换为多少位的数字量,例如8位分辨率可表示的范围为0~255(十进制)。
采样率:每秒转换的样本数(单位 Hz),需满足 Nyquist 定理(至少 2 倍信号最高频率)。
转换精度:实际输出与理论值的偏差,受噪声、非线性等因素影响。
输入范围:可转换的模拟信号电压范围(如 0~5V、±10V)。
功耗:低功耗设计适用于电池供电设备
SPI 协议基础
SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步的通信协议(是一种规则),广泛用于微控制器(如 Arduino、STM32)与外设(如 ADC、传感器、存储器)之间的短距离通信。
SPI由来
SPI最初由摩托罗拉(现 NXP)于 1980 年代开发,并未通过 ISO、IEEE 等国际组织的正式认证。但由于其简单高效,逐渐成为事实上的工业标准,被全球半导体厂商(如 ADI、TI、Microchip)广泛支持。主流 MCU(如 Arduino、STM32、ESP32)均内置硬件 SPI 控制器,且 ADC、传感器等外设芯片的 SPI 接口严格遵循摩托罗拉定义的时序规则。这种生态共识使 SPI 成为嵌入式领域的 “通用语言”。
SPI参数
主从架构:一个主设备(如 MCU或FPGA)控制多个从设备(如 ADC 芯片)。
四线通信:
SCK(时钟线):主设备发送时钟信号,同步数据传输。
MOSI(主出从入):主设备向从设备发送数据。
MISO(主入从出):从设备向主设备返回数据。
CS(片选线):主设备通过拉低特定从设备的 CS 线选择目标。
高速传输:支持兆赫兹级速率(如 STM32 可达 42MHz),适合高频数据场景。
全双工:数据可同时双向传输。
SPI规则
SPI怎么用,需要自己编写吗?
需要强调的是:是否需要自己编写 SPI 逻辑代码,取决于你使用的开发环境、硬件平台以及具体需求。
1. 有些较为基础、低成本的微控制器可能没有内置 SPI 硬件模块,例如一些简单的 8 位单片机。在这种情况下,你只能通过软件来模拟 SPI 的通信逻辑,也就是自己编写代码来控制通用输入输出引脚(GPIO),模拟时钟信号(SCK)、数据传输(MOSI 和 MISO)以及片选信号(CS)的时序。
2. 当你有特殊的通信需求,比如需要修改 SPI 的通信时序、协议格式,或者实现一些自定义的通信规则时,就可能需要自己编写 SPI 逻辑代码。例如,你可能需要调整时钟极性(CPOL)和时钟相位(CPHA)来满足特定 ADC 芯片的通信要求。
3. 大多数现代微控制器,如常见的 STM32 系列、Arduino 的部分型号等,都内置了 SPI 硬件外设。这些硬件外设可以直接配置和使用,芯片厂商通常会提供相应的库函数来简化 SPI 通信的操作。
工作原理
ADC 芯片通过 SPI 接口接收主设备的控制指令(如启动转换、配置参数),并将转换后的数字信号通过 SPI 返回给主设备。我们需要完成的任务即ADC芯片的驱动逻辑,依照芯片手册实现其逻辑代码编写(严格遵循SPI协议)。
问题分析
1. 我们需要编写的是芯片ADC128S102的驱动逻辑,有4个主要端口:cs_n(片选端); sclk(ADC芯片时钟); DIN(模拟量输入端口); DOUT(数字量输出端口); 根据芯片手册要满足其时序要求。其中SCLK频率为12.5HZ。
2. 我们依旧使用线性序列机(LSM)的思想来定义每个时刻该做的事情。以SCLK半个周期为最小时间单元。DOUT端口靠ADC芯片来驱动,FPGA端来接收。每个输出的数据信号在SCLK下降沿改变,同时FPGA在SCLK上升沿读取(此时信号已稳定);而DIN信号决定ADC芯片的哪个通道输入,在SCLK下降沿FPGA将信号输入到ADC芯片。详细参数可参照文末代码!
3. 同时还有一些额外的端口需要设置,例如用户的通道选择输入端口[2:0]addr(因ADC芯片有8个选择输入端口,所以需要3位来表示);数字数据输出端口[11:0](因芯片为12位分辨率);采样信号(决定什么时候采样)与采样完成信号端口的设置等。
4. 根据芯片时序手册,处理完一轮数据周期需要35个时钟,所以counter1的计数最大值为35,即需要6位二进制数来表示,所以为[5:0]counter1。ADC芯片的电路结构如下所示可作为了解,但是我们需要再次强调我们所写的是该芯片的驱动电路!!!
好的分析完上述问题,我们上代码
代码展示
//定义输入输出端口
module ADC_driver(clk,reset_n,conv_go,//采样请求信号addr,//用户输入选择通道端口conv_done,//采样完成信号data,//已经转换完成的数字信号sclk,cs_n,DOUT,DIN);input clk;input reset_n;input conv_go;input [2:0]addr;output reg conv_done;output reg [11:0]data;output reg sclk;output reg cs_n;output reg DIN;input DOUT;//定义相关时钟参数parameter clock_freq = 50_000_000;parameter sclk_freq = 12_500_000;//ADC芯片SCLK频率parameter mcnt = clock_freq / (sclk_freq * 2) - 1;//定义最小时间单元counter0reg en_counter0;reg [7:0]counter0;
always@(posedge clk or negedge reset_n)
if(!reset_n)counter0 <= 0;
else if(en_counter0) beginif(counter0 == mcnt)counter0 <= 0;else counter0 <= counter0 +1'd1;end
elsecounter0 <= 0;//定义位计数器counter1reg [5:0]counter1;
always@(posedge clk or negedge reset_n)
if(!reset_n)counter1 <= 6'd0;
else if(counter0 == mcnt) beginif(counter1 == 6'd34)//处理完一轮数据需要35个周期时钟counter1 <= 6'd0;elsecounter1 <= counter1 + 1'd1;end
elsecounter1 <= counter1;//定义存储器,防止数据发生变化reg [2:0]r_addr;
always@(posedge clk)
if(conv_go)r_addr <= addr;
elser_addr <= r_addr;//定义数据处理完成信号与处理后的数据存贮reg [11:0]data_r;
always@(posedge clk or negedge reset_n)
if(!reset_n) begindata <= 12'd0;conv_done <= 0;end
else if((counter1 == 34)&&(counter0 == mcnt)) begindata <= data_r;conv_done <= 1'd1;end
else begindata <= data;conv_done <= 0;end//定义使能信号en_counter0
always@(posedge clk or negedge reset_n)
if(!reset_n) en_counter0 <= 1'd0;
else if(conv_go)en_counter0 <= 1'd1;
else if((counter1 == 34)&&(counter0 == mcnt))en_counter0 <= 1'd0;
elseen_counter0 <= en_counter0; //定义每个时间点的操作,按照时序图来填表即可
always@(posedge clk or negedge reset_n)
if(!reset_n)begindata_r <= 12'd0;sclk <= 1'd1;DIN <= 1'd1;cs_n <= 1'd1;end
else if(counter0 == mcnt)begincase(counter1)0:begin cs_n <= 1'd1; sclk <= 1'd1; end1:begin sclk <= 1'd0; end2:begin sclk <= 1'd0; end3:begin sclk <= 1'd1; end4:begin sclk <= 1'd0; end5:begin sclk <= 1'd1; end6:begin sclk <= 1'd0; DIN <= r_addr[2];end7:begin sclk <= 1'd1; end8:begin sclk <= 1'd0; DIN <= r_addr[21];end9:begin sclk <= 1'd1; end10:begin sclk <= 1'd0; DIN <= r_addr[0];end11:begin sclk <= 1'd1; data_r[11] <= DOUT;end12:begin sclk <= 1'd0; end13:begin sclk <= 1'd1; data_r[10] <= DOUT;end14:begin sclk <= 1'd0; end15:begin sclk <= 1'd1; data_r[9] <= DOUT;end16:begin sclk <= 1'd0; end17:begin sclk <= 1'd1; data_r[8] <= DOUT;end18:begin sclk <= 1'd0; end19:begin sclk <= 1'd1; data_r[7] <= DOUT;end20:begin sclk <= 1'd0; end21:begin sclk <= 1'd1; data_r[6] <= DOUT;end22:begin sclk <= 1'd0; end23:begin sclk <= 1'd1; data_r[5] <= DOUT;end24:begin sclk <= 1'd0; end25:begin sclk <= 1'd1; data_r[4] <= DOUT;end26:begin sclk <= 1'd0; end27:begin sclk <= 1'd1; data_r[3] <= DOUT;end28:begin sclk <= 1'd0; end29:begin sclk <= 1'd1; data_r[2] <= DOUT;end30:begin sclk <= 1'd0; end31:begin sclk <= 1'd1; data_r[1] <= DOUT;end32:begin sclk <= 1'd0; end33:begin sclk <= 1'd1; data_r[0] <= DOUT;end34:begin sclk <= 1'd0; enddefault: cs_n <= 1'd1;endcase
endendmodule
综合出来的底层系统逻辑图schematic如下: