寒假在家一起练(2021)项目四
基于小脚丫FPGA的综合技能训练板制作一个能够定时、测温、报警、控制的系统 硬禾学堂的假期练习项目,历时近两周,基于STEP-MXO2小脚丫核心模块和Lattice Diamond软件,使用verilog语言完成
标签
FPGA
数字逻辑
辞廿啊
更新2021-02-21
1209

项目要求:

1. 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;

2. 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;

3. 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;

4. PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;

5. 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息

系统设计:

根据项目要求,完成任务需要用到OLED,测温,蜂鸣器,uart串口以及按键功能,首先完成并测试各模块代码,再通过control模块控制各个外设协同工作,实现项目要求,具体框架原理图如下:

框架

 

编译后生成的Netlist如图所示:

netlist

关键代码:

在做项目之前先把电子森林上的各种教程copy了一遍,大多数模块直接采用教程中的,下面主要介绍一下控制部分control_module:

首先定义模块的输入输出,设置了若干标志位,通过控制标志位来实现不同动作切换

module control_module
(
	input				clk,			// system clock
	input				rst_n,			// system reset
	input	    [3:0]	sw,	
	input      	[2:0] 	key,
	input       [11:0] temp_in,
	
	input              	rx_data_valid,
	input       [7:0]  rx_data_in,
	
	output  reg 		tx_data_valid,
	output  reg [7:0]	tx_data_out,
	
	output  reg	[31:0]	time_out,
	output  reg	[31:0]	temp_out,		
	
	output	reg		[15:0]	cycle
); 
	
	reg time_buffer_flag;//开始计时
	reg time_set_flag;	//闹钟设定完毕
	reg tone_set_flag; //乐谱数据传送完毕
	reg tone_flag;    //乐谱标志位
	reg tone_finish_flag;   //乐谱结束
	reg pwm_flag;      //pwm标志位
	reg pwm_finish_flag;	   //pwm结束
	reg temp_tx_flag;  //开始传送温度数据

	
	

首先是1分钟计时,然后下面部分通过配置三个标志位来控制时间的变化。其中time_buffer为0时停止计时并检测按键变化来设置当前时间,为1时开始计时,tone_flag表示蜂鸣器开始播放,此时会停止计时

	reg [29:0] cnt_1m;
	always @(posedge clk or negedge rst_n)begin //计时一分钟
		if(!rst_n) cnt_1m <= 1'b0;
		else if(cnt_1m >= 30'd719_999_999) cnt_1m <= 1'b0;
		else cnt_1m <= cnt_1m + 1'b1;
	end
	
		
	reg time_1plus_flag;
	reg time_60plus_flag;
	reg time_plus_full;	
	always@(posedge clk or negedge rst_n) begin       //*********************按键控制**************************//
		if(!rst_n)begin
			time_1plus_flag <= 1'b0;
			time_60plus_flag <= 1'b0;
			time_buffer_flag <= 1'b0;
			tone_flag <= 1'b0;
		end else begin	
			if(time_buffer_flag ==1'b0)begin           //停止计时并按键设置时间
				case(key)
					3'b100:time_60plus_flag <= 1'b1;							//按键4增加时
					3'b010:time_1plus_flag <= 1'b1;								//按键3增加分
					3'b001:time_buffer_flag <= 1'b1;							//按键2开始计时
					default:begin time_1plus_flag <= 1'b0;time_60plus_flag <= 1'b0;  end
				endcase
				if(tone_finish_flag == 1'b1) begin
					tone_flag <= 1'b0;       //停止演奏
					time_buffer_flag <= 1'b1;//开始计时
				end
			end else if(tone_flag == 1'b0 )begin                              //计时
				if(cnt_1m >= 30'd719_999_999)begin				
					time_1plus_flag <= 1'b1;
				end else begin      
					time_1plus_flag <= 1'b0;
					time_60plus_flag <= 1'b0;
				end	
				
				if(key == 3'b001 && tone_set_flag == 1'b1 ) begin
					tone_flag <= 1'b1;	       //开始演奏
					time_buffer_flag <= 1'b0; //停止计时
				end else begin
					tone_flag <= tone_flag ;
					time_buffer_flag <= time_buffer_flag ;
				end	
				
			end else begin
				time_1plus_flag <= 1'b0;
				time_60plus_flag <= 1'b0;	
			end
				
		end
	end
	

由于输出的时间为16进制,需要让数字按照正常时钟来变化

	reg [15:0] time_buffer;
	always@(posedge clk or negedge rst_n) begin       //**********************time+1*******************//
		if(!rst_n)begin			
			time_buffer[7:0] <= 8'b0;
		end else begin
			if(time_1plus_flag)begin
				if(time_buffer[3:0] >= 4'd9 && time_buffer[7:4] < 4'd5) time_buffer[7:0] <= time_buffer[7:0] + 4'd7;
				else if(time_buffer[3:0] >= 4'd9 && time_buffer[7:4] == 4'd5) begin
					time_buffer[7:0] <= 8'b0;
					time_plus_full <= 1'b1;				
				end else begin
					time_buffer[7:0] <= time_buffer[7:0] + 1'b1;
				end
			end else begin
				time_buffer[7:0] <= time_buffer[7:0];
				time_plus_full <= 1'b0;
			end
		end
	end
	
	
	always@(posedge clk or negedge rst_n) begin       //***********************time+60***************************//
		if(!rst_n)begin			
			time_buffer[15:8] <= 8'b0;
		end else begin
			if(time_60plus_flag||time_plus_full)begin
				if(time_buffer[11:8] >= 4'd9 && time_buffer[15:12] < 4'd2) time_buffer[15:8] <= time_buffer[15:8] + 4'd7;
				else if(time_buffer[11:8] >= 4'd3 && time_buffer[15:12] == 4'd2) time_buffer[15:8] <= 8'b0;
				else time_buffer[15:8] <= time_buffer[15:8] + 1'b1;
			end else begin
				time_buffer[15:8] <= time_buffer[15:8];
			end
		end
	end

uart串口输入报警的时间(4位十六进制数),然后配置time_set_flag为1,表示设置完毕

	reg cnt_rx ;
	reg [15:0]time_set;
	always@(posedge clk or negedge rst_n) begin       //*****************************uart设置time_set********************//
		if(!rst_n)begin
			cnt_rx <= 1'b0;
			time_set <= 16'b0;
			time_set_flag <= 1'b0;
		end else begin
			if(rx_data_valid && time_set_flag == 0)begin    
				cnt_rx <= !cnt_rx;									
				if(cnt_rx == 1'b0)begin
					time_set[15:8] <= rx_data_in[7:0];				
				end else begin
					time_set[7:0]  <= rx_data_in[7:0];	
					time_set_flag <= 1'b1;
				end
			end else begin            
				cnt_rx <= cnt_rx;
				time_set <= time_set;
			end						
		end 
	end

当检测到time_buffer与设定的time_set_flag相等时,报警(pwm_flag置1)并开始传送温度数据(temp_tx_flag)

	always@(posedge clk or negedge rst_n) begin       //*************************报警并uart传送温度数据***********************//
		if(!rst_n)begin
			pwm_flag <= 1'b0;
			temp_tx_flag <= 1'b0;			
		end else begin
			if(tone_flag ==1'b1) begin
				temp_tx_flag <= 1'b0;
			end else if(time_buffer == time_set && time_set_flag == 1'b1)begin
				temp_tx_flag <= 1'b1;
			end else if(tone_finish_flag == 1'b1)begin
				temp_tx_flag <= 1'b1;
			end else begin
				temp_tx_flag <= temp_tx_flag;
			end
			
			if(pwm_finish_flag==1'b1 ) begin 
				pwm_flag <= 1'b0;
			end else if(time_buffer == time_set && time_set_flag == 1'b1)begin
				pwm_flag <= 1'b1;
			end else begin 
				pwm_flag <= pwm_flag ;
			end
		end
	end			
			

temp_tx_flag置1时,开始每秒发送一次温度数据

	always @(posedge clk or negedge rst_n)begin      //*********************每秒发一个温度数据,16进制显示***********************//
		if(!rst_n) begin 
			tx_data_valid <= 1'b0; 
			tx_data_out <= 1'b0; 
		end else begin
			if(temp_tx_flag)begin       //发送温度数据
				if(cnt_uart == 24'd11_599_999 ) begin 
					tx_data_valid <= 1'b1; 
					tx_data_out <= temp_in[11:4];			 
				end else if(cnt_uart == 24'd11_999_999 ) begin 
					tx_data_valid <= 1'b1; 
					tx_data_out <= {temp_in[3:0],4'b0};
				end else begin 
					tx_data_valid <= 1'b0; tx_data_out <= tx_data_out;
				end
			end else begin
				tx_data_valid <= 1'b0; tx_data_out <= tx_data_out;
			end
		end
	end

制作了一个频率音符的对应表,方便串口直接输入音符A1B3C7等

	always @(posedge clk or negedge rst_n)begin       //***********************音符频率对应表***************************//
		if(!rst_n) begin
			cycle <= 8'b0;
			tone_finish_flag <= 1'b0;
		end else begin
			case(note)
				8'hA1: cycle <= 16'd45872;	//L1,
				8'hA2: cycle <= 16'd40858;	//L2,
				8'hA3: cycle <= 16'd36408;	//L3,
				8'hA4: cycle <= 16'd34364;	//L4,
				8'hA5: cycle <= 16'd30612;	//L5,
				8'hA6: cycle <= 16'd27273;	//L6,
				8'hA7: cycle <= 16'd24296;	//L7,
				8'hB1: cycle <= 16'd22931;	//M1,
				8'hB2: cycle <= 16'd20432;	//M2,
				8'hB3: cycle <= 16'd18201;	//M3,
				8'hB4: cycle <= 16'd17180;	//M4,
				8'hB5: cycle <= 16'd15306;	//M5,
				8'hB6: cycle <= 16'd13636;	//M6,
				8'hB7: cycle <= 16'd12148;	//M7,
				8'hC1: cycle <= 16'd11478;	//H1,
				8'hC2: cycle <= 16'd10215;	//H2,
				8'hC3: cycle <= 16'd9140;	//H3,
				8'hC4: cycle <= 16'd8598;	//H4,
				8'hC5: cycle <= 16'd7653;	//H5,
				8'hC6: cycle <= 16'd6818;	//H6,
				8'hC7: cycle <= 16'd6072;	//H7,
				8'hD1: cycle <= 16'd5730;	//V1,
				8'hD2: cycle <= 16'd5106;	//V2,
				8'hD3: cycle <= 16'd4548;	//V3,
				8'hD4: cycle <= 16'd4294;	//V4,
				8'hD5: cycle <= 16'd3826;	//V5,
				8'hD6: cycle <= 16'd3409;	//V6,
				8'hD7: cycle <= 16'd3036;	//V7,
				8'hFF: begin cycle <=16'd0; tone_finish_flag <= 1'b1; end
				default:cycle = 16'd0;
			endcase
		end
	end

资源占用:

资源

项目总结:

本次实验历经两周,前一周查看各类资料,并完成各模块代码,后一周开始正式着手与项目。

在完成项目的过程中遇到的问题以及解决方法:

  1. 对状态机的使用不够熟练,在学校内使用VHDL做实验时更多地采用拆分各个模块来写。在写OLED时,将spi,init,writedata几个部分拆分开写不仅复杂,而且容易出错,使用状态机的方式代码简短很多,但缺点是通用性不够强
  2. 写OLED时将字模写到ROM中,导致资源不够用,后来直接在数组中定义了字模
  3. 经常出现在多个进程中需要对同一个标志位A进行置位,此时需要定义许多标志位(如finish),然后在一个单独的进程中给标志位A赋值
  4. 温度测量偏高,推测是温度传感器受到开发板其它元件的发热干扰

完成项目后有以下感悟:

  1. 必须了解FPGA的结构和性能。不同厂家,不同系列的FPGA芯片都有不同的结构和性能,但是万变不离其宗
  2. VHDL和verilog各有优劣,其中verilog语法与c有些许相似之处,个人使用更加方便。但是语言仅仅只是一个工具,尤其在硬件设计里,代码写得漂不漂亮,并不重要,最关键的是设计思想
  3. 单片机和FPGA编程完全是两种思路,既然叫做硬件描述语言,不能按照写单片机的方式,而是要“心中有电路”,各语句是并行的!
  4. 要充分了解各种通信协议,FPGA编写通信协议要比C语言理解更加深刻
  5. 相比于单片机的引脚,FPGA引脚功能更加自由,设置更加灵活

 

附件下载
step_project01.7z
工程文件,包含源代码
团队介绍
北京邮电大学电子信息专业~
团队成员
杨涛
来自北邮的大三学生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号