寒假在家一起练项目4——黄宇康
寒假在家一起练项目4-小脚丫综合训练的个人报告和代码、视频。
标签
FPGA
数字逻辑
Huang
更新2021-03-01
1028
FjIg4_9B-k9L3m_c0CYIU0vVzSX3
1、按键输入/时钟
每500ms按键进行一次按键检测,如果发现和按键所在引脚电平逻辑发生改变,则对应向外输出宽度为一个系统时钟周期的高脉冲。时钟模块接收到高脉冲以后进入对应的状态。
`define M 22
module onebutton(input sys_clk,input rst_n,input key,output reg key_out);
reg[`M:1] cnt;
reg current;
always@(posedge sys_clk)
if(rst_n == 1'b0)begin current<=1'b1;cnt<=`N'b0;end
else begin
if(cnt == `M'd36_000_00)begin
cnt <= `M'd0;
current <= key;end
else begin
cnt <= cnt + `M'd1;
current <= 1'b1;end
end

always@(posedge sys_clk)
if(rst_n == 1'b0)begin key_out <= 3'b0;end
else
begin
if(current == 1'b0)
key_out <= 1'b1;
else
key_out <= 1'b0;
end
endmodule
2、DS18B20Z
这部分主要是参考了硬禾的参考程序,添加了一个将BCD码转码成4位十进制数的小模块。
module BCDtoDEC(
input [15:0]datin,
input sys_clk,
input rst,
input fresh_flag,


output reg[3:0]dat_h, //百位
output reg[3:0]dat_t, //十位
output reg[3:0]dat_u, //个位
output reg[3:0]dat_d //小数
);


reg [15:0] datcpy;
reg [3:0] dat_h_now;
reg [3:0] dat_t_now;
reg [3:0] dat_u_now;
reg [3:0] dat_d_now;
reg [3:0] dat_h_past;
reg [3:0] dat_t_past;
reg [3:0] dat_u_past;
reg [3:0] dat_d_past;

always@(posedge sys_clk)
begin
dat_h <= dat_h_past;
dat_t <= dat_t_past;
dat_u <= dat_u_past;
dat_d <= dat_d_past;
end


always@(posedge sys_clk)
begin
if(fresh_flag)
begin
dat_h_past <= dat_h_now;
dat_t_past <= dat_t_now;
dat_u_past <= dat_u_now;
dat_d_past <= dat_d_now;
end
else
begin
dat_h_past <= dat_h_past;
dat_t_past <= dat_t_past;
dat_u_past <= dat_u_past;
dat_d_past <= dat_d_past;
end
end

always@* //组合逻辑
begin
datcpy = datin;
if(datcpy[15] == 1'b1)
begin
datcpy = ~datcpy + 1'b1;
dat_h_now = 4'd2;
end
else
dat_h_now = 4'b0;


//负数为补码形式
//百位
if(datcpy >> 8 >= 4'b0110)//100 ->0110 0100
begin
dat_h_now = 4'b1;
datcpy = datcpy - 12'b0110_0100_0000;
end

//十位
if(datcpy>>4 >= 8'b0101_1010)//90
begin
dat_t_now = 4'd9;
datcpy = datcpy - 12'b0101_1010_0000;
end
else if(datcpy>>4 >= 8'b0101_0000)//80
begin
dat_t_now = 4'd8;
datcpy = datcpy - 12'b0101_0000_0000;
end
else if(datcpy>>4 >= 8'b0100_0110)//70
begin
dat_t_now = 4'd7;
datcpy = datcpy - 12'b0100_0110_0000;
end
else if(datcpy>>4 >= 8'b0011_1100)//60
begin
dat_t_now = 4'd6;
datcpy = datcpy - 12'b0011_1100_0000;
end
else if(datcpy>>4 >= 8'b0011_0010)//50
begin
dat_t_now = 4'd5;
datcpy = datcpy - 12'b0011_0010_0000;
end
else if(datcpy>>4 >= 8'b0010_1000)//40
begin
dat_t_now = 4'd4;
datcpy = datcpy - 12'b0010_1000_0000;
end
else if(datcpy>>4 >= 8'b0001_1110)//30
begin
dat_t_now = 4'd3;
datcpy = datcpy - 12'b0001_1110_0000;
end
else if(datcpy>>4 >= 8'b0001_0100)//20
begin
dat_t_now = 4'd2;
datcpy = datcpy - 12'b0001_0100_0000;
end
else if(datcpy>>4 >= 8'b0000_1010)//10
begin
dat_t_now = 4'd1;
datcpy = datcpy - 12'b0000_1010_0000;
end
else//00
dat_t_now = 4'd0;

//个位
if(datcpy >>4 >= 4'd9)//9
begin
dat_u_now = 4'd9;
datcpy = datcpy - 4'd9;
end
else if(datcpy >>4 >= 4'd8)//8
begin
dat_u_now = 4'd8;
datcpy = datcpy - 4'd8;
end
else if(datcpy >>4 >= 4'd7)//7
begin
dat_u_now = 4'd7;
datcpy = datcpy - 4'd7;
end
else if(datcpy >>4 >= 4'd6)//6
begin
dat_u_now = 4'd6;
datcpy = datcpy - 4'd6;
end
else if(datcpy >>4 >= 4'd5)//5
begin
dat_u_now = 4'd5;
datcpy = datcpy - 4'd5;
end
else if(datcpy >>4 >= 4'd4)//4
begin
dat_u_now = 4'd4;
datcpy = datcpy - 4'd4;
end
else if(datcpy >>4 >= 4'd3)//3
begin
dat_u_now = 4'd3;
datcpy = datcpy - 4'd3;
end
else if(datcpy >>4 >= 4'd2)//2
begin
dat_u_now = 4'd2;
datcpy = datcpy - 4'd2;
end
else if(datcpy >>4 >= 4'd1)//1
begin
dat_u_now = 4'd1;
datcpy = datcpy - 4'd1;
end
else
dat_u_now = 4'd0;

//小数位
datcpy = (datcpy <<12) >> 12;
if(datcpy >= 4'd14)
dat_d_now = 4'd9;
else if(datcpy >= 13)
dat_d_now = 4'd8;
else if(datcpy >= 11)
dat_d_now = 4'd7;
else if(datcpy >= 9)
dat_d_now = 4'd6;
else if(datcpy >= 8)
dat_d_now = 4'd5;
else if(datcpy >= 6)
dat_d_now = 4'd4;
else if(datcpy >= 4)
dat_d_now = 4'd3;
else if(datcpy >= 3)
dat_d_now = 4'd2;
else if(datcpy >= 1)
dat_d_now = 4'd1;
else
dat_d_now = 4'd0;
end
endmodule
3、OLED显示
主要参考于网站例程,修改了状态MAIN使得OLED前两行分别显示温度数据和实时时钟。
MAIN:begin
if(cnt_main >= 5'd6) cnt_main <= 5'd5;
else cnt_main <= cnt_main + 1'b1;
case(cnt_main) //MAIN状态
5'd0: begin state <= INIT; end
5'd1: begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "TEMP ";state <= SCAN; end //前四行的初值
5'd2: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end //前四行的初值
5'd3: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "TIME ";state <= SCAN; end //前四行的初值
5'd4: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " ";state <= SCAN; end //前四行的初值


5'd5:if(dat_h == 4'd2)
begin
y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 5; char <= {"-", 4'd0, dat_t, 4'd0, dat_u, ".", 4'd0, dat_d}; state <= SCAN; //传递参数直接给4bit二进制数数值就行
end
else if(dat_h == 4'd1)
begin
y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 5; char <= {4'd0, dat_h, 4'd0, dat_t, 4'd0, dat_u, ".", 4'd0, dat_d}; state <= SCAN; //num指代写入字(宽度为5/8)的个数
end //因为字库是5*8的,所以一个字的宽度是4,中间间隔一列的宽度
else //if(dat_h == 4'd0)
begin
y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 4; char <= {4'd0, dat_t, 4'd0, dat_u, ".", 4'd0, dat_d}; state <= SCAN;
end
5'd6: begin
y_p <= 8'hb2; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 5; char <= {4'd0, hour_t,4'd0, hour_u,":", 4'd0, min_t,4'd0, min_u}; state <= SCAN;
end

default: state <= IDLE;
endcase
end
4、UART收发
每到整点时发送一次温度、时间数据同时自动播放音频《小星星》,温度高于28℃时也发送温度、时间数据,并从PC上接收音频数据播放(手动发送)。
always @(posedge clk_en)
begin
if((one == 1 && two == 0 &&three == 0 && four == 0 && onoff_uart_t) || (temp_t >= 2 && temp_u >= 6))
begin
uart_data = {
1'd1,8'd13,1'd0, //回车
//1'd1,8'd10,1'd0, //换行
1'd1,4'd3,three,1'd0, //分钟低位
1'd1,4'd3,four,1'd0, //分钟高位
1'd1,8'd58,1'd0, //:
1'd1,4'd3,five,1'd0, //小时低位
1'd1,4'd3,six,1'd0, //小时高位
1'd1,8'd32,1'd0, //space
1'd1,8'd67,1'd0, //C
1'd1,4'd3,temp_d,1'd0, //one tenth
1'd1,8'd46,1'd0, //point
1'd1,4'd3,temp_u,1'd0, //unit
1'd1,4'd3,temp_t,1'd0, //ten
1'd1,1'd1
};
flag_1 = ~flag_1;
end
end
5、蜂鸣器
这一部分我参考了其他同学的案例,将每一个音调的播放时间控制在250ms。
6、资源使用情况
FiRIhc4uKd8vX2n95wP2jAu_QwLB
7、串口接收
报警温度设置为28℃,除第一行为整点发送外均为过热发送。
FrFHaVNUpJaEX2hEceJpKHDHxWU7
7、Diamond使用
   我在项目前期遇到的一大难题就是top模块的input总是被识别为unconnected port。导致这个问题最常见的原因就是设计本身的逻辑有问题,这就需要返回到源码去一一查看每个分支是否都考虑到了,把没有考虑到的补上。此外,如果两个模块之间的接口关联性不强(比如上述button和timeclock,需要36_000_00个时钟才会触发),Diamond会自动修剪掉同名的类似端口,此时最好的解决办法是在project->active strategy->translate Design里加上“-u”,可以防止综合时优化掉输入管脚;也可以将两个模块合并到一起。但是需要确保逻辑没问题,否则即使可以分配管脚,真正下载到板子上还是用不了。
 
8、Verilog的学习
   在本项目中,我最大的收获是在参考OLED示例时学会的状态机跳转写法。学会了直接将某个过程划分为几个简单的主要状态,如IDLE、MAIN、INIT、DELAY等,对每个状态分别创建一个计数器,在计数器内的各个值进行状态循环。用一个大的case将各个主要状态对应起来。由于同一时刻该module只会进入一个状态,对书写时序操作有很大的帮助。此外,每一个主要状态都可以对同一寄存器进行操作,因为同一时刻仅可能进入一个状态。
 
9、总结
   在完成项目的过程中,我学会了如何更好地去写好可综合的Verilog代码、协调各个模块之间的时序。更重要的是体会了不断试错、不断debug、不断完善的过程,增加了自己做项目的信心。希望以后能够继续不断积累经验,多多和同学们交流技术问题。
附件下载
top_all_impl1.jed
可下载验证的代码
团队介绍
合肥工业大学-电子科学与应用物理学院
团队成员
黄宇康
正在搬砖的本科生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号