【小月电子】ALTERA FPGA开发板系统学习教程-LESSON9简易测试系统

简易测试系统例程讲解

若要观看该博客配套的视频教程,可点击此链接

开发板实物图

在这里插入图片描述
在这里插入图片描述

图1.FPGA设计流程
根据多年工作经验,总结出的FPGA的设计流程,概括起来总共有以上12步,其中根据项目难易度可省去其中一些步骤。比如非常简单的项目,我们可以省去虚线框里面的步骤,但是我们的入门级课程,即使再简单,也按照这12个步骤来进行讲解。

1. 需求解读

1.1 需求

采用C#做一个上位机界面,通过异步串口可控制开发板上的数码管,LED灯,蜂鸣器,同时按下开发板上的按键,可在上位机界面上显示当前被按下是哪个按键。该实验即是一个最简单的工控设备,应用面非常广。

1.2 知识背景

    串口是“串行接口”的简称,即采用串行通信方式的接口。串行通信将数据字节分成一位一位的形式在一条数据线上逐个传送,其特点是通信线路简单,但传输速度较慢。因此串口广泛应用于嵌入式、工业控制等领域中对数据传输速度要求不高的场合。
    串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
    UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
    UART串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。 UART在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、 奇偶校验位和停止位,具体时序如图1所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时,对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数。
    UART通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为5、 6、 7、 8位,其中8位数据位是最常用的, 在实际应用中一般都选择8位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择1位(默认),1.5或2位。串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps( 位/秒),常用的波特率有9600、19200、38400、57600以及115200等。
    在设置好数据格式及传输速率之后,UART负责完成数据的串并转换,而信号的传输则由外部驱动电路实现。电信号的传输过程有着不同的电平标准和接口规范, 针对异步串行通信的接口标准有RS232、RS422、RS485等,它们定义了接口不同的电气特性,如RS-232是单端输入输出,而RS-422/485为差分输入输出等。
    RS232接口标准出现较早, 可实现全双工工作方式,即数据发送和接收可以同时进行。在传输距离较短时(不超过15m),RS232是串行通信最常用的接口标准,本章主要介绍针对RS-232标准的UART串口通信。
    RS-232标准的串口最常见的接口类型为DB9,样式如图2所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点的台式机都没有串口,它们一般通过USB转串口线(图3)来实现与外部设备的串口通信。
在这里插入图片描述

图1. 异步串口时序图

在这里插入图片描述

图2. DB9接头

在这里插入图片描述

图3. USB串口线

DB9接口定义以及各引脚功能说明如图 16.1.4所示,我们一般只用到其中的2(RXD)、3(TXD)、5(GND)引脚,其他引脚在普通串口模式下一般不使用。
在这里插入图片描述

图4. DB9接口定义
波特率9600bps:每秒传输9600bit。 传输1bit的时间为:1/9600(秒)=104167ns 也就是5208个时钟周期,需要采集1次 以上只是介绍串口相关的知识,我们XLOGIC开发板上板载了USB转TTL芯片(CH340),只用一根USB线即可与电脑进行串口通信。

1.3 硬件设计

这个系统我们会用到开发板上基本所有的外设,原理图就不截取出来了,大家可以直接看我们提供的开发板原理图

1.4 接口说明

信号名 方向 FPGA管脚号 说明
CLK50M 输入 E1 时钟信号,50MHZ
FPGA_RX 输入 B11 串口信号输入
FPGA_TX 输出 A11 串口信号输出
SMG_W0 输出 A2 位选控制信号,低电平可导通三极管,使其给数码管位选供电
SMG_W1 输出 A3 位选控制信号,低电平可导通三极管,使其给数码管位选供电
SMG_W2 输出 A4 位选控制信号,低电平可导通三极管,使其给数码管位选供电
SMG_W3 输出 B5 位选控制信号,低电平可导通三极管,使其给数码管位选供电
SMG_W4 输出 A5 位选控制信号,低电平可导通三极管,使其给数码管位选供电
SMG_W5 输出 E6 位选控制信号,低电平可导通三极管,使其给数码管位选供电
SMG_A 输出 A9 数码管段选控制信号,低电平点亮该段
SMG_B 输出 K8 数码管段选控制信号,低电平点亮该段
SMG_C 输出 D8 数码管段选控制信号,低电平点亮该段
SMG_D 输出 A7 数码管段选控制信号,低电平点亮该段
SMG_E 输出 E7 数码管段选控制信号,低电平点亮该段
SMG_F 输出 B9 数码管段选控制信号,低电平点亮该段
SMG_G 输出 A10 数码管段选控制信号,低电平点亮该段
SMG_DP 输出 C8 数码管段选控制信号,低电平点亮该段
KEY1 输出 M1 独立按键,按下低电平
KEY2 输出 M2 独立按键,按下低电平
KEY3 输出 K10 独立按键,按下低电平
KEY4 输出 L10 独立按键,按下低电平
KEY5 输出 M16 独立按键,按下低电平
KEY6 输出 M15 独立按键,按下低电平
LED0 输出 G16 与LED灯相连,低电平LED灯亮
LED1 输出 G15 与LED灯相连,低电平LED灯亮
LED2 输出 J15 与LED灯相连,低电平LED灯亮
LED3 输出 J16 与LED灯相连,低电平LED灯亮
LED4 输出 K15 与LED灯相连,低电平LED灯亮
LED5 输出 K16 与LED灯相连,低电平LED灯亮
LED6 输出 L15 与LED灯相连,低电平LED灯亮
LED7 输出 L16 与LED灯相连,低电平LED灯亮
BEEP 输出 M7 PWM方波,用于驱动无源蜂鸣器

1.5 通信协议

这一讲我们需要做一个测试系统,用上位机软件控制我们的开发板,那么就需要拟定控制协议。这个协议是自己定义的,只要保证上位机和下位机的程序都是按这个协议来写的程序就行,这样才能正确控制。

  1. 数码管控制协议-下发(上位机发给下位机)
    在这里插入图片描述
    2.LED灯控制协议-下发(上位机发给下位机)
    在这里插入图片描述
    3.无源蜂鸣器控制协议-下发(上位机发给下位机)
    在这里插入图片描述
    4.按键检测协议-回传(下位机发给上位机
    在这里插入图片描述
    以上便是我们该项目的通信协议,一个很简单的通信协议,起到抛砖引玉的作用,当控制系统复杂时,我们在定协议的时候需要考虑很多的问题。像我们现在这个简单的控制系统,协议非常简单,仅用于大家学习,便于让大家明白一个控制系统是如何工作的。

2 绘制理论波形图

在这里插入图片描述

工程逻辑框图

由于整个工程涉及到的模块较多,理论波形图就不绘制了。但是大家在写程序之前,一定要清楚每个模块内以及模块与模块之间的信号时序关系,这样在仿真出波形时,才能判断出我们当前的程序逻辑是否正确。不然仿真出了波形,也不知道这个波形对不对,那这个项目就没办法完成了。

3.新建QuartusII 工程

为了让工程看起来整洁,同时方便工程移植。我们新建4个文件夹,分别是Project,Source,Sim,Doc。
Project — 工程文件夹,里面放的QuartusII工程
Source — 源代码文件夹,里面放的工程源码(.v文件或.vhd文件)
Sim — 仿真文件夹,里面放的仿真相关的文件
Doc — 存放相关资料,比如数据手册,需求文档等

4 编写代码

4.1 按键消抖模块

///
//QQ:3181961725
//TEL/WX:13540738439
//工程师:Mr Wang
//模块介绍:按键消抖模块,主要用来消除按键机械抖动带来的干扰
///
module key_xd(
		input clk,
		input rst_n,
		input key_in,
		output reg	key_out
		);
reg	[3:0] curr_st;
reg	[31:0] wait_cnt;
reg		key_in_ff1;
reg		key_in_ff2;
parameter wait_time=100000;
//for sim
//parameter wait_time=8000;
parameter IDLE=4'd0,
					START=4'd1,
					WAIT=4'd2,
					KEY_VALID=4'd3,
					FINISH=4'd4;
always@(posedge clk)key_in_ff1<=key_in;
always@(posedge clk)key_in_ff2<=key_in_ff1;
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			curr_st<=IDLE;
		else
			case(curr_st)
				IDLE:
					begin
						if(key_in_ff2==0)
							curr_st<=START;
						else
							;
					end
				START:
					begin
						if(key_in_ff2==1)
							curr_st<=IDLE;
						else if(wait_cnt==wait_time)
							curr_st<=WAIT;
						else
							;
					end
				WAIT:
					begin
						if(key_in_ff2==1)
							curr_st<=IDLE;
						else if(key_in_ff2==0)
							curr_st<=KEY_VALID;
						else
							curr_st<=IDLE;
					end
				KEY_VALID:curr_st<=FINISH;
				FINISH:
					begin
						if(key_in_ff2==1)
							curr_st<=IDLE;
						else
							;
					end
				default:curr_st<=IDLE;
			endcase
	end
	always@(posedge clk or negedge rst_n)
		begin
			if(!rst_n)
				wait_cnt<=0;
			else if(curr_st==START)
				wait_cnt<=wait_cnt+1;
			else
				wait_cnt<=0;
		end
	always@(posedge clk or negedge rst_n)
		begin
			if(!rst_n)
				key_out<=0;
			else if(curr_st==KEY_VALID)
				key_out<=1;
			else
				key_out<=0;
		end
	endmodule
	

4.2 无源蜂鸣器驱动模块

///
//QQ:3181961725
//TEL/WX:13540738439
//工程师:Mr Wang
//模块介绍:产生PWM波,驱动无源蜂鸣器发声
///
module alarm_ctrl(
	input	clk,
	input	rst_n,
	output	reg	beep
	);
	reg	[15:0]	cnt=0;
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			cnt<=0;
		else if(cnt==10000)
			cnt<=0;
		else	
			cnt<=cnt+1;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			beep<=0;
		else if(cnt==0)
			beep<=~beep;
	end
endmodule

4.3 异步串口接收模块

///
//QQ:3181961725
//TEL/WX:13540738439
//工程师:Mr Wang
//模块介绍:串口接收模块,将串行数据转换成并行数据
///
module async_uart_rev(
	input				rst_n	,//复位信号,低电平有效
	input				clk		,//时钟信号,50MHZ
	input				rxd		,//串行接收数据
	output	reg	[7:0]	rev_data,//并行数据
	output	reg			rev_dvld //并行数据有效标志
	);
	parameter	baud_num=5207;//1/9600*1000000000/20
	parameter	IDLE		=4'd0;
	parameter	START_ST    =4'd1;
	parameter	STOP_ST     =4'd2;
	reg	[12:0]	baud_cnt;
	reg			baud_cnt_en;
	wire		sample_en;
	reg	[3:0]	sample_num;
	reg			rxd_ff1;
	reg			rxd_ff2;
	reg	[3:0]	curr_st;
	always@(posedge clk)rxd_ff2<=rxd_ff1;
	always@(posedge clk)rxd_ff1<=rxd;
	assign	sample_en=(baud_cnt==baud_num[12:1])?1'b1:1'b0;
	//状态机跳转程序
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			curr_st<=IDLE;
		else case(curr_st)
			IDLE:begin
				if(rxd_ff2==0)
					curr_st<=START_ST;
				else;
			end
			START_ST:begin
				if(sample_num==8&&sample_en)
					curr_st<=STOP_ST;
				else;
			end
			STOP_ST:begin
				if(rxd_ff2==1&&sample_en)
					curr_st<=IDLE;
				else;
			end
			default:;
		endcase
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			baud_cnt<=0;
		else if(curr_st==START_ST||curr_st==STOP_ST)begin
			if(baud_cnt==baud_num)
				baud_cnt<=0;
			else 
				baud_cnt<=baud_cnt+1;
		end else
			baud_cnt<=0;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)	
			sample_num<=0;
		else if(sample_en&&sample_num==9)
			sample_num<=0;
		else if(sample_en)
			sample_num<=sample_num+1;
		else;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			rev_data<=0;
		else if(sample_en)
			case(sample_num)
			1:rev_data[0]<=rxd_ff2;
			2:rev_data[1]<=rxd_ff2;
			3:rev_data[2]<=rxd_ff2;
			4:rev_data[3]<=rxd_ff2;
			5:rev_data[4]<=rxd_ff2;
			6:rev_data[5]<=rxd_ff2;
			7:rev_data[6]<=rxd_ff2;
			8:rev_data[7]<=rxd_ff2;
			default:;
		endcase
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)	
			rev_dvld<=0;
		else if(sample_num==9&&sample_en)
			rev_dvld<=1;
		else
			rev_dvld<=0;
	end
endmodule

4.4 异步串口发送模块

///
//QQ:3181961725
//TEL/WX:13540738439
//工程师:Mr Wang
//模块介绍:串口发送模块,将并行数据转换成串行数据
///
module async_uart_tran(
	input			rst_n		,//复位信号,低电平有效
	input			clk			,//时钟,50MHZ
	input	[7:0]	tran_data	,//输入的并行数据
	input			tran_dvld	,//输入的并行数据有效标志
	output	reg		txd          ,//串行输出数据
	output			byte_txfini	
	);
	parameter	baud_num=5207;//1/9600*1000000000/20
	parameter	IDLE		=4'd0;
	parameter	DATA_ST    =4'd1;
	parameter	START_ST    =4'd2;
	parameter	STOP_ST     =4'd3;
	reg	[12:0]	baud_cnt;
	reg			baud_cnt_en;
	wire		sample_en;
	reg	[3:0]	sample_num;
	reg	[3:0]	curr_st;
	assign	byte_txfini=(curr_st==STOP_ST&&sample_en)?1'b1:1'b0;
	assign	sample_en=(baud_cnt==baud_num)?1'b1:1'b0;
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			curr_st<=IDLE;
		else case(curr_st)
			IDLE:begin
				if(tran_dvld==1)
					curr_st<=START_ST;
				else;
			end
			START_ST:begin
				if(sample_en==1)
					curr_st<=DATA_ST;
			end
			DATA_ST:begin
				if(sample_en&&sample_num==8)
					curr_st<=STOP_ST;
				else;
			end
			STOP_ST:begin
				if(sample_en==1)
					curr_st<=IDLE;
				else;
			end
			default:;
		endcase
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			baud_cnt<=0;
		else if(curr_st==START_ST||curr_st==DATA_ST||curr_st==STOP_ST)begin
			if(baud_cnt==baud_num)
				baud_cnt<=0;
			else 
				baud_cnt<=baud_cnt+1;
		end else
			baud_cnt<=0;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)	
			sample_num<=0;
		else if(curr_st==IDLE)
			sample_num<=0;
		else if(sample_en)
			sample_num<=sample_num+1;
		else;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			txd<=1;
		else if(sample_en)
			case(sample_num)
			0:txd<=1'b0;
			1:txd<=tran_data[0];
			2:txd<=tran_data[1];
			3:txd<=tran_data[2];
			4:txd<=tran_data[3];
			5:txd<=tran_data[4];
			6:txd<=tran_data[5];
			7:txd<=tran_data[6];
			8:txd<=tran_data[7];
			9:txd<=1'b1;
			default:txd<=1;
		endcase
	end
endmodule

4.5 指令解析模块

///
//QQ:3181961725
//TEL/WX:13540738439
//工程师:Mr Wang
//模块介绍:将收到的指令按协议解析出来,分别控制数码管,LED灯和蜂鸣器模块
///
module cmd_ctrl(
	input				clk,
	input				rst_n,
	input				rev_dvld,
	input		[7:0]	rev_data,
	output	reg	[3:0]	smg_00,
	output	reg	[3:0]	smg_01,
	output	reg	[3:0]	smg_02,
	output	reg	[3:0]	smg_03,
	output	reg	[3:0]	smg_04,
	output	reg	[3:0]	smg_05,
	output	reg	[7:0]	led,
	output	reg			beep
	);
	parameter	IDLE		=4'd0;
	parameter	CMD0_ST	    =4'd1;
	parameter	SMG00_ST    =4'd2;
	parameter	SMG01_ST    =4'd3;
	parameter	SMG02_ST    =4'd4;
	parameter	SMG03_ST    =4'd5;
	parameter	SMG04_ST    =4'd6;
	parameter	SMG05_ST    =4'd7;
	parameter	LED_ST      =4'd8;
	parameter	ALARM_ST    =4'd9;
	reg	[3:0]	curr_st;
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			curr_st<=IDLE;
		end else case(curr_st)
			IDLE:begin
				if(rev_dvld&&rev_data==8'haa)
					curr_st<=CMD0_ST;
				else;
			end
			CMD0_ST:begin
				if(rev_dvld&&rev_data==8'h00)//数码管
					curr_st<=SMG00_ST;
				else if(rev_dvld&&rev_data==8'h01)//LED灯
					curr_st<=LED_ST;
				else if(rev_dvld&&rev_data==8'h02)//蜂鸣器
					curr_st<=ALARM_ST;
				else;
			end
			SMG00_ST:begin
				if(rev_dvld)
					curr_st<=SMG01_ST;
				else;
			end
			SMG01_ST:begin
				if(rev_dvld)
					curr_st<=SMG02_ST;
				else;
			end
			SMG02_ST:begin
				if(rev_dvld)
					curr_st<=SMG03_ST;
				else;
			end
			SMG03_ST:begin
				if(rev_dvld)
					curr_st<=SMG04_ST;
				else;
			end
			SMG04_ST:begin
				if(rev_dvld)
					curr_st<=SMG05_ST;
				else;
			end
			SMG05_ST:begin
				if(rev_dvld)
					curr_st<=IDLE;
				else;
			end
			LED_ST:begin
				if(rev_dvld)
					curr_st<=IDLE;
				else;
			end
			ALARM_ST:begin
				if(rev_dvld)
					curr_st<=IDLE;
				else;
			end
			default:;
		endcase
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			smg_00	<=0;
			smg_01	<=0;
			smg_02	<=0;
			smg_03	<=0;
			smg_04	<=0;
			smg_05	<=0;
		end else if(curr_st==SMG00_ST)begin
			smg_00<=rev_data;
		end else if(curr_st==SMG01_ST)begin
			smg_01<=rev_data;
		end else if(curr_st==SMG02_ST)begin
			smg_02<=rev_data;
		end else if(curr_st==SMG03_ST)begin
			smg_03<=rev_data;
		end else if(curr_st==SMG04_ST)begin
			smg_04<=rev_data;
		end else if(curr_st==SMG05_ST)begin
			smg_05<=rev_data;
		end else;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)begin
			led<=8'hff;
		end else if(curr_st==LED_ST)
			led<=rev_data;
		else;
	end 
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			beep<=0;
		else if(curr_st==ALARM_ST)
			beep<=rev_data[0];
		else;
	end
endmodule

4.6 通信协议生成模块

///
//QQ:3181961725
//TEL/WX:13540738439
//工程师:Mr Wang
//模块介绍:生成按键检测的通信协议
///
`timescale 1ns/1ps
module uart_data_gen(
	input				clk,
	input				rst_n,
	input				key0,
	input				key1,
	input				key2,
	input				key3,
	input				key4,
	input				key5,
	input				byte_end,
	output	reg	[ 7:0]	data,
	output	reg			data_valid
	);
parameter 	idle  			= 8'd0;
parameter 	word1 			= 8'd1;
parameter	word1_gap		= 8'd2;

reg	[ 7:0] 	curr_st	;
reg	[ 7:0] 	word_cnt	;
reg	[ 7:0]	title		;
reg			byte_end_ff1;
wire		byte_end_rise;
wire[7:0]	data_tmp	;
assign	byte_end_rise=byte_end&(!byte_end_ff1);	
always@(posedge clk)byte_end_ff1<=byte_end;
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			title<=8'hff;
		else if(key0)
			title<=8'h00;
		else if(key1)
			title<=8'h01;
		else if(key2)
			title<=8'h02;
		else if(key3)
			title<=8'h03;
		else if(key4)
			title<=8'h04;
		else if(key5)
			title<=8'h05;
		else;
	end
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			curr_st<=idle;
		else case(curr_st)
			idle:
				begin
					if(key0||key1||key2||key3||key4||key5)
						curr_st<=word1;
					else
						;
				end
			word1:
				begin	
					if(word_cnt==2)
						curr_st<=word1_gap;
					else
						;
				end
			word1_gap:curr_st<=idle;
			default:;
		endcase			
	end
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			word_cnt<=0;
		else if(curr_st==idle||curr_st==word1_gap)
			word_cnt<=0;
		else if((curr_st==word1)&byte_end)
			word_cnt<=word_cnt+1;
		else
			;
	end
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			begin
				data<=0;
				data_valid<=0;
			end
		else case({curr_st,word_cnt})
		    
			{word1,8'h0}:begin data<=8'haa ;data_valid<=1;end
			{word1,8'h1}:begin data<=title;data_valid<=1;end
			default:  begin data<=8'h00;data_valid<=0;end
		endcase
	end	
endmodule

4.7 数码管显示模块

///
//QQ:3181961725
//TEL/WX:13540738439
//工程师:Mr Wang
//模块介绍:数码管动态扫描
///
module smg(
	input				clk		,//时钟
	input				rst_n	,//复位,低电平有效
	input		[3:0] 	swan	,//十万位数
	input		[3:0] 	wan		,//万位数
	input		[3:0] 	qian	,//千位数
	input		[3:0] 	bai		,//百位数
	input		[3:0] 	shi		,//十位数
	input		[3:0] 	ge		,//个位数
	output 	reg	[5:0]	sm_bit	,//数码管选择输出引脚
	output 	reg	[7:0] 	sm_seg	 //数码管段输出引脚
	);
	
	reg	[ 3:0]	disp_dat;//定义显示数据寄存器
	reg	[24:0]	count	;//定义计数寄存器
//扫描信号产生部分
always @(posedge clk or negedge rst_n)//定义clock上升沿触发
begin
	if(!rst_n)
		count<=0;
	else if(count == 25'd250000)
		count <= 25'd0;						//计数器清零
	else
		count <= count + 1'b1;
end	
//数码管动态扫描显示部分
always @(posedge clk or negedge rst_n)   					
begin
	if(!rst_n)
		disp_dat<=0;
	else case(count[15:13])//选择扫描显示数据
		3'd0:disp_dat <= ge		;//个位
		3'd1:disp_dat <= shi	;//十位
		3'd2:disp_dat <= bai	;//百位
		3'd3:disp_dat <= qian	;//千位
		3'd4:disp_dat <= wan	;//万位
		3'd5:disp_dat <= swan	;//十万位
	endcase
end
always @(posedge clk)   			
begin
	case(count[15:13])			 //选择扫描显示数据
		3'd0:sm_bit <= 6'b111110;//选择第一个数码管显示
		3'd1:sm_bit <= 6'b111101;//选择第二个数码管显示
		3'd2:sm_bit <= 6'b111011;//选择第三个数码管显示
		3'd3:sm_bit <= 6'b110111;//选择第四个数码管显示
		3'd4:sm_bit <= 6'b101111;//选择第五个数码管显示
		3'd5:sm_bit <= 6'b011111;//选择第六个数码管显示
	endcase	
end
always @(posedge clk)
begin
	case(disp_dat)
		4'h0:sm_seg <= 8'hc0;	//显示0
		4'h1:sm_seg <= 8'hf9;	//显示1
		4'h2:sm_seg <= 8'ha4;	//显示2
		4'h3:sm_seg <= 8'hb0;	//显示3
		4'h4:sm_seg <= 8'h99;	//显示4
		4'h5:sm_seg <= 8'h92;	//显示5
		4'h6:sm_seg <= 8'h82;	//显示6
		4'h7:sm_seg <= 8'hf8;	//显示7
		4'h8:sm_seg <= 8'h80;	//显示8
		4'h9:sm_seg <= 8'h90;	//显示9
		4'ha:sm_seg <= 8'hbf;	//显示-
		default:sm_seg <= 8'hff;//不显示
	endcase
end
endmodule

4.8 顶层模块

///
//QQ:3181961725
//TEL/WX:13540738439
//工程师:Mr Wang
//模块介绍:顶层模块,例化各个功能模块,使工程看起来整洁清晰
///
module ctrl_system_top(
	input			clk			,
	input			rst_n		,
	input	[5:0]	key			,
	input			uart_rxd	,
	output			uart_txd	,
	output	[5:0]	sm_bit		,
	output	[7:0]	sm_seg		,
	output	[7:0]	led			,
	output			alarm
	);
wire	[7:0]	rev_data;
wire 	[3:0]	smg_00	;
wire 	[3:0]	smg_01  ;
wire 	[3:0]	smg_02  ;
wire 	[3:0]	smg_03  ;
wire 	[3:0]	smg_04  ;
wire 	[3:0]	smg_05  ;
wire	[7:0]	tran_data;
wire			beep;
wire			beep_en;
wire			key0_out,key1_out,key2_out,key3_out,key4_out,key5_out;
assign	alarm=(beep_en)?beep:1'b1;
key_xd Ukey_xd0(
	.clk	(clk		),
	.rst_n	(rst_n		),
	.key_in	(key[0]		),
	.key_out(key0_out	)
	);
key_xd Ukey_xd1(
	.clk	(clk		),
	.rst_n	(rst_n		),
	.key_in	(key[1]		),
	.key_out(key1_out	)
	);
key_xd Ukey_xd2(
	.clk	(clk		),
	.rst_n	(rst_n		),
	.key_in	(key[2]		),
	.key_out(key2_out	)
	);
key_xd Ukey_xd3(
	.clk	(clk		),
	.rst_n	(rst_n		),
	.key_in	(key[3]		),
	.key_out(key3_out	)
	);
key_xd Ukey_xd4(
	.clk	(clk		),
	.rst_n	(rst_n		),
	.key_in	(key[4]		),
	.key_out(key4_out	)
	);
key_xd Ukey_xd5(
	.clk	(clk		),
	.rst_n	(rst_n		),
	.key_in	(key[5]		),
	.key_out(key5_out	)
	);
async_uart_rev Uasync_uart_rev(
	.rst_n		(rst_n),
	.clk		(clk),
	.rxd		(uart_rxd),
	.rev_data	(rev_data),
	.rev_dvld   (rev_dvld)
	);
cmd_ctrl Ucmd_ctrl(
	.clk		(clk		),
	.rst_n		(rst_n		),
	.rev_dvld	(rev_dvld	),
	.rev_data	(rev_data	),
	.smg_00		(smg_00		),
	.smg_01		(smg_01		),
	.smg_02		(smg_02		),
	.smg_03		(smg_03		),
	.smg_04		(smg_04		),
	.smg_05		(smg_05		),
	.led		(led		),
	.beep       (beep_en    )
	);
smg Usmg(
	.clk		(clk		),//时钟
	.rst_n		(rst_n		),//复位,低电平有效
	.swan		(smg_00		),//十万位数
	.wan		(smg_01		),//万位数
	.qian		(smg_02		),//千位数
	.bai		(smg_03		),//百位数
	.shi		(smg_04		),//十位数
	.ge			(smg_05		),//个位数
	.sm_bit		(sm_bit		),//数码管选择输出引脚
	.sm_seg	 	(sm_seg		)//数码管段输出引脚
	);
alarm_ctrl Ualarm_ctrl(
	.clk		(clk),
	.rst_n		(rst_n),
	.beep       (beep)
	);
uart_data_gen Uuart_data_gen(
	.clk		(clk),
	.rst_n		(rst_n),
	.key0		(key0_out),
	.key1		(key1_out),
	.key2		(key2_out),
	.key3		(key3_out),
	.key4		(key4_out),
	.key5		(key5_out),
	.byte_end	(byte_txfini),
	.data		(tran_data),
	.data_valid (tran_dvld)
	);
async_uart_tran Uasync_uart_tran(
	.rst_n		(rst_n),//复位信号,低电平有效
	.clk		(clk),//时钟,50MHZ
	.tran_data	(tran_data),//输入的并行数据
	.tran_dvld	(tran_dvld),//输入的并行数据有效标志
	.txd        (uart_txd), //串行输出数据
	.byte_txfini(byte_txfini)
	);
endmodule

5. 编写仿真测试激励文件

我们知道,写仿真激励文件时,只关心输入信号,在该项目中,输入信号有clk,rst_n,key[5:0],uart_rxd,所以我们只需要在TB文件里对这几个信号操作即可。clk,rst_n,key[5:0],这几个信号的激励比较简单,在上几讲里都有涉及到,只是uart_rxd这个信号的激励相对来说要麻烦一些。因为,我们必须按异步串口通信的协议给这个uart_rxd赋值,相当于我们在TB文件里面要写一个串口发送模块,这样才能产生一个正确的uart_rxd激励信号。

5.1 顶层仿真文件

`timescale 1ns/1ns
module async_uart_top_tb;
	reg					clk		;
	reg					rst_n	;
	reg	[5:0]			key		;
	wire				rxd		;
	reg	[17:0]			cnt=0;
	wire[7:0]			tran_data;
initial
begin
	clk = 0;
	rst_n=0;
	key=6'h3f;
	#1000
	rst_n=1;
end
always #10 clk=~clk;
always@(posedge clk)cnt<=cnt+1;
always@(posedge clk)begin
	if(cnt>10000&&cnt<20000)//按下KEY1
		key<=6'b111110;
	else
		key<=6'b111111;
end
//生成通信协议数据
uart_data_gen_sim Uuart_data_gen_sim(
	.clk		(clk		),
	.rst_n		(rst_n		),
	.byte_end	(byte_txfini),
	.data		(tran_data	),
	.data_valid (tran_dvld	)
	);
//将通信协议以串口的形式发送出去
async_uart_tran Uasync_uart_tran(
	.rst_n		(rst_n		),
	.clk		(clk		),
	.tran_data	(tran_data	),
	.tran_dvld	(tran_dvld	),
	.txd        (rxd		),
	.byte_txfini(byte_txfini)
	);
//被仿真的工程顶层文件
ctrl_system_top Uctrl_system_top(
	.clk			(clk	),
	.rst_n			(rst_n	),
	.key			(key	),
	.uart_rxd		(rxd	),
	.uart_txd		(),
	.sm_bit			(),
	.sm_seg			(),
	.led			(),
	.alarm          ()
	);
endmodule

5.2 通信协议生成模块

该模块主要用于生成数码管控制协议,LED灯控制协议和蜂鸣器控制协议,在我们仿真时,一次只能仿真一个控制协议,如果我们仿真数码管控制协议就需要在程序中注释掉LED灯控制协议和蜂鸣器控制协议。

`timescale 1ns/1ps
module uart_data_gen_sim(
	input				clk,
	input				rst_n,
	input				byte_end,
	output	reg	[ 7:0]	data,
	output	reg			data_valid
	);
parameter 	idle  			= 8'd0;
parameter 	word1 			= 8'd1;
parameter	word1_gap		= 8'd2;
parameter	word_num		=8;//控制数码管的协议为8个字节
//parameter	word_num		=3;//控制LED灯和蜂鸣器的协议为3个字节
reg	[ 7:0] 	curr_st	;
reg	[ 7:0] 	word_cnt	;
reg	[ 7:0]	title		;
reg			byte_end_ff1;
wire		byte_end_rise;
wire[7:0]	data_tmp	;
assign	byte_end_rise=byte_end&(!byte_end_ff1);	
always@(posedge clk)byte_end_ff1<=byte_end;
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			curr_st<=idle;
		else case(curr_st)
			idle:curr_st<=word1;
			word1:
				begin	
					if(word_cnt==word_num)
						curr_st<=word1_gap;
					else
						;
				end
			word1_gap:;//curr_st<=idle;
			default:;
		endcase			
	end
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			word_cnt<=0;
		else if(curr_st==idle||curr_st==word1_gap)
			word_cnt<=0;
		else if((curr_st==word1)&byte_end)
			word_cnt<=word_cnt+1;
		else
			;
	end
always@(posedge clk or negedge rst_n)
	begin
		if(!rst_n)
			begin
				data<=0;
				data_valid<=0;
			end
		else case({curr_st,word_cnt})
		    /控制数码管/
			{word1,8'h0}:begin data<=8'haa;data_valid<=1;end
			{word1,8'h1}:begin data<=8'h00;data_valid<=1;end
			{word1,8'h2}:begin data<=8'h01;data_valid<=1;end//第一位数码管显示的值
			{word1,8'h3}:begin data<=8'h02;data_valid<=1;end//第二位数码管显示的值
			{word1,8'h4}:begin data<=8'h03;data_valid<=1;end//第三位数码管显示的值
			{word1,8'h5}:begin data<=8'h04;data_valid<=1;end//第四位数码管显示的值
			{word1,8'h6}:begin data<=8'h05;data_valid<=1;end//第五位数码管显示的值
			{word1,8'h7}:begin data<=8'h06;data_valid<=1;end//第六位数码管显示的值
			/控制LED灯/
			// {word1,8'h0}:begin data<=8'haa;data_valid<=1;end
			// {word1,8'h1}:begin data<=8'h01;data_valid<=1;end
			// {word1,8'h2}:begin data<=8'hfe;data_valid<=1;end//点亮第一个LED灯
			/控制蜂鸣器/
			// {word1,8'h0}:begin data<=8'haa;data_valid<=1;end
			// {word1,8'h1}:begin data<=8'h02;data_valid<=1;end
			// {word1,8'h2}:begin data<=8'h01;data_valid<=1;end//打开蜂鸣器
			default:  begin data<=8'h00;data_valid<=0;end
		endcase
	end	
endmodule

6. Modelsim仿真

Modelsim仿真一般有两种方法,第一种是图形化界面仿真,即所有的操作都是在Modelsim软件界面上来完成,该方式的优点是,简单易学,适用于简单的项目,缺点是操作步骤繁琐。第二种是批处理仿真,这种方式在仿真前需要编写相应的脚本文件,该方式的优点是,一键即可完成仿真,省时省力,缺点是前期需要编写脚本文件。为了更贴近工程实际,从第三课开始采用批处理的方式进行仿真。

6.1 数码管控制仿真波形如下:

在这里插入图片描述

通过上面的仿真波形我们可以看到,smg_00等于1,smg_01等于2…smg_05等于6,在通信协议生成模块(uart_data_gen_sim.v)中,我们给数码管赋值也是123456,仿真结果与设计相符,逻辑功能正确。

6.2 LED灯控制

在这里插入图片描述

通过上面的仿真波形我们可以看到,led等于8’b11111110,在通信协议生成模块(uart_data_gen_sim.v)中,我们给LED灯赋值也是8’b11111110,仿真结果与设计相符,逻辑功能正确。

6.3 蜂鸣器控制

在这里插入图片描述
通过上面的仿真波形我们可以看到,alarm输出了PWM方波信号,在通信协议生成模块(uart_data_gen_sim.v)中,我们给蜂鸣器赋值也是8’h01,即打开蜂鸣器,仿真结果也看到了有PWM信号输出,与设计相符,逻辑功能正确。

7.对比波形图

由于我们没有绘制理论波形,所以只需要在仿真时,确保仿真波形没问题就可以

8.编译综合

在这里插入图片描述

9.绑定管脚

当工程编译成功后,即可进行管脚分配(需要参考开发板的原理图)。我们店铺的开发板和模块在PCB板上均标注了信号名,在绑定管脚时也可以直接参照实物的连接关系。
在这里插入图片描述
管脚映射关系如下所示:
在这里插入图片描述
在这里插入图片描述

10.再次编译综合

在这里插入图片描述

11 下载SOF文件

再次编译综合成功后便可以将生成的SOF文件下载到开发板
在这里插入图片描述
在这里插入图片描述
下载成功后,便可以与我们的上位机测试软件进行通信,具体操作步骤如下
1)安装串口驱动
2)将电源线一端接入电脑USB接口 ,一端接入开发板供电口
3)下载FPGA程序
4)打开《开发板测试平台.exe》可执行文件
5)如果串口驱动安装成功,上位机会自动识别端口号,波特率选择9600,然后开启连接,如下图:
在这里插入图片描述
6)连接成功后,即可进行开发板控制,可更改数码管显示的数字,设置好以后,点击设置按钮,开发板上便显示上位机设置的数字
在这里插入图片描述
7)按下开发板上的按键,可显示当前按下的按键,如下图:
在这里插入图片描述
8)LED灯控制时,当打开开发板上的LED时,对应的按钮会变成红色,表示打开了对应的LED灯,如下图表示LED0,LED2,LED4被点亮。
在这里插入图片描述
9)按下蜂鸣器按钮,蜂鸣器报警,再按一次,报警关闭。
以上测试没问题就说明我们的程序是OK的,可以生成JIC文件,并完成程序固化。

12 生成并固化JIC文件

FPGA有一个特性,就是掉电后配置信息会丢失,所以我们需要将配置信息存储在配置芯片(FLASH)中,待开发板上电后,FPGA便会读取配置芯片中的配置信息,这样开发板掉电再上电后同样可正常工作。要将程序固化到配置芯片,需要先生成JIC文件。SOF文件转换成JIC文件步骤如下:

12.1 file–>Convert Programming File…

在这里插入图片描述

12.2 选择JIC文件以及配置芯片的型号,FPGA的型号在这里插入图片描述

标号1:选择生成文件的格式,我们选择JIC文件
标号2:选择配置芯片的型号,我们选择EPCS16
标号3:修改生成JIC文件的名字以及存放路径
标号4:鼠标左键点击Flash Loader
标号5:选择FPGA的型号,我们开发板用的是EP4CE6F17C8这款FPGA,所以我们也应该选这个型号,如下图所示:
在这里插入图片描述

12.3 选择SOF文件

在这里插入图片描述
标号1:鼠标左键点击SOF Data
标号2:添加SOF文件,选中我们工程生成的SOF文件,如下图:
在这里插入图片描述

12.3 生成JIC文件

在这里插入图片描述
到此,JIC文件生成好,可以进行固化操作了。

12.4 固化JIC文件

在这里插入图片描述
固化好以后,掉电程序也不会丢失了!

实验现象

与设计需求吻合,设计完成!

猜你喜欢

转载自blog.csdn.net/Moon_3181961725/article/details/126761368
今日推荐