任务:
1.实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
2.实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
3.定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
4.PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
5.音频文件播放完毕,OLED开始更新时间信息和当前的温度信息
资源报告:
任务实现:
这里记录一下我对于fpga的一些理解,和做这个项目的一些感受,我也是才刚上手fpga,对于一些细节甚至是基础知识点都有很多理解错误的地方,还请大家见谅。
我对于fpga的期待是直接去编程操作硬件,故名思意嘛,fpga中文名叫可编程逻辑门阵列,那我们编写代码的时候是不是直接在操作一个又一个的逻辑门,搭积木一样地把他们堆砌起来呢,其实不然,至少我初学时用的这个verilog语言不是这样的逻辑,它和c语言很像,但很多地方比起c语言还有低级,在学verilog之前我以为c语言就已经是倒数第二低级的语言了,倒数第一当然是汇编,这里低级不是贬义词,仅仅是形容这种语言的语义密度低,写了很多但是干的事少。不得不承认,我自恃有单片机的基础,做这个项目的态度不够严谨,很多东西没有学的太明白就草草上战场,去完成项目,我把任务各个模块的代码找来,觉得只要简单的把他们拼接到一起,项目就完成了,结果这个拼接的过程异常痛苦,自己都觉得写的极差,很多地方都只是勉强能用,这样做的后果是逻辑门使用率直逼100%,后来才反应过来,原来是自己只是学会了verilog的语法,并没有完全了解fpga的特点,而是去生搬硬套单片机的经验,导致写的代码人不人鬼不鬼的。一如中国革命一定要结合中国国情,照搬照抄外国的成功经验是行不通的。我打算接下来再去学习一下时钟分频、加法器、状态机这类看起来不怎么有实际意义的课时,希望在亲身接触到实际项目之后,在受过虐之后,能对这些内容有一个更深入的理解,写出来的代码能更加具备可读性和简洁性。到时候希望重写这个项目。
那么也说一下fpga这个平台的特点吧
1.和硬件联系紧密,虽比我想象中松了一点,不是直接操作,但无时无刻不在体现着硬件的意志。基础的话一共就只有wire和reg两种数据类型,其中wire类型简直就是操纵硬件这个木偶的丝线,是连接逻辑部分和逻辑部分、逻辑部分和硬件部分的桥梁,reg类型是寄存器,可以存数据,而wire是不能存储数据的。
2.延时都是自己算出来的,没有专门的延时函数,甚至没有任何函数,连次方运算都没有。
3.资源有限,很容易就把资源用完了。
4.多线程,习惯了单线程突然变成多线程会很不习惯。
原创代码介绍:
时钟模块:
module clock
(
input [2:0] BTN, //button key
input clk, //clk_in = 12mhz
input rst_n,
output reg [3:0] num1,
output reg [3:0] num2,
output reg [3:0] num3,
output reg [3:0] num4
);
reg [24:0] cnt1;
reg [24:0] cnt2;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt1<=0;
cnt2<=0;
num1<=0;
num2<=0;
num3<=0;
num4<=0;
end
else if(cnt1 >= 12000000-1)
begin
cnt1 <= 0;
cnt2 <= cnt2 + 1;
if(cnt2 >= 60-1)
begin
cnt2 <= 0;
num1 = num1 + 1;
end
end
else if(BTN[1])
begin
num1 <= num1 + 1;
end
else if (BTN[2])
begin
num3 = num3 + 1;
end
else if(num1 >= 10)
begin
num1 = 0;
num2 = num2 + 1;
end
else if(num2 >= 6)
begin
num2 = 0;
num3 = num3 + 1;
end
else if(num3 >= 10)
begin
num3 = 0;
num4 = num4 + 1;
end
else if(num4 == 2 && num3 == 4)
begin
num3 = 0;
num4 = 0;
end
else
begin
cnt1 <= cnt1 + 1;
end
end
endmodule
用于产生0~1440之间的数字,对于一天的1440分钟,将这个数传递给oled模块解析成电子时钟的四个数字,同时还要传递给串口和蜂鸣器这两个模块,执行播放音乐等操作,通过两个按钮调整时间,一个按一下小时数加一,另一个则时分钟数加一。
蜂鸣器音乐播放模块:
// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: tone
//
// Author: Step
//
// Description: tone
//
// Web: www.stepfapga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2016/04/20 |Initial ver
// --------------------------------------------------------------------
module tone
(
input clk,
input rst_n,
input [119:0] temp,
input play1,
input play2,
output reg [15:0] cycle,
output reg stop
);
reg [3:0] clock[30];
reg [3:0] music[30];
reg [4:0] cnt1;
reg [25:0] cnt2;
reg [1:0] state;
integer i;
always@(posedge clk or negedge rst_n)
begin
clock[0]=1;clock[1]=2;clock[2]=3;clock[3]=4;clock[4]=5;
clock[5]=6;clock[6]=7;clock[7]=8;clock[8]=9;clock[9]=10;
clock[10]=11;clock[11]=12;clock[12]=13;clock[13]=14;clock[14]=15;
clock[15]=15;clock[16]=14;clock[17]=13;clock[18]=12;clock[19]=11;
clock[20]=10;clock[21]=9;clock[22]=8;clock[23]=7;clock[24]=6;
clock[25]=5;clock[26]=4;clock[27]=3;clock[28]=2;clock[29]=1;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cnt1<=0;
state[0]<=1;
end
else if(play1==1)
begin
state[0]<=1;
for(i=0;i<30;i=i+1)
music[i]<=clock[i];
end
else if(play2==1)
begin
state[1]<=1;
stop <= 1;
for(i=0;i<30;i=i+1)
music[i] <= {temp[4*i+3],temp[4*i+2],temp[4*i+1],temp[4*i]};
end
else if(state[0]==0 && state[1]==0)
begin
for(i=0;i<30;i=i+1)
music[i] <= 0;
end
else if(cnt1>=30)
begin
state[0] <= 0;
state[1] <= 0;
cnt1 <= 0;
stop <= 0;
end
else if(state[0]==1 || state[1]==1)
begin
cnt2 <= cnt2 + 1'b1;
if(cnt2 >= 3000000)
begin
cnt2 <= 0;
cnt1 <= cnt1 + 1;
end
end
end
always@(cnt1)
begin
case(music[cnt1])
4'd0: cycle = 16'd0; //L1,
4'd1: cycle = 16'd40858; //L2,
4'd2: cycle = 16'd36408; //L3,
4'd3: cycle = 16'd34364; //L4,
4'd4: cycle = 16'd30612; //L5,
4'd5: cycle = 16'd27273; //L6,
4'd6: cycle = 16'd24296; //L7,
4'd7: cycle = 16'd22931; //M1,
4'd8: cycle = 16'd20432; //M2,
4'd9: cycle = 16'd18201; //M3,
4'd10: cycle = 16'd17180; //M4,
4'd11: cycle = 16'd15306; //M5,
4'd12: cycle = 16'd13636; //M6,
4'd13: cycle = 16'd12148; //M7,
4'd14: cycle = 16'd11478; //H1,
4'd15: cycle = 16'd10215; //H2,
default: cycle = 16'd0; //cycle为0,PWM占空比为0,低电平
endcase
end
endmodule
clock为自带的音乐,在接收到整点时间信号时,播放clock中的音乐,具体实现是将clock数组的内容赋给music数组,播放完毕后清空music数组,如果接收到串口信号,就将temp中的内容赋给music,也进行一次播放。
串口模块:
// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: Display_Ctl
//
// Author: Step
//
// Description: Real time display with segment led_out
//
// Web: www.stepfapga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date: |Changes Made:
// V1.0 |2016/04/20 |Initial ver
// --------------------------------------------------------------------
module uart_seg
(
input clk, //系统时钟 12MHz
input rst_n, //系统复位,低有效
input [2:0] send_pulse,
input [15:0] send,
input fpga_rx, //UART接收输入
output reg [119:0] receive,
output fpga_tx, //UART发送输出
output reg alarm
);
reg [25:0] temp1,temp2;
reg [25:0] send1;
reg [23:0] cnt1,cnt2;
reg flag;
reg [1:0] state;
reg tx_data_valid;
reg [1:0] num1;
reg [6:0] num2;
reg flag1;
wire rx_data_valid;
wire [7:0] rx_data_out;
always @(send_pulse)
begin
temp1 <= send[4]+ send[5]*2+ send[6]*4+
send[7]*8+ send[8]*16+ send[9]*32+
send[10]*64;
temp2 <= ( send[3]*8+ send[2]*4
+ send[1]*2+ send[0])*10/16;
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
tx_data_valid <= 1'b0;
else if(send_pulse[0]==1)
state <= 1;
else if(state>=1 && flag==1)
begin
tx_data_valid <= 1'b1;
if(state==1)
begin
send1 <= temp1;
state <= 2;
flag <= 0;
end
else
begin
send1 <= temp2;
state <= 0;
flag <= 0;
end
end
else if(state==0)
tx_data_valid <= 1'b0;
else if(cnt1>=6000000)
begin
flag <= 1;
cnt1 <= 0;
end
else
begin
cnt1 <= cnt1 + 1;
tx_data_valid <= 1'b0;
end
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
num1<=0;
num2<=0;
end
else if(rx_data_valid)
begin
flag1 = 1;
cnt2 = 0;
receive[num2] = rx_data_out[num1];num1 = num1 + 1;num2 = num2 + 1;
receive[num2] = rx_data_out[num1];num1 = num1 + 1;num2 = num2 + 1;
receive[num2] = rx_data_out[num1];num1 = num1 + 1;num2 = num2 + 1;
receive[num2] = rx_data_out[num1];num1 = num1 + 1;num2 = num2 + 1;
end
else
begin
if(flag1 == 1)
cnt2 <= cnt2 + 1;
if(alarm==0 && cnt2 >=1000000 && cnt2 <= 1000010)
alarm = 1;
if(alarm==1 && cnt2 >=1000010)
begin
alarm = 0;
flag1 = 0;
receive <= 120'b0;
num1 <= 0;
num2 <= 0;
end
end
end
//Uart_Bus module
Uart_Bus u1
(
.clk (clk ), //系统时钟 12MHz
.rst_n (rst_n ), //系统复位,低有效
//负责FPGA接收UART芯片的数据
.uart_rx (fpga_rx ), //UART接收输入
.rx_data_valid (rx_data_valid ), //接收数据有效脉冲
.rx_data_out (rx_data_out ), //接收到的数据
//负责FPGA发送数据给UART芯片
.tx_data_valid (tx_data_valid ),
.tx_data_in (send1 ),
.uart_tx (fpga_tx )
);
endmodule
通过发送和接收信号,发送时发送两个数字,分别为温度的整数位和小数位,间隔半秒,接收时,因为每收到一次数据会有一个脉冲信号,如果一段时间接收不到这个脉冲,就说明接收完成,将每次收到的8位信号合成为120位的music信号,同时发送一个脉冲信号告诉蜂鸣器模块报警了。