1,项目要求:
- 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
- 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
- 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
- PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
- 音频文件播放完毕,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、总结与展望
完成项目后有以下感悟:
- 必须了解FPGA的结构和性能。不同厂家,不同系列的FPGA芯片都有不同的结构和性能,但是万变不离其宗
- VHDL和verilog各有优劣,其中verilog语法与c有些许相似之处,个人使用更加方便。但是语言仅仅只是一个工具,尤其在硬件设计里,代码写得漂不漂亮,并不重要,最关键的是设计思想
- 单片机和FPGA编程完全是两种思路,既然叫做硬件描述语言,不能按照写单片机的方式,而是要“心中有电路”,各语句是并行的!
- 展望:在接下来的一段时间继续完善项目,增加新的功能。