寒假在家一起练(4)项目分享——基于小脚丫FPGA的定时测温报警系统(张至涵)
通过基于Lattice XO2-4000HC的STEP-MXO2小脚丫FPGA开发板,实现时间、温度显示,整点音乐报时,通过UART发送接收数据等功能。
标签
FPGA
数字逻辑
显示
ZHZHH
更新2021-02-28
1184

 

项目背景

    STEP-MXO2第二代是小脚丫团队推出的最新一款FPGA开发板,选用了Lattice公司的MXO2系列的4000HC,逻辑资源较一代产品提升了近4倍。同时,在板卡的背面集成了编程器,通过USB数据线能够完成FPGA的编程和下载,使用硬禾学堂的小脚丫训练板,可以实现多种不同方式的输入输出功能。

 

项目要求

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

 

整体思路

    我实现主要功能的思路如下图所示,通过不同模块实现特定的功能,并通过顶层模块中的wire变量进行数据交换。Fj84YM63zyD78avb5dcBMhnKiKGV

 

完成功能

    1. 在训练板的OLED显示屏上显示当前时间和功能,显示结果正确。FmAsLHkAo2GV_DfpoLcpvBroJq5l    2. 通过开发板上的开关和按键进行时间设置,KEY2选择需要更改的时间位,显示屏上H、h、M、m别代表小时高低位,分钟高低位,SW1~SW4四个开关以BCD码的形式调为需要设定的数字,按下KEY2后,对应位被设定。FuP_FmrOHRsShMockcwNsUIiZ2Up    3. 到达整点时,播放特定的一段音乐,此时OLED屏幕的显示信息不再刷新,开发板向电脑发送当前温度,显示屏温度一致。FnoRqsnvPrCeKtwjjBacfcXNAClX    4. 从电脑中由串口工具向开发板发送乐谱信息,发送完成后,蜂鸣器可以正确播放这段乐谱。
    5. 播放完成后OLED先视频继续刷新,显示当前的时间和温度。
    6. KEY3按键为重置按键,按下后系统中的多个状态寄存器会被归零,时间会被重置为“22:22”。

 

代码实现

    主模块包含了所有对外的输入输出声明以及子模块的例化,其中还包含了时钟分频的功能:

always @(posedge clk)//分频
	begin
		count=count+1;
		countt=countt+1;
		if(count>=720000000) count<=0;
		if(countt>=1250) countt<=0;
	end
	wire clk1min=count[29];
	wire clk2=count[18];
	wire clk_t=countt[10];

    时钟模块:
    输入为分频后的周期为1min的时钟,时钟沿到来时进行进位操作,调节时间时首先由寄存器choice确定需要改变的时间位(小时高位、小时低位、分钟高位、分钟低位),当置位按键低有效时,将四个开关sw所对应的BCD数值赋值给对应的时间位,实现时间调节。最开始,我尝试在检测按键按下后对对应时间位进行+1的操作,但开发板上的按键抖动十分严重,无法有效地调整时间,因此我采用了开关加按键的调整时间方法。

module clock(
input clk1min,clk2,clk,
input rst_n,
input [3:0] sw,
input cc,
input cd,
output reg[7:0]num,num2,num3,num4,
output [2:0]choose
);
reg [2:0]choice=0;
wire [2:0]choose=choice;
initial
begin
	num=2;
	num2=2;
	num3=2;
	num4=2;
	end
always@(posedge cc)
	begin
		choice=choice+1;
		if(choice>=5) choice=0;
   end
		
always@(negedge clk1min or negedge rst_n or negedge cd)
	begin
					
		if(!rst_n)
			begin
				num<=2;
				num2<=2;
				num3<=2;
				num4<=2;
				end
		else if(!cd)
			begin
				case(choice)
					    1:num=sw;
						2:num2=sw;
						3:num3=sw;
						4:num4=sw;
						endcase
				end
		
		else
			begin
if(num4>=9)
begin
num4<=0;
if(num3>=5)
begin
num3<=0;
if(num2>=9)
begin
num2<=0;
num<=num+1;
end
else if(num2>=3&&num>=2)
begin
num2<=0;
num<=0;
end
else
num2<=num2+1;
end
else
num3<=num3+1;
end
else
num4 <= num4+1;		
end
		end

endmodule

    OLED显示模块:
    主要参考了电子森林百科中的代码,输入时间、温度等信息,实现在OLED屏上实时显示。由于代码较长,不在此放出,详见附件。

    温度获取部分:
    包括DS18B20Z驱动模块以及BCD转换模块,前者直接使用了电子森林里的温度传感器驱动,得到16位的二进制温度信息。
    在BCD转换模块中对16位数据进行转换,由前5位判断温度的正负,将后12位的二进制数乘以0.0625即可得到实际的摄氏温度。由于电路无法处理小数,因此先乘以625后取十位、个位以及小数位,通过移位加三的方法实现BCD转换,以节约电路资源,移位加三的方法参考了电子森林中的分享案例。

module temtrans(
input clk,
input [15:0]temout,
output reg [7:0]t2,t1,t0,
output reg [7:0]ts
);
reg [45:0] shift_reg;
reg [10:0] x;
reg [20:0] y;
reg [3:0]z;
always @(posedge clk) begin
	x=temout[10:0];
    y=x*625;
	z=temout[15:12];
	if(z==0) ts<=8'h2B;
		else ts<=8'h2D;
	shift_reg= {25'h0,y};
		repeat(21)
			begin                                
				if (shift_reg[24:21] >= 5) shift_reg[24:21] = shift_reg[24:21] + 2'b11;
				if (shift_reg[28:25] >= 5) shift_reg[28:25] = shift_reg[28:25] + 2'b11;
				if (shift_reg[32:29] >= 5) shift_reg[32:29] = shift_reg[32:29] + 2'b11;
				if (shift_reg[36:33] >= 5) shift_reg[36:33] = shift_reg[36:33] + 2'b11;
				if (shift_reg[40:37] >= 5) shift_reg[40:37] = shift_reg[40:37] + 2'b11;
				if (shift_reg[44:41] >= 5) shift_reg[44:41] = shift_reg[44:41] + 2'b11;
				shift_reg = shift_reg << 1; 
			end         
		t2=shift_reg[44:41]; 
		t1=shift_reg[40:37];
		t0=shift_reg[36:33];
		end
endmodule

    UART部分包括发送和接收:
    发送部分我主要参考了电子森林中的分享案例,输入9600Hz的发送时钟,以及需要发送的温度的十位、个位、小数位以及符号,在使能有效时通过CH340发出。
    接收部分我采用了电子森林百科中的UART驱动代码,在上位机发送数据后,将数据储存在八位寄存器中,并令数据有效信号有效,代码在此不再赘述。

    音频播放部分:
    此部分的核心是蜂鸣器驱动模块,我直接使用了电子森林百科中的驱动源码,只需把需要播放的音调信号储存在一个五位寄存器中作为输入,再输入使能,就能播放对应的单音调。
    音频加载模块用于接收,用一个176位的寄存器存放乐谱,可以存放22个音符(开始的时候正好想到了一个22个音符的旋律,之后没有再修改),当检测接收数据有效时,将接收到的音频信息存放在寄存器的对应位,存满22个之后产生flag有效信号以便播放。

module load(
input valid,
input [7:0]rdata,
output reg[175:0] song,
input [5:0] state,
output reg flag,
input rst_n
);
reg [4:0] count=0;
initial
begin	
flag=0;
count=0;
	end

always@(posedge valid or negedge rst_n)
	begin	
		if(!rst_n)
			begin
				count<=0;
				flag<=0;
				end
	    else begin
				case(count)
			0:begin song[7:0]=rdata[7:0];song[7:0]=rdata[7:0];end
			1:begin song[15:8]=rdata[7:0];song[15:8]=rdata[7:0];end
			2:begin song[23:16]=rdata[7:0];song[23:16]=rdata[7:0];end
			3:begin song[31:24]=rdata[7:0];song[31:24]=rdata[7:0];end
			4:begin song[39:32]=rdata[7:0];song[39:32]=rdata[7:0];end
			5:begin song[47:40]=rdata[7:0];song[47:40]=rdata[7:0];end
			6:begin song[55:48]=rdata[7:0];song[55:48]=rdata[7:0];end
			7:begin song[63:56]=rdata[7:0];song[63:56]=rdata[7:0];end
			8:begin song[71:64]=rdata[7:0];song[71:64]=rdata[7:0];end
			9:begin song[79:72]=rdata[7:0];song[79:72]=rdata[7:0];end
			10:begin song[87:80]=rdata[7:0];song[87:80]=rdata[7:0];end
			11:begin song[95:88]=rdata[7:0];song[95:88]=rdata[7:0];end
			12:begin song[103:96]=rdata[7:0];song[103:96]=rdata[7:0];end
			13:begin song[111:104]=rdata[7:0];song[111:104]=rdata[7:0];end
			14:begin song[119:112]=rdata[7:0];song[119:112]=rdata[7:0];end
			15:begin song[127:120]=rdata[7:0];song[127:120]=rdata[7:0];end
			16:begin song[135:128]=rdata[7:0];song[135:128]=rdata[7:0];end
			17:begin song[143:136]=rdata[7:0];song[143:136]=rdata[7:0];end
			18:begin song[151:144]=rdata[7:0];song[151:144]=rdata[7:0];end
			19:begin song[159:152]=rdata[7:0];song[159:152]=rdata[7:0];end
			20:begin song[167:160]=rdata[7:0];song[167:160]=rdata[7:0];end
			21:begin song[175:168]=rdata[7:0];song[175:168]=rdata[7:0];end
			endcase
		count=count+1;
		if(count>=22) 
			begin
			count=0;
			flag=1;
			end
			end
			
			
		end
endmodule

    音频播放模块:
    核心在于一个54种状态的状态机,在状态0~29,播放内定的音频(这个音频有30个音符),每一种状态对应一个音符,状态为2的同时使能,将温度信息发送给上位机,并停止OLED的更新。30状态为等待输入状态,接收完乐谱信号后,才进入下一状态。状态31~52为播放外部音频的状态,将接收到的乐谱信全部播放出来。播放完后进入53状态,恢复OLED更新,停止对蜂鸣器使能。分钟数据更新后,状态变回0。连续相同音符中间需要有一定的间断,因此通过一个计数器,在每次计满前几个数时中断蜂鸣器使能,使每次播放的音符有间断。由于蜂鸣器模块中,8对应中音do,1的ascii码为49(十进制),将收到的acsii码数据减41之后就可以有1、2、3、……、7和do、re、mi、……ti的对应关系,而低音区域则通过ascii42~48表示,高音通过ascii56~63表示。

module music(
input clk,
input clk2,
input valid,
input [15:0]temout,
input [7:0]m1,m0,rdata,
output reg tone_en=0,
output reg oled_en=1,
output reg [4:0] tone,
input rst_n,
output reg tx_en,
input flag,
input [175:0]song,
output reg [5:0]state
);
initial 
begin
oled_en=1;
state=0;
end
reg [3:0]count;
reg [7:0]x;
always @(posedge clk2)
	begin  
		if(m1==0&&m0==0)
			begin
				if(state<30)
					begin
						if(count<4'b1101) tone_en=1;
							else            tone_en=0;
				        count<=count+1;
				        if(count==4'b1111)
					        begin
						      count<=0;
						      state<=state+1;
						    end
						if(state==2)
							begin
								tx_en=1;//发送数据
								oled_en=0;//暂停更新
						    end
						end
				else if(state==30)//载入乐曲状态
					begin
						tx_en=0;
						tone_en=0;
						if(flag==1) state=state+1;
						end
				else if(state<53&&state>30)
					begin
						if(count<4'b1101) tone_en=1;
							else          tone_en=0;
				        count<=count+1;
				        if(count==4'b1111)
					        begin
						      count<=0;
						      state<=state+1;
						    end
						end
			    else if(state==53)
                    begin
						oled_en=1;
						tone_en=0;
                    end						
			end
		else//不在整点,重置状态
			begin
				tone_en=0;
				state=0;
				oled_en=1;
				end
		end
always@(state)
	begin
		case(state)
			0:tone=10;
			1:tone=10;
			2:tone=11;
			3:tone=12;
			4:tone=12;
			5:tone=11;
			6:tone=10;
			7:tone=9;
			8:tone=8;
			9:tone=8;
			10:tone=9;
			11:tone=10;
			12:tone=10;
			13:tone=9;
			14:tone=9;
			15:tone=10;
			16:tone=10;
			17:tone=11;
			18:tone=12;
			19:tone=12;
			20:tone=11;
			21:tone=10;
			22:tone=9;
			23:tone=8;
			24:tone=8;
			25:tone=9;
			26:tone=10;
			27:tone=9;
			28:tone=8;
			29:tone=8;

			31:
			begin
			x=song[7:0]-41;
			tone=x[4:0];
			end
			32:
			begin
			x=song[15:8]-41;
			tone=x[4:0];
			end
			33:
			begin
			x=song[23:16]-41;
			tone=x[4:0];
			end
			34:
			begin
			x=song[31:24]-41;
			tone=x[4:0];
			end
			35:
			begin
			x=song[39:32]-41;
			tone=x[4:0];
			end
			36:
			begin
			x=song[47:40]-41;
			tone=x[4:0];
			end
			37:
			begin
			x=song[55:48]-41;
			tone=x[4:0];
			end
			38:
			begin
			x=song[63:56]-41;
			tone=x[4:0];
			end
			39:
			begin
			x=song[71:64]-41;
			tone=x[4:0];
			end
			40:
			begin
			x=song[79:72]-41;
			tone=x[4:0];
			end
			41:
			begin
			x=song[87:80]-41;
			tone=x[4:0];
			end
			42:
			begin
			x=song[95:88]-41;
			tone=x[4:0];
			end
			43:
			begin
			x=song[103:96]-41;
			tone=x[4:0];
			end
			44:
			begin
			x=song[111:104]-41;
			tone=x[4:0];
			end
			45:
			begin
			x=song[119:112]-41;
			tone=x[4:0];
			end
			46:
			begin
			x=song[127:120]-41;
			tone=x[4:0];
			end
			47:
			begin
			x=song[135:128]-41;
			tone=x[4:0];
			end
			48:
			begin
			x=song[143:136]-41;
			tone=x[4:0];
			end
			49:
			begin
			x=song[151:144]-41;
			tone=x[4:0];
			end
			50:
			begin
			x=song[159:152]-41;
			tone=x[4:0];
			end
			51:
			begin
			x=song[167:160]-41;
			tone=x[4:0];
			end
			52:
			begin
			x=song[175:168]-41;
			tone=x[4:0];
			end
			endcase
		end
endmodule

 

资源使用情况

FkCUO_oN5rl6R6nuCRpGrCfFNKmA

 

项目总结

    由于春节期间快递停运,没有及时下单,我在2月22号才收到开发板,时间比较紧迫。之前,我在学校的课程中接触到了基于Xillinx的FPGA开发板,在收到板子之前,我对各个模块进行了简单的规划,复习了Verilog语言的相关知识并且学习了Diamond软件的使用方法。
    开发板到手之后,我才发现,细节上的问题比我想象的要多很多,不实际烧录到板子上很多问题是发现不了的。我之前所做的FPGA都没有这次项目这么复杂,在多个模块的整合过程中,需要不断调整,也经常出现奇怪的问题,好在电子森林百科中有大量的小脚丫开发板的相关代码,模块的驱动可以直接使用现有的代码,这为此次项目降低了不少难度。
    最开始我打算通过乘除法实现温度数据的BCD转换,但这种方法需要的资源超出了开发板的资源数量,我参考了平台上的案例分享,使用移位加三的方法实现转换。进行硬件编程与对软件的编程有很大的区别,不仅思路上不一样,还需要关注到资源的使用情况,很多软件编程轻易实现的功能都需要多加考虑。
    多个always语句不能对同一个reg变量赋值的问题也给我添了不少麻烦,在实际编写之前没有考虑好,导致出现问题后大规模删改。在硬件编程时,需要有硬件的意识,以电路的角度思考能否实现。
    实现串口收发功能时,我尝试了多个电脑端的串口工具,在开发板作为接收端时,很多串口工具发送的乐谱接收后播放的声音不一致,多次测试之后,我才找到一个比较稳定的串口助手。但是,这也说明我相关的代码适应性不够好,以后需要再仔细研读UART的手册和时序,编写适应性更好的Verilog程序。


    未来计划:此次项目需要改进的地方还有很多,因为时间仓促,很多地方没有好好斟酌,资源的使用上应该还有很大的节约空间;时间调节部分,可以增加按键消抖模块,加入只通过按键进行时间调节的功能;整点报时部分,加入一些判断,在如深夜等时间不再报时;在音乐播放方面,增加可以输入乐谱的长度,并且可以进行不同长度的识别,并且,对半拍以及多拍的音符进行不同的处理;我还计划加入电脑向开发板发送的其他信息,实现电脑对开发板的控制,并自主编写可以自动操作的上位机;训练板中还有好几个模块这次没有使用到,今后可以加以利用,实现更多功能。

    最后,非常感谢硬禾学堂带给我这次机会,让我对数字逻辑电路、FPGA有了更深入的认识,对Verilog语言的使用也有了不少的提升,同时也给我了一个更加充实的假期。

 

参考资料

https://www.bilibili.com/read/cv3543776/
https://www.eetree.cn/wiki/stepfpga_training_board
https://www.eetree.cn/wiki/uart%E4%B8%B2%E5%8F%A3%E6%A8%A1%E5%9D%97
https://www.eetree.cn/explore/project?q=%E5%AF%92%E5%81%87%E5%9C%A8%E5%AE%B6%E7%BB%83

附件下载
xiaojiaoya.7z
项目工程文件,包含源码
xiaojiaoya_impl1.jed
jed文件,可直接烧录
串口工具与乐谱.zip
包含:串口工具SerialPort2,测试过程均使用此工具;乐谱,每行为一段音乐,可以整行发送或单个音符发送,但总数不能超过22个
团队介绍
北京理工大学信息与电子学院
团队成员
张至涵
北理工电信2018级
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号