1. 本教程重点

  1. 介绍模数转换原理。
  2. 介绍经典SPI通信协议。
  3. 介绍LTC2308工作原理。
  4. 学习Verilog代码设计。
  5. 学习ModelSim 仿真。
  6. 学习Matlab生成正弦信号。

2. 实验任务

通过FPGA对模数转换芯片(LTC2308)的采样控制,实现一个简易的数字电压表。具体功能如下:

设计一个数字电压表,通过滑动开关SW[2:0]切换LTC2308不同的ADC通道,通过按键KEY[1]触发LTC2308进行采样和转换,每次转换完点亮一个led灯作为指示,通过数码管HEX[3:0]将转换得到的数字值进行显示。

 

3.实验原理分析

关于模数转换原理请参考:

2-【友晶科技Terasic】基于FPGA实现LTC2308控制器的设计——模数转换原理 

关于LTC2308芯片解读请参考:

3-【友晶科技Terasic】基于FPGA实现LTC2308控制器的设计——LTC2308数据手册解读

关于DE10-Standard 开发板LTC2308电路图如下:

 

该实验的功能设计框图如下:

 

 工程最终的RTL Viewer图如下:

 

4. Verilog代码

 工程Top文件

module digital_voltmeter(
    input          CLOCK_50,
    input  [1:0] KEY,
    input  [2:0] SW,
    output       LED,    
    output [6:0] HEX0,
    output [6:0] HEX1,
    output [6:0] HEX2,
    output [6:0] HEX3,
    output ADC_CONVST,
    output ADC_SCK,
    output ADC_SDI,
    input ADC_SDO 
    
);

wire clk_40M;
pll_40 u0 (
        .refclk   (CLOCK_50),    //  refclk.clk
        .rst      (~KEY[0]), //   reset.reset
        .outclk_0 (clk_40M)  // outclk0.clk
    );
    
seg_display u1(
        .clk(clk_40M),
        .rst_n(KEY[0]),
        .seg_data(bcd_data[3:0]),
        .seg_out(HEX0)
);
seg_display u2(
        .clk(clk_40M),
        .rst_n(KEY[0]),
        .seg_data(bcd_data[7:4]),
        .seg_out(HEX1)
);
seg_display u3(
        .clk(clk_40M),
        .rst_n(KEY[0]),
        .seg_data(bcd_data[11:8]),
        .seg_out(HEX2)
);
seg_display u4(
        .clk(clk_40M),
        .rst_n(KEY[0]),
        .seg_data(bcd_data[15:12]),
        .seg_out(HEX3)
);

adc_ltc2308 u5 (
        .clk(clk_40M),  
        .measure_start(KEY[1]), 
        .measure_done(LED),
        .measure_ch(SW),
        .measure_dataread(bin_data),    
        .ADC_CONVST(ADC_CONVST),
        .ADC_SCK(ADC_SCK),
        .ADC_SDI(ADC_SDI),
        .ADC_SDO(ADC_SDO) 
    );

wire    [11:0]    bin_data;
wire    [15:0]    bcd_data;
bin_to_bcd u6(
        .bin_data(bin_data),
        .bcd_data(bcd_data)
);
    
endmodule

bin_to_bcd 二进制转十进制的代码:

module bin_to_bcd(
    input        [11:0]    bin_data,
    output    [15:0]    bcd_data

    
);

assign bcd_data[3:0]  = bin_data%10;                //算出十进制数的千分位
assign bcd_data[7:4]  = (bin_data/10)%10;            //算出十进制数的百分位
assign bcd_data[11:8] = (bin_data/100)%10;        //算出十进制数的十分位位
assign bcd_data[15:12] = (bin_data/1000)%10;        //算出十进制数的个位

endmodule

 

LTC2308控制模块

 关于SPI协议详解请参考:

SPI协议详解 

关于代码讲解可以参考链接:

4-【友晶科技Terasic】基于FPGA实现LTC2308控制器的设计——FPGA实现SPI协议

 

module adc_ltc2308(
    clk, // max 40mhz
    
    // start measure
    measure_start, // posedge triggle
    measure_ch,
    measure_done,
    measure_dataread,
    
    // adc interface
    ADC_CONVST,
    ADC_SCK,
    ADC_SDI,
    ADC_SDO 
);

input                                clk;

// start measure
input                                measure_start;
input        [2:0]                    measure_ch;
output    reg                    measure_done;
output    [11:0]                measure_dataread;



output                          ADC_CONVST;
output                          ADC_SCK;
output    reg                  ADC_SDI;
input                           ADC_SDO;


/////////////////////////////////
// Timing definition 

// using 40MHz clock
// to acheive fsample = 500KHz
// ntcyc = 2us / 25ns  = 80



`define DATA_BITS_NUM        12
`define CMD_BITS_NUM            6
`define CH_NUM                    8

`define tWHCONV            3   // CONVST High Time, min 20 ns
`define tCONV                 64 //52  // tCONV: type 1.3 us, MAX 1.6 us, 1600/25(assumed clk is 40mhz)=64  -> 1.3us/25ns = 52
                              // set 64 for suite for 1.6 us max
//                         +12 //data

`define tHCONVST           320 // 12  // here set 320( fsample = 100KHz) for if ADC input impedance is high, see below  
                                // If the source impedance of the driving circuit is low, the ADC inputs can be driven directly.
                                //Otherwise, more acquisition time should be allowed for a source with higher impedance.

                                          // for acheiving 500KHz  fmax. set n cyc = 80.
`define tCONVST_HIGH_START    0     
`define tCONVST_HIGH_END      (`tCONVST_HIGH_START+`tWHCONV) 

`define tCONFIG_START        (`tCONVST_HIGH_END)     
`define tCONFIG_END          (`tCLK_START+`CMD_BITS_NUM - 1)     

`define tCLK_START             (`tCONVST_HIGH_START+`tCONV)
`define tCLK_END                (`tCLK_START+`DATA_BITS_NUM)

`define tDONE                    (`tCLK_END+`tHCONVST)

// create triggle message: reset_n
reg pre_measure_start;
always @ (posedge clk)    
begin
    pre_measure_start <= measure_start;
end

/*synthesis keep*/wire reset_n;
assign reset_n = (pre_measure_start & ~measure_start)?1'b0:1'b1;

// tick
reg [15:0] tick;    
always @ (posedge clk or negedge reset_n)    
begin
    if (~reset_n)
        tick <= 0;
    else if (tick < `tDONE)
        tick <= tick + 1;
end


/////////////////////////////////
// ADC_CONVST 
assign ADC_CONVST = (tick >= `tCONVST_HIGH_START && tick < `tCONVST_HIGH_END)?1'b1:1'b0;

/////////////////////////////////
// ADC_SCK 

reg clk_enable; // must sync to clk in clk low
always @ (negedge clk or negedge reset_n)     
begin
    if (~reset_n)
        clk_enable <= 1'b0;
    else if ((tick >= `tCLK_START && tick < `tCLK_END))
        clk_enable <= 1'b1;
    else
        clk_enable <= 1'b0;
end

assign ADC_SCK = clk_enable?clk:1'b0;


///////////////////////////////
// read data
reg [(`DATA_BITS_NUM-1):0] read_data;
reg [3:0] write_pos;



assign measure_dataread = read_data;

always @ (negedge clk or negedge reset_n)    
begin
    if (~reset_n)
    begin
        read_data <= 0;
        write_pos <= `DATA_BITS_NUM-1;
    end
    else if (clk_enable)
    begin
        read_data[write_pos] <= ADC_SDO;
        write_pos <= write_pos - 1;
    end
end

///////////////////////////////
// measure done
wire read_ch_done;

assign read_ch_done = (tick == `tDONE)?1'b1:1'b0;

always @ (posedge clk or negedge reset_n)    
begin
    if (~reset_n)
        measure_done <= 1'b0;
    else if (read_ch_done)
        measure_done <= 1'b1;
end

///////////////////////////////
// adc channel config

// pre-build config command
reg [(`CMD_BITS_NUM-1):0] config_cmd;


`define UNI_MODE        1'b1   //1: Unipolar, 0:Bipolar
`define SLP_MODE        1'b0   //1: enable sleep

always @(negedge reset_n)
begin
    if (~reset_n)
    begin
        case (measure_ch)
            0 : config_cmd <= {4'h8, `UNI_MODE, `SLP_MODE}; 
            1 : config_cmd <= {4'hC, `UNI_MODE, `SLP_MODE}; 
            2 : config_cmd <= {4'h9, `UNI_MODE, `SLP_MODE}; 
            3 : config_cmd <= {4'hD, `UNI_MODE, `SLP_MODE}; 
            4 : config_cmd <= {4'hA, `UNI_MODE, `SLP_MODE}; 
            5 : config_cmd <= {4'hE, `UNI_MODE, `SLP_MODE}; 
            6 : config_cmd <= {4'hB, `UNI_MODE, `SLP_MODE}; 
            7 : config_cmd <= {4'hF, `UNI_MODE, `SLP_MODE}; 
            default : config_cmd <= {4'hF, 2'b00}; 
        endcase
    end
end

// serial config command to adc chip
wire config_init;
wire config_enable;
wire config_done;
reg [2:0] sdi_index;

assign config_init = (tick == `tCONFIG_START)?1'b1:1'b0;    
assign config_enable = (tick > `tCLK_START && tick <= `tCONFIG_END)?1'b1:1'b0;    // > because this is negative edge triggle
assign config_done = (tick > `tCONFIG_END)?1'b1:1'b0;    
always @(negedge clk)    
begin
    if (config_init)
    begin
        ADC_SDI <= config_cmd[`CMD_BITS_NUM-1];
        sdi_index <= `CMD_BITS_NUM-2;
    end
    else if (config_enable)
    begin
        ADC_SDI <= config_cmd[sdi_index];
        sdi_index <= sdi_index - 1;
    end
    else if (config_done)
        ADC_SDI <= 1'b0; //
end




endmodule

 

数码管模块

 数码管原理解说请参考:

基于FPGA的贪吃蛇游戏设计(二)——数码管驱动模块

或者点击链接:https://mp.weixin.qq.com/s/4p4HkPJ53kQS4s2-575plQ

module seg_display(
    input        clk,                        //系统时钟
    input        rst_n,                    //系统复位
    input        [3:0]        seg_data,   //数码管要显示的数值
    
    output    reg[6:0]    seg_out        //数码管段选信号
);

always @ (posedge clk or negedge rst_n)    
begin
    if (~rst_n)
        seg_out <= 0;
    else begin
    case(seg_data)
        4'b0001: seg_out= 7'b1111001;     //数码管显示数字1     
        4'b0010: seg_out= 7'b0100100;     //数码管显示数字2    
        4'b0011: seg_out= 7'b0110000;     //数码管显示数字3    
        4'b0100: seg_out= 7'b0011001;     //数码管显示数字4    
        4'b0101: seg_out= 7'b0010010;     //数码管显示数字5     
        4'b0110: seg_out= 7'b0000010;     //数码管显示数字6    
        4'b0111: seg_out= 7'b1111000;     //数码管显示数字7    
        4'b1000: seg_out= 7'b0000000;     //数码管显示数字8    
        4'b1001: seg_out= 7'b0011000;     //数码管显示数字9    
        4'b0000: seg_out= 7'b1000000;     //数码管显示数字0
        endcase 
    end
end

endmodule

5. LTC2308控制模块功能仿真

关于LTC2308控制模块的仿真请参考连接:

7-【友晶科技Terasic】基于FPGA实现LTC2308控制器的设计——ModelSim仿真与Matlab模拟信号产生

6. 实验准备工作

硬件平台 :DE10-Standard 开发板
(或者是DE1-SOC、DE25-Standard、C5G、TSP开发板也都支持,DE10-Nano、ADC-SOC、DE0-Nano-SOC 、TSOM(ADC在TSOM-BB底板上)这四种开发板上虽然有LTC2308器件,但是没有数码管显示,所以工程不能直接套用。 另外,针对DE25-Standard开发板需要Quartus Pro v24.1及以上版本才行)
开发和调试工具:Quartus17.1
 

7. 实验步骤

1. 在开始菜单里面点击Quartus软件打开它:

 2. 按照基于DE1-SOC的My_first_fpga去创建一个新的digital_voltmeter工程,top文件Verilog代码直接拷贝上面的。

3. 继续创建2个Verilog 文件(seg_display.v和adc_ltc2308.v),代码直接拷贝上面的。

4. 在IP Catalog里面输入PLL,并双击打开Altera PLL IP:

 5. 保存路径选择digital_voltmeter工程下,PLL 模块命名为pll_40:

 6. 设置PLL 的参数如下:

 7. 对工程进行分析和综合:

 

8. 进行引脚分配:

 根据DE10-Standard\Manual\DE10-Standard_User_manual.pdf手册的引脚分配表或者直接查看DE10-Standard\Schematic\DE10-Standard.pdf电路图完成以下引脚分配:

 9. 进行工程全编译,生成sof文件:

 

 

8. 下板验证

没有电位器的话也可以用一根杜邦线完成测试,具体测试如下:

1. 连接开发板的Blaster口到PC,然后连接开发板电源给开发板供电。

2. 继续参考基于DE1-SOC的My_first_fpga将digital_voltmeter.sof配置到FPGA 开发板当中。

3. 切换SW[2:0]=00,选择通道0。

4. 通道0 连接到GND引脚(参考下图找到GND引脚)

 

5. 按KEY0复位,按KEY1触发LTC2308进行测量,测得结果如下:

6. 继续选择通道0去连接到3.3V引脚,

 7. 按KEY1测得结果如下: