- 项目需求
1.可定时时钟的功能:采用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟。
2.温度计的功能:小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上。
3.OLED功能:同时显示时间和温度。
4.定时时钟整点报警功能:时间到达整点时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息。
5.电脑下发音频文件功能:电脑收到报警的温度信号以后,将一段音频文件(“两只老虎”)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件。
6.音频播放期间信息暂停更新功能:播放音乐时,OLED屏幕上显示的时间信息和温度信息都停住不再更新,音频文件播放完毕,OLED开始更新时间信息和当前的温度信息
- 开发环境
开发环境使用Lattice Diamond软件
1.下载地址
http://www.latticesemi.com/Products/DesignSoftwareAndIP/FPGAandLDS/LatticeDiamond
2.License安装
Lattice Diamond软件安装后,需要从官网申请免费的License,才能正常运行,license申请渠道有很多种。
2.1免费license申请
登录Lattice官网,注册Lattice账号,然后进入Diamond License下载页面 。然后填写自己的mac地址以及选取license所支持的功能和IP,最后点击Generate License,然后就可以在自己邮箱接收到申请的License了。
2.2 License安装
安装Diamond License,就是把申请的License.dat文件放到指定的文件夹。
- 硬件介绍
1、LED:Little feet板卡上有两种灯,2颗RGB LED灯和8颗普通LED灯。上电后除了期中一颗RGB LED 灯蓝色灯闪烁提示系统正在运行,其余灯引脚输出高电平,熄灭LED。
2、按键:按键无硬件滤波,默认接高电平。
3、温度传感器:采用DS18B20,引脚接1K上拉电阻。
4、蜂鸣器:采用无源蜂鸣器,S8050三极管驱动。
5、OLED:采用0.91寸OLED,像素为128*32,白色。
6、TTL电平转USB芯片:采用CH340C,最高2MHZ波特率。
- 系统设计
根据项目要求,采用LCMXO2-4000HC-4MG132,完成任务需要用到OLED,DS18B20,蜂鸣器,UART串口以及按键功能,首先完成并测试各模块代码,再通过顶层模块连接各个外设协同工作,实现项目要求,具体框架如下:
编译后生成的Netlist如图所示:
未使用ip核,占用资源如下:
上位机截图:
我采用一个文件中放一个module形式,参考了硬禾课堂、电子森林和杨彧同学的代码,文件目录如下:
1、顶层文件
顶层文件为lf_project,列出了所有输入输出引脚,将各个模块连接到一起。
module lf_project
(
input clk, // 12MHz系统时钟
input rst_n, // //系统复位,低有效
inout one_wire, // ds18b20z输入信号
input hour, //调整小时按键
input min_a, //调整分钟加按键
input min_d, //调整分钟减按键
input uart_in, //串口rx
output oled_csn, //OLCD液晶屏使能
output oled_rst, //OLCD液晶屏复位
output oled_dcn, //OLCD数据指令控制
output oled_clk, //OLCD时钟信号
output oled_dat, //OLCD数据信号
output reg R1, //板子上所有灯 初始状态熄灭
output reg G1,
output B1,
output reg R2,
output reg G2,
output reg B2,
output reg[7:0] L,
output beeper, //蜂鸣器
output uart_out //串口输出引脚
);
always@( negedge rst_n) begin
if(!rst_n) begin
R1<=1'b1; //板载不使用灯关闭
G1<=1'b1;
R2<=1'b1;
G2<=1'b1;
B2<=1'b1;
L[7:0]<=8'hff;
end
end
//DS18B20获取温度*************************************************************
wire [15:0] data_out;
DS18B20Z DS18B20Z_lf
(
.clk (clk ), // 12MHz系统时钟
.rst_n (rst_n ), // 系统复位,低有效
.one_wire (one_wire ), // ds18b20输入引脚
.data_out (data_out ) // ds18b20数据输出
);
// 判断温度符号
wire [3:0] temperature_flag = data_out[15:11]? 4'h0:4'h1;
// 获取温度
wire [10:0] temperature_code = temperature_flag? data_out[10:0]:(~data_out[10:0])+1'b1;
// 转换温度到BCD码****************************************************************
wire [20:0] bin_code = temperature_code * 16'd625;
wire [24:0] bcd_code; //十位[23:20],个位[19:16],小数位[14:12]
bin_to_bcd bin_to_bcd_lf
(
.rst_n (rst_n ), // 系统复位,低有效
.bin_code (bin_code ), // bin输入
.bcd_code (bcd_code ) // bcd输出
);
//转化小时到BCD码******************************************************************
wire [7:0] hour_out,min_out;//小时分钟输出信号
wire [20:0] bin_hour =hour_out*10000;
wire [24:0] bcd_hour;
bin_to_bcd bin_to_bcd_hour
(
.rst_n (rst_n ), // 系统复位,低有效
.bin_code (bin_hour ), // bin输入
.bcd_code (bcd_hour ) // bcd输出
);
//转化分钟到BCD码******************************************************************
wire [20:0] bin_min =min_out*10000;
wire [24:0] bcd_min;
bin_to_bcd bin_to_bcd_min
(
.rst_n (rst_n ), // 系统复位,低有效
.bin_code (bin_min ), // bin输入
.bcd_code (bcd_min ) // bcd输出
);
//oled********************************************************************************
wire display_flag;
OLED12832 OLED12832_lf
(
.clk (clk ),//12MHz系统时钟
.rst_n (rst_n ),//系统复位,低有效
.display_flag ( display_flag ),//显示开关
.hour_g (bcd_hour[19:16] ),//小时个位
.hour_s (bcd_hour[23:20] ),//小时十位
.min_g (bcd_min[19:16] ),//分钟个位
.min_s (bcd_min[23:20] ),//分钟十位
.tem_g ((bcd_code[19:16]) ),//温度个位
.tem_s ((bcd_code[23:20]) ),//温度十位
.tem_sign (temperature_flag ),//温度符号
.tem_d ((bcd_code[15:12]) ),//温度小数位
.oled_csn (oled_csn ),//OLCD液晶屏使能
.oled_rst (oled_rst ),//OLCD液晶屏复位
.oled_dcn (oled_dcn ),//OLCD数据指令控制
.oled_clk (oled_clk ),//OLCD时钟信号
.oled_dat (oled_dat )//OLCD数据信号
);
//蜂鸣器********************************************************************************
wire [311:0]data;
wire pwm_en;
buzzer buzzer_lf
(
.tri_uart (pwm_en ),//蜂鸣器发声标志
.clk (clk ),//12MHz系统时钟
.clk_buzzer (B1 ),//B1LED闪烁频率
.data_uart (data ),//串口接收数据
.stop (display_flag ),//播放标志位
.out (beeper ),//接蜂鸣器引脚
.rst_n (rst_n ) //系统复位,低有效
);
//产生9600hz频率,提供给串口发送使用********************************************************
wire clk_o;
clk_uart clk_uart_lf
(
.rst_n (rst_n ),//系统复位,低有效
.clk_in (clk ),//12MHz系统时钟
.clk_out (clk_o )//9600HZ时钟输出
);
//串口发送**********************************************************************************
wire uart_en; //串口发送的控制信号
uart_tx uart_tx_lf
(
.rst_n (rst_n ),
.tem_g ((bcd_code[19:16]) ),//温度个位
.tem_s ((bcd_code[23:20]) ),//温度十位
.tem_d ((bcd_code[15:12]) ),//温度小数位
.clk_tx (clk_o ),//发送时钟
.en (uart_en ),//串口发送标志
.uart_out (uart_out )//串口发送引脚
);
//串口接收*********************************************************************************
uart_rx uart_rx_lf
(
.rst_n (rst_n ),//系统复位,低有效
.uart_in (uart_in ),//串口接收引脚
.clk (clk ),//12MHz系统时钟
.receive (data ),//串口接收数据输出
.pwm_en (pwm_en ) //蜂鸣器发声标志
);
//产生1HZ信号模块******************************************************************************
wire clk_1ms;
clk_1ms clk_1ms_lf
(
.clk (clk ),//12MHz系统时钟
.rst_n (rst_n ),//复位信号
.clk_1ms (clk_1ms ) //1HZ信号输出
);
//时钟模块******************************************************************************
clock clock_lf
(
.hour (hour ),//调整小时按键
.min_a (min_a ),//调整分钟加按键
.min_d (min_d ),//调整分钟减按键
.clk_1ms (clk_1ms ),//1HZ信号输入
.clk_500ms (B1 ),//B1LED闪烁频率
.hour_out (hour_out ),//小时输出
.min_out (min_out ),//分钟输出
.rst_n (rst_n ),//复位信号
.uart_en (uart_en )//串口发送标志
);
//信号灯闪烁模块************************************************************************
signal_led signal_led_lf
(
.clk (clk ),//12MHz系统时钟
.rst_n (rst_n ),//复位信号
.led (B1 )//B1LED闪烁频率
);
endmodule
2、OLED模块
OLED参考了电子森林的代码,采用状态机。
module OLED12832
(
input clk, //12MHz系统时钟
input rst_n, //系统复位,低有效
input[3:0] tem_g,tem_s,tem_sign,tem_d,
input[3:0] hour_g,hour_s,min_g,min_s,
//小时个位,小时十位,分钟个位,分钟十位,
//温度个位,温度十位,温度符号,温度小数
input display_flag, //显示标志
output reg oled_csn, //OLCD液晶屏使能
output reg oled_rst, //OLCD液晶屏复位
output reg oled_dcn, //OLCD数据指令控制
output reg oled_clk, //OLCD时钟信号
output reg oled_dat //OLCD数据信号
);
localparam INIT_DEPTH = 16'd25; //LCD初始化的命令的数量
localparam IDLE = 6'h1, MAIN = 6'h2, INIT = 6'h4, SCAN = 6'h8, WRITE = 6'h10, DELAY = 6'h20;
localparam HIGH = 1'b1, LOW = 1'b0;
localparam DATA = 1'b1, CMD = 1'b0;
reg [7:0] cmd [24:0];
reg [39:0] mem [123:0];
reg [7:0] y_p, x_ph, x_pl;
reg [(8*21-1):0] char;
reg [7:0] num, char_reg;
reg [4:0] cnt_main, cnt_init, cnt_scan, cnt_write;
reg [15:0] num_delay, cnt_delay, cnt;
reg [5:0] state, state_back;
wire[7:0] sign;
assign sign = tem_sign?" ":"-";
reg clk_1hz;
reg [23:0] cnt_1hz;
always@(posedge clk or negedge rst_n) begin //产生1HZ 用来更新时钟冒号的闪烁
if(!rst_n) begin
cnt_1hz <= 24'h0;
clk_1hz <= 1'b1;
end else if(cnt_1hz >= 24'd 12000000) begin
cnt_1hz <= 24'h0;
clk_1hz <= ~clk_1hz;
end else begin
cnt_1hz <= cnt_1hz + 1'b1;
end
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= IDLE; state_back <= IDLE;
end else begin
case(state)
IDLE:begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
state <= MAIN; state_back <= MAIN;
end
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 <= "TIME: "; state <= SCAN; end
5'd2: begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "TEMP: "; state <= SCAN; end
5'd3: begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " "; state <= SCAN; end
5'd4: begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= " "; state <= SCAN; end
5'd5: begin if(display_flag)begin if(clk_1hz)begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 5; char <= {4'h0,hour_s,4'h0,hour_g,":",4'h0,min_s,4'h0,min_g}; state <= SCAN;end
else begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; num <= 5'd 5; char <= {4'h0,hour_s,4'h0,hour_g," ",4'h0,min_s,4'h0,min_g}; state <= SCAN;end
end end
5'd6: begin if(display_flag)begin y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd 7; char <= {sign,4'h0,tem_s,4'h0,tem_g,".",4'h0,tem_d,8'h7b,"C"}; state <= SCAN; end end
default: state <= IDLE;
endcase
end
INIT:begin //初始化状态
case(cnt_init)
5'd0: begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end //复位有效
5'd1: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于3us
5'd2: begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end //复位恢复
5'd3: begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于220us
5'd4: begin
if(cnt>=INIT_DEPTH) begin //当25条指令及数据发出后,配置完成
cnt <= 1'b0;
cnt_init <= cnt_init + 1'b1;
end else begin
cnt <= cnt + 1'b1; num_delay <= 16'd5;
oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
end
end
5'd5: begin cnt_init <= 1'b0; state <= MAIN; end //初始化完成,返回MAIN状态
default: state <= IDLE;
endcase
end
SCAN:begin //刷屏状态,从RAM中读取数据刷屏
if(cnt_scan == 5'd11) begin
if(num) cnt_scan <= 5'd3;
else cnt_scan <= cnt_scan + 1'b1;
end else if(cnt_scan == 5'd12) cnt_scan <= 1'b0;
else cnt_scan <= cnt_scan + 1'b1;
case(cnt_scan)
5'd 0: begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end //定位列页地址
5'd 1: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end //定位行地址低位
5'd 2: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end //定位行地址高位
5'd 3: begin num <= num - 1'b1;end
5'd 4: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 5: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 6: begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end //将5*8点阵编程8*8
5'd 7: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end
5'd 8: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end
5'd 9: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end
5'd10: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][15: 8]; state <= WRITE; state_back <= SCAN; end
5'd11: begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][ 7: 0]; state <= WRITE; state_back <= SCAN; end
5'd12: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
WRITE:begin //WRITE状态,将数据按照SPI时序发送给屏幕
if(cnt_write >= 5'd17) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
5'd 0: begin oled_csn <= LOW; end //9位数据最高位为命令数据控制位
5'd 1: begin oled_clk <= LOW; oled_dat <= char_reg[7]; end //先发高位数据
5'd 2: begin oled_clk <= HIGH; end
5'd 3: begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
5'd 4: begin oled_clk <= HIGH; end
5'd 5: begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
5'd 6: begin oled_clk <= HIGH; end
5'd 7: begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
5'd 8: begin oled_clk <= HIGH; end
5'd 9: begin oled_clk <= LOW; oled_dat <= char_reg[3]; end
5'd10: begin oled_clk <= HIGH; end
5'd11: begin oled_clk <= LOW; oled_dat <= char_reg[2]; end
5'd12: begin oled_clk <= HIGH; end
5'd13: begin oled_clk <= LOW; oled_dat <= char_reg[1]; end
5'd14: begin oled_clk <= HIGH; end
5'd15: begin oled_clk <= LOW; oled_dat <= char_reg[0]; end //后发低位数据
5'd16: begin oled_clk <= HIGH; end
5'd17: begin oled_csn <= HIGH; state <= DELAY; end //
default: state <= IDLE;
endcase
end
DELAY:begin //延时状态
if(cnt_delay >= num_delay) begin
cnt_delay <= 16'd0; state <= state_back;
end else cnt_delay <= cnt_delay + 1'b1;
end
default:state <= IDLE;
endcase
end
end
//OLED配置指令数据
always@(posedge rst_n)
begin
cmd[ 0] = {8'hae};
cmd[ 1] = {8'h00};
cmd[ 2] = {8'h10};
cmd[ 3] = {8'h00};
cmd[ 4] = {8'hb0};
cmd[ 5] = {8'h81};
cmd[ 6] = {8'hff};
cmd[ 7] = {8'ha1};
cmd[ 8] = {8'ha6};
cmd[ 9] = {8'ha8};
cmd[10] = {8'h1f};
cmd[11] = {8'hc8};
cmd[12] = {8'hd3};
cmd[13] = {8'h00};
cmd[14] = {8'hd5};
cmd[15] = {8'h80};
cmd[16] = {8'hd9};
cmd[17] = {8'h1f};
cmd[18] = {8'hda};
cmd[19] = {8'h00};
cmd[20] = {8'hdb};
cmd[21] = {8'h40};
cmd[22] = {8'h8d};
cmd[23] = {8'h14};
cmd[24] = {8'haf};
end
//5*8点阵字库数据
always@(posedge rst_n)
begin
mem[ 0] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0
mem[ 1] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1
mem[ 2] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2
mem[ 3] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3
mem[ 4] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4
mem[ 5] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5
mem[ 6] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6
mem[ 7] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7
mem[ 8] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8
mem[ 9] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9
mem[ 10] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A
mem[ 11] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B
mem[ 12] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C
mem[ 13] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D
mem[ 14] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E
mem[ 15] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F
mem[ 32] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00}; // 32 sp
mem[ 33] = {8'h00, 8'h00, 8'h2f, 8'h00, 8'h00}; // 33 !
mem[ 34] = {8'h00, 8'h07, 8'h00, 8'h07, 8'h00}; // 34
mem[ 35] = {8'h14, 8'h7f, 8'h14, 8'h7f, 8'h14}; // 35 #
mem[ 36] = {8'h24, 8'h2a, 8'h7f, 8'h2a, 8'h12}; // 36 $
mem[ 37] = {8'h62, 8'h64, 8'h08, 8'h13, 8'h23}; // 37 %
mem[ 38] = {8'h36, 8'h49, 8'h55, 8'h22, 8'h50}; // 38 &
mem[ 39] = {8'h00, 8'h05, 8'h03, 8'h00, 8'h00}; // 39 '
mem[ 40] = {8'h00, 8'h1c, 8'h22, 8'h41, 8'h00}; // 40 (
mem[ 41] = {8'h00, 8'h41, 8'h22, 8'h1c, 8'h00}; // 41 )
mem[ 42] = {8'h14, 8'h08, 8'h3E, 8'h08, 8'h14}; // 42 *
mem[ 43] = {8'h08, 8'h08, 8'h3E, 8'h08, 8'h08}; // 43 +
mem[ 44] = {8'h00, 8'h00, 8'hA0, 8'h60, 8'h00}; // 44 ,
mem[ 45] = {8'h08, 8'h08, 8'h08, 8'h08, 8'h08}; // 45 -
mem[ 46] = {8'h00, 8'h60, 8'h60, 8'h00, 8'h00}; // 46 .
mem[ 47] = {8'h20, 8'h10, 8'h08, 8'h04, 8'h02}; // 47 /
mem[ 48] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E}; // 48 0
mem[ 49] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00}; // 49 1
mem[ 50] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46}; // 50 2
mem[ 51] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31}; // 51 3
mem[ 52] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10}; // 52 4
mem[ 53] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39}; // 53 5
mem[ 54] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30}; // 54 6
mem[ 55] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03}; // 55 7
mem[ 56] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36}; // 56 8
mem[ 57] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E}; // 57 9
mem[ 58] = {8'h00, 8'h36, 8'h36, 8'h00, 8'h00}; // 58 :
mem[ 59] = {8'h00, 8'h56, 8'h36, 8'h00, 8'h00}; // 59 ;
mem[ 60] = {8'h08, 8'h14, 8'h22, 8'h41, 8'h00}; // 60 <
mem[ 61] = {8'h14, 8'h14, 8'h14, 8'h14, 8'h14}; // 61 =
mem[ 62] = {8'h00, 8'h41, 8'h22, 8'h14, 8'h08}; // 62 >
mem[ 63] = {8'h02, 8'h01, 8'h51, 8'h09, 8'h06}; // 63 ?
mem[ 64] = {8'h32, 8'h49, 8'h59, 8'h51, 8'h3E}; // 64 @
mem[ 65] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C}; // 65 A
mem[ 66] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36}; // 66 B
mem[ 67] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22}; // 67 C
mem[ 68] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C}; // 68 D
mem[ 69] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41}; // 69 E
mem[ 70] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01}; // 70 F
mem[ 71] = {8'h3E, 8'h41, 8'h49, 8'h49, 8'h7A}; // 71 G
mem[ 72] = {8'h7F, 8'h08, 8'h08, 8'h08, 8'h7F}; // 72 H
mem[ 73] = {8'h00, 8'h41, 8'h7F, 8'h41, 8'h00}; // 73 I
mem[ 74] = {8'h20, 8'h40, 8'h41, 8'h3F, 8'h01}; // 74 J
mem[ 75] = {8'h7F, 8'h08, 8'h14, 8'h22, 8'h41}; // 75 K
mem[ 76] = {8'h7F, 8'h40, 8'h40, 8'h40, 8'h40}; // 76 L
mem[ 77] = {8'h7F, 8'h02, 8'h0C, 8'h02, 8'h7F}; // 77 M
mem[ 78] = {8'h7F, 8'h04, 8'h08, 8'h10, 8'h7F}; // 78 N
mem[ 79] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h3E}; // 79 O
mem[ 80] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h06}; // 80 P
mem[ 81] = {8'h3E, 8'h41, 8'h51, 8'h21, 8'h5E}; // 81 Q
mem[ 82] = {8'h7F, 8'h09, 8'h19, 8'h29, 8'h46}; // 82 R
mem[ 83] = {8'h46, 8'h49, 8'h49, 8'h49, 8'h31}; // 83 S
mem[ 84] = {8'h01, 8'h01, 8'h7F, 8'h01, 8'h01}; // 84 T
mem[ 85] = {8'h3F, 8'h40, 8'h40, 8'h40, 8'h3F}; // 85 U
mem[ 86] = {8'h1F, 8'h20, 8'h40, 8'h20, 8'h1F}; // 86 V
mem[ 87] = {8'h3F, 8'h40, 8'h38, 8'h40, 8'h3F}; // 87 W
mem[ 88] = {8'h63, 8'h14, 8'h08, 8'h14, 8'h63}; // 88 X
mem[ 89] = {8'h07, 8'h08, 8'h70, 8'h08, 8'h07}; // 89 Y
mem[ 90] = {8'h61, 8'h51, 8'h49, 8'h45, 8'h43}; // 90 Z
mem[ 91] = {8'h00, 8'h7F, 8'h41, 8'h41, 8'h00}; // 91 [
mem[ 92] = {8'h55, 8'h2A, 8'h55, 8'h2A, 8'h55}; // 92
mem[ 93] = {8'h00, 8'h41, 8'h41, 8'h7F, 8'h00}; // 93 ]
mem[ 94] = {8'h04, 8'h02, 8'h01, 8'h02, 8'h04}; // 94 ^
mem[ 95] = {8'h40, 8'h40, 8'h40, 8'h40, 8'h40}; // 95 _
mem[ 96] = {8'h00, 8'h01, 8'h02, 8'h04, 8'h00}; // 96 '
mem[ 97] = {8'h20, 8'h54, 8'h54, 8'h54, 8'h78}; // 97 a
mem[ 98] = {8'h7F, 8'h48, 8'h44, 8'h44, 8'h38}; // 98 b
mem[ 99] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h20}; // 99 c
mem[100] = {8'h38, 8'h44, 8'h44, 8'h48, 8'h7F}; // 100 d
mem[101] = {8'h38, 8'h54, 8'h54, 8'h54, 8'h18}; // 101 e
mem[102] = {8'h08, 8'h7E, 8'h09, 8'h01, 8'h02}; // 102 f
mem[103] = {8'h18, 8'hA4, 8'hA4, 8'hA4, 8'h7C}; // 103 g
mem[104] = {8'h7F, 8'h08, 8'h04, 8'h04, 8'h78}; // 104 h
mem[105] = {8'h00, 8'h44, 8'h7D, 8'h40, 8'h00}; // 105 i
mem[106] = {8'h40, 8'h80, 8'h84, 8'h7D, 8'h00}; // 106 j
mem[107] = {8'h7F, 8'h10, 8'h28, 8'h44, 8'h00}; // 107 k
mem[108] = {8'h00, 8'h41, 8'h7F, 8'h40, 8'h00}; // 108 l
mem[109] = {8'h7C, 8'h04, 8'h18, 8'h04, 8'h78}; // 109 m
mem[110] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h78}; // 110 n
mem[111] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h38}; // 111 o
mem[112] = {8'hFC, 8'h24, 8'h24, 8'h24, 8'h18}; // 112 p
mem[113] = {8'h18, 8'h24, 8'h24, 8'h18, 8'hFC}; // 113 q
mem[114] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h08}; // 114 r
mem[115] = {8'h48, 8'h54, 8'h54, 8'h54, 8'h20}; // 115 s
mem[116] = {8'h04, 8'h3F, 8'h44, 8'h40, 8'h20}; // 116 t
mem[117] = {8'h3C, 8'h40, 8'h40, 8'h20, 8'h7C}; // 117 u
mem[118] = {8'h1C, 8'h20, 8'h40, 8'h20, 8'h1C}; // 118 v
mem[119] = {8'h3C, 8'h40, 8'h30, 8'h40, 8'h3C}; // 119 w
mem[120] = {8'h44, 8'h28, 8'h10, 8'h28, 8'h44}; // 120 x
mem[121] = {8'h1C, 8'hA0, 8'hA0, 8'hA0, 8'h7C}; // 121 y
mem[122] = {8'h44, 8'h64, 8'h54, 8'h4C, 8'h44}; // 122 z
mem[123] = {8'h00, 8'h00, 8'h00, 8'h03, 8'h03}; // 123 摄氏度小圈
end
endmodule
3、温度传感器
DS18B20代码使用了电子森林的。
module DS18B20Z
(
input clk, // system clock
input rst_n, // system reset, active low
inout one_wire, // ds18b20z one-wire-bus
output reg [15:0] data_out // ds18b20z data_out
);
localparam IDLE = 3'd0;
localparam MAIN = 3'd1;
localparam INIT = 3'd2;
localparam WRITE = 3'd3;
localparam READ = 3'd4;
localparam DELAY = 3'd5;
//generate clk_1mhz clock
reg clk_1mhz;
reg [2:0] cnt_1mhz;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_1mhz <= 3'd0;
clk_1mhz <= 1'b0;
end else if(cnt_1mhz >= 3'd6) begin
cnt_1mhz <= 3'd0;
clk_1mhz <= ~clk_1mhz;
end else begin
cnt_1mhz <= cnt_1mhz + 1'b1;
end
end
reg one_wire_buffer;
reg [3:0] cnt_main;
reg [7:0] data_wr;
reg [7:0] data_wr_buffer;
reg [2:0] cnt_init;
reg [19:0] cnt_delay;
reg [19:0] num_delay;
reg [5:0] cnt_write;
reg [5:0] cnt_read;
reg [15:0] temperature;
reg [7:0] temperature_buffer;
reg [2:0] state = IDLE;
reg [2:0] state_back = IDLE;
always@(posedge clk_1mhz or negedge rst_n) begin
if(!rst_n) begin
state <= IDLE;
state_back <= IDLE;
cnt_main <= 4'd0;
cnt_init <= 3'd0;
cnt_write <= 6'd0;
cnt_read <= 6'd0;
cnt_delay <= 20'd0;
one_wire_buffer <= 1'bz;
temperature <= 16'h0;
end else begin
case(state)
IDLE:begin
state <= MAIN;
state_back <= MAIN;
cnt_main <= 4'd0;
cnt_init <= 3'd0;
cnt_write <= 6'd0;
cnt_read <= 6'd0;
cnt_delay <= 20'd0;
one_wire_buffer <= 1'bz;
end
MAIN:begin
if(cnt_main >= 4'd11) cnt_main <= 1'b0;
else cnt_main <= cnt_main + 1'b1;
case(cnt_main)
4'd0: begin state <= INIT; end
4'd1: begin data_wr <= 8'hcc;state <= WRITE; end
4'd2: begin data_wr <= 8'h44;state <= WRITE; end
4'd3: begin num_delay <= 20'd750000;state <= DELAY;state_back <= MAIN; end
4'd4: begin state <= INIT; end
4'd5: begin data_wr <= 8'hcc;state <= WRITE; end
4'd6: begin data_wr <= 8'hbe;state <= WRITE; end
4'd7: begin state <= READ; end
4'd8: begin temperature[7:0] <= temperature_buffer; end
4'd9: begin state <= READ; end
4'd10: begin temperature[15:8] <= temperature_buffer; end
4'd11: begin state <= IDLE;data_out <= temperature; end
default: state <= IDLE;
endcase
end
INIT:begin
if(cnt_init >= 3'd6) cnt_init <= 1'b0;
else cnt_init <= cnt_init + 1'b1;
case(cnt_init)
3'd0: begin one_wire_buffer <= 1'b0; end
3'd1: begin num_delay <= 20'd500;state <= DELAY;state_back <= INIT; end
3'd2: begin one_wire_buffer <= 1'bz; end
3'd3: begin num_delay <= 20'd100;state <= DELAY;state_back <= INIT; end
3'd4: begin if(one_wire) state <= IDLE; else state <= INIT; end
3'd5: begin num_delay <= 20'd400;state <= DELAY;state_back <= INIT; end
3'd6: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
WRITE:begin
if(cnt_write >= 6'd50) cnt_write <= 1'b0;
else cnt_write <= cnt_write + 1'b1;
case(cnt_write)
//lock data_wr
6'd0: begin data_wr_buffer <= data_wr; end
//write bit 0
6'd1: begin one_wire_buffer <= 1'b0; end
6'd2: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
6'd3: begin one_wire_buffer <= data_wr_buffer[0]; end
6'd4: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
6'd5: begin one_wire_buffer <= 1'bz; end
6'd6: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
//write bit 1
6'd7: begin one_wire_buffer <= 1'b0; end
6'd8: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
6'd9: begin one_wire_buffer <= data_wr_buffer[1]; end
6'd10: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
6'd11: begin one_wire_buffer <= 1'bz; end
6'd12: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
//write bit 2
6'd13: begin one_wire_buffer <= 1'b0; end
6'd14: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
6'd15: begin one_wire_buffer <= data_wr_buffer[2]; end
6'd16: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
6'd17: begin one_wire_buffer <= 1'bz; end
6'd18: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
//write bit 3
6'd19: begin one_wire_buffer <= 1'b0; end
6'd20: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
6'd21: begin one_wire_buffer <= data_wr_buffer[3]; end
6'd22: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
6'd23: begin one_wire_buffer <= 1'bz; end
6'd24: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
//write bit 4
6'd25: begin one_wire_buffer <= 1'b0; end
6'd26: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
6'd27: begin one_wire_buffer <= data_wr_buffer[4]; end
6'd28: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
6'd29: begin one_wire_buffer <= 1'bz; end
6'd30: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
//write bit 5
6'd31: begin one_wire_buffer <= 1'b0; end
6'd32: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
6'd33: begin one_wire_buffer <= data_wr_buffer[5]; end
6'd34: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
6'd35: begin one_wire_buffer <= 1'bz; end
6'd36: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
//write bit 6
6'd37: begin one_wire_buffer <= 1'b0; end
6'd38: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
6'd39: begin one_wire_buffer <= data_wr_buffer[6]; end
6'd40: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
6'd41: begin one_wire_buffer <= 1'bz; end
6'd42: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
//write bit 7
6'd43: begin one_wire_buffer <= 1'b0; end
6'd44: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
6'd45: begin one_wire_buffer <= data_wr_buffer[7]; end
6'd46: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
6'd47: begin one_wire_buffer <= 1'bz; end
6'd48: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end
//back to main
6'd49: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end
6'd50: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
READ:begin
if(cnt_read >= 6'd48) cnt_read <= 1'b0;
else cnt_read <= cnt_read + 1'b1;
case(cnt_read)
//read bit 0
6'd0: begin one_wire_buffer <= 1'b0; end
6'd1: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
6'd2: begin one_wire_buffer <= 1'bz; end
6'd3: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
6'd4: begin temperature_buffer[0] <= one_wire; end
6'd5: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
//read bit 1
6'd6: begin one_wire_buffer <= 1'b0; end
6'd7: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
6'd8: begin one_wire_buffer <= 1'bz; end
6'd9: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
6'd10: begin temperature_buffer[1] <= one_wire; end
6'd11: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
//read bit 2
6'd12: begin one_wire_buffer <= 1'b0; end
6'd13: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
6'd14: begin one_wire_buffer <= 1'bz; end
6'd15: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
6'd16: begin temperature_buffer[2] <= one_wire; end
6'd17: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
//read bit 3
6'd18: begin one_wire_buffer <= 1'b0; end
6'd19: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
6'd20: begin one_wire_buffer <= 1'bz; end
6'd21: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
6'd22: begin temperature_buffer[3] <= one_wire; end
6'd23: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
//read bit 4
6'd24: begin one_wire_buffer <= 1'b0; end
6'd25: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
6'd26: begin one_wire_buffer <= 1'bz; end
6'd27: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
6'd28: begin temperature_buffer[4] <= one_wire; end
6'd29: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
//read bit 5
6'd30: begin one_wire_buffer <= 1'b0; end
6'd31: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
6'd32: begin one_wire_buffer <= 1'bz; end
6'd33: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
6'd34: begin temperature_buffer[5] <= one_wire; end
6'd35: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
//read bit 6
6'd36: begin one_wire_buffer <= 1'b0; end
6'd37: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
6'd38: begin one_wire_buffer <= 1'bz; end
6'd39: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
6'd40: begin temperature_buffer[6] <= one_wire; end
6'd41: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
//read bit 7
6'd42: begin one_wire_buffer <= 1'b0; end
6'd43: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end
6'd44: begin one_wire_buffer <= 1'bz; end
6'd45: begin num_delay <= 20'd10;state <= DELAY;state_back <= READ; end
6'd46: begin temperature_buffer[7] <= one_wire; end
6'd47: begin num_delay <= 20'd55;state <= DELAY;state_back <= READ; end
//back to main
6'd48: begin state <= MAIN; end
default: state <= IDLE;
endcase
end
DELAY:begin
if(cnt_delay >= num_delay) begin
cnt_delay <= 1'b0;
state <= state_back;
end else cnt_delay <= cnt_delay + 1'b1;
end
endcase
end
end
assign one_wire = one_wire_buffer;
endmodule
4、BIN转BCD代码也是参考电子森林,在顶层文件中调用了三次。
module bin_to_bcd #
(
parameter B_SIZE = 21
)
(
input rst_n, // system reset, active low
input [B_SIZE-1:0] bin_code, // binary code
output reg [B_SIZE+3:0] bcd_code // bcd code
);
reg [2*B_SIZE+3:0] shift_reg;
always@(bin_code or rst_n)begin
shift_reg= {25'h0,bin_code};
if(!rst_n) bcd_code <= 0;
else begin
repeat(B_SIZE)//repeat B_SIZE times
begin
if (shift_reg[24:21] >= 5) shift_reg[24:21] = shift_reg[24:21] + 2'b11;
if (shift_reg[28:25] >= 5) shift_reg[28:25] = shift_reg[28:25] + 2'b11;
if (shift_reg[32:29] >= 5) shift_reg[32:29] = shift_reg[32:29] + 2'b11;
if (shift_reg[36:33] >= 5) shift_reg[36:33] = shift_reg[36:33] + 2'b11;
if (shift_reg[40:37] >= 5) shift_reg[40:37] = shift_reg[40:37] + 2'b11;
if (shift_reg[44:41] >= 5) shift_reg[44:41] = shift_reg[44:41] + 2'b11;
shift_reg = shift_reg << 1;
end
bcd_code<=shift_reg[45:21];
end
end
endmodule
5、led闪烁,该模块对12MHZ时钟分频3000000,产生4HZ时钟供LED闪烁和调整小时分钟使用。
module signal_led
(
input clk,
input rst_n,
output led
);
//generate clk_4hz clock
reg clk_4hz;
reg [23:0] cnt_4hz;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_4hz <= 24'h0;
clk_4hz <= 1'b1;
end else if(cnt_4hz >= 24'd 1500000) begin
cnt_4hz <= 24'h0;
clk_4hz <= ~clk_4hz;
end else begin
cnt_4hz <= cnt_4hz + 1'b1;
end
end
assign led=clk_4hz;
endmodule
6、产生9600HZ时钟,该时钟供给串口发送时使用。
module clk_uart
(
input clk_in,
input rst_n,
output reg clk_out
);
reg [9:0] times_lf;
always@(posedge clk_in or negedge rst_n)
begin
if(!rst_n) begin
times_lf<=10'h0;
clk_out<=1'b1;
end else if(times_lf < 10'd625)
times_lf <= times_lf+1'b1;
else
begin
times_lf <= 10'h0;
clk_out <= ~clk_out;
end
end
endmodule
7、定时时钟模块
该模块参考了杨彧同学,主要的目标是进行计时和对时间的设置。时间可以通过板载的四个按键进行设置。按键抖动时间较长(约为10ms),故没有采用边沿触发的方式,而是选择了定时检测四个按键状态的方式减少按键抖动的影响。该模块有两个输入时钟,低频时钟4hz用于检测按键状态,并根据按键状态改变寄存器中保存的时间值;高频时钟(周期为1ms)用于计时,并查看设置的时间是否有更新。同时,当时间到达整点时,输出一个脉冲控制uart发送温度信息到电脑。
module clock
(
input hour,
input min_a,
input min_d,
input clk_1ms,
input clk_500ms,
input rst_n,
output reg [7:0] hour_out,min_out,
output reg uart_en
);
reg[7:0] hour_mem,min_mem,hour_set,min_set;
reg[15:0] second;
always @(posedge clk_500ms or negedge rst_n) begin
if(!rst_n) begin
hour_set = 0;
min_set = 0;
end else begin
if(hour == 0&&min_a == 0&&min_d == 0)
begin
hour_set = 0;
min_set = 0;
end
else if(hour == 0)
begin
hour_set = hour_out;
min_set = min_out;
hour_set = hour_set+1'b1;
end
else if(min_a == 0)
begin
hour_set = hour_out;
min_set = min_out;
min_set = min_set+1'b1;
end
else if(min_d == 0)
begin
hour_set = hour_out;
min_set = min_out;
min_set = min_set-1'b1;
end
if(hour_set == 8'd24)
hour_set = 1'b0;
if(hour_set > 8'd24)
hour_set = 8'd23;
if(min_set == 8'd60)
min_set = 1'b0;
if(min_set > 8'd60)
min_set = 8'd59;
end
end
always @(posedge clk_1ms or negedge rst_n) begin
if(!rst_n) begin
min_out=0;
hour_out=0;
uart_en = 0;
hour_mem = 0;
min_mem = 0;
second = 0;
end else begin
if(uart_en == 1'b1)
uart_en = 1'b0;
if(hour_set != hour_mem || min_set != min_mem)
begin
hour_mem = hour_set;
min_mem = min_set;
hour_out = hour_set;
min_out = min_set;
end
else
begin
if(second < 16'd60000)
second = second+1'b1;
else
begin
min_out = min_out+1'b1;
second = 1'b0;
if(min_out == 8'd60)
begin
hour_out = hour_out+1'b1;
min_out = 1'b0;
uart_en = 1'b1;
if(hour_out == 8'd24)
begin
hour_out = 1'b0;
end
end
end
end
end
end
endmodule
8、1KHZ模块,产生1KHZ,供给时钟模块使用。
module clk_1ms
(
input clk,
input rst_n,
output clk_1ms
);
//generate clk_1000hz clock
reg clk_1000hz;
reg [15:0] cnt_1000hz;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_1000hz <= 16'h0;
clk_1000hz <= 1'b1;
end else if(cnt_1000hz >= 16'd 6000) begin
cnt_1000hz <= 16'h0;
clk_1000hz <= ~clk_1000hz;
end else begin
cnt_1000hz <= cnt_1000hz + 1'b1;
end
end
assign clk_1ms=clk_1000hz;
endmodule
9、串口接收部分
串口通信的通信协议为:1位起始位“0”,8位数据位,一位停止位“1”。串口通信速率为9600波特。接收时采用12MHz时钟,通过对连续1250个周期时接收到的值进行判断这一比特对应的数据,减少因噪声而产生的误码概率。在接收到足够的数据后,触发蜂鸣器播放音乐。
module uart_rx
(
input clk,
input uart_in,
input rst_n,
output reg [311:0] receive,//39B
output reg pwm_en
);
localparam IDLE = 2'b00;
localparam CHEC = 2'b01;
localparam RECE = 2'b11;
localparam TIMES_L = 1250;
reg[1:0] state;
reg[10:0] times,times_zero;
reg[5:0] num;
reg[5:0] B;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
begin
times = 0;
times_zero = 0;
state = 0;
num = 0;
B = 0;
receive = 0;
end else begin
if(pwm_en == 1)
pwm_en = 0;
case(state)
IDLE:
begin
if(uart_in == 0)
state = CHEC;
end
CHEC:
begin
if(uart_in == 0)
times_zero = times_zero+1'b1;
times = times+1'b1;
if(times >= TIMES_L)
begin
if(times_zero >= (TIMES_L>>1))
begin
state = RECE;
times = 0;
times_zero = 0;
end
else
begin
state = IDLE ;
times = 0;
times_zero = 0;
end
end
end
RECE:
begin
times = times+1;
if(uart_in == 0)
begin
times_zero = times_zero+1'b1;
end
if(times >= TIMES_L)
begin
if(times_zero >= (TIMES_L>>1))
begin
receive[B*8+num] = 1'b0;
end
else
begin
receive[B*8+num] = 1'b1;
end
num = num+1'b1;
times = 0;
times_zero = 0;
end
if(num > 7)
begin
state = IDLE;
num = 0;
times = 0;
times_zero = 0;
B = B+1'b1;
if(B == 39)
begin
B = 0;
pwm_en = 1;
end
end
end
endcase
end
end
endmodule
10、串口发送部分
发送时需发送“XX.X℃”几个字符,将这几个字符及起始位、终止位存入寄存器中,依次发出。发送时钟为9600Hz。
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
11、蜂鸣器部分
当串口接收到39个字节时,蜂鸣器播放收到的音乐,播发期间控制OLED不再刷新。
module buzzer
(
input clk,
input tri_uart,
input clk_buzzer,
input rst_n,
input [311:0] data_uart,
output reg stop,out
);
localparam size = 40;
localparam IDLE = 2'b00;
localparam LOAD = 2'b01;
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 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;
if(judge==1'b1)
begin
state = LOAD;
end
else
state=IDLE;
end
LOAD:
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 = tri_uart;
if(judge != 0)
begin
num_delay = 4000000;
end
else
num_delay = 24'd0;
end
end
end
endmodule
- 项目总结
本次实验历经三周,前一周查看各类资料,研究他人代码,完成各模块代码,后两周开始正式着手项目,经过漫长的调试过程,终于完成了题目要求。
在完成项目的过程中遇到的问题以及解决方法:
- 自己添加复位信号后,整个工程乱跑。后研究发现,我没有正确写出异步复位逻辑,缺少else。
- 串口接收到音频数据后,最后一个字节无法播放。检查发现忽略了并行执行的代码,造成错误。
完成项目后有以下感悟:
- VHDL和verilog各有优劣,其中verilog语法与c有些许相似之处,个人学习使用更加方便。但是语言仅仅只是一个工具,尤其在硬件设计里,代码写得漂不漂亮,并不重要,最关键的是设计思想。
- 单片机和FPGA编程完全是两种思路,既然叫做硬件描述语言,不能按照写单片机的方式,而是要“心中有电路”,各语句是并行的!
- 要充分了解各种通信协议,FPGA编写通信协议要比C语言理解更加深刻。
- 相比于单片机的引脚,FPGA引脚功能更加自由,设置更加灵活。。
- FPGA分析代码要比C语言顺序执行的难很多,所以要理清思路,时刻记住并行思想。
- 后期计划
1.OLED上显示汉字
2.增加显示年月日功能
3.通过上位机设置时间
4.优化播放声音的生硬感