利用FPGA制作数字电压表

实训目的

        随着电子技术的飞速发展,数字电压表在电压测量领域的作用显得更加重要,本次实训课程旨在掌握基于FPGA的数字电压表设计。本次设计在Quartus II 环境下进行,以Verilog HDL语言实现分频、模数转换、数码管显示、led灯蜂鸣器声光报警等系统所需模块。利用层次化、模块化的开发的方法完成设计,并进行波形仿真。利用SignalTap II完成系统的在线调试与验证。

实训具体目标:

        使用FPGA芯片对AD芯片TLC549进行读取模拟量的值,在数码管上显示出来并判断模拟量是否超出设定的阈值,如果超过则进行声光报警(),如果低于设定阈值则也进行报警()。

硬件列表

ZX_2开发板

  1. Altera的主控EP4CE6E22C8
    1. 针脚数144
      RAM大小276480 bit
      输入/输出数91 Input
      工作温度(Max)85 ℃
      工作温度(Min)0 ℃
      电源电压1.15V ~ 1.25V
      封装QFP-144
      逻辑块LAB392
      逻辑单元LE6272
      锁相环(PLL)2

      全局时钟(Global Clocks)

      10
      嵌入式存储器270Kbit
    2. PS:Memory Bits与embedded multiplier 9-bit elements实际是一回事,后者简称M9K,M9K就是指位宽为9bit,深度为1K的RAM。这里一共有30个M9K,大家计算一下9102430=276480就是Memory Bits。
  2. 晶振50MHz
  3. TLC549

开发环境

Quartus II 13.1

硬件电路

AD_TLC549电路

基准电压电路

顶层功能连接与描述

 

        本设计主要分为四部分:AD监测部分、AD数据计算部分、数码管显示部分、报警模块。其中输入有:tlc549芯片的返回值、复位按键和时钟输入,其中输出有:tlc549芯片的片选接口与芯片的时钟输入、数码管的片选与位选、输出的蜂鸣器驱动信号和LED驱动信号。

        顶层主要功能为拼接各个子模块,已完成设计。

硬件介绍

       REF2925

        三端稳压器输出标准电压2.5V

       TLC549       

        TLC549是 TI公司生产的一种低价位、高性能的8位 A/D转换器,它以8位开关电容逐次逼近的方法实现 A/D转换,其转换速度小于 17us,最大转换速率为 40000HZ,4MHZ典型内部 系统时钟,电源为 3V至 6V。它能方便地采用三线串行接口方式与各种 微处理器连接,构成各种廉价的测控应用系统。

REF+:正基准电压输入 2.5V≤REF+≤Vcc+0.1。

REF-:负基准电压输入端,-0.1V≤REF-≤2.5V。且要求:(REF+)-(REF-)≥1V。

VCC:系统电源3V≤Vcc≤6V。

GND:接地端。

/CS:芯片选择输入端,要求输入高电平 VIN≥2V,输入低电平 VIN≤0.8V。

DATA OUT:转换结果数据串行输出端,与 TTL 电平兼容,输出时高位在前,低位在后。

ANALOGIN: 模拟信号输入端,0≤ANALOGIN≤Vcc,当 ANALOGIN≥REF+电压时,转换结果为全“1”(0FFH),ANALOGIN≤REF-电压时,转换结果为全“0”(00H)。

I/O CLOCK:外接输入/输出时钟输入端,同于同步芯片的输入输出操作,无需与芯片内部系统 时钟同步。

        当/CS变为低电平后, TLC549芯片被选中, 同时前次转换结果的最高有效位MSB (A7)自 DATA OUT 端输出,接着要求自 I/O CLOCK端输入8个外部 时钟信号,前7个 I/O CLOCK信号的作用,是配合 TLC549 输出前次转换结果的 A6-A0 位,并为本次转换做准备:在第4个 I/O CLOCK 信号由高至低的跳变之后,片内采样/保持电路对输入模拟量采样开始,第8个 I/O CLOCK 信号的下降沿使片内采样/保持电路进入保持状态并启动 A/D开始转换。转换时间为 36 个系统 时钟周期,最大为 17us。直到 A/D转换完成前的这段时间内,TLC549 的控制逻辑要求:或者/CS保持高电平,或者 I/O CLOCK 时钟端保持36个系统 时钟周期的低电平。由此可见,在自 TLC549的 I/O CLOCK 端输入8个外部 时钟信号期间需要完成以下工作:读入前次A/D转换结果;对本次转换的输入 模拟信号采样并保持;启动本次 A/D转换开始。 

TLC549 - 搜狗百科TLC549是TI公司生产的一种低价位、高性能的8位 A/D转换器TLC549是 TI公司生产的一种低价位、高性能的8位 A/D转换器,它以8位开关电容逐次逼近的方法实现 A/D转换,其转换速度小于 17us,最大转换速率为 40000HZ,4MHZ典型内部 系统时钟,电源为 3V至 6V。它能方便地采用三线串行接口方式与各种 微处理器连接,构成各种廉价的测控应用系统。REF+:正基准电压输入 2https://baike.sogou.com/v56124385.htm?fromTitle=TLC549

模块功能介绍

        AD驱动

         AD驱动计算以及显示模块:主要有驱动部分、计算部分、二/十进制转换部分、与数码管驱动部分。

        AD驱动部分:AD芯片模块主要负责与AD芯片进行通讯,包括产生AD所需要的驱动时钟已经片选信号与接收信号。

 TLC549_ad_driver:主要对AD芯片进行驱动,并读取8位AD数据,把数据传输给conv_ctrl

       代码

module tlc549_ad_driver(
	input							clk,				//晶振时钟:50MHZ
	input							rst_n,			//低有效
	input							ad_in,			//输入模拟电压
	
	output	reg				ad_clk,			//最大1.1MHZ
	output	reg				cs_n,				//低有效
	output	reg				flag,				//采样完成标志
	output	reg	[7:0]		data_o			//转换完成数据输出
);

	/********LSM_1S:节拍计数********/
	reg				[10:0]	cnt;				//节拍计数器:记录50MHZ周期次数
	
	always @ (posedge clk, negedge rst_n)	begin
		if(rst_n == 1'b0)	
			cnt <= 11'd0;
		else	if(cnt < 11'd1300)
			cnt <= cnt + 1'd1;
		else
			cnt <= 11'd0;
	end
	
	/********LSM_2S:输出动作********/
	reg			[7:0]	temp_data;
	
	always @ (posedge clk, negedge rst_n)	begin
		if(rst_n == 1'b0)	begin
			ad_clk <= 1'b0;										//初值
			cs_n <= 1'b1;
			flag <= 1'b0;
			data_o <= 8'h0;
			temp_data <= 8'h0;
		end
		else
			case(cnt)	
				11'd1		:		cs_n <= 1'b0;					//cs_n拉低
				
				11'd71	:		begin	
										ad_clk <= 1'b1; 			//MSB-->LSB
										temp_data[7] <= ad_in;
									end
			
				11'd96	:		ad_clk<= 1'b0;
				
				11'd121	:		begin	
										ad_clk <= 1'b1; 			
										temp_data[6] <= ad_in;
									end
			
				11'd146	:		ad_clk<= 1'b0;
				
				11'd171	:		begin	
										ad_clk <= 1'b1; 			
										temp_data[5] <= ad_in;
									end
			
				11'd196	:		ad_clk<= 1'b0;
				
				11'd221	:		begin	
										ad_clk <= 1'b1; 			
										temp_data[4] <= ad_in;
									end
			
				11'd246	:		ad_clk<= 1'b0;
				
				11'd271	:		begin	
										ad_clk <= 1'b1; 			
										temp_data[3] <= ad_in;
									end
			
				11'd296	:		ad_clk<= 1'b0;
				
				11'd321	:		begin	
										ad_clk <= 1'b1; 			
										temp_data[2] <= ad_in;
									end
			
				11'd346	:		ad_clk<= 1'b0;
				
				11'd371	:		begin	
										ad_clk <= 1'b1; 			
										temp_data[1] <= ad_in;
									end
			
				11'd396	:		ad_clk<= 1'b0;
				
				11'd421	:		begin	
										ad_clk <= 1'b1; 			
										temp_data[0] <= ad_in;		//LSB
									end
			
				11'd446	:		begin			
										ad_clk<= 1'b0;					//第8个ad_clk下降沿:采样完成
										flag <= 1'b1;
										cs_n <= 1'b1;					//准备转换
									end
									
				11'd447	:		begin
										flag <= 1'b0;					//尖峰脉冲信号(1个时钟周期)
										data_o <= temp_data;
									end
									
				11'd1300	:			;
			endcase
	end
	
endmodule

        计算

        计算部分:因为芯片所返回的AD信号为8位二进制的数字信号,我需要进行转换,因为芯片不擅长与浮点数的运算,所以我们将标准电压上限设定为2500(12位二进制数),我们用到公式来进行计算,其公式如下

Vo=vref*data_o/256=2500mv*data_o/256

输出电压    =(标准电压)*(芯片输出数据)/(芯片输出数据的上限)

CONV_ctrl:进行对AD数据进行计算输出12位的电压数据

module conv_ctrl(
	input							clk,
	input							rst_n,
	input							flag,			//采样完成的标志
	input				[7:0]		code,			//ad转换输出8为数据

	output	reg	[11:0]	data_temp
);

	`define		VREF	2500
	
	reg		flag1;
	
	always @ (posedge clk)		flag1 <= flag;

	always @ (posedge clk, negedge rst_n)	begin
		if(rst_n == 1'b0)
			data_temp <= 12'h0;
		else	if(flag1 == 1'b1)
			data_temp <= `VREF * code / 256;
		else
			data_temp <= data_temp;
	end

endmodule

        二进制转十进制模块

        因为计算模块输出的数值为4位十进制数据,无法直接在数码管上显示,需要进行转换分割,将4位10进制数据变换为4个4位二进制编码。

        数码管直接显示的是4个4位二进制编码.

//二进制转BCD:对10求余
module bin_bcd(
	input		[11:0]		bin,		

	output	[15:0]		bcd
);

	assign	bcd[15:12] = bin / 1000 % 10;		//千位
	assign	bcd[11:8]  = bin / 100  % 10;		//百位
	assign	bcd[7:4]   = bin / 10   % 10;		//十位
	assign	bcd[3:0]   = bin        % 10;		//个位

endmodule

         数码管驱动

        数码管驱动主要是进行依次的点亮每一位数码管并显示对应的数据。因为有六位数码管为了模块的复用性所以将输入变量设置为24位二进制数(每位数码管由4位二进制数进行驱动,有六位数码管所以4*6=24位)

        seg7_drive:将4位十进制电压数据进行显示,因为本次驱动为6位数码管,其中4位为AD数据显示,其余的两位为空余显示也可以显示字符“V”。

        需要在变量前面数据添加数据时可以使用    {添加数据,变量}

module seg7_drive (pi_clk, pi_rst_n, pi_data, po_seg, po_sel);

	input pi_clk;
	input pi_rst_n;

	input [23:0] pi_data;

	output reg [7:0] po_seg;
	output reg [2:0] po_sel;

	`define T1ms  50_000
//	`define T1ms  5 // test

	reg [15:0] cnt;

	always @ (posedge pi_clk)
		begin
			if (!pi_rst_n)
				begin
					cnt <= 16'd0;
				end
			else
				begin
					if (cnt < `T1ms - 1)
						begin
							cnt <= cnt + 1'b1;
						end
					else
						begin
							cnt <= 16'd0;
						end
				end
		end

	wire flag;

	assign flag = (cnt == `T1ms - 1) ? 1'b1 : 1'b0;

	reg [3:0] show_data;
	reg [2:0] state;



	localparam TWO 	    = 3'b000;
	localparam THREE 	= 3'b001;
	localparam FOUR 	= 3'b010;
	localparam FIVE 	= 3'b011;
	localparam SIX 	    = 3'b100;
	localparam ONE 	    = 3'b101;

	always @ (posedge pi_clk)
		begin
			if (!pi_rst_n)
				begin
					state <= ONE;
					show_data <= 4'd0;
					po_sel <= 3'b000;
				end
			else
				begin
					case(state)
						ONE :	begin
									if (flag)
										begin
											state <= TWO;
										end
									else
										begin
											show_data <= pi_data[23:20];
											po_sel <= ONE;
										end
								end

						TWO :	begin
									if (flag)
										begin
											state <= THREE;
										end
									else
										begin
											show_data <= pi_data[19:16];

											po_sel <= TWO;
										end
								end
				      THREE :	begin
									if (flag)
										begin
											state <= FOUR;
										end
									else
										begin
											show_data <= pi_data[15:12];

											po_sel <= THREE;
										end
								end
				      FOUR :	begin
									if (flag)
										begin
											state <= FIVE;
										end
									else
										begin
											show_data <= pi_data[11:8];
											po_sel <= FOUR;
										end
								end
				      FIVE :	begin
									if (flag)
										begin
											state <= SIX;
										end
									else
										begin
											show_data <= pi_data[7:4];
											po_sel <= FIVE;
										end
								end
				      SIX :	begin
									if (flag)
										begin
											state <= ONE;
										end
									else
										begin
											show_data <= pi_data[3:0];
											po_sel <= SIX;
										end
								end
						default : state <= ONE;
					endcase
				end
		end

	always @ (*)
		begin
			case (show_data)
				4'd0 : po_seg = 8'b1100_0000;
				4'd1 : po_seg = 8'b1111_1001;
				4'd2 : po_seg = 8'b1010_0100;
				4'd3 : po_seg = 8'b1011_0000;

				4'd4 : po_seg = 8'b1001_1001;
				4'd5 : po_seg = 8'b1001_0010;
				4'd6 : po_seg =8'b1000_0010;
				4'd7 : po_seg =8'b1111_1000;

				4'd8 : po_seg =8'b1000_0000;
				4'd9 : po_seg =8'b1001_0000;
				4'd10: po_seg =8'b1000_1000;
				4'd11: po_seg =8'b1000_0011;

				4'd12: po_seg =8'b1100_0110;
				4'd13: po_seg =8'b1010_0001;
				4'd14: po_seg =8'b1100_0001;
				4'd15: po_seg =8'b1111_1111;			//不亮

				default : po_seg =8'hff;

			endcase
			if(po_sel == THREE)
			begin
				po_seg <= po_seg & 8'b0111_1111;
			end
		end

endmodule

报警部分

        时钟模块

         因为报警部分需要多频率进行,所以通过多个分频器提前将频率进行做好,当需要那个频率进行输出时,则将这个分频模块的频率直接输出到控制引脚。

         时钟模块主要将外部输入时钟使用计数器的方式进行分频至各个模块所需要的时钟,例如下图时钟模块的使用是将作为报警模块的时钟源。

module ad_fenpin_beep_low (
    input					clk,
	input					rst_n,
    output       reg     beep_clk
);

    `define t 50_000_000/800/2-1  //t=N/2-1  N为分频系??  需要的频率=时钟频率/N
	reg [30:0]cnt;    //计数容量

	always @(posedge clk,negedge rst_n)begin
	if(!rst_n)
		begin
		cnt<=0;
		beep_clk<=1;
		end
	else
		if(cnt== `t )
			begin
				cnt<=0;
				beep_clk<=~beep_clk;
			end
		else
			begin
				cnt<=cnt+1;
				beep_clk<=beep_clk;
			end
	end


endmodule

        报警模块

        报警模块:当电压范围超过阈值时,则进行声光报警,超过上限以及下限时分别进行两种频率的报警。

module ad_if(clk,rst_n,beep_clk_in,led_clk_in,led_clk_low_in,beep_clk_low_in,beep_clk,led_clk,dianya_in);

	input clk;
	input rst_n;
    input beep_clk_in;
	input beep_clk_low_in;
	input [3:0] led_clk_in;
	input [3:0] led_clk_low_in;

    input [11:0] dianya_in;

	output reg beep_clk;
	output reg	[3:0] led_clk;

	parameter dianya_biaozhun_high=1200;//高阈值
    parameter dianya_biaozhun_low=500;  //低阈值

	always @(posedge clk,negedge rst_n)begin
	if(!rst_n)
		beep_clk<=1;
	else
        begin
            if(dianya_in > dianya_biaozhun_high)
				begin
				beep_clk <= beep_clk_in;
				led_clk<=led_clk_in;
				end

		    else if(dianya_in < dianya_biaozhun_low)
				begin
				beep_clk <= beep_clk_low_in;
				led_clk <= led_clk_low_in;
				end
		    else
			begin
				led_clk[0]<=1;
				led_clk[1]<=1;
				led_clk[2]<=1;
				led_clk[3]<=1;
				beep_clk <= 1;
			end
        end

	end
endmodule


实训总结及分析

        对于本次实训我学到了很多的东西,主要的分为两个方面:FPGA的学习与遇到的问题。

        FPGA的学习:在以前只是了解了FPGA是一种超高速的可编程逻辑单元阵列器件并没有深入了解,以为会像STM32一样调用函数进行使用以及出来,通过学习知道了什么叫硬件语言Verilog HDL这种在硬件上进行编辑方式(不经过处理器)造就了他的运行速度是非常非常高的。

        遇到的问题:通过本次的实验我出现了很多的问题,例如在调试的时候总是出现读取的电压数值不稳定,当蜂鸣器状态由开到关,或者由关到开的时候,会出现读取电压的严重跳变,通过排查可能是由于U1(REF2925)的损坏导致的,因为通过测量,这个芯片无法进行输出正确的标准电压,正常情况应该有2.5V电压可测量只有1.4左右并且会随着蜂鸣器工作出现严重的跳变,当蜂鸣器运行时AD芯片的标准电压会将至0.6V左右。在AD输入值为阈值内的值时,数码管可正常显示电压,但电压会有偏差而且很严重。当输入数值为低于阈值时,蜂鸣器运行,导致标准电压降低值0.6V,因为电位器电压不变,从而导致AD读取的值为超过上限值。

蜂鸣器未工作:三端稳压器输出1.4V电压

蜂鸣器工作:三端稳压器出现电压跳变到0.44V

代码链接:「09_tlc549_ad_vo.png」https://www.aliyundrive.com/s/bGv9CGXJnes 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无需下载极速在线查看,视频原画倍速播放。

ps:下载好请更改后缀为.rar格式

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值