寒假在家一起练4-基于小脚丫FPGA的定时、测温、报警、控制平台
寒假在家一起练4-基于小脚丫latticeFPGA的定时、测温、报警、控制平台
标签
嵌入式系统
FPGA
数字逻辑
USB
ala灯神丁
更新2021-02-27
1111

1,项目要求:

  1. 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
  2. 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
  3. 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
  4. PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
  5. 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息

 

2,项目系统设计

项目需求分析:在本项目中,需要实现时钟与温度计的基本功能,在此基础上实现人机交互,定时报警,以及双机串口通信的任务。

所以在此项目中,我以时钟和温度计两个模块为基础,依次设计实现了时钟和温度计,使其可以在OLED上显示。之后增加串口通信模块,将原有时钟模块加以改进,增加综合连接模块,将温度计发送的温度在整点发送,当收到PC端发送的音乐信息后,处理后通过PWM波驱动无源蜂鸣器播放。

 

 

3、模块工程代码:

(1)Clock_set模块:此模块可以设置时间,定时计时。同时整点时发送整点脉冲,发送给蜂鸣器及串口发送模块,作为其整点报时和发送温度信息的信号。

 

module clock_set(

          input                 clk,
		  input                 rst_n,
		  input       [2:0]     key,
		  output reg [31:0]	time_out,
		  output reg             uart_en
		      
			  );
		      
 
        wire  [2:0]  key_pulse;
		wire		      clk1h;        //1Hz时钟
		reg [3:0]hour1_set = 4'd0;
		reg [3:0]hour2_set = 4'd0;
		reg [3:0]min1_set  = 4'd0;
		reg [3:0]min2_set  = 4'd0;
		
		reg [3:0] hour1 = 4'd1;
		reg [3:0] hour2 = 4'd2;
		reg [3:0] min1  = 4'd5;
		reg [3:0] min2 = 4'd5;
		reg [5:0] count_sec = 6'd0;
		 
				//同步临时变量
		reg [3:0] hour1_temp1;
		reg [3:0] hour2_temp1;
		reg [3:0] min1_temp1;
		reg [3:0] min2_temp1;
		
		reg [3:0] hour1_temp2;
		reg [3:0] hour2_temp2;
		reg [3:0] min1_temp2;
		reg [3:0] min2_temp2;
	
		
		reg Time_count_flag =1'b1;     // 计数与时间设置标志位
		
		
		always@(posedge clk or negedge rst_n)begin
			if(!rst_n)begin
				Time_count_flag <= 1'b0;
				end
			else if(key_pulse[0])begin
				Time_count_flag <= ~Time_count_flag;
			end
			else begin
				Time_count_flag <= Time_count_flag;
				end
		end
			

	   always@(posedge clk or negedge rst_n)begin
		   if(!rst_n)begin
			   hour1_set<=hour1;
			   hour2_set<=hour2;
			   min1_set<=min1;
			   min2_set<=min2;
	       end
			else if(Time_count_flag)begin   
				   if(key_pulse[1])begin
						hour2_set <= hour2_set + 4'd1;
						if(hour2_set==4'd9)begin
						 hour2_set <= 4'd0;
						 hour1_set <= hour1_set +4'd1;
						 end
						 else if(hour1_set==4'd2&&hour2_set==4'd3)begin
							    hour2_set<=4'd0;
								hour1_set<=4'd0;
						end
				   end
											   
				  else if(key_pulse[2]) begin
					 min2_set <= min2_set +4'd1;
					if(min2_set == 4'd9) begin
						  min2_set <=4'd0;
						  min1_set <=min1_set+4'd1;
					      if(min1_set==4'd5)begin
							  min1_set<=4'd0;
							  min2_set<=4'd0;
						  end
					end
				 end
		end
        else 
			begin
                hour1_set <= hour1;
                hour2_set <= hour2; 
                 min1_set <= min1;
                 min2_set <= min2;				
           end
    end	   
					
		always @(posedge clk )begin
		        hour1_temp1 <= hour1_set ;
				hour1_temp2<=hour1_temp1;
                hour2_temp1<=hour2_set ; 
				hour2_temp2<=hour2_temp1;
				
                 min1_temp1 <= min1_set ;
				 min1_temp2 <= min1_temp1;
                 min2_temp1<= min2_set ;
                 min2_temp2<= min2_temp1;				 
end


	 
               

		
		always@(posedge clk1h or negedge rst_n)
			begin
				if(!rst_n)begin
                    hour1 <=4'd0;
                    hour2 <=4'd0;
                    min1  <=4'd0;
					min2  <=4'd0;
					uart_en<= 1'b0;
				end
				
               else if(!Time_count_flag)
				   begin
					   
					   count_sec <= count_sec +6'd1;
					   if(uart_en ==1'b1)begin
						   uart_en <=1'b0;
					   end
					   
					   
					if(count_sec == 6'd59)begin
						 count_sec<=6'd0;
						 min2 <= min2 +4'd1;
						 if(min2 == 4'd9)begin
						  min2<=4'd0;
						  min1<=min1+4'd1;
						 if(min1==4'd5)begin
						   min1<=4'd0;
						  hour2<= hour2+4'd1;
						 uart_en <=1'b1;
									 
                                               if(hour2==4'd9)begin
										 
                                                   hour2<=4'd0;
										 
                                                 hour1<=hour1+4'd1;
						end
						else if(hour1==4'd2&&hour2==4'd3)begin
											 
                                                         hour1<=4'd0;
											 
                                                         hour2<=4'd0;
									end
							   end
					   end
				 end
			end
			else begin
				  count_sec<=6'd0;
				  hour1 <=hour1_temp2;
					hour2 <=hour2_temp2;
					min1  <=min1_temp2;
					min2 <=min2_temp2;
			end
	end
	
	
	
		
		
				   
		always@(posedge clk or negedge rst_n)
		if(!rst_n) time_out <= 32'b0;
		else begin 	
			
				time_out[27:24] <= hour1;	      
				time_out[19:16] <= hour2;
				time_out[11:8]  <= min1;	
				time_out[3:0]   <= min2;
            end	
	
				   
				   
            // 启动/暂停按键进行消抖
	debounce #(.N(3)) U2 (
				.clk(clk),
				.rst(rst_n),
				.key(key),
				.key_pulse(key_pulse)
				);
				
	// 用于分出一个1Hz的频率	
	divide #(.WIDTH(32),.N(12000000)) U1 ( 
			.clk(clk),
			.rst_n(rst_n),      
			.clkout(clk1h)
			);      				   
							
 					
      
 endmodule

(2)分频模块divide,去抖动模块debounce,以及转BCD码模块bin_to_bcd,DS18b20Z温度传感器,OLED12832x显示屏代码,PWM模块均参考电子森林上的开源代码。

在OLED的显示模块中,输入clk为时钟信号,rst为复位信号,tim为时间数据,temperature为温度数据,输出csn,rst,dcn,clk,dat 5个信号控制OLED的显示,在MAIN状态下显示,INIT为初始化状态,SCAN为刷屏状态,WRITE为写状态,将数据按照SPI时序发送给屏幕。在输入信号中设置了一个信号位display_flag,通过改变信号位来实现时间和温度数据停止显示,同时又没有使时间停止计数。

 

(3)tempcontrol温度控制模块,此模块通过调用bin_to_bcd模块把从DS18B20Z模块上输出的数据转换为bcd码形式

module tempcontrol(
         input clk,
		 input rst_n, 
		 inout       one_wire,
		 output reg [31:0] temp_out
		 );
		 
		 
	
		
		wire	[15:0]	data_out;
		//Drive DS18B20Z to get temperature code
		DS18B20Z DS18B20Z_uut
		(
		.clk					(clk			),	// system clock
		.rst_n					(rst_n			),	// system reset, active low
		.one_wire				(one_wire		),	// ds18b20z one-wire-bus
		.data_out				(data_out		)	// ds18b20z data_out
		);




// judge sign of temperature
wire temperature_flag = data_out[15:11]? 1'b0:1'b1;
// complement if negative
wire [10:0] temperature_code = temperature_flag? data_out[10:0]:(~data_out[10:0])+1'b1; 
// translate temperature_code to real temperature
wire [20:0] bin_code = temperature_code * 16'd625;
wire [24:0] bcd_code; //十位[23:20],个位[19:16],小数位[14:12]
//Translate binary code to bcd code
bin_to_bcd bin_to_bcd_uut
(
.rst_n	(rst_n			),	// system reset, active low
.bin_code				(bin_code		),	// binary code
.bcd_code				(bcd_code		)	// bcd code
		);
		
		
		
		
always@(posedge clk or negedge rst_n) begin     
if(!rst_n)begin
	temp_out <= 32'b0;
	end 
else begin			
temp_out <= {4'b0,bcd_code[23:20],4'b0,bcd_code[19:16],4'b0,bcd_code[15:12],8'b0};
	end
end
	
		
		
			
	
endmodule

 

(4)串口通信模块发送与接收,通过USB端口来进行UART数据的传输,采用异步协议。

module uart_tx
(
	input[3:0]  tem_g,tem_s,tem_d,
	input rst_n,
	input clk_tx,
	input en,
	output reg uart_out
);
localparam IDLE = 2'b0;
localparam SEND = 2'b1;
reg flag_1,flag_2,state;
reg[60:0] uart_data;
reg[7:0] tab[9:0];
reg[5:0] i;
 
always @(posedge en  or negedge rst_n) begin
	 if(!rst_n)
	 begin
		 flag_1 = 0;
		 uart_data = 1;
	 end else
		 begin
			 uart_data = {1'd1,8'he6,1'd0,1'd1,8'ha1,1'd0,1'd1,tab[tem_d],1'd0,1'd1,8'h2e,1'd0,1'd1,tab[tem_g],1'd0,1'd1,tab[tem_s],1'd0,1'd1};
			 flag_1 = ~flag_1;
		end
	end
	 
	 
always @(posedge clk_tx or negedge rst_n) begin
	if(!rst_n)
		begin
			 tab[0] = 8'h30;
			 tab[1] = 8'h31;
			 tab[2] = 8'h32;
			 tab[3] = 8'h33;
			 tab[4] = 8'h34;
			 tab[5] = 8'h35;
			 tab[6] = 8'h36;
			 tab[7] = 8'h37;
			 tab[8] = 8'h38;
			 tab[9] = 8'h39;
			 flag_2 = 0;
			 i = 0;
			 state = IDLE;
		 end else begin
			 case(state)
				 IDLE:
				 if(flag_2 != flag_1)
					  begin
						flag_2 = flag_1;
						state = SEND;
						end
				  SEND:
				  if(i < 61)
					  begin
						uart_out = uart_data[i];
						i = i+1'b1;
						end
				  else
					  begin
						i = 0;
						state = IDLE;
						end
			endcase
		end
	 end 
endmodule

(5)蜂鸣器模块,用于整点报时及播放音乐。在其中例化了分频模块,用于产生2HZ的时钟来驱动模块,通过PWM信号控制蜂鸣器发声的频率,在整点报时时,通过预设的一段数据控制蜂鸣器发出声音,通过PC传来的数据控制蜂鸣器发出两只老虎的一段音乐。

module buzzer
(
input clk,
input tri_uart,
input uart_en,
input rst_n,

input [311:0] data_uart,

output reg stop,out

);

localparam size = 40;
localparam IDLE = 2'b00;
localparam LOAD1 = 2'b01;
localparam LOAD2 = 2'b10;
localparam MAIN = 2'b11;
reg[7:0] tone;
reg[5:0] num;
reg[15:0] time_end;
reg[17:0] time_cnt;
reg[size*8-1:0] music;
reg[1:0] state;
reg[23:0] num_delay;


reg [2:0]judge;

always @(posedge clk_buzzer  or negedge rst_n)
    begin
	if(!rst_n) begin
	 stop = 1'b1;
	 music = 312'd0;
	 state = 2'b00;
	 num = 6'd40;

	end else begin
	
	 case(state)
	     IDLE:
		  begin
		  music = 312'd0;
		  case(judge)
			  2'b01:state = LOAD1;
			  2'b10:state = LOAD2;
			  default:state =IDLE;
			  endcase
			  end
			
	      LOAD1:
		  begin
			  music = 120'h0606070708080a0a0d0d00;
			  num = 6'd14;
			  state = MAIN;
			 end
		  LOAD2:
		  begin
		  music = data_uart;
		  num = 6'd40;
		  state = MAIN;
		  stop = 1'b0;
		  end
		  MAIN:
		  begin
		  
	     if(num == 0)
		      begin
	            num = 6'd0;
				state = IDLE;
				stop = 1'b1; 
				end
		  else
		      begin
				  num  = num - 1'b1;
				  tone = music[((num*8)-1)-:8];
	          
				end
		  end
		  default:state = IDLE;
    endcase
	end
	end
	
always@(posedge clk  or negedge rst_n)
    begin
		if(!rst_n) begin
        time_cnt = 0;
	    end else begin
	 case(tone)
	      5'd1:	 	time_end = 16'd45872;	//L1,
		  5'd2: 	time_end = 16'd40858;	//L2,
		  5'd3: 	time_end = 16'd36408;	//L3,
		  5'd4:	 	time_end = 16'd34364;	//L4,
		  5'd5:	 	time_end = 16'd30612;	//L5,
		  5'd6:		time_end = 16'd27273;	//L6,
		  5'd7: 	time_end = 16'd24296;	//L7,
		  5'd8:     time_end = 16'd22931;	//M1,
		  5'd9:     time_end = 16'd20432;	//M2,
		  5'd10:    time_end = 16'd18201;	//M3,
		  5'd11:    time_end = 16'd17180;	//M4,
		  5'd12:    time_end = 16'd15306;	//M5,
		  5'd13:    time_end = 16'd13636;	//M6,
		  5'd14:    time_end = 16'd12148;	//M7,
		  5'd15:	time_end =	16'd5740;	//H1,0f
		  5'd16:	time_end =	16'd5107;	//H2,11
		  5'd17:	time_end =	16'd4549;	//H3,12
		  5'd18:	time_end =	16'd4294;	//H4,13
		  5'd19:	time_end =	16'd3825;	//H5,14
		  5'd20:	time_end =	16'd3408;	//H6,15
		  5'd21:	time_end =	16'd3036;	//H7,16
		  default:  time_end = 16'd0;
	 endcase
	 if(time_end == 0||num == 0)
	     out <= 1'b0;
	 else if(time_cnt >= time_end)
	     begin
		  out <= ~out;
		  time_cnt <= 1'b0;
		  end
	 else
	     time_cnt <= time_cnt + 1'b1;
		 end 
	 end
always @(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
	 num_delay=24'd0;
	
	end else begin
		if(num_delay != 0)
			 begin
			  num_delay = num_delay - 1'b1;
			  end
	    else
			 begin
			  judge[0] = uart_en;
			  judge[1] =tri_uart;
			  if(judge != 0)
				 begin
				  num_delay = 4000000;
				  end
			  else
				  num_delay = 24'd0;
			  end
		end
	end

		  
	//例化分频模块,产生一个1s周期的时钟信号
	divide #
	(								  
	.WIDTH(23),		
	.N(6000000)		
	)
	u_divide
	(
	.clk(clk),		
	.rst_n(rst_n),		
	.clkout(clk_buzzer)		
	);	
	
endmodule

4、项目的功能

参考项目需求部分,满足了全部需求。

5、遇到的问题

(1)在实现项目要求的过程中,我经历了看不懂代码,不懂语法。然后通过认真研究别人代码和分析电子森林上的开源代码。先逐步建立基本工程,实现基本功能。

(2)将time作为变量,系统始终报错,然后改正之后才解决此问题。

(3)忘记分配Uart_tx引脚导致上位机时钟没有显示。

6、总结与展望

完成项目后有以下感悟:

  1. 必须了解FPGA的结构和性能。不同厂家,不同系列的FPGA芯片都有不同的结构和性能,但是万变不离其宗
  2. VHDL和verilog各有优劣,其中verilog语法与c有些许相似之处,个人使用更加方便。但是语言仅仅只是一个工具,尤其在硬件设计里,代码写得漂不漂亮,并不重要,最关键的是设计思想
  3. 单片机和FPGA编程完全是两种思路,既然叫做硬件描述语言,不能按照写单片机的方式,而是要“心中有电路”,各语句是并行的!
  4. 展望:在接下来的一段时间继续完善项目,增加新的功能。

 

软硬件
电路图
团队介绍
安徽大学电气学院
团队成员
袁冲
电气专业大三学生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号