1. 本教程重点
- 介绍模数转换原理。
- 介绍经典SPI通信协议。
- 介绍LTC2308工作原理。
- 学习Verilog代码设计。
- 学习ModelSim 仿真。
- 学习Matlab生成正弦信号。
2. 实验任务
通过FPGA对模数转换芯片(LTC2308)的采样控制,实现一个简易的数字电压表。具体功能如下:
设计一个数字电压表,通过滑动开关SW[2:0]切换LTC2308不同的ADC通道,通过按键KEY[1]触发LTC2308进行采样和转换,每次转换完点亮一个led灯作为指示,通过数码管HEX[3:0]将转换得到的数字值进行显示。
3.实验原理分析
关于模数转换原理请参考:
2-【友晶科技Terasic】基于FPGA实现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协议详解请参考:
关于代码讲解可以参考链接:
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
数码管模块:
数码管原理解说请参考:
或者点击链接: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. 实验准备工作
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测得结果如下: