基于SPI接口的ADC芯片功能实现(128S102芯片)

目录

一、SPI原理

1.协议特点

2.SPI拓扑结构

二、ADC介绍

三、系统整体设计

1.系统原理图

2.引脚说明

3.ADC波形图

4.线性序列机结构说明

5.代码+详细注释

6.仿真代码

7.仿真波形验证分析


本文根据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.仿真波形验证分析

我们放大看看时序是否正确:

非常正确哈~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值