实训目的
随着电子技术的飞速发展,数字电压表在电压测量领域的作用显得更加重要,本次实训课程旨在掌握基于FPGA的数字电压表设计。本次设计在Quartus II 环境下进行,以Verilog HDL语言实现分频、模数转换、数码管显示、led灯蜂鸣器声光报警等系统所需模块。利用层次化、模块化的开发的方法完成设计,并进行波形仿真。利用SignalTap II完成系统的在线调试与验证。
实训具体目标:
使用FPGA芯片对AD芯片TLC549进行读取模拟量的值,在数码管上显示出来并判断模拟量是否超出设定的阈值,如果超过则进行声光报警(),如果低于设定阈值则也进行报警()。
硬件列表
ZX_2开发板
- Altera的主控EP4CE6E22C8
-
针脚数 144 RAM大小 276480 bit 输入/输出数 91 Input 工作温度(Max) 85 ℃ 工作温度(Min) 0 ℃ 电源电压 1.15V ~ 1.25V 封装 QFP-144 逻辑块LAB 392 逻辑单元LE 6272 锁相环(PLL) 2 全局时钟(Global Clocks)
10 嵌入式存储器 270Kbit - PS:Memory Bits与embedded multiplier 9-bit elements实际是一回事,后者简称M9K,M9K就是指位宽为9bit,深度为1K的RAM。这里一共有30个M9K,大家计算一下9102430=276480就是Memory Bits。
-
- 晶振50MHz
- 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转换开始。
模块功能介绍
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格式