2021暑假一起练一基于小脚丫FPGA的数字电压表
2021年暑假一起练一基于小脚丫FPGA的数字电压表,其中包括电压信号采集、oled电压值显示、和串口发送上位机显示等主要内容。
标签
FPGA
大写DDG
更新2021-09-05
939

基于小脚丫FPGA的数字电压表

一、硬件连接示意图

Fveezcd0h3rYKt7dcj3VoFzO6J5O

其中,电位计采集到的电压信号由ADS7868进行AD转换到FPGA里面,经过内部数字逻辑转换,将电压值在oled屏幕上显示,同时经串口发送给电脑上位机显示。

 

二、顶层模块

FsVxPEw1r7OPhOD5Ccj9oDH6c0lJ

其中,顶层三个例化模块负责与硬件中的三个主要模块负责对接。代码如下:

module top(
	input         clk,
	input         rst,
	input     adc_sda,  //adc7868 SPI总线SDA
	
	output    adc_cs,   //adc7868 SPI总线CS
	output    adc_clk,  //adc7868 SPI总线SCK
	
	input  uart_rxd_pin, //串口接收引脚
	output uart_txd_pin, //串口发送引脚
	
	output   reg [13:0]led, //led灯全部熄灭 
	
	output	 oled_csn,	//OLED使能 
	output	 oled_rst,	//OLED复位
	output	 oled_dcn,	//OLED数据指令控制
	output	 oled_clk,	//olEd时钟
	output	 oled_dat	//OLED数据

);
	//用于 电压采集 例化模块的连接
	wire adc_cs0,adc_clk0;
	wire [19:0]bcd_code;
	
	assign adc_cs  = adc_cs0 ;
	assign adc_clk = adc_clk0;
	
	//例化 电压采集模块
	voltage_acquisition u1(
		.clk(clk),
		.rst(rst),
		.adc_dat(adc_sda),   
		.adc_clk(adc_clk0),	  
		.adc_cs(adc_cs0),	  
		.bcd_code(bcd_code)
	);	
	
	
	wire	 oled_csn0;	//OLED使能 
	wire	 oled_rst0;	//OLED复位
	wire	 oled_dcn0;	//OLED数据指令控制
	wire	 oled_clk0;	//olEd时钟
	wire	 oled_dat0;	//OLED数据
	assign oled_csn = oled_csn0;
	assign oled_rst = oled_rst0;
	assign oled_dcn = oled_dcn0;
	assign oled_clk = oled_clk0;
	assign oled_dat = oled_dat0;
	
	//检测电压值是否发生变化
	reg [7:0]voltage,voltage_pre; //当前状态/前一个状态
	always @(posedge clk or negedge rst)
		if(!rst) begin
			led <= 14'b111_111_1111_1111; //led灯全部熄灭
			voltage <= 8'd0;
			voltage_pre <= 8'd0;
		end
		else begin
			voltage <= {bcd_code[19:12]};
			voltage_pre <= voltage;
		end
		
		
	reg v_change_flag; //电压值变化标志位
	always @(posedge clk or negedge rst) begin
		if(!rst)
			v_change_flag <= 1'b0;
		else if(voltage == voltage_pre)
			v_change_flag <= 1'b0;
		else
			v_change_flag <= 1'b1;
	end	
	
	//电压值改变 触发 串口发送标志位
	reg [23:0]time_cnt;
	always @(posedge clk or negedge rst) begin
		if(!rst) begin
			txd_ready_flag <= 1'b0;
			time_cnt <= 24'd0;
		end
		else if(v_change_flag == 1'b1) begin
			txd_ready_flag <= 1'b1;
			time_cnt <= 24'd0;
		end
		else if(time_cnt == 24'd6_000_000) begin
			time_cnt <= 24'd0;
			txd_ready_flag <= 1'b1;
		end
		else begin
			time_cnt <= time_cnt + 1'b1;
			txd_ready_flag <= 1'b0;
		end
	end
			
	
	wire  [7:0]v_data;
	assign v_data = voltage;
	//例化 oled显示模块
	oled12832 u2(
		.clk(clk),		        //12MHz系统时钟
		.rst_n(rst),		    //系统复位,低有效
		.voltage(v_data),		
		.oled_csn(oled_csn0),	//OLED液晶屏使能
		.oled_rst(oled_rst0),	//OLED液晶屏复位
		.oled_dcn(oled_dcn0),	//OLED数据指令控制
		.oled_clk(oled_clk0),	//OLED时钟信号
		.oled_dat(oled_dat0)	//OLED数据信号
	);
	
	

	//例化 电压采集模块
	reg txd_ready_flag;
	wire [7:0]txd_data;
	assign txd_data = voltage;
	wire uart_txd_pin0;
	assign uart_txd_pin = uart_txd_pin0;
	wire rxd_finish_flag;
	wire [7:0]rxd_data;
	
	uart_validdata u4(
		.clk(clk),
		.rst(rst),
		.uart_rxd_pin(uart_rxd_pin),
		.txd_ready_flag(txd_ready_flag),
		.txd_data(txd_data),       //高四位是整数的bcd码值,低四位是小数的bcd码值
	
		.uart_txd_pin(uart_txd_pin0),
		.rxd_finish_flag(rxd_finish_flag),
		.rxd_data(rxd_data)
	);


endmodule

 

三、电压采集模块

Fo1-4C7KNh17VjXm3uGG4Di3eCt5

电压采集模块主要由 ADC7868_driver 和 bin_to_bcd 两个部分组成,功能分别是:

ADC7868_driver:通过spi协议读取当前的数字信号(8'b0000_0000 ~ 8'b1111_1111)。

bin_to_bcd:将当前计算出来的二进制存储的电压信号转化为bcd码存储的电压信号。

代码如下(主要参考简易电压表设计,其中ADC081S101与ADC7868都是3线的spi通信接口,驱动逻辑大致都相同,可以简单修改后使用):

module voltage_acquisition(
	input clk,
	input rst,
	
	input  adc_dat,   //adc7868 SPI总线SDA
	output adc_clk,	  //adc7868 SPI总线SCK
	output adc_cs,	  //adc7868 SPI总线CS

	output [19:0]bcd_code

);	
	wire       adc_done;
	wire  [7:0]adc_data;
	wire cs,sclk;
	assign	adc_cs   =  cs; 
	assign	adc_clk  =  sclk;	
	
	ADC7868_driver u1(
		.clk(clk),
		.rst_n(rst),
		.adc_cs(cs),
		.adc_clk(sclk),
		.adc_dat(adc_dat),
		.adc_done(adc_done),
		.adc_data(adc_data)
	);
		
	wire    [15:0]bin_code;
	assign  bin_code = adc_data * 16'd130;
	
	bin_to_bcd u2(
		.rst_n(rst),
		.bin_code(bin_code),
		.bcd_code(bcd_code)
	);


endmodule

 

(一)ADC7868_driver模块

功能:将电位计的电压信号(模拟量)转为数字信号。

代码如下(参考简易电压表设计其中ADC081S101与ADC7868都是3线的spi通信接口,驱动逻辑大致都相同,可以简单修改后使用):

module ADC7868_driver(clk,rst_n,adc_cs,adc_clk,adc_dat,adc_done,adc_data);
	input				clk;		//系统时钟
	input				rst_n;  	//系统复位,低有效
	output	reg			adc_cs;		//SPI总线CS
	output	reg			adc_clk;	//SPI总线SCK
	input				adc_dat;	//SPI总线SDA
	output	reg			adc_done;	//ADC采样完成标志
	output	reg [7:0]	adc_data;	//ADC采样数据
	
	parameter
		HIGH = 1'b1,
		LOW  = 1'b0;
	
	reg [7:0] cnt; //计数器
	always @(posedge clk or negedge rst_n) begin
		if(!rst_n) cnt <= 1'b0;
		else if(cnt >= 8'd34) cnt <= 1'b0;
		else cnt <= cnt + 1'b1;
	end
	
	reg [7:0] data;
	always @(posedge clk or negedge rst_n)
		if(!rst_n) begin
			adc_cs <= HIGH; adc_clk <= HIGH;
		end else case(cnt)
			8'd0 :  begin adc_cs <= HIGH; adc_clk <= HIGH; end
			8'd1 :  begin adc_cs <= LOW;  adc_clk <= HIGH; end
			8'd2,8'd4,8'd6,8'd8,8'd10,8'd12,8'd14,8'd16,
			8'd18,8'd20,8'd22,8'd24,8'd26,8'd28,8'd30,8'd32:	
					begin adc_cs <= LOW;  adc_clk <= LOW;  end
			8'd3 :  begin adc_cs <= LOW;  adc_clk <= HIGH; end //0
			8'd5 :  begin adc_cs <= LOW;  adc_clk <= HIGH; end //1
			8'd7 :  begin adc_cs <= LOW;  adc_clk <= HIGH; end //2
			8'd9 :  begin adc_cs <= LOW;  adc_clk <= HIGH; data[7] <= adc_dat; end //3
			8'd11 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[6] <= adc_dat; end //4
			8'd13 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[5] <= adc_dat; end //5
			8'd15 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[4] <= adc_dat; end //6
			8'd17 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[3] <= adc_dat; end //7
			8'd19 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[2] <= adc_dat; end //8
			8'd21 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[1] <= adc_dat; end //9
			8'd23 : begin adc_cs <= LOW;  adc_clk <= HIGH; data[0] <= adc_dat; end //10
			8'd25 : begin adc_cs <= LOW;  adc_clk <= HIGH; adc_data <= data; end //11
			8'd27 : begin adc_cs <= LOW;  adc_clk <= HIGH; adc_done <= HIGH; end //12
			8'd29 : begin adc_cs <= LOW;  adc_clk <= HIGH; adc_done <= LOW; end //13
			8'd31 : begin adc_cs <= LOW;  adc_clk <= HIGH; end //14
			8'd33 : begin adc_cs <= LOW;  adc_clk <= HIGH; end //15
			8'd34 : begin adc_cs <= HIGH;  adc_clk <= HIGH; end
			default : begin adc_cs <= HIGH;  adc_clk <= HIGH;  end
		endcase
endmodule

 

(二)bin_to_bcd模块

功能:将电压值(二进制)转换为bcd码形式(可以理解为C编程中的除法和取余),便于对单个数字做处理和驱动显示等。

代码如下:

module bin_to_bcd(rst_n,bin_code,bcd_code);
	input               rst_n;
	input      [15:0]bin_code;
	output reg [19:0]bcd_code;
	
	reg		[35:0]		shift_reg;     // 
	always @(bin_code or rst_n)begin
		shift_reg = {20'h0,bin_code};  //20 + 16 = 36 位
		if(!rst_n) 
			bcd_code = 0; 
		else begin 
			repeat(16) begin //循环16次  
				//BCD码各位数据作满5加3操作,
				if (shift_reg[19:16] >= 5) shift_reg[19:16] = shift_reg[19:16] + 2'b11;
				if (shift_reg[23:20] >= 5) shift_reg[23:20] = shift_reg[23:20] + 2'b11;
				if (shift_reg[27:24] >= 5) shift_reg[27:24] = shift_reg[27:24] + 2'b11;
				if (shift_reg[31:28] >= 5) shift_reg[31:28] = shift_reg[31:28] + 2'b11; 
				if (shift_reg[35:32] >= 5) shift_reg[35:32] = shift_reg[35:32] + 2'b11; //最高位
				shift_reg = shift_reg << 1; 
			end
			bcd_code = shift_reg[35:16];   
		end  
	end
	
endmodule

 

四、oled显示模块

这里直接使用的是开源出来的代码,就不展示了。参考oled显示

 

五、串口收发协议模块

(一)串口收发基本模块

主要参考串口监视系统设计

下图为串口收发1byte数据底层RTL视图

FplI4_OmTSk43J8DkkkDXhu9dm4O

串口收发的基本模块如上图,主要由四个部分组成

u0:baud-串口发送的节拍模块(匹配波特率),串口发送每一位的数据都由此模块产生的脉冲信号 来决定。

u1:baud-串口接收的节拍模块(匹配波特率),串口接收每一位的数据都由此模块产生的脉冲信号 来采样。

u2:uart_txd-串口发送模块,将需要发送的tx_data_in[7:0](加起始位和停止位)按照band的节拍频率 来依次循环地从uart_txd_pin引脚上发送数据。

u3:uart_txd-串口接收模块,将按照baud的节拍频率采集到的数据(去除起始位和停止位)依次地存储,最后接收时存储到到rx_data_out[7:0]。

具体源代码可以参考串口监视系统设计

 

(二)串口收发协议(自定义)

   1.自定义协议介绍

(1)串口收发的数据分析

<1>本项目串口收发只有两个终端设备,不需要分配和识别地址。

<2>收发的数据为电压值,整数位和小数位,分别组成一个byte发送。

<3>发送的byte遵循ASCII码值(8位有效数据)。

(2)自定义串口上层协议

<1>第一个byte:字符'z' (对应8位ASCII码值为 8’d122 )

<2>第二个byte:电压值整数位的ASCII码值 (8‘d48≤ <8’d58)

<3>第三个byte:字符'x' (对应8位ASCII码值为 8’d120 )

<4>第四个byte:电压值小数位的ASCII码值 (8‘d48≤ <8’d58)

<5>没有结束符

2.自定义协议模块

   本人觉得我写的这个代码可移植性不强(但是代码可用且稳定),后续会逐渐补充完善。代码展示就不贴了,有需要的可以去附件区自行下载。

 

(三)labview编写的上位机

我曾经在其他项目中接触并且实际编写了用485(基于串口)传输的测试控制平台,所以对于Labview这个软件做上位机使用还比较熟悉。

以下是我编写的上位机的前面板和后面板截图:

FmF_vEvnVDG3859kxJ8YhQKRWSaE

FgmNetpW5hNSWgf9OEhNK9yOtTjK

 

六、实物展示

FqOi2STvgwc6ptqxsfR4c_rWuMfT

 

七、遇到的问题

1.oled的显示问题,我用了一种很笨的方式来显示16X16字符的汉字,听说可以采用ROM存储的方式显示,但没有什么参考代码,只能下回在试试了。

2.ADC7868的驱动问题,我试了一下用例程ADC081S101的代码直接套用,可以直接套用(但是有人说这样有问题,刷新时候有问题),后面我按照数据手册写了一下,感觉没有什么变化。

3.我自己加了一个上位机显示的功能,同步上存在问题,比如说较快的转动电位计,在0ms的时刻检测到电位计转动到2.5V,串口发送2.5V这个数据,在1ms的时候才能发送结束,但是在0.5ms的时候电位计又发生了改变为2.6V,所以就会把2.6V这个数据丢失,造成不同步的问题。问题正在解决。

附件下载
digital_voltmeter_impl1.jed
直接烧录的二进制 .jed文件
mstudy5_project1.zip
工程全部文件
团队介绍
中北大学 信息与通信工程学院 2020级研究生
团队成员
董殿国
中北大学普通学生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号