目录
本文根据b站小梅哥FPGA编写,仅用于笔记使用。
一、SPI原理
SPI 是串行外围设备接口(Serial Peripheral Interface)的缩写。串行外围设备接口是一种高速全双工同步的通信总线,被广泛应用于 ADC、LCD 等设备与 MCU 间要求通信速率较高的场合。
SPI 使用 4 根标准信号线进行通信:MISO(主输入-从输出)、MOSI(主输出-从输入)、时钟 SCLK、从机选择信号 SS(有时也称为片选信号 CS)。
SS(SlaveSelect):片选信号线,低电平有效。当有多个 SPI 设备与 MCU 相连时,每个设备的这个片选信号线是与 MCU 的引脚单独相连的,而其他的 SCK、MOSI、MISO 线则由多个设备并联到相同的 SPI 总线上。
SCK(Serial Clock):时钟信号线,由主通信设备产生,不同的设备支持的时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 f PCLK /2。
MOSI(Master Output, Slave Input):主设备输出/从设备输入引脚,即这条线上数据的方向为主机到从机。
MISO(Master Input, Slave Output):主设备输入/从设备输出引脚。即在这条线上数据的方向为从机到主机。
1.协议特点
主-从模式:一主多从。一个主机设备通过提供 SCLK 信号、选择 SS 信号(图中是NSS:低电平有效)来控制多个从机设备。因此从机设备是无法主动向主机设备发送数据的,因为 SPI 是一种“时钟驱动”的协议,没有 SCLK 时无法正常工作。
同步传输:主机设备在要交换数据时输出时钟信号,相位 CLK_PHA(CPHA:第一个边沿采样or第二个边沿采样) 和极性 CLK_POL(CPOL:空闲时时钟为低电平or高电平) 的不同,配置组成了通常所说的 4 种 SPI 模式。只要主机和从机选择同样的配置,即可完成同步传输。
SPI 工作时序如图,CS 即片选信号 SS,CPOL 代表时钟 SCK 的极性,CPHA 为MOSI/MISO 数据传输过程,当 CPOL 为 0 时,则其空闲状态为低电平,为 1 时,其空闲状态为高电平;当 CPHA 为 0 时,会在第一个边沿对数据进行采样(虚线 1),第二个边沿对数据进行输出(虚线 2);当 CPHA 为 1 时,会在第二个边沿对数据进行检测(虚线 2),第三个边沿输出(虚线 3)。
2.SPI拓扑结构
SPI 在进行一主多从工作时其连接的拓扑结构有两种, SPI 主机通常为 MCU,三个从机通常为传感器
(1)左图:SPI 总线的 SCLK,MOSI,MISO,与从机共用,片选信号 SS 独立,分别对应连接到各个器件的片选上,可以分别控制每一个从器件,通信相互独立,互不干涉,在使用中通常使用该拓扑结构
(2)SPI 主线的 SCLK,SS 与从机共用,MOSI 连接到第一个从机的 MOSI,第一个从机的 MISO 并不连回主机,而是连接到第二个从机的 MOSI,以此类推,通过最后一个从机的 MISO 连回主机,由于 SS 是共用的,所以从机会同时工作,在一次通信过程中便能把所有从机数据传输给主机,通常适用于串行转并行的场合。
二、ADC介绍
1.功能:模数转换(什么是模拟信号和数字信号不做赘述)
2.常见指标参数
·分辨率:ADC能分辨量化的最小信号能力,用二进制位数表示。有8位、12位、16位分辨率等。比如8位分辨率就是可以将模拟信号量化为一个8位的数据,数值范围是0~255。
·采样范围:ADC能进行转换的模拟信号范围是有限的,一般不高。比如ADC0809其只能转化的电压范围为0-5v,超出这个范围无法转化为准确的数字信号
假如ADC是8位,采样范围是0~5v:U=code*5/(2^8)[code:数字信号值0~255]
·采样速率:ADC一秒能够进行多少次模拟量到数字量的转化。
3.ADC128S102芯片介绍
T/H是ADC采样保持器(具体原理可以去网上搜索,下面提供一张T/H工作波形图有基础的同学可以回忆一下),控制逻辑详见第三部分。
纵坐标是数字信号(0x000-0xfff:2^12=4096),横坐标是模拟信号(0-3.3v)
此ADC芯片CPOH和CPOL以默认是0,0;即空闲时SCLK为低电平,第一个上升沿采样,下降沿输出。
三、系统整体设计
1.系统原理图
2.引脚说明
芯片-driver接口设计 | 功能 |
CS_N | 片选信号 |
SCLK | 时钟信号 |
DIN | 将数据/命令输入到芯片 |
DOUT | 将ADC数据输出到driver |
USER_CTRL-Driver接口设计 | 功能 |
Addr[2:0] | 告诉驱动需要ADC哪个通道 |
data[11:0] | 把ADC处理好的12位数据最终传给用户控制器处理 |
Conv_Go | 统一开始命令 |
Conv_Done | 完成标志位 |
3.ADC波形图
这是ADC处理16位数据的波形图:DOUT数据前四位是0(芯片特性)后12位是采样数据,DIN在第六个时间点开始输入的ADC2,1,0指的是上位机要求的通道地址。所有数据都是上升沿采样下降沿输出。
4.线性序列机结构说明
从以上波形图分析得到线性序列机
我们写的是主机(MCU驱动器部分)所以DIN(对于ADC来说是输入,对于MCU来说是输出)在下降沿赋值,DOUT(对于ADC来说是输出,对于MCU来说是输入)所以在上升沿采样
1.计数/分频单元:定义一个计数器,用以计时得到最小时间单元。
2.序列计数器,标记每一个时间点(0-34)。
3.驱动部分,负责根据时刻表中各个信号的值,在对应时间点驱动信号变化
5.代码+详细注释
`timescale 1ns / 1ps
module adc128s102(
Clk,
Reset_n,
Conv_Go,
Addr,
Conv_Done,
Data,
ADC_SCLK,
ADC_CS_N,
ADC_DIN,
ADC_DOUT
);
input Clk;
input Reset_n;
input Conv_Go;//开始工作信号
input [2:0] Addr;//ADC通道地址,由用户控制系统输入给FPGA驱动
output reg Conv_Done;//转化完成标志信号
output reg[11:0]Data;//ADC处理好的数据,输出给用户控制系统
output reg ADC_SCLK;
output reg ADC_CS_N;//片选信号,低有效
output reg ADC_DIN;//对于ADC芯片来说是输入,对于我们FPGA驱动来说是输出
input ADC_DOUT;//对于ADC芯片来说是输出,对于我们FPGA驱动来说是输入
parameter CLOCK_FREQ = 50_000_000;//系统频率
parameter SCLK_FREQ = 12_500_000;//时钟线频率
parameter MCNT_DIV_CNT = CLOCK_FREQ/(SCLK_FREQ*2)-1;//2分频
reg [7:0] DIV_CNT;//最小时间单位分频计数器
reg [5:0] LSM_CNT;//序列计数器
reg [11:0] Data_r;//ADC输出数据存储单元,为了防止避免逐位接受过程中未完成的结果体现在输出端口上,等12位结果更新完了再一次性更新到Data输出端口
reg [2:0] r_Addr;//ADC通道地址存储单元,与Data_r差不多作用
always@(posedge Clk)
if(Conv_Go)
r_Addr<=Addr;//将用户控制系统输入的通道地址传送给存储单元
else
r_Addr<=r_Addr;
reg Conv_En;//转换使能
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Conv_En <= 1'd0;
else if(Conv_Go)
Conv_En <= 1'd1;
else if((LSM_CNT == 6'd34)&&(DIV_CNT==MCNT_DIV_CNT))
Conv_En <= 1'd0;
else
Conv_En <= Conv_En;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
DIV_CNT<=0;
else if(Conv_En)begin
if(DIV_CNT==MCNT_DIV_CNT)
DIV_CNT<=0;
else
DIV_CNT<= DIV_CNT+1'b1;
end
else
DIV_CNT<=0;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
LSM_CNT<=6'b0;
else if(DIV_CNT==MCNT_DIV_CNT)begin
if(LSM_CNT==6'd34)
LSM_CNT<=6'b0;
else
LSM_CNT<= LSM_CNT+1'b1;
end
else
LSM_CNT<=LSM_CNT;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
Data_r <= 12'd0;
ADC_SCLK <= 1'd1;
ADC_DIN <= 1'd1;
ADC_CS_N <= 1'd1;
end
else if(DIV_CNT==MCNT_DIV_CNT)begin
case(LSM_CNT)
0:begin ADC_CS_N<=1'd1;ADC_SCLK<=1'd1;end
1:begin ADC_CS_N<=1'd0;end
2:begin ADC_SCLK<=1'd0;end
3:begin ADC_SCLK<=1'd1;end
4:begin ADC_SCLK<=1'd0;end
5:begin ADC_SCLK<=1'd1;end
6:begin ADC_SCLK<=1'd0;ADC_DIN<=r_Addr[2];end
7:begin ADC_SCLK<=1'd1;end
8:begin ADC_SCLK<=1'd0;ADC_DIN<=r_Addr[1];end
9:begin ADC_SCLK<=1'd1;end
10:begin ADC_SCLK<=1'd0;ADC_DIN<=r_Addr[0];end
11:begin ADC_SCLK<=1'd1;Data_r[11]<=ADC_DOUT;end
12:begin ADC_SCLK<=1'd0;end
13:begin ADC_SCLK<=1'd1;Data_r[10]<=ADC_DOUT;end
14:begin ADC_SCLK<=1'd0;end
15:begin ADC_SCLK<=1'd1;Data_r[9]<=ADC_DOUT;end
16:begin ADC_SCLK<=1'd0;end
17:begin ADC_SCLK<=1'd1;Data_r[8]<=ADC_DOUT;end
18:begin ADC_SCLK<=1'd0;end
19:begin ADC_SCLK<=1'd1;Data_r[7]<=ADC_DOUT;end
20:begin ADC_SCLK<=1'd0;end
21:begin ADC_SCLK<=1'd1;Data_r[6]<=ADC_DOUT;end
22:begin ADC_SCLK<=1'd0;end
23:begin ADC_SCLK<=1'd1;Data_r[5]<=ADC_DOUT;end
24:begin ADC_SCLK<=1'd0;end
25:begin ADC_SCLK<=1'd1;Data_r[4]<=ADC_DOUT;end
26:begin ADC_SCLK<=1'd0;end
27:begin ADC_SCLK<=1'd1;Data_r[3]<=ADC_DOUT;end
28:begin ADC_SCLK<=1'd0;end
29:begin ADC_SCLK<=1'd1;Data_r[2]<=ADC_DOUT;end
30:begin ADC_SCLK<=1'd0;end
31:begin ADC_SCLK<=1'd1;Data_r[1]<=ADC_DOUT;end
32:begin ADC_SCLK<=1'd0;end
33:begin ADC_SCLK<=1'd1;Data_r[0]<=ADC_DOUT;end
34:begin ADC_CS_N<=1'd1;end
default:ADC_CS_N<=1'd1;
endcase
end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
Data <=12'b0;
Conv_Done <=0;
end
else if((LSM_CNT == 34)&&(DIV_CNT==MCNT_DIV_CNT))begin
Conv_Done<=1;
Data<=Data_r;
end
else begin
Conv_Done <=0;
Data<=Data;
end
endmodule
6.仿真代码
`timescale 1ns / 1ps
module adc128s102_tb;
reg Clk;
reg Reset_n;
reg Conv_Go;//开始工作信号
reg [2:0] Addr;//ADC通道地址,由用户控制系统输入给FPGA驱动
wire Conv_Done;//转化完成标志信号
wire [11:0]Data;//ADC处理好的数据,输出给用户控制系统
wire ADC_SCLK;
wire ADC_CS_N;//片选信号,低有效
wire ADC_DIN;//对于ADC芯片来说是输入,对于我们FPGA驱动来说是输出
reg ADC_DOUT;//对于ADC芯片来说是输出,对于我们FPGA驱动来说是输入
adc128s102 adc128s102_inst(
.Clk(Clk),
.Reset_n(Reset_n),
.Conv_Go(Conv_Go),
.Addr(Addr),
.Conv_Done(Conv_Done),
.Data(Data),
.ADC_SCLK(ADC_SCLK),
.ADC_CS_N(ADC_CS_N),
.ADC_DIN(ADC_DIN),
.ADC_DOUT(ADC_DOUT)
);
initial Clk =1;
always #10 Clk=~Clk;
initial begin
Reset_n = 0;
Conv_Go =0;
Addr=0;
#201;
Reset_n = 1;
#200;
Conv_Go =1;
Addr=3;//ADC通道地址
#20;
Conv_Go =0;
wait(!ADC_CS_N);//等待片选信号
//16'h0a58
@(negedge ADC_SCLK);//从机(ADC芯片)下降沿输出
ADC_DOUT =0;//DB15
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB14
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB13
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB12
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB11
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB10
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB9
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB8
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB7
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB6
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB5
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB4
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB3
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB2
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB1
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB0
wait(ADC_CS_N);
#20000;
//传输第二个数据
Conv_Go = 1;
Addr = 7;//ADC通道地址
#20;
Conv_Go=0;
wait(!ADC_CS_N);
//16'h0893
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB15
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB14
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB13
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB12
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB11
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB10
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB9
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB8
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB7
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB6
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB5
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB4
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB3
@(negedge ADC_SCLK);
ADC_DOUT =0;//DB2
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB1
@(negedge ADC_SCLK);
ADC_DOUT =1;//DB0
wait(ADC_CS_N);
#200;
#20000;
$stop;
end
endmodule
7.仿真波形验证分析
我们放大看看时序是否正确:
非常正确哈~