FPGA初级项目9——基于SPI的ADC芯片进行模数转换
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;
//定义最小时间单元counter0
reg en_counter0;
reg [7:0]counter0;
always@(posedge clk or negedge reset_n)
if(!reset_n)
counter0 <= 0;
else if(en_counter0) begin
if(counter0 == mcnt)
counter0 <= 0;
else
counter0 <= counter0 +1'd1;
end
else
counter0 <= 0;
//定义位计数器counter1
reg [5:0]counter1;
always@(posedge clk or negedge reset_n)
if(!reset_n)
counter1 <= 6'd0;
else if(counter0 == mcnt) begin
if(counter1 == 6'd34)//处理完一轮数据需要35个周期时钟
counter1 <= 6'd0;
else
counter1 <= counter1 + 1'd1;
end
else
counter1 <= counter1;
//定义存储器,防止数据发生变化
reg [2:0]r_addr;
always@(posedge clk)
if(conv_go)
r_addr <= addr;
else
r_addr <= r_addr;
//定义数据处理完成信号与处理后的数据存贮
reg [11:0]data_r;
always@(posedge clk or negedge reset_n)
if(!reset_n) begin
data <= 12'd0;
conv_done <= 0;
end
else if((counter1 == 34)&&(counter0 == mcnt)) begin
data <= data_r;
conv_done <= 1'd1;
end
else begin
data <= 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;
else
en_counter0 <= en_counter0;
//定义每个时间点的操作,按照时序图来填表即可
always@(posedge clk or negedge reset_n)
if(!reset_n)begin
data_r <= 12'd0;
sclk <= 1'd1;
DIN <= 1'd1;
cs_n <= 1'd1;
end
else if(counter0 == mcnt)begin
case(counter1)
0:begin cs_n <= 1'd1; sclk <= 1'd1; end
1:begin sclk <= 1'd0; end
2:begin sclk <= 1'd0; end
3:begin sclk <= 1'd1; end
4:begin sclk <= 1'd0; end
5:begin sclk <= 1'd1; end
6:begin sclk <= 1'd0; DIN <= r_addr[2];end
7:begin sclk <= 1'd1; end
8:begin sclk <= 1'd0; DIN <= r_addr[21];end
9:begin sclk <= 1'd1; end
10:begin sclk <= 1'd0; DIN <= r_addr[0];end
11:begin sclk <= 1'd1; data_r[11] <= DOUT;end
12:begin sclk <= 1'd0; end
13:begin sclk <= 1'd1; data_r[10] <= DOUT;end
14:begin sclk <= 1'd0; end
15:begin sclk <= 1'd1; data_r[9] <= DOUT;end
16:begin sclk <= 1'd0; end
17:begin sclk <= 1'd1; data_r[8] <= DOUT;end
18:begin sclk <= 1'd0; end
19:begin sclk <= 1'd1; data_r[7] <= DOUT;end
20:begin sclk <= 1'd0; end
21:begin sclk <= 1'd1; data_r[6] <= DOUT;end
22:begin sclk <= 1'd0; end
23:begin sclk <= 1'd1; data_r[5] <= DOUT;end
24:begin sclk <= 1'd0; end
25:begin sclk <= 1'd1; data_r[4] <= DOUT;end
26:begin sclk <= 1'd0; end
27:begin sclk <= 1'd1; data_r[3] <= DOUT;end
28:begin sclk <= 1'd0; end
29:begin sclk <= 1'd1; data_r[2] <= DOUT;end
30:begin sclk <= 1'd0; end
31:begin sclk <= 1'd1; data_r[1] <= DOUT;end
32:begin sclk <= 1'd0; end
33:begin sclk <= 1'd1; data_r[0] <= DOUT;end
34:begin sclk <= 1'd0; end
default: cs_n <= 1'd1;
endcase
end
endmodule
综合出来的底层系统逻辑图schematic如下: