基于iCE40UP5K-FPGA学习平台实现的数字电压表
在iCE40UP5K的FPGA学习平台上利用比较器和PWM波基于sigma delta原理实现了ADC功能,测量板载电位器的电压值,制作的数字电压表,通过OLED实时显示,精度0.1V以内。
标签
FPGA
ADC
2022寒假在家练
karn
更新2022-03-02
中山大学
658

基于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制作一个数字电压表

项目实现要求:

  1. 旋转电位计可以产生0-3.3V的电压
  2. 利用板上的串行ADC对电压进行转换
  3. 将电压值在板上的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的工作原理

简易Sigma Delta ADC的工作原理

 

在这个参考设计中,模拟输入信号被过采样并转换为数字值。简单SigmaDeltaADC(SSDADC)采用内部和外部组件的组合来实现:模拟比较器、低通RC网络、采样元件、累加器和简单数字低通滤波器(LPF)。在支持LVDSI/O的格子CPLDs或FPGAs中,只有RC网络需要在外部实现,从而减少了部件数量和成本。用户可以输入参数值来定义ADC的比特精度和采样率。

这里为了简化设计,没有非常高的量化精度和速度要求,不需要太多组件和参数,采用了最简单的直接连接方式:

Fsf7dRaboWB84cN0wTmWw5J6ruiH

直接连接 - 被测模拟信号的幅度范围为0-3.3V

 

通过电压采样、积分器、数字低通滤波、抽样等步骤就可以实现ADC功能。

(3)数据处理和输出显示:

OLED屏幕上可以显示一个中文提示“当前电压为”,然后显示电压的数值“x.x V”。

通过上面ADC模块采集转换得到的数值,通过标定得到对应实际的电压数值,将它的整数和小数部分分开,然后对这些内容进行译码,选定显示位置,通过控制端口输出到OLED屏幕,按一定时钟频率进行刷新显示。

 

项目设计框图:

Fn_4Fn9YglTSVybVlvcEJVEN_us1

 

逻辑电路图为下图所示:

Fn89mYHnlogoCOTyVavnM-tPsUEQ

下面简单介绍各个模块功能:

  •  PLL120模块,是用来将晶振时钟通过锁相环PLL进行倍频的模块,提高时钟频率可以提高OLED的刷新率,本想倍频成120M,但是这款FPGA好像不支持这么高的时钟,于是倍频到网站介绍中给出的48MHz。
  •  ADC模块,是用PWM和比较器实现的sigmadelta_adc,采集板载电位器的模拟电压,转换为数字信号传递给OLED模块。
  •  OLED模块,是将OLED点亮,以及将ADC传递过来的数字信号译码成对应的电压值的模块。

 

下面具体介绍板子的硬件电路:

FtP7F13GQQhN3kCVjVtZPcUqs8EW

RV1为可旋转的电位器,旋转旋钮能使其电压从0V-3.3V变化,其电压输出到GPIO28管脚。

 

FpM8_IZ6BjhyWKJh_FnxwHjcMiiO

GPIO28管脚和Ain2相连,其作为比较器C_OUT2的一端输入,另一端输入由核心板IOB_24管脚输出的PWM模拟波形。

 

FlKyBOz2FiyzYci3km6QTnweb9lt

上图能看出OLED和FPGA的管脚连接情况,通过这几个管脚输出相应的控制信息,进行刷屏显示

 

图片展示效果为:

FojzJAWK-n5EdhQhHhxGliaKn6RI

制作的数字电压表可以实时显示当前电位计的电压值。

电位计可以从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

资源报告:

FjywijqnxuJFM7oOKwzsIFEUzPEu

Fi965Bt4B9-Po36FPoVGdrF5BSOR

FhIcLT0RRUbXb7RQ3QEMEmoik5ge

 

遇到的主要难题及解决方法:

顶层对各模块的例化一直让我很头疼,多亏了电子森林上例程和很多同学开源的项目,在反复学习反复修改下,完成了顶层模块的编写。对比单片机的单线执行,FPGA更像是在组织各个模块分工合作。

板子执行逻辑顺序混乱,各部分执行先后顺序、因果关系错误。在各个模块中设置了状态变量,从状态机的角度去解决问题,并且配套使用许多if-else语句用来做条件选择。

 

项目总结:

总的来说,这次项目很简单,所需要的模块也不多,主要部分是通过比较器基于简单SigmaDeltaADC的原理来进行模数转换、OLED显示等。在Lattice的官网和电子森林官网上都可以搜索到现成的例程可以直接使用。对于整个项目,就基本简化成为,理解例程代码和方法,把几个部分,通过一个总文件串起来,形成一个整体。而这一部分也相当简单。通过本次项目,我大致学会了Verliog硬件编译语言和FPGA的一些应用,例如,时钟倍频PLL、OLED显示并深化了对状态机的理解。同时这次项目也锻炼了我在短时间学习知识的能力、信息搜集能力并培养了我读懂数据手册和时序图的能力。

 

未来的计划或建议:

通过硬禾学堂的这次机会,我对数字逻辑电路、FPGA,还有Verilog语言的使用有了入门认识,未来计划更深入地了解更多知识。

数字电压表其实可以作为示波器的简化版本,在后续开发中简化版本可以去了解基于FPGA的数字示波器的开发

在后期的学习过程中要加深对时序的理解,提高自己的FPGA技术。

 

附件下载
oled_ADC.7z
项目工程文件
团队介绍
苏梓康-在读研究生
团队成员
karn
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号