一、项目要求
-
实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
-
实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
-
定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
-
PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
-
音频文件播放完毕,OLED开始更新时间信息和当前的温度信息。
二、系统设计
1、系统结构
根据项目要求,该系统主要由五个模块构成,分别为时间产生模块(time_generator.v),温度采集模块(DS18B20Z.v),OLED显示模块(OLED12832.v),串口通信模块(uart_top.v),蜂鸣器控制模块(beeper_control.v)。整个工程的结构图如下:
2、布线设计
由Lattice Diamond软件综合产生的布线设计。
3、资源占用
三、各模块实现 1、顶层模块
顶层模块主要包括各个子模块的例化,各模块之间的信号连接,以及系统整体的输入输出。
module system_top(
input clk, //12MHz系统时钟
input rst_n, //系统复位,低有效
input [2:0] key, //2个按键控制时分
output reg [6:0] led,
output reg [2:0] rgb_led1,
output reg [2:0] rgb_led2,
output reg en,
inout one_wire, //DS18B20Z传感器单总线
input rs232_rx,
output rs232_tx,
output piano_out,
output oled_csn, //OLCD液晶屏使能
output oled_rst, //OLCD液晶屏复位
output oled_dcn, //OLCD数据指令控制
output oled_clk, //OLCD时钟信号
output oled_dat //OLCD数据信号
);
wire [11:0] tamp_data; //温度数据{[11:4]:整数部分,[3:0]:小数部分,BCD码}
wire [23:0] time_data; //时间数据{[11:8]:时,[7:4]:分,[3:0]:秒,BCD码}
wire flag;
wire play_done;
always @ ( posedge clk or negedge rst_n )
if ( ~rst_n ) begin
led <= 8'd0;
rgb_led1 <= 3'd0;
rgb_led2 <= 3'd0;
end
else begin
led <= 8'b11111111;
rgb_led1 <= 3'b111;
rgb_led2 <= 3'b111;
end
always @ ( posedge clk or negedge rst_n )
if ( ~rst_n )
en <= 1'b1;
else if ( flag )
en <= 1'b0;
else if ( play_done )
en <= 1'b1;
//时间产生
time_generator time_generator_inst(
.clk (clk ),
.rst_n (rst_n ),
.control (key ),
.en (en ),
.time_out (time_data ),
.flag (flag )
);
//温度采集
DS18B20Z DS18B20Z_inst(
.clk (clk ),
.rst_n (rst_n ),
.en (en ),
.one_wire (one_wire ),
.tamp_out (tamp_data )
);
//显示
OLED12832 OLED12832_inst(
.clk (clk ),
.rst_n (rst_n ),
.time_data (time_data ),
.tamp_data (tamp_data ),
.oled_csn (oled_csn ),
.oled_rst (oled_rst ),
.oled_dcn (oled_dcn ),
.oled_clk (oled_clk ),
.oled_dat (oled_dat )
);
wire rx_done;
wire [9:0] data_length;
wire [959:0] data_buffer;
//串口
uart_top #
(
.BPS_PARA (1250 )
)
uart_top_inst(
.clk (clk ),
.rst_n (rst_n ),
.flag (flag ),
.tamp_data (tamp_data ),
.rs232_rx (rs232_rx ),
.rs232_tx (rs232_tx ),
.data_length (data_length ),
.data_buffer (data_buffer ),
.rx_done (rx_done )
);
//蜂鸣器控制
beeper_control beeper_control_inst(
.clk (clk ),
.rst_n (rst_n ),
.en (~en ),
.rx_done (rx_done ),
.data_length (data_length ),
.data_buffer (data_buffer ),
.piano_out (piano_out ),
.play_done (play_done )
);
endmodule
2、时间产生模块
时间产生模块的功能为产生时分秒信息,设置时间以及整点报时。时间的设置最快可以每秒变化5个基本单位,到整点时会产生一个系统时钟周期的标志信号flag脉冲。
module time_generator(
input clk , //12M
input rst_n ,
input [2:0] control ,
input en ,
output [23:0] time_out,
output reg flag
);
//计数器分频产5Hz的时钟信号(设置时间)
wire clk_5hz;
reg [23:0] cnt_5hz;
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_5hz <= 24'd0;
else if(cnt_5hz == 24'd2399999)
cnt_5hz <= 24'd0;
else if( en )
cnt_5hz <= cnt_5hz + 1'b1;
assign clk_5hz = (cnt_5hz == 24'd2000000)?1'b1:1'b0;
reg [2:0] cnt;
always @ ( posedge clk or negedge rst_n )
if (~rst_n)
cnt <= 3'd0;
else if ( cnt == 3'd4 && cnt_5hz == 24'd2399999)
cnt <= 3'd0;
else if (cnt_5hz == 24'd2399999)
cnt <= cnt + 1'b1;
reg [7:0] time_shi;
reg [7:0] time_fen;
reg [7:0] time_miao;
//time_shi
always @ ( posedge clk_5hz or negedge rst_n )
if (~rst_n)
time_shi <= 8'd0;
else if( en && ((time_fen[7:4]==4'd5 && time_fen[3:0]==4'd9 && time_miao[7:4]==4'd5 && time_miao[3:0]==4'd9 && cnt == 3'd4) || ~control[2]) )begin
if( time_shi[7:4]==4'd2 && time_shi[3:0]==4'd3 )
time_shi <= 8'd0;
else if ( time_shi[3:0]==4'd9 ) begin
time_shi[7:4] <= time_shi[7:4] + 1'b1;
time_shi[3:0] <= 4'd0;
end
else
time_shi[3:0] <= time_shi[3:0] + 1'b1;
end
//time_fen
always @ ( posedge clk_5hz or negedge rst_n )
if (~rst_n)
time_fen <= 8'd0;
else if( en && ((time_miao[7:4]==4'd5 && time_miao[3:0]==4'd9 && cnt == 3'd4) || ~control[1]) )begin
if( time_fen[7:4]==4'd5 && time_fen[3:0]==4'd9 )
time_fen <= 8'd0;
else if ( time_fen[3:0]==4'd9 ) begin
time_fen[7:4] <= time_fen[7:4] + 1'b1;
time_fen[3:0] <= 4'd0;
end
else
time_fen[3:0] <= time_fen[3:0] + 1'b1;
end
//time_miao
always @ ( posedge clk_5hz or negedge rst_n )
if (~rst_n)
time_miao <= 8'd0;
else if ( en && (cnt == 3'd4 || ~control[0])) begin
if( time_miao[7:4]==4'd5 && time_miao[3:0]==4'd9)
time_miao <= 8'd0;
else if ( time_miao[3:0]==4'd9 ) begin
time_miao[7:4] <= time_miao[7:4] + 1'b1;
time_miao[3:0] <= 4'd0;
end
else
time_miao[3:0] <= time_miao[3:0] + 1'b1;
end
assign time_out = {time_shi,time_fen,time_miao};
always @ ( posedge clk or negedge rst_n )
if ( ~rst_n )
flag <= 1'b0;
else if ( flag == 1'b1 )
flag <= 1'b0;
else if (time_fen==8'd0&&time_miao==8'd0&&cnt==3'd0&&cnt_5hz == 24'd2399999)
flag <= 1'b1;
endmodule
3、温度采集模块
温度采集模块使用STEP FPGA开源社区温度传感器模块的相关源码。主要添加采集到的温度信息处理部分。
//tamp_out
always @ (posedge clk_1mhz or negedge rst_n)
if ( ~rst_n )
tamp_out <= 12'd0;
else if (en) begin
case(data_out[2:0])
3'b000: tamp_out[3:0] <= (data_out[3])?4'd5:4'd0;
3'b001,3'b010: tamp_out[3:0] <= (data_out[3])?4'd6:4'd1;
3'b011: tamp_out[3:0] <= (data_out[3])?4'd7:4'd2;
3'b100,3'b101: tamp_out[3:0] <= (data_out[3])?4'd8:4'd3;
3'b110,3'b111: tamp_out[3:0] <= (data_out[3])?4'd9:4'd4;
endcase
case(data_out[7:4])
4'b0000: begin tamp_out[11:8] <= (data_out[8])?4'd1:4'd0; tamp_out[7:4] <= (data_out[8])?4'd6:4'd0; end
4'b0001: begin tamp_out[11:8] <= (data_out[8])?4'd1:4'd0; tamp_out[7:4] <= (data_out[8])?4'd7:4'd1; end
4'b0010: begin tamp_out[11:8] <= (data_out[8])?4'd1:4'd0; tamp_out[7:4] <= (data_out[8])?4'd8:4'd2; end
4'b0011: begin tamp_out[11:8] <= (data_out[8])?4'd1:4'd0; tamp_out[7:4] <= (data_out[8])?4'd9:4'd3; end
4'b0100: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd0; tamp_out[7:4] <= (data_out[8])?4'd0:4'd4; end
4'b0101: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd0; tamp_out[7:4] <= (data_out[8])?4'd1:4'd5; end
4'b0110: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd0; tamp_out[7:4] <= (data_out[8])?4'd2:4'd6; end
4'b0111: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd0; tamp_out[7:4] <= (data_out[8])?4'd3:4'd7; end
4'b1000: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd0; tamp_out[7:4] <= (data_out[8])?4'd4:4'd8; end
4'b1001: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd0; tamp_out[7:4] <= (data_out[8])?4'd5:4'd9; end
4'b1010: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd1; tamp_out[7:4] <= (data_out[8])?4'd6:4'd0; end
4'b1011: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd1; tamp_out[7:4] <= (data_out[8])?4'd7:4'd1; end
4'b1100: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd1; tamp_out[7:4] <= (data_out[8])?4'd8:4'd2; end
4'b1101: begin tamp_out[11:8] <= (data_out[8])?4'd2:4'd1; tamp_out[7:4] <= (data_out[8])?4'd9:4'd3; end
4'b1110: begin tamp_out[11:8] <= (data_out[8])?4'd3:4'd1; tamp_out[7:4] <= (data_out[8])?4'd0:4'd4; end
4'b1111: begin tamp_out[11:8] <= (data_out[8])?4'd3:4'd1; tamp_out[7:4] <= (data_out[8])?4'd1:4'd5; end
endcase
end
4、OLED显示模块
OLED显示模块借鉴电子森林应用案例及参考代码OLED显示模块相关代码,修改显示模式为垂直显示模式。字模数据的制作使用PCtoLCD2002软件。该模块可以显示时分秒以及温度信息。
module OLED12832(
input clk, //12MHz系统时钟
input rst_n, //系统复位,低有效
input [23:0] time_data,
input [11:0] tamp_data, //温度数据{[11:4]:整数部分,[3:0]:小数部分}
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 = 6'd29; //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 [28:0];
reg [63:0] mem1 [95:0];
reg [63:0] mem2 [23:0];
reg [7:0] x_ph, x_pl;
reg [7:0] y_ph, y_pl;
reg [7:0] char;
reg [7:0] num1, num2, char_reg;
reg [4:0] cnt_main, cnt_init, cnt_scan, cnt_write;
reg [15:0] num1_delay, cnt_delay, cnt;
reg [5:0] state, state_back;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 5'b0; cnt_write <= 1'b0;
x_ph <= 8'h7f; x_pl <= 8'h00;
y_ph <= 8'h03; y_pl <= 8'h00;
num1 <= 8'b0; num2 <= 8'b0; char <= 8'b0; char_reg <= 8'b0;
num1_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 <= 5'b0; cnt_write <= 1'b0;
x_ph <= 8'h7f; x_pl <= 8'h00;
y_ph <= 8'h03; y_pl <= 8'h00;
num1 <= 8'b0; num2 <= 8'b0; char <= 8'b0; char_reg <= 1'b0;
num1_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'd14) cnt_main <= 5'd1;
else cnt_main <= cnt_main + 1'b1;
case(cnt_main) //MAIN状态
5'd0: begin state <= INIT; end
5'd1: begin x_ph <= 8'h7f; x_pl <= 8'h00; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= time_data[23:20]<<3;state <= SCAN; end
5'd2: begin x_ph <= 8'h7f; x_pl <= 8'h10; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= time_data[19:16]<<3;state <= SCAN; end
5'd3: begin x_ph <= 8'h7f; x_pl <= 8'h20; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= 8'd80;state <= SCAN; end
5'd4: begin x_ph <= 8'h7f; x_pl <= 8'h30; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= time_data[15:12]<<3;state <= SCAN; end
5'd5: begin x_ph <= 8'h7f; x_pl <= 8'h40; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= time_data[11:8]<<3;state <= SCAN; end
5'd6: begin x_ph <= 8'h57; x_pl <= 8'h50; y_ph <= 8'h01; y_pl <= 8'h00; num2 <= 8'd2; char <= tamp_data[11:8]<<1;state <= SCAN; end
5'd7: begin x_ph <= 8'h5f; x_pl <= 8'h58; y_ph <= 8'h01; y_pl <= 8'h00; num2 <= 8'd2; char <= tamp_data[7:4]<<1;state <= SCAN; end
5'd8: begin x_ph <= 8'h67; x_pl <= 8'h60; y_ph <= 8'h01; y_pl <= 8'h00; num2 <= 8'd2; char <= 8'd20;state <= SCAN; end
5'd9: begin x_ph <= 8'h6f; x_pl <= 8'h68; y_ph <= 8'h01; y_pl <= 8'h00; num2 <= 8'd2; char <= tamp_data[3:0]<<1;state <= SCAN; end
5'd10: begin x_ph <= 8'h57; x_pl <= 8'h50; y_ph <= 8'h03; y_pl <= 8'h02; num2 <= 8'd2; char <= 8'd22;state <= SCAN; end
5'd11: begin x_ph <= 8'h5f; x_pl <= 8'h58; y_ph <= 8'h03; y_pl <= 8'h02; num2 <= 8'd2; char <= time_data[7:4]<<1;state <= SCAN; end
5'd12: begin x_ph <= 8'h67; x_pl <= 8'h60; y_ph <= 8'h03; y_pl <= 8'h02; num2 <= 8'd2; char <= time_data[3:0]<<1;state <= SCAN; end
5'd13: begin x_ph <= 8'h6f; x_pl <= 8'h68; y_ph <= 8'h03; y_pl <= 8'h02; num2 <= 8'd2; char <= 8'd22;state <= SCAN; end
5'd14: begin x_ph <= 8'h7f; x_pl <= 8'h70; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= 8'd88;state <= SCAN; 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 num1_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 num1_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end //延时大于220us
5'd4: begin
if(cnt>=INIT_DEPTH) begin //当29条指令及数据发出后,配置完成
cnt <= 1'b0;
cnt_init <= cnt_init + 1'b1;
end else begin
cnt <= cnt + 1'b1; num1_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'd7) begin
if(num2) cnt_scan <= cnt_scan + 5'd10; //16*8
else cnt_scan <= cnt_scan + 1'b1; //32*16
end
else if(cnt_scan == 5'd16) begin
if(num1) cnt_scan <= 5'd8;
else cnt_scan <= cnt_scan + 5'd10;
end
else if(cnt_scan == 5'd25) begin
if(num2) cnt_scan <= 5'd17;
else cnt_scan <= cnt_scan + 1'b1;
end
else if(cnt_scan == 5'd26) cnt_scan <= 1'b0;
else cnt_scan <= cnt_scan + 1'b1;
case(cnt_scan)
5'd0: begin oled_dcn <= CMD; char_reg <= 8'h20; state <= WRITE; state_back <= SCAN; end
5'd1: begin oled_dcn <= CMD; char_reg <= 8'h01; state <= WRITE; state_back <= SCAN; end
5'd2: begin oled_dcn <= CMD; char_reg <= 8'h21; state <= WRITE; state_back <= SCAN; end
5'd3: begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end
5'd4: begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end
5'd5: begin oled_dcn <= CMD; char_reg <= 8'h22; state <= WRITE; state_back <= SCAN; end
5'd6: begin oled_dcn <= CMD; char_reg <= y_pl; state <= WRITE; state_back <= SCAN; end
5'd7: begin oled_dcn <= CMD; char_reg <= y_ph; state <= WRITE; state_back <= SCAN; end
5'd8: begin num1 <= num1 - 1'b1;end
5'd9: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][63:56]; state <= WRITE; state_back <= SCAN; end
5'd10: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][55:48]; state <= WRITE; state_back <= SCAN; end
5'd11: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][47:40]; state <= WRITE; state_back <= SCAN; end
5'd12: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][39:32]; state <= WRITE; state_back <= SCAN; end
5'd13: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][31:24]; state <= WRITE; state_back <= SCAN; end
5'd14: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][23:16]; state <= WRITE; state_back <= SCAN; end
5'd15: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][15: 8]; state <= WRITE; state_back <= SCAN; end
5'd16: begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][ 7: 0]; state <= WRITE; state_back <= SCAN; end
5'd17: begin num2 <= num2 - 1'b1;end
5'd18: begin oled_dcn <= DATA; char_reg <= mem2[char+1-num2][63:56]; state <= WRITE; state_back <= SCAN; end
5'd19: begin oled_dcn <= DATA; char_reg <= mem2[char+1-num2][55:48]; state <= WRITE; state_back <= SCAN; end
5'd20: begin oled_dcn <= DATA; char_reg <= mem2[char+1-num2][47:40]; state <= WRITE; state_back <= SCAN; end
5'd21: begin oled_dcn <= DATA; char_reg <= mem2[char+1-num2][39:32]; state <= WRITE; state_back <= SCAN; end
5'd22: begin oled_dcn <= DATA; char_reg <= mem2[char+1-num2][31:24]; state <= WRITE; state_back <= SCAN; end
5'd23: begin oled_dcn <= DATA; char_reg <= mem2[char+1-num2][23:16]; state <= WRITE; state_back <= SCAN; end
5'd24: begin oled_dcn <= DATA; char_reg <= mem2[char+1-num2][15: 8]; state <= WRITE; state_back <= SCAN; end
5'd25: begin oled_dcn <= DATA; char_reg <= mem2[char+1-num2][ 7: 0]; state <= WRITE; state_back <= SCAN; end
5'd26: 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'd0: begin oled_csn <= LOW; end //9位数据最高位为命令数据控制位
5'd1: begin oled_clk <= LOW; oled_dat <= char_reg[7]; end
5'd2: begin oled_clk <= HIGH; end
5'd3: begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
5'd4: begin oled_clk <= HIGH; end
5'd5: begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
5'd6: begin oled_clk <= HIGH; end
5'd7: begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
5'd8: begin oled_clk <= HIGH; end
5'd9: 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 >= num1_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'h20}; //设置寻址模式
cmd[ 2] = {8'h01}; //垂直寻址模式
cmd[ 3] = {8'h21}; //设置起始/终止列地址
cmd[ 4] = {8'h00}; //设置起始列地址
cmd[ 5] = {8'h7f}; //设置终止列地址
cmd[ 6] = {8'h22}; //设置起始/终止页地址
cmd[ 7] = {8'h00}; //设置起始页地址
cmd[ 8] = {8'h03}; //设置终止页地址
cmd[ 9] = {8'h81}; //设置对比度
cmd[10] = {8'hff}; //指定对比度
cmd[11] = {8'ha1}; //设置SEG重映射列地址127映射到SEG0
cmd[12] = {8'ha6}; //设置显示模式(A6:1亮0灭)
cmd[13] = {8'ha8}; //设置复用率
cmd[14] = {8'h1f}; //指定复用率(显示32行)
cmd[15] = {8'hc8}; //设置COM扫描方向(C8:从COM[N-1]到COM0)
cmd[16] = {8'hd3}; //设置偏移
cmd[17] = {8'h00}; //指定偏移
cmd[18] = {8'hd5}; //设置时钟分频
cmd[19] = {8'h80}; //振荡频率:8,分频:1
cmd[20] = {8'hd9}; //设置预充电周期
cmd[21] = {8'h1f}; //指定预充电周期
cmd[22] = {8'hda}; //设置COM硬件
cmd[23] = {8'h00}; //禁止COM左右反置
cmd[24] = {8'hdb}; //设置VCOMH电平
cmd[25] = {8'h40}; //指定VCOMH电平
cmd[26] = {8'h8d};
cmd[27] = {8'h14};
cmd[28] = {8'haf}; //打开屏幕
end
//5*8点阵字库数据
always@(posedge rst_n)
begin
//0
mem1[ 0] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'hF0, 8'h1F, 8'h00};
mem1[ 1] = {8'h00, 8'hFE, 8'hFF, 8'h00, 8'h00, 8'hFF, 8'hFF, 8'h03};
mem1[ 2] = {8'h80, 8'h07, 8'hC0, 8'h03, 8'hC0, 8'h01, 8'h00, 8'h07};
mem1[ 3] = {8'hC0, 8'h00, 8'h00, 8'h06, 8'hC0, 8'h00, 8'h00, 8'h0E};
mem1[ 4] = {8'hC0, 8'h00, 8'h00, 8'h0E, 8'hC0, 8'h00, 8'h00, 8'h06};
mem1[ 5] = {8'hC0, 8'h03, 8'h00, 8'h07, 8'h80, 8'h0F, 8'hE0, 8'h03};
mem1[ 6] = {8'h00, 8'hFF, 8'hFF, 8'h01, 8'h00, 8'hFC, 8'h7F, 8'h00};
mem1[ 7] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//1
mem1[ 8] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
mem1[ 9] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h1C, 8'h00, 8'h00};
mem1[10] = {8'h00, 8'h0C, 8'h00, 8'h00, 8'h00, 8'h06, 8'h00, 8'h00};
mem1[11] = {8'h00, 8'h03, 8'h00, 8'h00, 8'hC0, 8'hFF, 8'hFF, 8'h07};
mem1[12] = {8'hC0, 8'hFF, 8'hFF, 8'h07, 8'hC0, 8'hFF, 8'hFF, 8'h07};
mem1[13] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
mem1[14] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
mem1[15] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//2
mem1[16] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h04, 8'h00, 8'h00};
mem1[17] = {8'h00, 8'h07, 8'h00, 8'h07, 8'h80, 8'h07, 8'h80, 8'h07};
mem1[18] = {8'hC0, 8'h03, 8'hC0, 8'h07, 8'hC0, 8'h01, 8'hE0, 8'h07};
mem1[19] = {8'hC0, 8'h00, 8'h70, 8'h06, 8'hC0, 8'h00, 8'h38, 8'h06};
mem1[20] = {8'hC0, 8'h00, 8'h1E, 8'h06, 8'hC0, 8'h00, 8'h0F, 8'h06};
mem1[21] = {8'hC0, 8'hC1, 8'h03, 8'h06, 8'h80, 8'hFF, 8'h01, 8'h06};
mem1[22] = {8'h80, 8'hFF, 8'h00, 8'h06, 8'h00, 8'h1E, 8'h00, 8'h06};
mem1[23] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//3
mem1[24] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h40, 8'h00};
mem1[25] = {8'h00, 8'h06, 8'hC0, 8'h01, 8'h00, 8'h0F, 8'hE0, 8'h03};
mem1[26] = {8'h80, 8'h03, 8'h80, 8'h07, 8'hC0, 8'h01, 8'h00, 8'h07};
mem1[27] = {8'hC0, 8'h80, 8'h01, 8'h06, 8'hC0, 8'h80, 8'h01, 8'h0E};
mem1[28] = {8'hC0, 8'h80, 8'h01, 8'h0E, 8'hC0, 8'hC0, 8'h03, 8'h06};
mem1[29] = {8'hC0, 8'hC1, 8'h03, 8'h07, 8'h80, 8'h7F, 8'hDF, 8'h03};
mem1[30] = {8'h80, 8'h7F, 8'hFE, 8'h03, 8'h00, 8'h1E, 8'hFC, 8'h00};
mem1[31] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//4
mem1[32] = {8'h00, 8'h00, 8'h70, 8'h00, 8'h00, 8'h00, 8'h78, 8'h00};
mem1[33] = {8'h00, 8'h00, 8'h7E, 8'h00, 8'h00, 8'h00, 8'h7F, 8'h00};
mem1[34] = {8'h00, 8'hC0, 8'h73, 8'h00, 8'h00, 8'hE0, 8'h71, 8'h00};
mem1[35] = {8'h00, 8'h70, 8'h70, 8'h00, 8'h00, 8'h3C, 8'h70, 8'h00};
mem1[36] = {8'h00, 8'h1E, 8'h70, 8'h00, 8'h80, 8'h07, 8'h70, 8'h00};
mem1[37] = {8'hC0, 8'hFF, 8'hFF, 8'h07, 8'hC0, 8'hFF, 8'hFF, 8'h07};
mem1[38] = {8'h00, 8'h00, 8'h70, 8'h00, 8'h00, 8'h00, 8'h70, 8'h00};
mem1[39] = {8'h00, 8'h00, 8'h70, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//5
mem1[40] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h80, 8'hC1, 8'h01};
mem1[41] = {8'h00, 8'hF8, 8'hE1, 8'h03, 8'hC0, 8'hFF, 8'h81, 8'h07};
mem1[42] = {8'hC0, 8'hCF, 8'h00, 8'h06, 8'hC0, 8'h60, 8'h00, 8'h0E};
mem1[43] = {8'hC0, 8'h60, 8'h00, 8'h0E, 8'hC0, 8'h60, 8'h00, 8'h0E};
mem1[44] = {8'hC0, 8'h60, 8'h00, 8'h0E, 8'hC0, 8'hE0, 8'h00, 8'h07};
mem1[45] = {8'hC0, 8'hC0, 8'h01, 8'h07, 8'hC0, 8'hC0, 8'hE7, 8'h03};
mem1[46] = {8'hC0, 8'h80, 8'hFF, 8'h01, 8'h00, 8'h00, 8'hFE, 8'h00};
mem1[47] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//6
mem1[48] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h7C, 8'h00};
mem1[49] = {8'h00, 8'h00, 8'hFF, 8'h01, 8'h00, 8'hC0, 8'hFF, 8'h03};
mem1[50] = {8'h00, 8'hF0, 8'h83, 8'h07, 8'h00, 8'hF8, 8'h01, 8'h07};
mem1[51] = {8'h00, 8'hFE, 8'h00, 8'h06, 8'h00, 8'hCF, 8'h00, 8'h0E};
mem1[52] = {8'hC0, 8'hC7, 8'h00, 8'h0E, 8'hC0, 8'hC1, 8'h00, 8'h0E};
mem1[53] = {8'hC0, 8'hC0, 8'h00, 8'h06, 8'h00, 8'hC0, 8'h01, 8'h07};
mem1[54] = {8'h00, 8'h80, 8'hFF, 8'h03, 8'h00, 8'h00, 8'hFF, 8'h01};
mem1[55] = {8'h00, 8'h00, 8'hFE, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//7
mem1[56] = {8'h00, 8'h00, 8'h00, 8'h00, 8'hC0, 8'h00, 8'h00, 8'h00};
mem1[57] = {8'hC0, 8'h00, 8'h00, 8'h00, 8'hC0, 8'h00, 8'h00, 8'h00};
mem1[58] = {8'hC0, 8'h00, 8'h00, 8'h04, 8'hC0, 8'h00, 8'h80, 8'h07};
mem1[59] = {8'hC0, 8'h00, 8'hF0, 8'h07, 8'hC0, 8'h00, 8'hFE, 8'h01};
mem1[60] = {8'hC0, 8'h80, 8'h3F, 8'h00, 8'hC0, 8'hE0, 8'h07, 8'h00};
mem1[61] = {8'hC0, 8'hF8, 8'h00, 8'h00, 8'hC0, 8'h3E, 8'h00, 8'h00};
mem1[62] = {8'hC0, 8'h0F, 8'h00, 8'h00, 8'hC0, 8'h03, 8'h00, 8'h00};
mem1[63] = {8'hC0, 8'h01, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//8
mem1[64] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'hFC, 8'h01};
mem1[65] = {8'h00, 8'h7F, 8'hFE, 8'h03, 8'h80, 8'h7F, 8'hCF, 8'h07};
mem1[66] = {8'hC0, 8'hE1, 8'h07, 8'h07, 8'hC0, 8'hC0, 8'h03, 8'h06};
mem1[67] = {8'hC0, 8'h80, 8'h01, 8'h0E, 8'hC0, 8'h80, 8'h01, 8'h0E};
mem1[68] = {8'hC0, 8'h80, 8'h01, 8'h0E, 8'hC0, 8'hC0, 8'h03, 8'h06};
mem1[69] = {8'hC0, 8'hE1, 8'h03, 8'h07, 8'h80, 8'h7F, 8'h8F, 8'h07};
mem1[70] = {8'h00, 8'h7F, 8'hFE, 8'h03, 8'h00, 8'h1E, 8'hFC, 8'h01};
mem1[71] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//9
mem1[72] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'hFE, 8'h01, 8'h00};
mem1[73] = {8'h00, 8'hFF, 8'h03, 8'h00, 8'h80, 8'h87, 8'h07, 8'h00};
mem1[74] = {8'hC0, 8'h01, 8'h07, 8'h08, 8'hC0, 8'h01, 8'h06, 8'h0E};
mem1[75] = {8'hC0, 8'h00, 8'h86, 8'h0F, 8'hC0, 8'h00, 8'hE6, 8'h03};
mem1[76] = {8'hC0, 8'h00, 8'hFE, 8'h01, 8'hC0, 8'h01, 8'h7F, 8'h00};
mem1[77] = {8'hC0, 8'h83, 8'h1F, 8'h00, 8'h80, 8'hFF, 8'h07, 8'h00};
mem1[78] = {8'h00, 8'hFF, 8'h03, 8'h00, 8'h00, 8'h7E, 8'h00, 8'h00};
mem1[79] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//:
mem1[80] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
mem1[81] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
mem1[82] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
mem1[83] = {8'h00, 8'h0F, 8'hF0, 8'h00, 8'h00, 8'h0F, 8'hF0, 8'h00};
mem1[84] = {8'h00, 8'h0F, 8'hF0, 8'h00, 8'h00, 8'h0F, 8'hF0, 8'h00};
mem1[85] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
mem1[86] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
mem1[87] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//:
mem1[88] = {8'h00, 8'h00, 8'h18, 8'h00, 8'h0C, 8'h00, 8'h3C, 8'h00};
mem1[89] = {8'h12, 8'h00, 8'h3C, 8'h00, 8'h12, 8'h00, 8'hF8, 8'h00};
mem1[90] = {8'h8C, 8'h0F, 8'hC0, 8'h01, 8'hE0, 8'h38, 8'h80, 8'h0F};
mem1[91] = {8'h30, 8'h20, 8'h1C, 8'h3F, 8'h10, 8'h60, 8'hFE, 8'h7F};
mem1[92] = {8'h18, 8'h40, 8'hFE, 8'h7F, 8'h08, 8'h40, 8'h1C, 8'h3F};
mem1[93] = {8'h08, 8'h60, 8'h80, 8'h0F, 8'h08, 8'h20, 8'hC0, 8'h01};
mem1[94] = {8'h10, 8'h30, 8'hF8, 8'h00, 8'h30, 8'h0C, 8'h3C, 8'h00};
mem1[95] = {8'h00, 8'h00, 8'h3C, 8'h00, 8'h00, 8'h00, 8'h18, 8'h00};
end
always@(posedge rst_n)
begin
//0
mem2[ 0] = {8'h00, 8'h00, 8'hE0, 8'h0F, 8'h10, 8'h10, 8'h08, 8'h20};
mem2[ 1] = {8'h08, 8'h20, 8'h10, 8'h10, 8'hE0, 8'h0F, 8'h00, 8'h00};
//1
mem2[ 2] = {8'h00, 8'h00, 8'h10, 8'h20, 8'h10, 8'h20, 8'hF8, 8'h3F};
mem2[ 3] = {8'h00, 8'h20, 8'h00, 8'h20, 8'h00, 8'h00, 8'h00, 8'h00};
//2
mem2[ 4] = {8'h00, 8'h00, 8'h70, 8'h30, 8'h08, 8'h28, 8'h08, 8'h24};
mem2[ 5] = {8'h08, 8'h22, 8'h88, 8'h21, 8'h70, 8'h30, 8'h00, 8'h00};
//3
mem2[ 6] = {8'h00, 8'h00, 8'h30, 8'h18, 8'h08, 8'h20, 8'h88, 8'h20};
mem2[ 7] = {8'h88, 8'h20, 8'h48, 8'h11, 8'h30, 8'h0E, 8'h00, 8'h00};
//4
mem2[ 8] = {8'h00, 8'h00, 8'h00, 8'h07, 8'hC0, 8'h04, 8'h20, 8'h24};
mem2[ 9] = {8'h10, 8'h24, 8'hF8, 8'h3F, 8'h00, 8'h24, 8'h00, 8'h00};
//5
mem2[10] = {8'h00, 8'h00, 8'hF8, 8'h19, 8'h08, 8'h21, 8'h88, 8'h20};
mem2[11] = {8'h88, 8'h20, 8'h08, 8'h11, 8'h08, 8'h0E, 8'h00, 8'h00};
//6
mem2[12] = {8'h00, 8'h00, 8'hE0, 8'h0F, 8'h10, 8'h11, 8'h88, 8'h20};
mem2[13] = {8'h88, 8'h20, 8'h18, 8'h11, 8'h00, 8'h0E, 8'h00, 8'h00};
//7
mem2[14] = {8'h00, 8'h00, 8'h38, 8'h00, 8'h08, 8'h00, 8'h08, 8'h3F};
mem2[15] = {8'hC8, 8'h00, 8'h38, 8'h00, 8'h08, 8'h00, 8'h00, 8'h00};
//8
mem2[16] = {8'h00, 8'h00, 8'h70, 8'h1C, 8'h88, 8'h22, 8'h08, 8'h21};
mem2[17] = {8'h08, 8'h21, 8'h88, 8'h22, 8'h70, 8'h1C, 8'h00, 8'h00};
//9
mem2[18] = {8'h00, 8'h00, 8'hE0, 8'h00, 8'h10, 8'h31, 8'h08, 8'h22};
mem2[19] = {8'h08, 8'h22, 8'h10, 8'h11, 8'hE0, 8'h0F, 8'h00, 8'h00};
//.
mem2[20] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h30};
mem2[21] = {8'h00, 8'h30, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
//space
mem2[22] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
mem2[23] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00, 8'h00};
end
endmodule
5、串口通讯模块
串口通讯借鉴STEP FPGA开源社区uart串口模块的相关代码,添加发送温度数据和接收音符信号相关的逻辑,当接收音符数据完成后,产生一个系统时钟周期的rx_done脉冲信号。本项目中音符的接收和播放是分开的,即先接收音符,并计算音符的长度,当接收完成后送入蜂鸣器控制模块进行播放。
module uart_top #
(
parameter BPS_PARA = 1250 //当使用12MHz时钟时波特率参数选择1250对应9600的波特率
)
(
input clk, //系统时钟
input rst_n, //系统复位,低有效
input flag,
input [11:0] tamp_data,
input rs232_rx, //FPGA中UART接收端,分配给UART模块中的发送端TXD
output rs232_tx, //FPGA中UART发送端,分配给UART模块中的接收端RXD
output reg [9:0] data_length,
output reg [959:0] data_buffer,
output reg rx_done
);
//************flag整点后发送温度数据*******************
wire bps_en_tx,bps_clk_tx;
wire bps_en_rx,bps_clk_rx;
wire [7:0] rx_data;
reg [1:0] tx_data_length;
wire tx_done;
//bps_en_tx_r
reg bps_en_tx0,bps_en_tx1,bps_en_tx2;
//多级延时锁存去除亚稳态
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
bps_en_tx0 <= 1'b0;
bps_en_tx1 <= 1'b0;
bps_en_tx2 <= 1'b0;
end else begin
bps_en_tx0 <= bps_en_tx;
bps_en_tx1 <= bps_en_tx0;
bps_en_tx2 <= bps_en_tx1;
end
end
//tx_done发送完成信号
assign tx_done = bps_en_tx2 & bps_en_tx1 & (~bps_en_tx0) & (~bps_en_tx);
//tx_data_length
always @ ( posedge clk or negedge rst_n )
if ( ~rst_n )
tx_data_length <= 2'd0;
else if ( flag )
tx_data_length <= 2'd1;
else if ( tx_done )
tx_data_length <= tx_data_length - 1'b1;
//tx_data
reg [7:0] tx_data;
always @ ( posedge clk or negedge rst_n )
if ( ~rst_n )
tx_data <= 8'd0;
else if ( flag )
tx_data <= tamp_data[11:4];
else if ( tx_done )
tx_data <= {4'd0,tamp_data[3:0]};
//tx_en
wire tx_en;
assign tx_en = ( flag || (tx_done&&tx_data_length)) ? 1'b1:1'b0;
//tx_en_r
reg tx_en_r;
always @ ( posedge clk or negedge rst_n )
if ( ~rst_n )
tx_en_r <= 1'b0;
else
tx_en_r <= tx_en;
//********接收音符,接收完成后产生rx_done脉冲**********
wire ready;
always @ ( posedge clk or negedge rst_n )
if ( ~rst_n )
data_length <= 10'd0;
else if ( rx_done )
data_length <= 10'd0;
else if ( ready && rx_data==8'h1f) //结束标志
data_length <= data_length + 1'b1;
else if ( ready )
data_length <= data_length + 1'b1;
always @ ( posedge clk or negedge rst_n )
if ( ~rst_n )
data_buffer <= 960'd0;
else if ( rx_done )
data_buffer <= 960'd0;
else if ( ready && rx_data==8'h1f)
data_buffer <= data_buffer;
else if ( ready ) begin
data_buffer[7:0] <= rx_data;
data_buffer[959:8] <= data_buffer[951:0];
end
always @ (posedge clk or negedge rst_n)
if(!rst_n)
rx_done <= 1'b0;
else if (rx_done)
rx_done <= 1'b0;
else if ( ready && rx_data==8'h1f)
rx_done <= 1'b1;
/////////////////////////////////UART接收功能模块例化////////////////////////////////////
//UART接收波特率时钟控制模块 例化
baud_generator #
(
.BPS_PARA (BPS_PARA )
)
baud_rx_ins
(
.clk (clk ), //系统时钟
.rst_n (rst_n ), //系统复位,低有效
.bps_en (bps_en_rx ), //接收时钟使能
.bps_clk (bps_clk_rx ) //接收时钟输出
);
//UART接收数据模块 例化
uart_rx uart_rx_ins
(
.clk (clk ), //系统时钟
.rst_n (rst_n ), //系统复位,低有效
.bps_en (bps_en_rx ), //接收时钟使能
.bps_clk (bps_clk_rx ), //接收时钟输入
.rs232_rx (rs232_rx ), //UART接收输入
.rx_data (rx_data ), //接收到的数据
.ready (ready )
);
/////////////////////////////////UART发送功能模块例化////////////////////////////////////
//UART发送波特率时钟控制模块 例化
baud_generator #
(
.BPS_PARA (BPS_PARA )
)
baud_tx_ins
(
.clk (clk ), //系统时钟
.rst_n (rst_n ), //系统复位,低有效
.bps_en (bps_en_tx ), //发送时钟使能
.bps_clk (bps_clk_tx ) //发送时钟输出
);
//UART发送数据模块 例化
uart_tx uart_tx_ins
(
.clk (clk ), //系统时钟
.rst_n (rst_n ), //系统复位,低有效
.bps_en (bps_en_tx ), //发送时钟使能
.bps_clk (bps_clk_tx ), //发送时钟输入
.tx_en (tx_en_r ),
.tx_data (tx_data ), //需要发出的数据
.rs232_tx (rs232_tx ) //UART发送输出
);
endmodule
6、蜂鸣器控制模块
蜂鸣器控制模块接收串口收到的音符数据并控制蜂鸣器进行播放,蜂鸣器模块使用STEP FPGA开源社区蜂鸣器模块的相关源码。
module beeper_control(
input clk , //12M
input rst_n ,
input en ,
input rx_done , //接收数据完成标志,一个时钟脉冲
input [9:0] data_length ,
input [959:0] data_buffer ,
output piano_out ,
output play_done
);
reg [23:0] cnt;
always@(posedge clk or negedge rst_n)
if(~rst_n)
cnt <= 24'd0;
else if(cnt == 24'd3999999)
cnt <= 24'd0;
else
cnt <= cnt + 1'b1;
reg [9:0] data_length_reg;
always@(posedge clk or negedge rst_n)
if(~rst_n)
data_length_reg <= 10'd0;
else if ( rx_done )
data_length_reg <= data_length;
else if ( data_length_reg && cnt == 24'd3999999)
data_length_reg <= data_length_reg - 1'b1;
reg [959:0] data_buffer_reg;
always@(posedge clk or negedge rst_n)
if(~rst_n)
data_buffer_reg <= 960'd0;
else if ( rx_done )
data_buffer_reg <= data_buffer;
else if ( data_length_reg && cnt == 24'd3999999) begin
data_buffer_reg <= data_buffer_reg>>8;
end
wire [4:0] tone;
assign tone = data_buffer_reg[4:0];
wire play_en;
assign play_en = (data_length_reg == 10'd0)?1'b0:1'b1;
Beeper Beeper_inst(
.clk (clk ), //系统时钟
.rst_n (rst_n ), //系统复位,低有效
.tone_en (play_en ), //蜂鸣器使能信号
.tone (tone ), //蜂鸣器音节控制
.piano_out (piano_out ) //蜂鸣器控制输出
);
assign play_done = ( data_length_reg == 10'd1 && en)?1'b1:1'b0;
endmodule
7、串口解析上位机
串口解析使用python的serial库,当接收到两个字节的温度信息后,通过串口发送音符数据到fpga,音符数据以0x1f为结束标志。
import serial
ser = serial.Serial('com3',9600)
if ser.is_open:
print("打开串口成功!")
while True:
print("等待接收温度数据...")
temperature = str(ser.read(size=2).hex())
print("当前温度为:", temperature[0:2], ".", temperature[-1], "℃")
print("发送音符...")
ser.write("\x00\x08\x0c\x09".encode("utf-8"))
ser.write("\x00\x08\x0c\x09".encode("utf-8"))
ser.write("\x00\x08\x0a\x0b\x0c\x0d\x0c".encode("utf-8"))
ser.write("\x00\x08\x0a\x0b\x0c\x0d\x0c".encode("utf-8"))
ser.write("\x00\x0c\x0b\x0a".encode("utf-8"))
ser.write("\x00\x0c\x0b\x0a".encode("utf-8"))
ser.write("\x00\x08\x0a\x09\x08".encode("utf-8"))
ser.write("\x00\x08\x0a\x09\x08".encode("utf-8"))
ser.write("\x1f".encode("utf-8"))
else:
print("打开串口失败!")
四、项目总结
本项目基于小脚丫FPGA实现定时时钟,测温,串口通讯,OLED显示等功能。
在该项目过程中,我是先实现了各个模块,然后将各个模块进行整合。由于各种开源代码很丰富,所以各个模块的实现并不是很难,但是在整合的过程中遇到了一些小麻烦,因此,如果先做好整体的规划然后再分模块实现可能会更顺利一些。
最后,感谢电子森林提供的此次学习和交流的机会!