项目需求:
该项目基于小脚丫FPGA的综合技能训练板,制作一个能够计时、测温、定时报警、串口控制的系统,具体功能如下:
- 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
- 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
- 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
- PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
- 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息
设计思路:
该项目使用的模块包括:板载的12MHz时钟、温度传感器、OLED显示屏、按键、蜂鸣器和uart串口,大体思路如下图(这确确实实是我自己用xmind画的,也许思路会与别人的有相同的,但你不能说这不是我画的,你说有一模一样的,可以发来给我看吗?):
模块实现:
1、时钟系统
由于时钟信号是贯穿所有模块的,于是我选择将时钟模块作为了顶层模块,并在此模块的基础上放置别的例化模块,其中的时钟系统的建立是参考了电子森林里的60秒计时例程。这是截取的计时代码,通过分频器产生1Hz的时钟信号,并在该时钟的基础下建立起时、分、秒的计时。
wire clk1h; //1分时钟
reg [7:0] cnt1,cnt2,cnt3; //计时计数器——cnt1秒、cnt2分、cnt3时
reg flag; //启动暂停标志
divide # //例化分频器产生1分时钟信号
(
.WIDTH(24),
.N(12_000_000)
) u1
(
.clk(clk),
.rst_n(rst),
.clkout(clk1h)
);
always @(posedge clk1h or negedge rst) //24小时计数器
begin
if(!rst)
begin
cnt1 <= 8'h00; //复位初值显示00
cnt2 <= 8'h00;
cnt3 <= 8'h00;
end
else if(flag)
begin
if(cnt1[3:0] == 4'd9) //秒个位满九?
begin
cnt1[3:0] <= 4'd0; //秒个位清零
if(cnt1[7:4] == 4'd5 ) //秒十位满五?
begin
cnt1[7:4] <= 4'd0; //秒十位清零?
if(cnt2[3:0] == 4'd9)//分钟个位满九?
begin
cnt2[3:0] <= 4'd0; //分钟个位清零
if (cnt2[7:4] == 4'd5)
begin
cnt2[7:4] <= 4'd0; //分钟十位满5就清零,否则加一
if(cnt3[3:0] == 4'd9)
begin
cnt3[3:0] <= 4'd0;//小时个位满9清零,否则加一
cnt3[7:4] <= cnt3[7:4] + 1'b1; //小时十位加一
if(cnt3[3:0] == 4'd3 && cnt3[7:4] == 4'd2) cnt3 <= 8'd0; //满24小时小时清零
end
else cnt3[3:0] <= cnt3[3:0] + 1'b1;
end
else cnt2[7:4] <= cnt2[7:4] + 1'b1;
end
else cnt2[3:0] <= cnt2[3:0] + 1'b1;
end
else
cnt1[7:4] <= cnt1[7:4] + 1'b1; //十位加一
end
else cnt1[3:0] <= cnt1[3:0] + 1'b1; //个位加一
end
2、按键消抖
按键消抖通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。这部分代码是参照了正点原子的教学视频,通过设置20ms的延时,来实现按键的重复检测实现消抖。具体代码如下:
module key_debounce(
input clk,
input rst,
input key,
output reg key_value,
output reg key_flag
);
reg key_reg;
reg [22:0] delay_cnt;
always @(posedge clk or negedge rst) begin
if (!rst) begin
key_reg <= 1'b1;
end
else begin
key_reg <= key;
if (key_reg != key)
delay_cnt <= 23'd2400000; //设置20ms的延时
else
if(delay_cnt > 23'd0)
delay_cnt <= delay_cnt - 1'b1;
else
delay_cnt <= 23'd0;
end
end
always @(posedge clk or negedge rst) begin
if (!rst) begin
key_value <= 1'b1;
key_flag <= 1'b0;
end
else begin
if (delay_cnt == 23'd1)begin
key_flag <= 1'b1;
key_value <= key;
end
else begin
key_flag <= 1'b0;
key_value <= key_value;
end
end
end
endmodule
3、OLED显示
OLED显示部分我是在电子森林里找到的例程,并加以修改,下面是显示部分代码:
MAIN:begin
if(cnt_main >= 5'd14 && flag_tx != 1'b1)begin cnt_main <= 5'd5; cn <= cnt4; end
else if (cnt_main >= 5'd14 && flag_tx == 1'b1) begin cnt_main <= 5'd5; cn <= cn; end
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 <= " The time: ";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 <= "The temperature:";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: begin y_p <= 8'hb1; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= cnt1[7:4]; state <= SCAN; end
5'd6: begin y_p <= 8'hb1; x_ph <= 8'h16; x_pl <= 8'h00; num <= 5'd 1; char <= cnt1[3:0]; state <= SCAN; end
5'd7: begin y_p <= 8'hb1; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <= cnt2[7:4]; state <= SCAN; end
5'd8: begin y_p <= 8'hb1; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; char <= cnt2[3:0]; state <= SCAN; end
5'd9: begin y_p <= 8'hb3; x_ph <= 8'h16; x_pl <= 8'h00; num <= 5'd 1; char <= cn[3:0]; state <= SCAN; end
5'd10: begin y_p <= 8'hb3; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= cn[7:4]; state <= SCAN; end
5'd11: begin y_p <= 8'hb3; x_ph <= 8'h14; x_pl <= 8'h00; num <= 5'd 1; char <= cn[11:8]; state <= SCAN; end
5'd12: begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 1; char <= cn[15:12]; state <= SCAN; end
5'd13: begin y_p <= 8'hb1; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 1; char <= cnt3[3:0]; state <= SCAN; end
5'd14: begin y_p <= 8'hb1; x_ph <= 8'h11; x_pl <= 8'h00; num <= 5'd 1; char <= cnt3[7:4]; state <= SCAN; end
default: state <= IDLE;
endcase
end
4、蜂鸣器模块
在蜂鸣器内留有音符库,通过读取串口发送来的音频数据来播放音乐。
reg [23:0] cnt;
always@(posedge clk or negedge rst_n)
if(!rst_n) begin cnt <= 1'b0; end
else if (cnt >= 23'd5_999_999)begin cnt <= 23'd0; end
else cnt <= cnt + 1'b1;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)begin cycle =16'd0; end
else if(cnt == 23'd5_999_999 && flag_tx == 1'b1) //整点时且1秒时钟到来时蜂鸣器报时
begin
case(rx_data_out[3:0])
4'h0: cycle = 16'd45872; //L1,
4'h1: cycle = 16'd40858; //L2,
4'h2: cycle = 16'd36408; //L3,
4'h3: cycle = 16'd34364; //L4,
4'h4: cycle = 16'd30612; //L5,
4'h5: cycle = 16'd27273; //L6,
4'h6: cycle = 16'd24296; //L7,
4'h7: cycle = 16'd22931; //M1,
4'h8: cycle = 16'd20432; //M2,
4'h9: cycle = 16'd18201; //M3,
4'ha: cycle = 16'd17180; //M4,
4'hb: cycle = 16'd15306; //M5,
4'hc: cycle = 16'd13636; //M6,
4'hd: cycle = 16'd12148; //M7,
4'he: cycle = 16'd11478; //H1,
4'hf: cycle = 16'd10215; //H2,
default: cycle = 16'd0; //cycle为0,PWM占空比为0,低电平
endcase
end
else if(cnt == 23'd5_999_999 / 2) cycle = 16'd0; //响应时间为0.5s
end
5、DS18B20测温模块
该部分是在电子森林里的例程,基本可以直接沿用,以下是模块的例化以及数据的处理,将原始的数据转化为bcd码以便oled的引用:
wire [15:0] data_out;
//DS18B20温度传感器模块————————————————————//
DS18B20Z DS18B20Z_uut
(
.clk (clk ), // system clock
.rst_n (rst ), // system reset, active low
.one_wire (one_wire ), // ds18b20z one-wire-bus
.data_out (data_out ) // ds18b20z data_out
);
wire temperature_flag = data_out[15:11]? 1'b0:1'b1;
wire [10:0] temperature_code = temperature_flag? data_out[10:0]:(~data_out[10:0])+1'b1;
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 ),
.bin_code (bin_code ), // binary code
.bcd_code (bcd_code ) // bcd code
);
6、串口模块
该模块也是沿用找到的例程,在此基础上修改,如下是整点时向pc端传输数据时的代码:
reg tx_data_valid; //发送标志位
reg flag_rst;
reg [7:0] tx_data_in; //温度数据
always @(posedge clk or negedge rst_n) //整点信号flag_tx为1时,向pc端发送温度数据
if(!rst_n) begin tx_data_valid <= 1'b0; tx_data_in <= 1'b0; flag_rst <= 1'b0; end
else if(flag_tx == 1'b1 && flag_rst == 1'b0) begin tx_data_valid <= 1'b1; tx_data_in <= cnt3; flag_rst <= 1'b1; end
else if(flag_tx == 1'b0 && flag_rst == 1'b1) begin flag_rst <= 1'b0; end
else begin tx_data_valid <= 1'b0; tx_data_in <= tx_data_in; end
遇到的困难:
1、初次基础FPGA,对于Verilog语言的使用还比较生疏。
2、对于上位机部分不太了解,为了实现使用Uart自动传输音频文件无从下手,后来发现通过串口助手的自动传输功能可以实现音频信号的自动传输。
3、对于底层模块的理解还不够彻底,以至于有些功能的实现难以实现。
资源占用:
项目总结:
通过这次的FPGA项目,我从零开始学习Verilog编程语言,从最开始的新建工程到点亮LED灯一直到实现整个项目,在此期间我收获颇多,不仅仅是在编程语言层面的提升,还学会了如何收集现有资源来学习所需知识,完成对应模块。与此同时,还发现了自身依然存在诸多不足之处,对于上位机一窍不通,对于底层知识理解也还不够到位。希望自己在将来的学习道路中能够更加努力。