基于iCE40UP5K-FPGA学习平台实现的数字电压表
项目使用基于iCE40UP5K的FPGA学习平台,包含基于iCE40UP5K的FPGA核心板和基于树莓派Pico的嵌入式系统学习拓展板。
其中最具特色的是核心板基于Lattice的ICE40UP5K FPGA,板载LPC11U35下载器,可以通过USB-C接口进行FPGA的配置,支持在ICE40UP5K上对RISC-V软核的移植以及开源的FPGA开发工具链,板上RGB三色LED灯用于简单的调试,总计28个IO用于扩展使用。
关于学习平台的具体功能特性还有很多资料都放在电子森林网站上:https://www.eetree.cn/project/detail/131
练习项目为2022年“寒假在家一起练”活动的项目1:利用ADC制作一个数字电压表
项目实现要求:
-
旋转电位计可以产生0-3.3V的电压
-
利用板上的串行ADC对电压进行转换
-
将电压值在板上的OLED屏幕上显示出来
设计思路:
根据项目的实现要求来看,该设计可以拆分成三个功能模块实现:
(1)模拟电压的产生和调节;(2)通过模数转换电路进行数据采集;(3)数据处理和输出显示
怎么去实现上面的不同的功能模块,主要从板子上给的元件和电路来考虑。
(1)模拟电压的产生和调节:
板子上带有一个蓝色的手拧电位器,实际上就是一个滑动变阻器(RV1),两端连着3.3V和GND,旋转电位计可以产生0-3.3V的电压,注意可以输出到哪个管脚(Ain2)
(2)通过模数转换电路进行数据采集:
板子上并没有ADC芯片,而项目要求则需要用ADC对直流电压等进行量化(模数转换),
这个也是这个项目的重点所在,注意到电位器的输出连到了一个比较器(TP1961-TR)的一个输入端,另一端输入为PWM波。
在要求转换速率不高的情况下,可以借助一颗高速比较器来实现对模拟信号的量化,Lattice的官网有一些资料介绍了这种简易的Sigma Delta ADC的原理:
简易Sigma Delta ADC的工作原理
在这个参考设计中,模拟输入信号被过采样并转换为数字值。简单SigmaDeltaADC(SSDADC)采用内部和外部组件的组合来实现:模拟比较器、低通RC网络、采样元件、累加器和简单数字低通滤波器(LPF)。在支持LVDSI/O的格子CPLDs或FPGAs中,只有RC网络需要在外部实现,从而减少了部件数量和成本。用户可以输入参数值来定义ADC的比特精度和采样率。
这里为了简化设计,没有非常高的量化精度和速度要求,不需要太多组件和参数,采用了最简单的直接连接方式:
直接连接 - 被测模拟信号的幅度范围为0-3.3V
通过电压采样、积分器、数字低通滤波、抽样等步骤就可以实现ADC功能。
(3)数据处理和输出显示:
OLED屏幕上可以显示一个中文提示“当前电压为”,然后显示电压的数值“x.x V”。
通过上面ADC模块采集转换得到的数值,通过标定得到对应实际的电压数值,将它的整数和小数部分分开,然后对这些内容进行译码,选定显示位置,通过控制端口输出到OLED屏幕,按一定时钟频率进行刷新显示。
项目设计框图:
逻辑电路图为下图所示:
下面简单介绍各个模块功能:
- PLL120模块,是用来将晶振时钟通过锁相环PLL进行倍频的模块,提高时钟频率可以提高OLED的刷新率,本想倍频成120M,但是这款FPGA好像不支持这么高的时钟,于是倍频到网站介绍中给出的48MHz。
- ADC模块,是用PWM和比较器实现的sigmadelta_adc,采集板载电位器的模拟电压,转换为数字信号传递给OLED模块。
- OLED模块,是将OLED点亮,以及将ADC传递过来的数字信号译码成对应的电压值的模块。
下面具体介绍板子的硬件电路:
RV1为可旋转的电位器,旋转旋钮能使其电压从0V-3.3V变化,其电压输出到GPIO28管脚。
GPIO28管脚和Ain2相连,其作为比较器C_OUT2的一端输入,另一端输入由核心板IOB_24管脚输出的PWM模拟波形。
上图能看出OLED和FPGA的管脚连接情况,通过这几个管脚输出相应的控制信息,进行刷屏显示
图片展示效果为:
制作的数字电压表可以实时显示当前电位计的电压值。
电位计可以从0调到3.3V,OLED屏一一对应显示。
代码介绍:
主要的代码分为ADC实现,OLED显示,电压数值标定三个部分。
1.利用比较器基于Sigma Delta原理实现ADC功能,来实现对模拟信号的量化。
参考Lattice官网上关于Sigma Delta ADC的介绍,可以在网站上下载到简易Sigma Delta ADC的Verilog源代码:
https://www.latticesemi.com/products/designsoftwareandip/intellectualproperty/referencedesigns/referencedesign03/simplesigmadeltaadc
module ADC_top (
clk_in,
rstn,
digital_out,
analog_cmp,
analog_out,
sample_rdy);
parameter
ADC_WIDTH = 8, // ADC Convertor Bit Precision
ACCUM_BITS = 10, // 2^ACCUM_BITS is decimation rate of accumulator
LPF_DEPTH_BITS = 3, // 2^LPF_DEPTH_BITS is decimation rate of averager
INPUT_TOPOLOGY = 1; // 0: DIRECT: Analog input directly connected to + input of comparitor
// 1: NETWORK:Analog input connected through R divider to - input of comp.
//input ports
input clk_in; // 62.5Mhz on Control Demo board
input rstn;
input analog_cmp; // from LVDS buffer or external comparitor
//output ports
output analog_out; // feedback to RC network
output sample_rdy;
output [7:0] digital_out; // connected to LED field on control demo bd.
//**********************************************************************
//
// Internal Wire & Reg Signals
//
//**********************************************************************
wire clk;
wire analog_out_i;
wire sample_rdy_i;
wire [ADC_WIDTH-1:0] digital_out_i;
wire [ADC_WIDTH-1:0] digital_out_abs;
assign clk = clk_in;
//***********************************************************************
//
// SSD ADC using onboard LVDS buffer or external comparitor
//
//***********************************************************************
sigmadelta_adc #(
.ADC_WIDTH(ADC_WIDTH),
.ACCUM_BITS(ACCUM_BITS),
.LPF_DEPTH_BITS(LPF_DEPTH_BITS)
)
SSD_ADC(
.clk(clk),
.rstn(rstn),
.analog_cmp(analog_cmp),
.digital_out(digital_out_i),
.analog_out(analog_out_i),
.sample_rdy(sample_rdy_i)
);
assign digital_out_abs = INPUT_TOPOLOGY ? ~digital_out_i : digital_out_i;
//***********************************************************************
//
// output assignments
//
//***********************************************************************
assign digital_out = ~digital_out_abs; // invert bits for LED display
assign analog_out = analog_out_i;
assign sample_rdy = sample_rdy_i;
endmodule
2:OLED的点亮,汉字和电压数值的刷新:
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;cnt_chinese <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= IDLE; state_back <= IDLE;
end
else begin
case(state)
IDLE:begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;mem_hanzi_num<=8'd0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= MAIN; state_back <= MAIN;
end
MAIN:begin
if(cnt_main >= 5'd17) cnt_main <= 5'd16;//接下来执行空操作,实现数据只刷新一次
else cnt_main <= cnt_main + 1'b1;
case(cnt_main) //MAIN状态
5'd0 : begin state <= INIT; end
5'd1 : begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd2 : begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd3 : begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd4 : begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd5 : begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd6 : begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd7 : begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd8 : begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end
5'd9 : begin y_p <= 8'hb2; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd0; state <= CHINESE; end
5'd10: begin y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd2; state <= CHINESE; end
5'd11 : begin y_p <= 8'hb2; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd4; state <= CHINESE; end
5'd12: begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd6; state <= CHINESE; end
5'd13 : begin y_p <= 8'hb2; x_ph <= 8'h16; x_pl <= 8'h00; mem_hanzi_num <= 8'd8; state <= CHINESE; end
5'd14 : begin y_p <= 8'hb5; x_ph <= 8'h15; x_pl <= 8'h08; num <= 5'd1; char <= 'd86;state <= SCAN; end //.
5'd15 : begin y_p <= 8'hb5; x_ph <= 8'h13; x_pl <= 8'h08; num <= 5'd1; char <= 'd46;state <= SCAN; end // V
5'd16 : begin y_p <= 8'hb5; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd1; char <= cesi1;state <= SCAN; end
5'd17 : begin y_p <= 8'hb5; x_ph <= 8'h14; x_pl <= 8'h08; num <= 5'd1; char <= cesi2;state <= SCAN; end
// 5'd14: begin y_p <= 8'hb5; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd6; char <= "World!";state <= SCAN; end
// default: state <= IDLE; //如果你需要动态刷新一些信息,此行应该取消注释
endcase
end
INIT:begin //初始化状态
case(cnt_init)
5'd0: begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end //复位有效
5'd1: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于3us
5'd2: begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end //复位恢复
5'd3: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于220us
5'd4: begin
if(cnt>=INIT_DEPTH) begin //当25条指令及数据发出后,配置完成
cnt <= 1'b0;
cnt_init <= cnt_init + 1'b1;
end else begin
cnt <= cnt + 1'b1; num_delay <= 16'd5;
oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
end
end
5'd5: begin cnt_init <= 1'b0; state <= MAIN; end //初始化完成,返回MAIN状态
default: state <= IDLE;
endcase
end
SCAN:begin //刷屏状态,从RAM中读取数据刷屏
if(cnt_scan == 5'd11) begin
if(num) cnt_scan <= 5'd3;
else cnt_scan <= cnt_scan + 1'b1;
end
else if(cnt_scan == 5'd12) cnt_scan <= 1'b0;
else cnt_scan <= cnt_scan + 1'b1;
case(cnt_scan)
5'd 0: begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end //定位列页地址
5'd 1: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end //定位行地址低位
5'd 2: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end //定位行地址高位
5'd 3: begin num <= num - 1'b1;end
5'd 4: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 5: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 6: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 7: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end
5'd 8: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end
5'd 9: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end
5'd10: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][15:8]; state <= WRITE; state_back <= SCAN; end
5'd11: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][7:0]; state <= WRITE; state_back <= SCAN; end
5'd12: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
CHINESE:begin //显示汉字
if(cnt_chinese == 6'd38) cnt_chinese <= 1'b0;
else cnt_chinese <= cnt_chinese+1'b1;
case(cnt_chinese)
6'd 0: begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= CHINESE; end //定位列页地址
6'd 1: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= CHINESE; end //定位行地址低位
6'd 2: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= CHINESE; end //定位行地址高位
6'd3 : begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][127:120]; state <= WRITE; state_back <= CHINESE; end
6'd4 : begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][119:112]; state <= WRITE; state_back <= CHINESE; end
6'd5 : begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][111:104]; state <= WRITE; state_back <= CHINESE; end
6'd6 : begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][103:96] ; state <= WRITE; state_back <= CHINESE; end
6'd7 : begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][95:88] ; state <= WRITE; state_back <= CHINESE; end
6'd8 : begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][87:80] ; state <= WRITE; state_back <= CHINESE; end
6'd9 : begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][79:72] ; state <= WRITE; state_back <= CHINESE; end
6'd10: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][71:64] ; state <= WRITE; state_back <= CHINESE; end
6'd11: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][63:56]; state <= WRITE; state_back <= CHINESE; end
6'd12: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][55:48]; state <= WRITE; state_back <= CHINESE; end
6'd13: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][47:40]; state <= WRITE; state_back <= CHINESE; end
6'd14: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][39:32]; state <= WRITE; state_back <= CHINESE; end
6'd15: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][31:24]; state <= WRITE; state_back <= CHINESE; end
6'd16: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][23:16]; state <= WRITE; state_back <= CHINESE; end
6'd17: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][15: 8]; state <= WRITE; state_back <= CHINESE; end
6'd18: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num][ 7: 0]; state <= WRITE; state_back <= CHINESE; end
6'd19: begin oled_dcn <= CMD; char_reg <= y_p+1; state <= WRITE; state_back <= CHINESE; end //定位列页地址
6'd20: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= CHINESE; end //定位行地址低位
6'd21: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= CHINESE; end //定位行地址高位
6'd22: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][127:120]; state <= WRITE; state_back <= CHINESE; end
6'd23: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][119:112]; state <= WRITE; state_back <= CHINESE; end
6'd24: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][111:104]; state <= WRITE; state_back <= CHINESE; end
6'd25: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][103:96] ; state <= WRITE; state_back <= CHINESE; end
6'd26: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][95:88] ; state <= WRITE; state_back <= CHINESE; end
6'd27: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][87:80] ; state <= WRITE; state_back <= CHINESE; end
6'd28: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][79:72] ; state <= WRITE; state_back <= CHINESE; end
6'd29: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][71:64] ; state <= WRITE; state_back <= CHINESE; end
6'd30: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][63:56]; state <= WRITE; state_back <= CHINESE; end
6'd31: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][55:48]; state <= WRITE; state_back <= CHINESE; end
6'd32: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][47:40]; state <= WRITE; state_back <= CHINESE; end
6'd33: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][39:32]; state <= WRITE; state_back <= CHINESE; end
6'd34: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][31:24]; state <= WRITE; state_back <= CHINESE; end
6'd35: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][23:16]; state <= WRITE; state_back <= CHINESE; end
6'd36: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][15: 8]; state <= WRITE; state_back <= CHINESE; end
6'd37: begin oled_dcn <= DATA; char_reg <= mem_hanzi[mem_hanzi_num+1][ 7: 0]; state <= WRITE; state_back <= CHINESE; end
6'd38: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
WRITE:begin //WRITE状态,将数据按照SPI时序发送给屏幕
if(cnt_write >= 5'd17) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
5'd 0: begin oled_csn <= LOW; end //9位数据最高位为命令数据控制位
5'd 1: begin oled_clk <= LOW; oled_dat <= char_reg[7]; end //先发高位数据
5'd 2: begin oled_clk <= HIGH; end
5'd 3: begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
5'd 4: begin oled_clk <= HIGH; end
5'd 5: begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
5'd 6: begin oled_clk <= HIGH; end
5'd 7: begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
5'd 8: begin oled_clk <= HIGH; end
5'd 9: begin oled_clk <= LOW; oled_dat <= char_reg[3]; end
5'd10: begin oled_clk <= HIGH; end
5'd11: begin oled_clk <= LOW; oled_dat <= char_reg[2]; end
5'd12: begin oled_clk <= HIGH; end
5'd13: begin oled_clk <= LOW; oled_dat <= char_reg[1]; end
5'd14: begin oled_clk <= HIGH; end
5'd15: begin oled_clk <= LOW; oled_dat <= char_reg[0]; end //后发低位数据
5'd16: begin oled_clk <= HIGH; end
5'd17: begin oled_csn <= HIGH; state <= DELAY; end //
default: state <= IDLE;
endcase
end
DELAY:begin //延时状态
if(cnt_delay >= num_delay) begin
cnt_delay <= 16'd0; state <= state_back;
end else cnt_delay <= cnt_delay + 1'b1;
end
default:state <= IDLE;
endcase
end
end
//OLED配置指令数据
always@(posedge rst_n)
begin
cmd[0 ] = {8'hae};
cmd[1 ] = {8'hd5};
cmd[2 ] = {8'h80};
cmd[3 ] = {8'ha8};
cmd[4 ] = {8'h3f};
cmd[5 ] = {8'hd3};
cmd[6 ] = {8'h00};
cmd[7 ] = {8'h40};
cmd[8 ] = {8'h8d};
cmd[9 ] = {8'h14};
cmd[10] = {8'h20};
cmd[11] = {8'h02};
cmd[12] = {8'hc8};
cmd[13] = {8'ha1};
cmd[14] = {8'hda};
cmd[15] = {8'h12};
cmd[16] = {8'h81};
cmd[17] = {8'hcf};
cmd[18] = {8'hd9};
cmd[19] = {8'hf1};
cmd[20] = {8'hdb};
cmd[21] = {8'h40};
cmd[22] = {8'haf};
end
3.数字信号译码成对应电压数值的代码:
这是遇到的主要难题之一,需要做一个准确的标定。这里用了数值标定方法,与实际电压表测试数值一致,精度在0.1V。
有兴趣的可以研究下数字电路的细节,给出拟合关系。不过我感觉受限于比较器的方法,精度不能提高多少。
将PWM积分方式得出的数值转换为电位器真实电压值后,将整数部分与小数部分分开,刷新OLED屏的显示数值。
always@ (posedge clk or negedge rst_n)
if(!rst_n)begin
dy_state <=0;
end
else if(data_in>250)
dy_state <= 33;
else if(data_in>240)
dy_state <= 32;
else if(data_in>230)
dy_state <= 31;
else if(data_in>222)
dy_state <= 30;
else if(data_in>214)
dy_state <= 29;
else if(data_in>206)
dy_state <= 28;
else if(data_in>198)
dy_state <= 27;
else if(data_in>190)
dy_state <= 26;
else if(data_in>182)
dy_state <= 25;
else if(data_in>176)
dy_state <= 24;
else if(data_in>163)
dy_state <= 23;
else if(data_in>154)
dy_state <= 22;
else if(data_in>154)//
dy_state <= 21;
else if(data_in>152)
dy_state <= 20;
else if(data_in>150)///
dy_state <= 19;
else if(data_in>136)
dy_state <= 18;
else if(data_in>130)
dy_state <= 17;
else if(data_in>124)
dy_state <= 16;
else if(data_in>112)
dy_state <= 15;
else if(data_in>104)
dy_state <= 14;
else if(data_in>98)
dy_state <= 13;
else if(data_in>95)
dy_state <= 12;
else if(data_in>82)//
dy_state <= 11;
else if(data_in>78)
dy_state <= 10;
else if(data_in>75)
dy_state <= 9;
else if(data_in>65)
dy_state <= 8;
else if(data_in>55)
dy_state <= 7;
else if(data_in>48)
dy_state <= 6;
else if(data_in>40)
dy_state <= 5;
else if(data_in>36)
dy_state <= 4;
else if(data_in>27)
dy_state <= 3;
else if(data_in>17)
dy_state <= 2;
else if(data_in>10)
dy_state <= 1;
else begin
dy_state <= 0;
end
always@ (posedge clk or negedge rst_n)
if(!rst_n)begin
cesi1<=0;
cesi2<=0;
end
else begin
case (dy_state)
33: begin
cesi1<=3;
cesi2<=3;
end
32: begin
cesi1<=3;
cesi2<=2;
end
31: begin
cesi1<=3;
cesi2<=1;
end
30: begin
cesi1<=3;
cesi2<=0;
end
29: begin
cesi1<=2;
cesi2<=9;
end
28: begin
cesi1<=2;
cesi2<=8;
end
27: begin
cesi1<=2;
cesi2<=7;
end
26: begin
cesi1<=2;
cesi2<=6;
end
25: begin
cesi1<=2;
cesi2<=5;
end
24: begin
cesi1<=2;
cesi2<=4;
end
23: begin
cesi1<=2;
cesi2<=3;
end
22: begin
cesi1<=2;
cesi2<=2;
end
21: begin
cesi1<=2;
cesi2<=1;
end
20: begin
cesi1<=2;
cesi2<=0;
end
19: begin
cesi1<=1;
cesi2<=9;
end
18: begin
cesi1<=1;
cesi2<=8;
end
17: begin
cesi1<=1;
cesi2<=7;
end
16: begin
cesi1<=1;
cesi2<=6;
end
15: begin
cesi1<=1;
cesi2<=5;
end
14: begin
cesi1<=1;
cesi2<=4;
end
13: begin
cesi1<=1;
cesi2<=3;
end
12: begin
cesi1<=1;
cesi2<=2;
end
11: begin
cesi1<=1;
cesi2<=1;
end
10: begin
cesi1<=1;
cesi2<=0;
end
9: begin
cesi1<=0;
cesi2<=9;
end
8: begin
cesi1<=0;
cesi2<=8;
end
7: begin
cesi1<=0;
cesi2<=7;
end
6: begin
cesi1<=0;
cesi2<=6;
end
5: begin
cesi1<=0;
cesi2<=5;
end
4: begin
cesi1<=0;
cesi2<=4;
end
3: begin
cesi1<=0;
cesi2<=3;
end
2: begin
cesi1<=0;
cesi2<=2;
end
1: begin
cesi1<=0;
cesi2<=1;
end
default:begin
cesi1<=0;
cesi2<=0;
end
endcase
end
项目的工程及完整的源代码见百度网盘链接:
链接:https://pan.baidu.com/s/1Lv9MqF-5rlk-GCKKd6Ya8w
提取码:ky29
资源报告:
遇到的主要难题及解决方法:
顶层对各模块的例化一直让我很头疼,多亏了电子森林上例程和很多同学开源的项目,在反复学习反复修改下,完成了顶层模块的编写。对比单片机的单线执行,FPGA更像是在组织各个模块分工合作。
板子执行逻辑顺序混乱,各部分执行先后顺序、因果关系错误。在各个模块中设置了状态变量,从状态机的角度去解决问题,并且配套使用许多if-else语句用来做条件选择。
项目总结:
总的来说,这次项目很简单,所需要的模块也不多,主要部分是通过比较器基于简单SigmaDeltaADC的原理来进行模数转换、OLED显示等。在Lattice的官网和电子森林官网上都可以搜索到现成的例程可以直接使用。对于整个项目,就基本简化成为,理解例程代码和方法,把几个部分,通过一个总文件串起来,形成一个整体。而这一部分也相当简单。通过本次项目,我大致学会了Verliog硬件编译语言和FPGA的一些应用,例如,时钟倍频PLL、OLED显示并深化了对状态机的理解。同时这次项目也锻炼了我在短时间学习知识的能力、信息搜集能力并培养了我读懂数据手册和时序图的能力。
未来的计划或建议:
通过硬禾学堂的这次机会,我对数字逻辑电路、FPGA,还有Verilog语言的使用有了入门认识,未来计划更深入地了解更多知识。
数字电压表其实可以作为示波器的简化版本,在后续开发中简化版本可以去了解基于FPGA的数字示波器的开发
在后期的学习过程中要加深对时序的理解,提高自己的FPGA技术。