项目需求:
- 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
- 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
- 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
- PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
过程分析:
本项目的具体框架图如下图所示,图中已经注释的很清楚了,不再过多解释,PC与温度处理模块和时钟模块以及向蜂鸣器的通信是通过UART传输的。
Diamond生成的Netlist如下,上传之后这个字很小,看不太清楚,可以直接从我附件里查看。
该项目的top代码如下,共有7个子模块,其中OLED显示模块是按照电子森林里的案例稍加修改得到的,这里不再详细叙述,分频器模块也乏善可陈,故下面只对时钟模块,温度处理模块,蜂鸣器模块以及UART传输模块进行叙述。
top模块代码如下,项目的重点还是在其它模块
module top(
input clk,
input rst, //按键4复位
input hourup, //按键1
input minup, //按键2
input mindown, //按键3
input onoff_uart_t,//开启后可以进行PC与板子的信息传递sw1
inout one_wire,
input uart_rxd,
output oled_csn,
output oled_rst,
output oled_dcn,
output oled_clk,
output oled_dat,
output uart_out, //串口输出
output beeout
);
wire[3:0] tempb;
wire[3:0] temps;
wire[3:0] tempg;
wire[3:0] tempx;
wire[3:0] hour;
wire[3:0] hours;
wire[3:0] min;
wire[3:0] mins;
wire[3:0] sce;
wire[3:0] sces;
wire[7:0] uart_data;
wire clk1h;
wire judge;
OLED12832 OLED12832_v1(
.clk(clk),
.rst(rst),
.tempb(tempb),
.temps(temps),
.tempg(tempg),
.tempx(tempx),
.hour(hour),
.hours(hours),
.min(min),
.mins(mins),
.sce(sce),
.sces(sces),
.oled_clk(oled_clk),
.oled_csn(oled_csn),
.oled_dat(oled_dat),
.oled_dcn(oled_dcn),
.oled_rst(oled_rst)
);
DS18B20Z DS18B20Z_v1(
.clk(clk),
.rst(rst),
.one_wire(one_wire),
.judge(judge),
.tempb(tempb),
.temps(temps),
.tempg(tempg),
.tempx(tempx)
);
Div Div_v1(
.clk(clk),
.rst(rst),
.clkout(clk1h)
);
clock clock_v1(
.clk(clk1h),
.rst(rst),
.mindown(mindown),
.minup(minup),
.hourup(hourup),
.judge(judge),
.hour(hour),
.hours(hours),
.min(min),
.mins(mins),
.sce(sce),
.sces(sces)
);
uart_t uart_t_v1(
.clk(clk),
.onoff_uart_t(onoff_uart_t),
.temps(temps),
.tempg(tempg),
.tempx(tempx),
.hour(hour),
.hours(hours),
.min(min),
.mins(mins),
.sces(sces),
.sce(sce),
.uart_out(uart_out)
);
beepall beepall_v1(
.clk(clk),
.clk1h(clk1h),
.rst(rst),
.uart_done(uart_done),
.uart_data(uart_data),
.sce(sce),
.mins(mins),
.min(min),
.judge(judge),
.beeout(beeout)
);
uart_r uart_r_v1(
.sys_clk(clk),
.sys_rst_n(rst),
.uart_rxd(uart_rxd),
.uart_done(uart_done),
.uart_data(uart_data)
);
endmodule
下面分析温度处理模块,温度处理模块的作用就是把温度传感器模块输出的数据转成易于处理的十进制数值(实际上是百位,十位,个位,小数位分开储存),并且将数据传给PC,OLED和蜂鸣器,这样就能快捷进行比较数据大小,进行数据运算等操作,这部分代码特别长,我就不全部放在这里了,需要参考的话可以看我的附件Ds18.v这个文件。
//类似进制转换的代码//
always@(*)
begin
tempcopy = date_out; //tempcopy就是温度传感器数据的复制,用于计算
//tempb百位只考虑1或0,不过实际测量中最高只有30多度//
if(tempcopy >> 8 >= 4'b0110) //这里参考我在网上看到的改进的判断方式,先移位再判断大小,较我之前的列出16位进行判断节省了很多空间
begin
tempbreal = 4'b1;
tempcopy = tempcopy - 12'b0110_0100_0000;
end
else
begin
tempbreal = 4'b0;
end
//temps十位//
if(tempcopy>>4 >= 8'b0101_1010) //和上面一样,利用位移运算可以节省很多空间
begin
tempsreal = 4'd9;
tempcopy = tempcopy - 12'b0101_1010_0000;
end
else if(tempcopy>>4 >= 8'b0101_0000)
begin
tempsreal = 4'd8;
tempcopy = tempcopy - 12'b0101_0000_0000;
end
else if(tempcopy>>4 >= 8'b0100_0110)
begin
tempsreal = 4'd7;
tempcopy = tempcopy - 12'b0100_0110_0000;
end
else if(tempcopy>>4 >= 8'b0011_1100)
begin
tempsreal = 4'd6;
tempcopy = tempcopy - 12'b0011_1100_0000;
end
else if(tempcopy>>4 >= 8'b0011_0010)
begin
tempsreal = 4'd5;
tempcopy = tempcopy - 12'b0011_0010_0000;
end
else if(tempcopy>>4 >= 8'b0010_1000)
begin
tempsreal = 4'd4;
tempcopy = tempcopy - 12'b0010_1000_0000;
end
else if(tempcopy>>4 >= 8'b0001_1110)
begin
tempsreal = 4'd3;
tempcopy = tempcopy - 12'b0001_1110_0000;
end
else if(tempcopy>>4 >= 8'b0001_0100)
begin
tempsreal = 4'd2;
tempcopy = tempcopy - 12'b0001_0100_0000;
end
else if(tempcopy>>4 >= 8'b0000_1010)
begin
tempsreal = 4'd1;
tempcopy = tempcopy - 12'b0000_1010_0000;
end
else
begin
tempsreal = 4'd0;
end
//tempg个位//
if(tempcopy >>4 >= 4'd9)
begin
tempgreal = 4'd9;
tempcopy = tempcopy - 4'd9;
end
else if(tempcopy >>4 >= 4'd8)
begin
tempgreal = 4'd8;
tempcopy = tempcopy - 4'd8;
end
else if(tempcopy >>4 >= 4'd7)
begin
tempgreal = 4'd7;
tempcopy = tempcopy - 4'd7;
end
else if(tempcopy >>4 >= 4'd6)
begin
tempgreal = 4'd6;
tempcopy = tempcopy - 4'd6;
end
else if(tempcopy >>4 >= 4'd5)
begin
tempgreal = 4'd5;
tempcopy = tempcopy - 4'd5;
end
else if(tempcopy >>4 >= 4'd4)
begin
tempgreal = 4'd4;
tempcopy = tempcopy - 4'd4;
end
else if(tempcopy >>4 >= 4'd3)
begin
tempgreal = 4'd3;
tempcopy = tempcopy - 4'd3;
end
else if(tempcopy >>4 >= 4'd2)
begin
tempgreal = 4'd2;
tempcopy = tempcopy - 4'd2;
end
else if(tempcopy >>4 >= 4'd1)
begin
tempgreal = 4'd1;
tempcopy = tempcopy - 4'd1;
end
else
begin
tempgreal = 4'd0;
end
//tempx小数//
tempcopy = (tempcopy <<12) >> 12; //小数位部分较上面偏难,这里我参考了网上的代码,但与上面大体规律相同
if(tempcopy >= 4'd14)
tempxreal = 4'd9;
else if(tempcopy >= 13)
tempxreal = 4'd8;
else if(tempcopy >= 11)
tempxreal = 4'd7;
else if(tempcopy >= 9)
tempxreal = 4'd6;
else if(tempcopy >= 8)
tempxreal = 4'd5;
else if(tempcopy >= 6)
tempxreal = 4'd4;
else if(tempcopy >= 4)
tempxreal = 4'd3;
else if(tempcopy >= 3)
tempxreal = 4'd2;
else if(tempcopy >= 1)
tempxreal = 4'd1;
else
tempxreal = 4'd0;
时钟模块的作用是模拟时钟,需要对4个按键功能(我的是复位,分钟减少,分钟增加,小时增加)进行编程,只需要注意小时,分钟和秒都是有上限的,多多注意细节即可,我的板子四个按键不太灵敏,要按很多下才行,具体可以参考我附录的clock模块,这里就不展示代码了。
UART传输模块我认为是该项目最难的一部分,直到我翻阅到了https://blog.csdn.net/weifengdq/article/details/103168587这个CSDN博客文章,我参考其它完成任务的同学的项目时发现这个博客文章的引用率很高,群里也应该给出了这个博客的链接,我对文章里附录的代码进行了一些分析,以此为基础完成了我的UART传输模块,代码有些长,我放在了附录里,没有思路的同学可以去这个博客链接里看一看,博客里写的非常详细。
蜂鸣器模块也是比较复杂的一个模块,该模块的主要功能是整点报警和温度报警,整点报警我用的是固定的高声调15循环,温度报警就用自己的音乐,我这里参考了其它同学分享的案例,就是先整理出一个音调表,我在下面给出了,蜂鸣器接受到不同的数据会发出不同音调,比如收到1就发低音1声调,用XCOM循环发送就能发出不同的报警声,这部分代码我在下面给出。
//这一模块我是参考其它同学使用XCOM这个串口工具所用的,通过在XCOM循环发送1到15,a到f中的某几项就可以编出高,中,低各7个音调,非常方便
always @(posedge clk)
begin
if(uart_done)
begin
case(uart_data)
8'h1:count_end1 <=16'd22935; //L1,
8'h2:count_end1 <=16'd20428; //L2,
8'h3:count_end1 <=16'd18203; //L3,
8'h4:count_end1 <=16'd17181; //L4,
8'h5:count_end1 <=16'd15305; //L5,
8'h6:count_end1 <=16'd13635; //L6,
8'h7:count_end1 <=16'd12147; //L7,
8'h8:count_end1 <=16'd11464; //M1,
8'h9:count_end1 <=16'd10215; //M2,
8'ha:count_end1 <=16'd9100; //M3,
8'hb:count_end1 <=16'd8589; //M4,
8'hc:count_end1 <=16'd7652; //M5,
8'hd:count_end1 <=16'd6817; //M6,
8'he:count_end1 <=16'd6073; //M7,
8'hf:count_end1 <=16'd5740; //H1,
8'h10:count_end1 <=16'd5107; //H2,
8'h11:count_end1 <=16'd4549; //H3,
8'h12:count_end1 <=16'd4294; //H4,
8'h13:count_end1 <=16'd3825; //H5,
8'h14:count_end1 <=16'd3408; //H6,
8'h15:count_end1 <=16'd3036; //H7,
default:count_end1 <=16'd65535;
endcase
end
end
这是我实验过程中的截图(上面温度不变是由于达到了报警温度,下面22.1C处关闭了数据发送,停止了温度报警功能,温度又可以变化),我觉得音乐还可以,我附件中有可以导入XCOM的数据以及XCOM串口工具的安装包,使用方法就是链接小脚丫基板端口,选择串口,打开串口,几秒后就能接收到数据,启用温度警报的方法是设置报警温度(在uartdate.v这个文件里设置,默认20摄氏度),多条发送(先输入发送数据,按我下图里的就行,自己编也可以,然后点击16进制发送和自动循环发送),之后电脑就启动温度报警功能,超过报警温度就会播放PC发送的音乐。
注:温度报警的优先级高于整点报警,但是XCOM串口工具不太灵敏,有时不显示接受的信号,需要不断打开和关闭串口才能显示,有时达到了温度报警和整点报警的要求但是蜂鸣器响设定的报警声,不响温度报警声,这是由于小脚丫没有收到信号,重新开关两个发送选项几次就可以解决这个问题。
性能分析
性能分析详情可以看这张图或者解压我附录的代码查看,这里给出比较重要的几部分
Number of registers: 721 out of 4635 (16%)
Number of SLICEs: 1176 out of 2160 (54%)
Number of LUT4s: 2347 out of 4320 (54%)
总结与反思
在完成小脚丫FGPA项目中我遇到了很多问题,我在一开始的Diamond软件的安装就出现了很多问题,最后通过修改环境变量解决了安装过程中许可证不能用的问题,然后又遇到代码不高亮的问题,一直没理解更改window用户名的含义,网上资料很少,问了很多同学也没能解决,拖了1天才找到方法。
在开始构架模块的过程中没有明白UART传输的原理,直到看见了我上文提到的博客才搞明白,然后传输时电脑一直收不到信息,后来请教他人后发现小脚丫的接头应该接基板上的才能传输,最后成功传输了数据。
这个项目总的来说给了我很大的启发,让我对FGPA,verilog语言这些大学时只为应付考试匆匆略过的知识有新的认知,尤其是让我之前对学不会verilog语言的反感变成学会一技之长的满足,这种感觉极大地鼓舞了我学习硬件语言的信心。