寒假一起练项目四
基于小脚丫FPGA综合训练板实现了定时,测温,通讯,显示等功能
标签
FPGA
lyxin7627
更新2021-02-26
1050

一、项目要求

  1. 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;

  2. 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;

  3. 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;

  4. PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;

  5. 音频文件播放完毕,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显示等功能。

   在该项目过程中,我是先实现了各个模块,然后将各个模块进行整合。由于各种开源代码很丰富,所以各个模块的实现并不是很难,但是在整合的过程中遇到了一些小麻烦,因此,如果先做好整体的规划然后再分模块实现可能会更顺利一些。

   最后,感谢电子森林提供的此次学习和交流的机会!

 

 
附件下载
system.zip
工程文件,上位机程序,取模工具
团队介绍
北理工-信息与电子学院
团队成员
刘泳鑫
来自北理工的大三学生
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号