项目需求
- 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
- 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
- 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
- PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
- 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息
实现思路
- OLED模块
OLED模块使用的代码是根据硬禾学堂提供的开源代码经过我自己的修改后得到的。重点是学习开源OLED代码中可变部分代码的表达格式和建立起适合自己需求的链接字库,代码比较繁杂,具体可在文后的附件中查看。这里仅显示MAIN部分
MAIN:begin
if(cnt_main >= 5'd8) 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'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 <= "Tem ";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'h12; x_pl <= 8'h00; num <= 5'd 5; char <= {tim[31:24],tim[23:16],8'd58,tim[15:8],tim[7:0]}; state <= SCAN; end
5'd6: begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= " "; state <= SCAN; end
5'd7: begin y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 5; char <= {tem[31:24],tem[23:16],8'd46,tem[15:8],tem[7:0]}; state <= SCAN; end
5'd8: begin y_p <= 8'hb2; x_ph <= 8'h15; x_pl <= 8'h00; num <= 5'd 1; char <= " "; state <= SCAN; end
default: state <= IDLE;
endcase
end
- 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'd5) 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
- PWM蜂鸣器控制模块
PWM控制信号主要通过不同音调对应的频率值对应脉冲宽度cycle产生,cycle由中心处理判断结构查找音符switch表进行赋值。控制程序参考了杨涛同学的案例方法。
reg [WIDTH-1:0] cnt;
//counter for cycle
always @(posedge clk or negedge rst_n)
if(!rst_n) cnt <= 1'b1;
else if(cnt >= cycle) cnt <= 1'b1;
else cnt <= cnt + 1'b1;
//pulse with duty
always @(posedge clk or negedge rst_n)
if(!rst_n) pwm_out <= 1'b1;
else if(cnt < duty) pwm_out <= 1'b1;
else pwm_out <= 1'b0;
always @(posedge clk or negedge rst_n)begin //************************蜂鸣器**************************//
if(!rst_n) begin
note <= 8'd0;
pwm_finish_flag <= 1'b0;
end else begin
if(pwm_flag)begin
case(cnt_pwm[28:24])
5'd0: note <= 8'hA1; //L1,
5'd1: note <= 8'hA2; //L2,
5'd2: note <= 8'hA3; //L3,
5'd3: note <= 8'hB1; //L4,
5'd4: note <= 8'hB2; //L5,
5'd5: note <= 8'hB3; //L6,
5'd6: note <= 8'hC1; //L7,
5'd7: note <= 8'hC2; //M1,
5'd8: note <= 8'hC3; //M2,
5'd9: note <= 8'hA1; //L1,
5'd10: note <= 8'hA2; //L2,
5'd11: note <= 8'hC1; //L3,
5'd12: note <= 8'hA3; //L4,
5'd13: note <= 8'hB1; //L5,
5'd14: note <= 8'hB2; //L6,
5'd15: note <= 8'hB3; //L7,
5'd16: note <= 8'hC7; //M1,
5'd17: note <= 8'hC1; //M2,
default: begin note <= 8'h0;pwm_finish_flag <= 1'b1; end //cycle为0,PWM占空比为0,低电平
endcase
end else if(tone_flag)begin
case(cnt_tone[26:22])
5'd0:note <= tone[255:248] ;
5'd1:note <= tone[247:240] ;
5'd2:note <= tone[239:232] ;
5'd3:note <= tone[231:224] ;
5'd4:note <= tone[223:216] ;
5'd5:note <= tone[215:208] ;
5'd6:note <= tone[207:200] ;
5'd7:note <= tone[199:192] ;
5'd8:note <= tone[191:184] ;
5'd9:note <= tone[183:176] ;
5'd10:note <= tone[175:168] ;
5'd11:note <= tone[167:160] ;
5'd12:note <= tone[159:152] ;
5'd13:note <= tone[151:144] ;
5'd14:note <= tone[143:136] ;
5'd15:note <= tone[135:128] ;
5'd16:note <= tone[127:120] ;
5'd17:note <= tone[119:112] ;
5'd18:note <= tone[111:104] ;
5'd19:note <= tone[103:96] ;
5'd20:note <= tone[95:88] ;
5'd21:note <= tone[87:80] ;
5'd22:note <= tone[79:72] ;
5'd23:note <= tone[71:64] ;
5'd24:note <= tone[63:56] ;
5'd25:note <= tone[55:48] ;
5'd26:note <= tone[47:40] ;
5'd27:note <= tone[39:32] ;
5'd28:note <= tone[31:24] ;
5'd29:note <= tone[23:16] ;
5'd30:note <= tone[15:8] ;
5'd31:note <= 8'hFF ;
default: note <= 8'h0; //cycle为0,PWM占空比为0,低电平
endcase
end else begin
note <= 8'h0;
end
end
end
always @(posedge clk or negedge rst_n)begin //***********************音符频率对应表***************************//
if(!rst_n) begin
cycle <= 8'b0;
tone_finish_flag <= 1'b0;
end else begin
case(note)
8'hA1: cycle <= 16'd45872; //L1,
8'hA2: cycle <= 16'd40858; //L2,
8'hA3: cycle <= 16'd36408; //L3,
8'hA4: cycle <= 16'd34364; //L4,
8'hA5: cycle <= 16'd30612; //L5,
8'hA6: cycle <= 16'd27273; //L6,
8'hA7: cycle <= 16'd24296; //L7,
8'hB1: cycle <= 16'd22931; //M1,
8'hB2: cycle <= 16'd20432; //M2,
8'hB3: cycle <= 16'd18201; //M3,
8'hB4: cycle <= 16'd17180; //M4,
8'hB5: cycle <= 16'd15306; //M5,
8'hB6: cycle <= 16'd13636; //M6,
8'hB7: cycle <= 16'd12148; //M7,
8'hC1: cycle <= 16'd11478; //H1,
8'hC2: cycle <= 16'd10215; //H2,
8'hC3: cycle <= 16'd9140; //H3,
8'hC4: cycle <= 16'd8598; //H4,
8'hC5: cycle <= 16'd7653; //H5,
8'hC6: cycle <= 16'd6818; //H6,
8'hC7: cycle <= 16'd6072; //H7,
8'hD1: cycle <= 16'd5730; //V1,
8'hD2: cycle <= 16'd5106; //V2,
8'hD3: cycle <= 16'd4548; //V3,
8'hD4: cycle <= 16'd4294; //V4,
8'hD5: cycle <= 16'd3826; //V5,
8'hD6: cycle <= 16'd3409; //V6,
8'hD7: cycle <= 16'd3036; //V7,
8'hFF: begin cycle <=16'd0; tone_finish_flag <= 1'b1; end
default:cycle = 16'd0;
endcase
end
end
endmodule
蜂鸣器控制程序中主题if-else if语句中第一部分是自己制定的针对于整点报警情况的30秒左右的报警音频;第二部分是针对于串行传输发送给板子的音符数据对应的接收方法。控制程序的重点是通过状态变量确保每一部分有序运行。
- 串行传输发送模块
主要通过串行传输发送使电脑端的显示界面一秒钟更新一次时间和温度信息,因此在串行发送模块最重要的是分频和串行传输帧格式的体现,这里采用了9600Baud的串行传输方式。发送格式参考了叶开同学的方法,使得能够以在显示界面字符串的形式清晰显示温度时间信息。
//驱动发送数据操作
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
bps_en <= 1'b0;
tx_data_r <= 10'd0;
end else if(tx_data_valid && (!bps_en))begin
bps_en <= 1'b1; //当检测到接收时钟使能信号的下降沿,表明接收完成,需要发送数据,使能发送时钟使能信号
tx_data_r <= {1'b1,tx_data_in,1'b0};
end else if(num==4'd10) begin
bps_en <= 1'b0; //一次UART发送需要10个时钟信号,然后结束
end
end
//当处于工作状态中时,按照发送时钟的节拍发送数据
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
num <= 1'b0;
uart_tx <= 1'b1;
end else if(bps_en) begin
if(bps_clk) begin
num <= num + 1'b1;
uart_tx <= tx_data_r[num];
end else if(num>=4'd10) begin
num <= 4'd0;
end
end
end
module Baud #
(
parameter BPS_PARA = 1250 //12MHz时钟时参数1250对应9600的波特率
)
(
input clk, //系统时钟
input rst_n, //系统复位,低有效
input bps_en, //接收或发送时钟使能
output reg bps_clk //接收或发送时钟输出
);
reg [12:0] cnt;
//计数器计数满足波特率时钟要求
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 1'b0;
else if((cnt >= BPS_PARA-1)||(!bps_en)) //当时钟信号不使能(bps_en为低电平)时,计数器清零并停止计数
cnt <= 1'b0; //当时钟信号使能时,计数器对系统时钟计数,周期为BPS_PARA个系统时钟周期
else
cnt <= cnt + 1'b1;
end
//产生相应波特率的时钟节拍,接收模块将以此节拍进行UART数据接收
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
bps_clk <= 1'b0;
else if(cnt == (BPS_PARA>>1)) //右移一位等于除以2,终值BPS_PARA为数据更替点,中值数据稳定,做采样点
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
end
endmodule
reg [23:0] cnt_uart;
always @(posedge clk or negedge rst_n)begin //计时一秒
if(!rst_n) cnt_uart <= 1'b0;
else if(cnt_uart >= 24'd11_999_999) cnt_uart <= 1'b0;
else if (temp_tx_flag) cnt_uart <= cnt_uart + 1'b1;
else cnt_uart <= cnt_uart;
end
always @(posedge clk or negedge rst_n)begin //*********************每秒发一个温度时间数据,字符串显示***********************//
if(!rst_n) begin
tx_data_valid <= 1'b0;
tx_data_out <= 1'b0;
end else begin
if(temp_tx_flag)begin //发送温度数据
if(cnt_uart == 24'd10_599_999 ) begin //温度.高位
tx_data_valid <= 1'b1;
tx_data_out <= {4'd3,temp_in[3:0]};
end
else if(cnt_uart == 24'd11_999_999 ) begin //换行
tx_data_valid <= 1'b1;
tx_data_out <= 8'd10;
end
else if(cnt_uart == 24'd11_099_999 ) begin //温度.低位
tx_data_valid <= 1'b1;
tx_data_out <= {4'd3,4'b0};
end
else if(cnt_uart == 24'd10_099_999 ) begin //“.”
tx_data_valid <= 1'b1;
tx_data_out <= 8'd46;
end
else if(cnt_uart == 24'd9_599_999 ) begin //温度低位
tx_data_valid <= 1'b1;
tx_data_out <= {4'd3,temp_in[7:4]};
end
else if(cnt_uart == 24'd9_099_999 ) begin //温度高位
tx_data_valid <= 1'b1;
tx_data_out <= {4'd3,temp_in[11:8]};
end
else if(cnt_uart == 24'd11_599_999 ) begin //"C"
tx_data_valid <= 1'b1;
tx_data_out <= 8'd67;
end
else if(cnt_uart == 24'd8_599_999 ) begin //“ ”
tx_data_valid <= 1'b1;
tx_data_out <= 8'd32;
end
else if(cnt_uart == 24'd8_099_999 ) begin //分低位
tx_data_valid <= 1'b1;
tx_data_out <= {4'd3,time_buffer[3:0]};
end
else if(cnt_uart == 24'd7_599_999 ) begin //分高位
tx_data_valid <= 1'b1;
tx_data_out <= {4'd3,time_buffer[7:4]};
end
else if(cnt_uart == 24'd7_099_999 ) begin //“:”
tx_data_valid <= 1'b1;
tx_data_out <= 8'd58;
end
else if(cnt_uart == 24'd6_599_999 ) begin //时低位
tx_data_valid <= 1'b1;
tx_data_out <= {4'd3,time_buffer[11:8]};
end
else if(cnt_uart == 24'd6_099_999 ) begin //时高位
tx_data_valid <= 1'b1;
tx_data_out <= {4'd3,time_buffer[15:12]};
end
else begin
tx_data_valid <= 1'b0; tx_data_out <= tx_data_out;
end
end else begin
tx_data_valid <= 1'b0; tx_data_out <= tx_data_out;
end
end
end
- 时钟模块
时钟模块包含按键设置功能与时钟相对应和自动计时两方面,我主要参考了硬禾学堂的开源代码时钟分频和杨涛同学案例中的使用方法。
reg [29:0] cnt_1m;
always @(posedge clk or negedge rst_n)begin //计时一分钟
if(!rst_n) cnt_1m <= 1'b0;
else if(cnt_1m >= 30'd719_999_999) cnt_1m <= 1'b0;
else cnt_1m <= cnt_1m + 1'b1;
end
reg time_1plus_flag;
reg time_60plus_flag;
reg time_plus_full;
always@(posedge clk or negedge rst_n) begin //*********************按键控制**************************//
if(!rst_n)begin
time_1plus_flag <= 1'b0;
time_60plus_flag <= 1'b0;
time_buffer_flag <= 1'b0;
tone_flag <= 1'b0;
end else begin
if(time_buffer_flag ==1'b0)begin //停止计时并按键设置时间
case(key)
3'b100:time_60plus_flag <= 1'b1; //按键4增加时
3'b010:time_1plus_flag <= 1'b1; //按键3增加分
3'b001:time_buffer_flag <= 1'b1; //按键2开始计时
default:begin time_1plus_flag <= 1'b0;time_60plus_flag <= 1'b0; end
endcase
if(tone_finish_flag == 1'b1) begin
tone_flag <= 1'b0; //停止演奏
time_buffer_flag <= 1'b1;//开始计时
end
end else if(tone_flag == 1'b0 )begin //计时
if(cnt_1m >= 30'd719_999_999)begin
time_1plus_flag <= 1'b1;
end else begin
time_1plus_flag <= 1'b0;
time_60plus_flag <= 1'b0;
end
if(key == 3'b001 && tone_set_flag == 1'b1 ) begin
tone_flag <= 1'b1; //开始演奏
time_buffer_flag <= 1'b0; //停止计时
end else begin
tone_flag <= tone_flag ;
time_buffer_flag <= time_buffer_flag ;
end
end else begin
time_1plus_flag <= 1'b0;
time_60plus_flag <= 1'b0;
end
end
end
reg [15:0] time_buffer;
always@(posedge clk or negedge rst_n) begin //**********************time+1*******************//
if(!rst_n)begin
time_buffer[7:0] <= 8'b0;
end else begin
if(time_1plus_flag)begin
if(time_buffer[3:0] >= 4'd9 && time_buffer[7:4] < 4'd5) time_buffer[7:0] <= time_buffer[7:0] + 4'd7;
else if(time_buffer[3:0] >= 4'd9 && time_buffer[7:4] == 4'd5) begin
time_buffer[7:0] <= 8'b0;
time_plus_full <= 1'b1;
end else begin
time_buffer[7:0] <= time_buffer[7:0] + 1'b1;
end
end else begin
time_buffer[7:0] <= time_buffer[7:0];
time_plus_full <= 1'b0;
end
end
end
always@(posedge clk or negedge rst_n) begin //***********************time+60***************************//
if(!rst_n)begin
time_buffer[15:8] <= 8'b0;
end else begin
if(time_60plus_flag||time_plus_full)begin
if(time_buffer[11:8] >= 4'd9 && time_buffer[15:12] < 4'd2) time_buffer[15:8] <= time_buffer[15:8] + 4'd7;
else if(time_buffer[11:8] >= 4'd3 && time_buffer[15:12] == 4'd2) time_buffer[15:8] <= 8'b0;
else time_buffer[15:8] <= time_buffer[15:8] + 1'b1;
end else begin
time_buffer[15:8] <= time_buffer[15:8];
end
end
end
- 串口接收模块
依然是利用了硬禾学堂的开源代码,主要特点与发送类似,不再赘述。
reg uart_rx0,uart_rx1,uart_rx2;
//多级延时锁存去除亚稳态
always @ (posedge clk) begin
uart_rx0 <= uart_rx;
uart_rx1 <= uart_rx0;
uart_rx2 <= uart_rx1;
end
//检测UART接收输入信号的下降沿
wire neg_uart_rx = uart_rx2 & ~uart_rx1;
reg [3:0] num;
//接收时钟使能信号的控制
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
bps_en <= 1'b0;
else if(neg_uart_rx && (!bps_en)) //当空闲状态(bps_en为低电平)时检测到UART接收信号下降沿,进入工作状态(bps_en为高电平),控制时钟模块产生接收时钟
bps_en <= 1'b1;
else if(num==4'd9) //当完成一次UART接收操作后,退出工作状态,恢复空闲状态
bps_en <= 1'b0;
end
reg [7:0] rx_data;
//当处于工作状态中时,按照接收时钟的节拍获取数据
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
num <= 4'd0;
rx_data <= 8'd0;
end else if(bps_en) begin
if(bps_clk) begin
num <= num + 1'b1;
if(num<=4'd8) rx_data[num-1] <= uart_rx1; //先接受低位再接收高位,8位有效数据
end else if(num == 4'd9) begin //完成一次UART接收操作后,将获取的数据输出
num <= 4'd0;
end
end else begin
num <= 4'd0;
end
end
//将接收的数据输出,同时控制输出有效信号产生脉冲
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
rx_data_out <= 8'd0;
rx_data_valid <= 1'b0;
end else if(num == 4'd9) begin
rx_data_out <= rx_data;
rx_data_valid <= 1'b1;
end else begin
rx_data_out <= rx_data_out;
rx_data_valid <= 1'b0;
end
end
- 顶层top模块
这次实验给我最大的帮助之一就是让我认识到顶层top模块的设计和应用,这种编写方式就像C++语言中的头文件和函数一样,大大增强了代码的可移植性,使得很多开源的代码能够直接应用到一个很大的项目中去。
top模块的编写格式参考了杨涛同学的案例中的例化方法。
module top
(
input clk, // system clock
input rst_n, // system reset, active low
input [3:0] sw,
input [2:0] key,
inout one_wire,
input fpga_rx, //UART接收输入
output fpga_tx, //UART发送输出
output oled_csn, //OLCD液晶屏使能
output oled_rst, //OLCD液晶屏复位
output oled_dcn, //OLCD数据指令控制
output oled_clk, //OLCD时钟信号
output oled_dat, //OLCD数据信号
output beeper
);
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
);
wire [31:0] time_oled;
wire [31:0] temp_oled;
OLED12832 OLED12832_uut
(
.clk (clk ), // system clock
.rst_n (rst_n ), // system reset, active low
.tim (time_oled ), // ds18b20z one-wire-bus
.tem (temp_oled ), // ds18b20z data_out
.oled_csn (oled_csn ),
.oled_rst (oled_rst ),
.oled_dcn (oled_dcn ),
.oled_clk (oled_clk ),
.oled_dat (oled_dat )
);
wire rx_data_valid;
wire [7:0] rx_data;
wire tx_data_valid;
wire [7:0] tx_data;
//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 ), //接收到的数据 8位
//负责FPGA发送数据给UART芯片
.tx_data_valid (tx_data_valid ),
.tx_data_in (tx_data ),
.uart_tx (fpga_tx )
);
wire [15:0] cycle;
//根据不同音节的周期cycle值产生对应的PWM信号
PWM #
(
.WIDTH (16 ) //ensure that 2**WIDTH > cycle
)
PWM_uut
(
.clk (clk ),
.rst_n (rst_n ),
.cycle (cycle ), //cycle > duty
.duty (cycle>>1 ), //duty < cycle
.pwm_out (beeper )
);
wire [2:0] key_pulse;
key_debounce #
(
.N (3 )
)
key_debounce_uut
(
.clk (clk ),
.rst (rst_n ),
.key (key ),
.key_pulse (key_pulse )
);
control_module control_module_uut
(
.clk (clk ), // system clock
.rst_n (rst_n ), // system reset, active low
.sw (sw ),
.key (key_pulse ),
.temp_in (bcd_code[23:12] ), // ds18b20z data_out
.rx_data_valid (rx_data_valid ),
.rx_data_in (rx_data ),
.tx_data_valid (tx_data_valid ),
.tx_data_out (tx_data ),
.time_out (time_oled ),
.temp_out (temp_oled ),
.cycle (cycle )
);
endmodule
完成的功能
参考项目需求部分,满足了全部需求。
遇到的难题
- 整个项目比较庞大,一开始设计出来后发现板子执行逻辑顺序混乱,各部分执行先后顺序、因果关系错误。后来在程序各个模块中设置了很多状态变量,从数字电路的角度也可以看成是门控信号,并且配套了很多if-else语句用来调配顺序,解决了问题。
- 串行传输如果直接按十六进制显示,时间和温度信息没有区分度,而且在界面上是一个接一个格式,参考了叶开同学的代码后,将直接十六进制数的传输改成了ACSll码字符串的传输,实现了符号的表达和换行等功能的使用,使信息更有区分度。
- 自己设计乐谱时,只能对着相关歌曲的乐谱进行格式改编,10秒左右的音频选自歌曲《大鱼》。
未来计划与建议
- 汉字字库的实现和在OLED屏上的显示
- 蜂鸣器发声音质的改变,通过调节使连在一起的音符发声产生间断感。