2021寒假在家一起练4——黄熠昕
实现小脚丫FPGA的报警、定时、测温、串口通信功能,并通过上位机调试音频播放,通过oled显示调试信息
标签
FPGA
小虎哥
更新2021-02-27
1064

项目背景:

      微控制器作为目前嵌入式系统设计的主力军在各行各业得到了广泛的应用,但随着物联网、智能硬件、VR等一系列新兴概念产品的问世,市场产品有明显的多样化趋势,功能更为丰富,因而这也给工程师在嵌入式产品设计的时候提出了新的挑战。举个例子,虽然如今的微控制器产品系列细分化更为彻底,对每个层次的领域都有相关的MCU产品支持,但这也意味着器件的选型和资源评估需要更加谨慎,传统MCU开发平台的选型就是一个难题,对于讲究适用就够的原则,选性能功能强大丰富的微控制器浪费资源、浪费成本,选低端的入门级微控制器可能又会出现功能不支持,IO口不足等问题,另外不同平台的有不同的开发环境流程等要熟悉,这也大大延长了工程师在项目开发中的时间;而在另一个对于以前来说相对小众的FPGA领域中,随着工艺的进步和EDA设计工具的不断发展,FPGA的集成度越来越高,而对应的功耗和成本却在不断降低,FPGA的门槛(学习成本和价格成本)也相应地越来越低,因而也使得其被广泛应用到各种领域中去,越来越多的嵌入式系统设计直接采FPGA设计,或者使用FPGA产品作为系统功能的拓展,总之,目前的嵌入式系统设计中越来越多的出现FPGA的身影。

项目描述及需要实现的功能:

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

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

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

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

  5. 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息

项目实现的思路:

FmhsNEu4kRBVcuYe0dr20eFGlnWM

引脚定义:

FidPm8p5zjQ48ADw7E_Lr1JE7cN0

FqiEc_OFXqlt5eKOYhaiDmZBFJyR

资源使用率:

FmDicxecnTuDRXdgD9VjEzJTxhUD

综合好之后的电路图:

Fl8mG_ppESe8w8FDU9qbQN7f2voQ

操作与按键功能介绍:

  1. 拨码开关1:调节时间的界面切换
  2. 拨码开关2:控制蜂鸣器,使其使能与不使能
  3. 拨码开关4:复位
  4. 按键1:将设定好的时间赋值给时钟
  5. 按键2:时/分/秒减少
  6. 按键3:时/分/秒增加
  7. 按键4:切换对时/分/秒的调节
  8. LED1:亮即为正在调节时间
  9. LED2:亮即为蜂鸣器处于使能状态
  10. LED7:亮即为FPGA在向上位机发送信息
  11. LED8:亮即为FPGA处于复位状态

FoqyqaxoBsLezhzrofgth-CljhmH

核心代码介绍:

温度模块:

      温度模块我采用的是硬核学堂提供的温度采集模块,直接将其例化后加入到top文件中使用,具体代码就不在这展示了,稍后我会打包放在附件里面,说一下实现的思路。DS18B20采集到数据后,返回到tempture中进行处理,得到一个十六进制的数,然后将其转换位8421BCD码,放到一个数组中,再将其按照十、个、小数的顺序一位一位输出到oled屏上。这个实现的难度相对较为简单,只要直接抄代码就行。

时钟模块:

      本来我参考的是彧老哥的代码,想用时、分、秒一个一个的写,然后再通过除十求余法把两位数一位一位的分开,再一位一位的显示在oled上来实现。这样写的好处是代码很简单,占用的资源也少,但是我遇到了一个致命的问题,就是我不能通过除十求余法把两位数给分开为两个数字,我尝试了三天,用尽了各种办法来分开显示,还是没有效果,遂放弃该方案。不过我仍然觉得如果这种方案能实现的话,一定是最好的方案。所以,经过折衷考虑,加上向各位同学咨询,确定了一种避免分位数的方法,也就是一位一位的来加。秒的各位满10进1,秒的十位满6进1,分的个位满10进一,分的十位满6进1……以此类推,最终完成了我的时钟模块,而且可以实现精准计时以及显示了。

module clock(
    input Clk,
    input Rst_n,
    input key1_in,
    
    input [3:0]timer_sec_l,
    input [3:0]timer_sec_h,
    input [3:0]timer_min_l,
    input [3:0]timer_min_h,
    input [3:0]timer_hour_l,
    input [3:0]timer_hour_h,
    
    output reg[3:0]sec_l,//输出量
    output reg[3:0]sec_h,
    output reg[3:0]min_l,
    output reg[3:0]min_h,
    output reg[3:0]hour_l,
    output reg[3:0]hour_h,
    
    output timeout
);
    reg Clk_1s;
    reg [23:0]cnt_1s;
    reg key1;
    
    assign timeout = (min_h == 4'd0 && min_l == 4'd0 && sec_h == 4'd0 && sec_l == 4'd0);
   
    always @(posedge Clk, negedge Rst_n)//1HZ时钟
        if (!Rst_n)begin
            Clk_1s <= 1'd0;
            cnt_1s <= 24'd0;
        end
        else if (cnt_1s == 24'd12_000_000 - 1) begin
            Clk_1s <= 1'd1;
            cnt_1s <= 24'd0;
        end
        else begin
            Clk_1s <= 1'd0;
            cnt_1s <= cnt_1s + 1'b1;
        end
        
    always @(posedge key1_in or posedge Clk_1s or negedge Rst_n)//更新设定时间
        if (!Rst_n)
            key1 <= 1'b0;
        else if (key1_in)
            key1 <= 1'b1;
        else
            key1 <= 1'b0;
        
    always @(posedge Clk_1s, negedge Rst_n)//秒
        if (!Rst_n)
            sec_l <= 4'd0;
        else if (key1)
            sec_l <= timer_sec_l;
        else if (sec_l == 4'd9)
            sec_l <= 4'd0;
        else
            sec_l <= sec_l + 1'b1;
            
    always @(posedge Clk_1s, negedge Rst_n)
        if (!Rst_n)
            sec_h <= 4'd5;
        else if (key1)
            sec_h <= timer_sec_h;
        else if (sec_h == 4'd5 && sec_l == 4'd9)
            sec_h <= 4'd0;
        else if (sec_l == 4'd9)
            sec_h <= sec_h + 1'b1;
        else
            sec_h <= sec_h;

    always @(posedge Clk_1s, negedge Rst_n)//分
        if (!Rst_n)
            min_l <= 4'd9;  
        else if (key1)
            min_l <= timer_min_l;      
        else if (sec_h == 4'd5 && sec_l == 4'd9)
            if (min_l == 4'd9)
                min_l <= 4'd0;
            else
                min_l <= min_l + 1'b1;
        else
            min_l <= min_l;
            
    always @(posedge Clk_1s, negedge Rst_n)
        if (!Rst_n)
            min_h <= 4'd5;    
        else if (key1)
            min_h <= timer_min_h;    
        else if (min_h == 4'd5 && min_l == 4'd9 && sec_h == 4'd5 && sec_l == 4'd9)
            min_h <= 4'd0;
        else if (min_l == 4'd9 && sec_h == 4'd5 && sec_l == 4'd9)
            min_h <= min_h + 1'b1;
        else
            min_h <= min_h;
            
    always @(posedge Clk_1s, negedge Rst_n)//时
        if (!Rst_n)
            hour_l <= 4'd3;
        else if (key1)
            hour_l <= timer_hour_l;
        else if (min_h == 4'd5 && min_l == 4'd9 && sec_h == 4'd5 && sec_l == 4'd9)
            if (hour_l == 4'd9 || (hour_h == 4'd2 && hour_l == 4'd3 && min_h == 4'd5 && min_l == 4'd9 && sec_h == 4'd5 && sec_l == 4'd9))
                hour_l <= 4'd0;
            else
                hour_l <= hour_l + 1'b1;
        else
            hour_l <= hour_l;
            
    always @(posedge Clk_1s, negedge Rst_n)
        if (!Rst_n)
            hour_h <= 4'd2; 
        else if (key1)
            hour_h <= timer_hour_h;       
        else if (hour_h == 4'd2 && hour_l == 4'd3 && min_h == 4'd5 && min_l == 4'd9 && sec_h == 4'd5 && sec_l == 4'd9)
            hour_h <= 4'd0;
        else if (hour_l == 4'd9 && min_h == 4'd5 && min_l == 4'd9 && sec_h == 4'd5 && sec_l == 4'd9)
            hour_h <= hour_h + 1'b1;
        else
            hour_h <= hour_h;
            

endmodule

 

下面是时钟调节的控制模块,因为之前用51以及DS1302做过一个时钟,精准的控制了时钟每一位的加减,所以将这个思想移植到这里来,也能很好的实现我们所需要的功能。

module timer(
    input Clk,
    input Rst_n,
    input key2_in,//minus
    input key3_in,//add
    input key4_in,//shift

    output [3:0]timer_sec_l,
    output [3:0]timer_sec_h,
    output [3:0]timer_min_l,
    output [3:0]timer_min_h,
    output [3:0]timer_hour_l,
    output [3:0]timer_hour_h
    
);    

    reg [3:0]timer[5:0];
    integer i;
    
    assign timer_hour_h = timer[0];
    assign timer_hour_l = timer[1];
    assign timer_min_h = timer[2];
    assign timer_min_l = timer[3];
    assign timer_sec_h = timer[4];
    assign timer_sec_l = timer[5];
    
    initial begin
        timer[0] = 4'd2;
        timer[1] = 4'd3;
        timer[2] = 4'd5;
        timer[3] = 4'd9;
        timer[4] = 4'd5;
        timer[5] = 4'd0;
    end

    always @(posedge Clk or negedge Rst_n)
        if (!Rst_n)
            i <= 0;
        else if (key4_in)
            if (i == 5)
                i <= 0;
            else 
                i <= i + 1;
        else
            i <= i;
        
    always @(posedge Clk)
        if (key3_in)
            case (i)
                0: 
                if (timer[i] == 4'd2)
                    timer[i] <= 4'd0;
                else
                    timer[i] <= timer[i] + 1'b1; 
                1:
                if (timer[0] == 4'd2)begin
                  if (timer[i] == 4'd4)
                        timer[i] <= 4'd0;
                  else
                    timer[i] <= timer[i] + 1'b1; 
                end
                else begin
                  if (timer[i] == 4'd9)
                        timer[i] <= 4'd0;
                     else
                         timer[i] <= timer[i] + 1'b1; 
                end
                2:
                if (timer[i] == 4'd5)
                    timer[i] <= 4'd0;
                else
                    timer[i] <= timer[i] + 1'b1; 
                4:
                if (timer[i] == 4'd5)
                    timer[i] <= 4'd0;
                else
                    timer[i] <= timer[i] + 1'b1; 
                default: 
                    if (timer[i] == 4'd9)
                        timer[i] <= 4'd0;
                     else
                         timer[i] <= timer[i] + 1'b1; 
            endcase   
        else if (key2_in)
            case (i)
                0: 
                if (timer[i] == 4'd0)
                    timer[i] <= 4'd2;
                else
                    timer[i] <= timer[i] - 1'b1; 
                1:
                if (timer[0] == 4'd2)begin
                  if (timer[i] == 4'd0)
                        timer[i] <= 4'd4;
                  else
                    timer[i] <= timer[i] - 1'b1; 
                end
                else begin
                  if (timer[i] == 4'd0)
                        timer[i] <= 4'd9;
                     else
                         timer[i] <= timer[i] - 1'b1; 
                end
                2:
                if (timer[i] == 4'd0)
                    timer[i] <= 4'd5;
                else
                    timer[i] <= timer[i] - 1'b1; 
                4:
                if (timer[i] == 4'd0)
                    timer[i] <= 4'd5;
                else
                    timer[i] <= timer[i] - 1'b1; 
                default: 
                    if (timer[i] == 4'd0)
                        timer[i] <= 4'd9;
                     else
                         timer[i] <= timer[i] - 1'b1; 
                endcase
        else
            timer[i] <= timer[i];


endmodule

 

按键处理:

      不得不说,当我看见杨彧老哥通过FPGA特有的时钟边沿信号来对按键采样、消抖的时候,我不禁大为惊叹,这个想法避免了大段的按键消抖的处理过程,但是后来也出现了一个问题,就是按键检测不灵敏,有的时候按下去没反应,于是我又去到处找按键处理的程序,终于在曹坤的代码中找到了答案,学谁的代码不是学的,在此表示感谢,他的代码是通过状态机的方式来实现消抖和按键采集的,我试了一下,很不错,很灵敏,再次表示感谢,我贴在下面。

module key(
    Clk,
    Rst_n,
    key_in,
    key_flag,
    key_state
    );

input Clk;
input Rst_n;
input key_in;

output reg key_flag;//按键是否处于触发沿
output reg key_state;//按键当前电平



reg key_in_s1,key_in_s2;
always@(posedge Clk, negedge Rst_n)//对外部输入信号进行处理
    if(!Rst_n)begin
        key_in_s1 <= 1'b0;
        key_in_s2 <= 1'b0;
    end
    else begin
        key_in_s1 <= key_in;
        key_in_s2 <= key_in_s1;
    end
    
reg key_tmp1,key_tmp2;
wire pedge,nedge;
always@(posedge Clk,negedge Rst_n)//D触发器存储两个相邻时钟上升沿时的外部输入信号
    if(!Rst_n)begin
        key_tmp1 <= 1'b0;
        key_tmp2 <= 1'b0;
    end
    else begin
        key_tmp1 <= key_in_s2;
        key_tmp2 <= key_tmp1;
    end
    
assign pedge = (!key_tmp1) & key_tmp2;//跳变沿
assign nedge = key_tmp1 & (!key_tmp2);


reg[19:0]cnt;
reg en_cnt;
reg cnt_full;
always@(posedge Clk,negedge Rst_n)//计数器
    if (!Rst_n)
        cnt <= 20'b0;
    else if (en_cnt)
        cnt <= cnt + 1'b1;
    else
        cnt <= 20'b0;
        
always@(posedge Clk,negedge Rst_n)
    if (!Rst_n)
        cnt_full <= 1'b0;
    else if (cnt == 20'd999_999)
        cnt_full <= 1'b1;
    else
        cnt_full <= 1'b0;


localparam
    IDEL    =   4'b0001,
    FILTER0 =   4'b0010,
    DOWN    =   4'b0100,
    FILTER1 =   4'b1000;
    
reg [3:0]state;

always @(posedge Clk,negedge Rst_n)//按键状态机
    if (!Rst_n)begin
        state = IDEL;
        key_flag <= 1'b0;//防止发生复位时其他参数未复位
        key_state <= 1'b1;
        en_cnt = 1'b1;
    end
    else begin
        case(state)
            IDEL: begin
                key_flag <= 1'b0;
                if (nedge)begin
                    state <= FILTER0;
                    en_cnt <= 1'b1;
                end
                else
                    state <= IDEL;
            end
            FILTER0: begin
                if (cnt_full)begin
                    key_flag <= 1'b1;
                    key_state <= 1'b0;
                    en_cnt <=1'b0;
                    state <= DOWN;
                end
                else if (pedge)begin
                    state <= IDEL;
                    en_cnt <= 1'b0;
                end
                else
                    state <= FILTER0;
            end
            DOWN: begin
                key_flag = 1'b0;
                if (pedge)begin
                    state <= FILTER1;
                    en_cnt <= 1'b1;
                end
                else
                    state = DOWN;
            end
            FILTER1: begin
                if (cnt_full) begin
                    key_flag <= 1'b1;
                    key_state <= 1'b1;
                    state <= IDEL;
                    en_cnt <= 1'b0;
                end
                else if(nedge)begin
                    en_cnt <= 1'b0;
                    state = DOWN;
                end                    
                else
                    state <= FILTER1;
            end        
            default: begin
                state <= IDEL;
                en_cnt <= 1'b0;
                key_flag = 1'b0;
                key_state = 1'b1;
            end
        endcase
    end

endmodule

 

OLED显示模块:

      有一点不得不说,硬禾的有些例程做的确实很好,全部都放在网站上,有需要的自己都可以找到,锻炼大家的动手搜资料的能力。我这个显示模块就是基于硬禾的OLED显示例程,这个网页里不仅有OLED的例程,还有SPI通信总线控制OLED的一些基本的原理方法,讲的很清楚了,这里就不过多的赘述了。当然啦,我也要说一下我改动的地方,其实也很简单。一个是修改时间的页面重新写了一个放在了下面,很简单,只要重新编辑一个页面,然后把那些可以自己修改的变量放上就行了。还有一个很重要的功能实现我也放在了这个OLED模块里面,就是UART传数据给小脚丫的时候,小脚丫OLED上的信息不更新,当小脚丫接收到上位机传来的信息时,会将此刻的时间和温度信息保存在past这个变量中,然后在fresh_flag这个标志位的作用下跳转到只显示这个最后保存到past中的信息,以实现OLED信息的停止更新,代码我放在下面了。

module OLED_SPI
(
	input				clk,		//12MHz系统时钟
	input				rst_n,		//系统复位,低有效
    input               sw_1_oledmode,
	
	input      [7:0]sec_l,
    input      [7:0]sec_h,
    input      [7:0]min_l,
    input      [7:0]min_h,
    input      [7:0]hour_l,   
    input      [7:0]hour_h,
    
    input      [7:0]temp_h,
    input      [7:0]temp_l,
    input      [7:0]temp_s,
    
    input      [7:0]timer_sec_l,
    input      [7:0]timer_sec_h,
    input      [7:0]timer_min_l,
    input      [7:0]timer_min_h,
    input      [7:0]timer_hour_l,
    input      [7:0]timer_hour_h,

	input 		fresh_flag,//暂停标志位
	
	output	reg			oled_csn,	//OLCD液晶屏使能
	output	reg			oled_rst,	//OLCD液晶屏复位
	output	reg			oled_dcn,	//OLCD数据指令控制
	output	reg			oled_clk,	//OLCD时钟信号
	output	reg			oled_dat	//OLCD数据信号
);
	
	localparam INIT_DEPTH = 16'd25; //LCD初始化的命令的数量
	localparam IDLE = 6'h1, MAIN = 6'h2, INIT = 6'h4, SCAN = 6'h8, WRITE = 6'h10, DELAY = 6'h20;
	localparam HIGH	= 1'b1, LOW = 1'b0;
	localparam DATA	= 1'b1, CMD = 1'b0;
	
	reg [7:0] cmd [24:0];
	reg [39:0] mem [122:0];
	reg	[7:0]	y_p, x_ph, x_pl;
	reg	[(8*21-1):0] char;
	reg	[7:0]	num, char_reg;				//
	reg	[4:0]	cnt_main, cnt_init, cnt_scan, cnt_write;
	reg	[15:0]	num_delay, cnt_delay, cnt;
	reg	[5:0] 	state, state_back;
	
	reg [7:0] sec_l_past;
	reg [7:0] sec_h_past;
	reg [7:0] min_l_past;
	reg [7:0] min_h_past;
	reg [7:0] hour_l_past;
	reg [7:0] hour_h_past;
	reg [7:0] temp_h_past;
	reg [7:0] temp_l_past;
	reg [7:0] temp_s_past;
	always @(negedge fresh_flag) begin
		sec_l_past  <= sec_l; 
		sec_h_past  <= sec_h;
		min_l_past  <= min_l;
		min_h_past  <= min_h;
		hour_l_past <= hour_l;
		hour_h_past <= hour_h;
		temp_h_past <= temp_h;
		temp_l_past <= temp_l;
		temp_s_past <= temp_s;
	end

	always@(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
			y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
			num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
			num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
			oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
			state <= IDLE; state_back <= IDLE;
		end else begin
			case(state)
				IDLE:begin
						cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 1'b0; cnt_write <= 1'b0;
						y_p <= 1'b0; x_ph <= 1'b0; x_pl <= 1'b0;
						num <= 1'b0; char <= 1'b0; char_reg <= 1'b0;
						num_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
						oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
						state <= MAIN; state_back <= MAIN;
					end
				MAIN:begin
					if(fresh_flag)begin
                        if (sw_1_oledmode) begin//时间温度显示模式
                            if(cnt_main >= 5'd4) cnt_main <= 5'd1;
                            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 <= "   NICE CLOCK   ";state <= SCAN; end
                                5'd2:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "      TIME      ";state <= SCAN; end
                                5'd3:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= {"    ",hour_h,hour_l,":",min_h,min_l,":",sec_h,sec_l,"    "};state <= SCAN; end
                                5'd4:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= {"  ","TEMP:",temp_h,temp_l,".",temp_s," C","   "};state <= SCAN; end

                                default: state <= IDLE;
                            endcase
                        end
                        
                        else begin//时间设置模式
                            if(cnt_main >= 5'd4) cnt_main <= 5'd1;
                            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 SETTING  ";state <= SCAN; end
                                5'd2:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "   HOU MIN SEC  ";state <= SCAN; end
                                5'd3:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= {"    ",timer_hour_h,timer_hour_l,":",timer_min_h,timer_min_l,":",timer_sec_h,timer_sec_l,"    "};state <= SCAN; end
                                5'd4:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "PRESS K1 TO SET ";state <= SCAN; end

                                default: state <= IDLE;
                            endcase
                        end 
					end
					else begin
						 if(cnt_main >= 5'd4) cnt_main <= 5'd1;
                            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 <= "   NICE CLOCK   ";state <= SCAN; end
                                5'd2:	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "      TIME      ";state <= SCAN; end
                                5'd3:	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= {"    ",hour_h_past,hour_l_past,":",min_h_past,min_l_past,":",sec_h_past,sec_l_past,"    "};state <= SCAN; end
                                5'd4:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= {"  ","TEMP:",temp_h_past,temp_l_past,".",temp_s_past," C","   "};state <= SCAN; end

                                default: state <= IDLE;
							endcase
						end
					end
				INIT:begin	//初始化状态
						case(cnt_init)
							5'd0:	begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end	//复位有效
							5'd1:	begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时大于3us
							5'd2:	begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end	//复位恢复
							5'd3:	begin num_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	//延时大于220us
							5'd4:	begin 
										if(cnt>=INIT_DEPTH) begin	//当25条指令及数据发出后,配置完成
											cnt <= 1'b0;
											cnt_init <= cnt_init + 1'b1;
										end else begin	
											cnt <= cnt + 1'b1; num_delay <= 16'd5;
											oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
										end
									end
							5'd5:	begin cnt_init <= 1'b0; state <= MAIN; end	//初始化完成,返回MAIN状态
							default: state <= IDLE;
						endcase
					end
				SCAN:begin	//刷屏状态,从RAM中读取数据刷屏
						if(cnt_scan == 5'd11) begin
							if(num) cnt_scan <= 5'd3;
							else cnt_scan <= cnt_scan + 1'b1;
						end else if(cnt_scan == 5'd12) cnt_scan <= 1'b0;
						else cnt_scan <= cnt_scan + 1'b1;
						case(cnt_scan)
							5'd 0:	begin oled_dcn <= CMD; char_reg <= y_p; state <= WRITE; state_back <= SCAN; end		//定位列页地址
							5'd 1:	begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end	//定位行地址低位
							5'd 2:	begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end	//定位行地址高位
							
							5'd 3:	begin num <= num - 1'b1;end
							5'd 4:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//将5*8点阵编程8*8
							5'd 5:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//将5*8点阵编程8*8
							5'd 6:	begin oled_dcn <= DATA; char_reg <= 8'h00; state <= WRITE; state_back <= SCAN; end	//将5*8点阵编程8*8
							5'd 7:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][39:32]; state <= WRITE; state_back <= SCAN; end
							5'd 8:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][31:24]; state <= WRITE; state_back <= SCAN; end
							5'd 9:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][23:16]; state <= WRITE; state_back <= SCAN; end
							5'd10:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][15: 8]; state <= WRITE; state_back <= SCAN; end
							5'd11:	begin oled_dcn <= DATA; char_reg <= mem[char[(num*8)+:8]][ 7: 0]; state <= WRITE; state_back <= SCAN; end
							5'd12:	begin state <= MAIN; end
							default: state <= IDLE;
						endcase
					end
				WRITE:begin	//WRITE状态,将数据按照SPI时序发送给屏幕
						if(cnt_write >= 5'd17) cnt_write <= 1'b0;
						else cnt_write <= cnt_write + 1'b1;
						case(cnt_write)
							5'd 0:	begin oled_csn <= LOW; end	//9位数据最高位为命令数据控制位
							5'd 1:	begin oled_clk <= LOW; oled_dat <= char_reg[7]; end	//先发高位数据
							5'd 2:	begin oled_clk <= HIGH; end
							5'd 3:	begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
							5'd 4:	begin oled_clk <= HIGH; end
							5'd 5:	begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
							5'd 6:	begin oled_clk <= HIGH; end
							5'd 7:	begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
							5'd 8:	begin oled_clk <= HIGH; end
							5'd 9:	begin oled_clk <= LOW; oled_dat <= char_reg[3]; end
							5'd10:	begin oled_clk <= HIGH; end
							5'd11:	begin oled_clk <= LOW; oled_dat <= char_reg[2]; end
							5'd12:	begin oled_clk <= HIGH; end
							5'd13:	begin oled_clk <= LOW; oled_dat <= char_reg[1]; end
							5'd14:	begin oled_clk <= HIGH; end
							5'd15:	begin oled_clk <= LOW; oled_dat <= char_reg[0]; end	//后发低位数据
							5'd16:	begin oled_clk <= HIGH; end
							5'd17:	begin oled_csn <= HIGH; state <= DELAY; end	//
							default: state <= IDLE;
						endcase
					end
				DELAY:begin	//延时状态
						if(cnt_delay >= num_delay) begin
							cnt_delay <= 16'd0; state <= state_back; 
						end else cnt_delay <= cnt_delay + 1'b1;
					end
				default:state <= IDLE;
			endcase
		end
	end
	
	//OLED配置指令数据
	always@(posedge rst_n)
		begin
			cmd[ 0] = {8'hae}; 
			cmd[ 1] = {8'h00}; 
			cmd[ 2] = {8'h10}; 
			cmd[ 3] = {8'h00}; 
			cmd[ 4] = {8'hb0}; 
			cmd[ 5] = {8'h81}; 
			cmd[ 6] = {8'hff}; 
			cmd[ 7] = {8'ha1}; 
			cmd[ 8] = {8'ha6}; 
			cmd[ 9] = {8'ha8}; 
			cmd[10] = {8'h1f}; 
			cmd[11] = {8'hc8};
			cmd[12] = {8'hd3};
			cmd[13] = {8'h00};
			cmd[14] = {8'hd5};
			cmd[15] = {8'h80};
			cmd[16] = {8'hd9};
			cmd[17] = {8'h1f};
			cmd[18] = {8'hda};
			cmd[19] = {8'h00};
			cmd[20] = {8'hdb};
			cmd[21] = {8'h40};
			cmd[22] = {8'h8d};
			cmd[23] = {8'h14};
			cmd[24] = {8'haf};
		end 
		
	//5*8点阵字库数据
	always@(posedge rst_n)
		begin
			mem[  0] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E};   // 48  0
			mem[  1] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00};   // 49  1
			mem[  2] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46};   // 50  2
			mem[  3] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31};   // 51  3
			mem[  4] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10};   // 52  4
			mem[  5] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39};   // 53  5
			mem[  6] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30};   // 54  6
			mem[  7] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03};   // 55  7
			mem[  8] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36};   // 56  8
			mem[  9] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E};   // 57  9			
			mem[ 10] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C};   // 65  A			
			mem[ 11] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36};   // 66  B			
			mem[ 12] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22};   // 67  C			
			mem[ 13] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C};   // 68  D			
			mem[ 14] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41};   // 69  E			
			mem[ 15] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01};   // 70  F

			mem[ 32] = {8'h00, 8'h00, 8'h00, 8'h00, 8'h00};   // 32  sp 
			mem[ 33] = {8'h00, 8'h00, 8'h2f, 8'h00, 8'h00};   // 33  !  
			mem[ 34] = {8'h00, 8'h07, 8'h00, 8'h07, 8'h00};   // 34  
			mem[ 35] = {8'h14, 8'h7f, 8'h14, 8'h7f, 8'h14};   // 35  #
			mem[ 36] = {8'h24, 8'h2a, 8'h7f, 8'h2a, 8'h12};   // 36  $
			mem[ 37] = {8'h62, 8'h64, 8'h08, 8'h13, 8'h23};   // 37  %
			mem[ 38] = {8'h36, 8'h49, 8'h55, 8'h22, 8'h50};   // 38  &
			mem[ 39] = {8'h00, 8'h05, 8'h03, 8'h00, 8'h00};   // 39  '
			mem[ 40] = {8'h00, 8'h1c, 8'h22, 8'h41, 8'h00};   // 40  (
			mem[ 41] = {8'h00, 8'h41, 8'h22, 8'h1c, 8'h00};   // 41  )
			mem[ 42] = {8'h14, 8'h08, 8'h3E, 8'h08, 8'h14};   // 42  *
			mem[ 43] = {8'h08, 8'h08, 8'h3E, 8'h08, 8'h08};   // 43  +
			mem[ 44] = {8'h00, 8'h00, 8'hA0, 8'h60, 8'h00};   // 44  ,
			mem[ 45] = {8'h08, 8'h08, 8'h08, 8'h08, 8'h08};   // 45  -
			mem[ 46] = {8'h00, 8'h60, 8'h60, 8'h00, 8'h00};   // 46  .
			mem[ 47] = {8'h20, 8'h10, 8'h08, 8'h04, 8'h02};   // 47  /
			mem[ 48] = {8'h3E, 8'h51, 8'h49, 8'h45, 8'h3E};   // 48  0
			mem[ 49] = {8'h00, 8'h42, 8'h7F, 8'h40, 8'h00};   // 49  1
			mem[ 50] = {8'h42, 8'h61, 8'h51, 8'h49, 8'h46};   // 50  2
			mem[ 51] = {8'h21, 8'h41, 8'h45, 8'h4B, 8'h31};   // 51  3
			mem[ 52] = {8'h18, 8'h14, 8'h12, 8'h7F, 8'h10};   // 52  4
			mem[ 53] = {8'h27, 8'h45, 8'h45, 8'h45, 8'h39};   // 53  5
			mem[ 54] = {8'h3C, 8'h4A, 8'h49, 8'h49, 8'h30};   // 54  6
			mem[ 55] = {8'h01, 8'h71, 8'h09, 8'h05, 8'h03};   // 55  7
			mem[ 56] = {8'h36, 8'h49, 8'h49, 8'h49, 8'h36};   // 56  8
			mem[ 57] = {8'h06, 8'h49, 8'h49, 8'h29, 8'h1E};   // 57  9
			mem[ 58] = {8'h00, 8'h36, 8'h36, 8'h00, 8'h00};   // 58  :
			mem[ 59] = {8'h00, 8'h56, 8'h36, 8'h00, 8'h00};   // 59  ;
			mem[ 60] = {8'h08, 8'h14, 8'h22, 8'h41, 8'h00};   // 60  <
			mem[ 61] = {8'h14, 8'h14, 8'h14, 8'h14, 8'h14};   // 61  =
			mem[ 62] = {8'h00, 8'h41, 8'h22, 8'h14, 8'h08};   // 62  >
			mem[ 63] = {8'h02, 8'h01, 8'h51, 8'h09, 8'h06};   // 63  ?
			mem[ 64] = {8'h32, 8'h49, 8'h59, 8'h51, 8'h3E};   // 64  @
			mem[ 65] = {8'h7C, 8'h12, 8'h11, 8'h12, 8'h7C};   // 65  A
			mem[ 66] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h36};   // 66  B
			mem[ 67] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h22};   // 67  C
			mem[ 68] = {8'h7F, 8'h41, 8'h41, 8'h22, 8'h1C};   // 68  D
			mem[ 69] = {8'h7F, 8'h49, 8'h49, 8'h49, 8'h41};   // 69  E
			mem[ 70] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h01};   // 70  F
			mem[ 71] = {8'h3E, 8'h41, 8'h49, 8'h49, 8'h7A};   // 71  G
			mem[ 72] = {8'h7F, 8'h08, 8'h08, 8'h08, 8'h7F};   // 72  H
			mem[ 73] = {8'h00, 8'h41, 8'h7F, 8'h41, 8'h00};   // 73  I
			mem[ 74] = {8'h20, 8'h40, 8'h41, 8'h3F, 8'h01};   // 74  J
			mem[ 75] = {8'h7F, 8'h08, 8'h14, 8'h22, 8'h41};   // 75  K
			mem[ 76] = {8'h7F, 8'h40, 8'h40, 8'h40, 8'h40};   // 76  L
			mem[ 77] = {8'h7F, 8'h02, 8'h0C, 8'h02, 8'h7F};   // 77  M
			mem[ 78] = {8'h7F, 8'h04, 8'h08, 8'h10, 8'h7F};   // 78  N
			mem[ 79] = {8'h3E, 8'h41, 8'h41, 8'h41, 8'h3E};   // 79  O
			mem[ 80] = {8'h7F, 8'h09, 8'h09, 8'h09, 8'h06};   // 80  P
			mem[ 81] = {8'h3E, 8'h41, 8'h51, 8'h21, 8'h5E};   // 81  Q
			mem[ 82] = {8'h7F, 8'h09, 8'h19, 8'h29, 8'h46};   // 82  R
			mem[ 83] = {8'h46, 8'h49, 8'h49, 8'h49, 8'h31};   // 83  S
			mem[ 84] = {8'h01, 8'h01, 8'h7F, 8'h01, 8'h01};   // 84  T
			mem[ 85] = {8'h3F, 8'h40, 8'h40, 8'h40, 8'h3F};   // 85  U
			mem[ 86] = {8'h1F, 8'h20, 8'h40, 8'h20, 8'h1F};   // 86  V
			mem[ 87] = {8'h3F, 8'h40, 8'h38, 8'h40, 8'h3F};   // 87  W
			mem[ 88] = {8'h63, 8'h14, 8'h08, 8'h14, 8'h63};   // 88  X
			mem[ 89] = {8'h07, 8'h08, 8'h70, 8'h08, 8'h07};   // 89  Y
			mem[ 90] = {8'h61, 8'h51, 8'h49, 8'h45, 8'h43};   // 90  Z
			mem[ 91] = {8'h00, 8'h7F, 8'h41, 8'h41, 8'h00};   // 91  [
			mem[ 92] = {8'h55, 8'h2A, 8'h55, 8'h2A, 8'h55};   // 92  .
			mem[ 93] = {8'h00, 8'h41, 8'h41, 8'h7F, 8'h00};   // 93  ]
			mem[ 94] = {8'h04, 8'h02, 8'h01, 8'h02, 8'h04};   // 94  ^
			mem[ 95] = {8'h40, 8'h40, 8'h40, 8'h40, 8'h40};   // 95  _
			mem[ 96] = {8'h00, 8'h01, 8'h02, 8'h04, 8'h00};   // 96  '
			mem[ 97] = {8'h20, 8'h54, 8'h54, 8'h54, 8'h78};   // 97  a
			mem[ 98] = {8'h7F, 8'h48, 8'h44, 8'h44, 8'h38};   // 98  b
			mem[ 99] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h20};   // 99  c
			mem[100] = {8'h38, 8'h44, 8'h44, 8'h48, 8'h7F};   // 100 d
			mem[101] = {8'h38, 8'h54, 8'h54, 8'h54, 8'h18};   // 101 e
			mem[102] = {8'h08, 8'h7E, 8'h09, 8'h01, 8'h02};   // 102 f
			mem[103] = {8'h18, 8'hA4, 8'hA4, 8'hA4, 8'h7C};   // 103 g
			mem[104] = {8'h7F, 8'h08, 8'h04, 8'h04, 8'h78};   // 104 h
			mem[105] = {8'h00, 8'h44, 8'h7D, 8'h40, 8'h00};   // 105 i
			mem[106] = {8'h40, 8'h80, 8'h84, 8'h7D, 8'h00};   // 106 j
			mem[107] = {8'h7F, 8'h10, 8'h28, 8'h44, 8'h00};   // 107 k
			mem[108] = {8'h00, 8'h41, 8'h7F, 8'h40, 8'h00};   // 108 l
			mem[109] = {8'h7C, 8'h04, 8'h18, 8'h04, 8'h78};   // 109 m
			mem[110] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h78};   // 110 n
			mem[111] = {8'h38, 8'h44, 8'h44, 8'h44, 8'h38};   // 111 o
			mem[112] = {8'hFC, 8'h24, 8'h24, 8'h24, 8'h18};   // 112 p
			mem[113] = {8'h18, 8'h24, 8'h24, 8'h18, 8'hFC};   // 113 q
			mem[114] = {8'h7C, 8'h08, 8'h04, 8'h04, 8'h08};   // 114 r
			mem[115] = {8'h48, 8'h54, 8'h54, 8'h54, 8'h20};   // 115 s
			mem[116] = {8'h04, 8'h3F, 8'h44, 8'h40, 8'h20};   // 116 t
			mem[117] = {8'h3C, 8'h40, 8'h40, 8'h20, 8'h7C};   // 117 u
			mem[118] = {8'h1C, 8'h20, 8'h40, 8'h20, 8'h1C};   // 118 v
			mem[119] = {8'h3C, 8'h40, 8'h30, 8'h40, 8'h3C};   // 119 w
			mem[120] = {8'h44, 8'h28, 8'h10, 8'h28, 8'h44};   // 120 x
			mem[121] = {8'h1C, 8'hA0, 8'hA0, 8'hA0, 8'h7C};   // 121 y
			mem[122] = {8'h44, 8'h64, 8'h54, 8'h4C, 8'h44};   // 122 z
			mem[123] = {8'h00, 8'h00, 8'h00, 8'h03, 8'h03};   // 123 
		end
	
endmodule

 

串口通信模块:

      首先我想说的就是uart_rx,也就是串口接收模块,这个发送模块我参考了CSDN上的一段代码,直接搬过来用,很好用,收到上位机的信息后,直接输出一个接受完成标志和一组输出数据,同时与蜂鸣器联动,控制蜂鸣器发出音乐,蜂鸣器是很重要的一个部分,也是很有意思的一个部分,这个我放到下面去说。

module uart_rxd(
    input			  sys_clk,                  //系统时钟
    input             sys_rst_n,                //系统复位,低电平有效   
    input             uart_rxd,                 //UART接收端口
    output  reg       uart_done,                //接收一帧数据完成标志信号
    output  reg [7:0] uart_data                 //接收的数据
    );
    
    //parameter define
    parameter  CLK_FREQ = 12_000_000;                 //系统时钟频率
    parameter  UART_BPS = 9600;                     //串口波特率
    localparam BPS_CNT  = CLK_FREQ/UART_BPS;        //为得到指定波特率,
                                                    //需要对系统时钟计数BPS_CNT次
    //reg define
    reg        uart_rxd_d0;
    reg        uart_rxd_d1;
    reg [15:0] clk_cnt;                             //系统时钟计数器
    reg [ 3:0] rx_cnt;                              //接收数据计数器
    reg        rx_flag;                             //接收过程标志信号
    reg [ 7:0] rxdata;                              //接收数据寄存器

    //wire define
    wire       start_flag;

    //*****************************************************
    //**                    main code
    //*****************************************************
    //捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
    assign  start_flag = uart_rxd_d1 & (~uart_rxd_d0);    

    //对UART接收端口的数据延迟两个时钟周期
    always @(posedge sys_clk or negedge sys_rst_n) begin 
        if (!sys_rst_n) begin 
            uart_rxd_d0 <= 1'b0;
            uart_rxd_d1 <= 1'b0;          
        end
        else begin
            uart_rxd_d0  <= uart_rxd;                   
            uart_rxd_d1  <= uart_rxd_d0;
        end   
    end

    //当脉冲信号start_flag到达时,进入接收过程           
    always @(posedge sys_clk or negedge sys_rst_n) begin         
        if (!sys_rst_n)                                  
            rx_flag <= 1'b0;
        else begin
            if(start_flag)                          //检测到起始位
                rx_flag <= 1'b1;                    //进入接收过程,标志位rx_flag拉高
            else if((rx_cnt == 4'd9)&&(clk_cnt == BPS_CNT/2))
                rx_flag <= 1'b0;                    //计数到停止位中间时,停止接收过程
            else
                rx_flag <= rx_flag;
        end
    end

    //进入接收过程后,启动系统时钟计数器与接收数据计数器
    always @(posedge sys_clk or negedge sys_rst_n) begin         
        if (!sys_rst_n) begin                             
            clk_cnt <= 16'd0;                                  
            rx_cnt  <= 4'd0;
        end                                                      
        else if ( rx_flag ) begin                   //处于接收过程
                if (clk_cnt < BPS_CNT - 1) begin
                    clk_cnt <= clk_cnt + 1'b1;
                    rx_cnt  <= rx_cnt;
                end
                else begin
                    clk_cnt <= 16'd0;               //对系统时钟计数达一个波特率周期后清零
                    rx_cnt  <= rx_cnt + 1'b1;       //此时接收数据计数器加1
                end
            end
            else begin                              //接收过程结束,计数器清零
                clk_cnt <= 16'd0;
                rx_cnt  <= 4'd0;
            end
    end

    //根据接收数据计数器来寄存uart接收端口数据
    always @(posedge sys_clk or negedge sys_rst_n) begin 
        if ( !sys_rst_n)  
            rxdata <= 8'd0;                                     
        else if(rx_flag)                            //系统处于接收过程
            if (clk_cnt == BPS_CNT/2) begin         //判断系统时钟计数器计数到数据位中间
                case ( rx_cnt )
                4'd1 : rxdata[0] <= uart_rxd_d1;   //寄存数据位最低位
                4'd2 : rxdata[1] <= uart_rxd_d1;
                4'd3 : rxdata[2] <= uart_rxd_d1;
                4'd4 : rxdata[3] <= uart_rxd_d1;
                4'd5 : rxdata[4] <= uart_rxd_d1;
                4'd6 : rxdata[5] <= uart_rxd_d1;
                4'd7 : rxdata[6] <= uart_rxd_d1;
                4'd8 : rxdata[7] <= uart_rxd_d1;   //寄存数据位最高位
                default:;                                    
                endcase
            end
            else 
                rxdata <= rxdata;
        else
            rxdata <= 8'd0;
    end

    //数据接收完毕后给出标志信号并寄存输出接收到的数据
    always @(posedge sys_clk or negedge sys_rst_n) begin        
        if (!sys_rst_n) begin
            uart_data <= 8'd0;                               
            uart_done <= 1'b0;
        end
        else if(rx_cnt == 4'd9) begin               //接收数据计数器计数到停止位时           
            uart_data <= rxdata;                    //寄存输出接收到的数据
            uart_done <= 1'b1;                      //并将接收完成标志位拉高
        end
        else begin
            uart_data <= 8'd0;                                   
            uart_done <= 1'b0; 
        end    
    end

endmodule	

 

      然后我要说的是uart_tx,这个是下位机发送的模块,但是这个不是我操作的重点,这部分代码我就放到下面打包的程序当中去了,我直接讲重点。重点是我控制温度信息传送的代码,这个模块名叫timeout_tx_ctrl,当然最重要的先讲,这段代码如下:

always @(posedge Clk or negedge Rst_n)
        if (!Rst_n) begin
            tx_data <= 8'h00;
            send_out <= 1'b0;
        end
        else if (timeout_flag)
            case (cnt)
                 0: tx_data <= "T";
                 1: tx_data <= "E";
                 2: tx_data <= "M";
                 3: tx_data <= "P"; 
                 4: tx_data <= 8'hA3;//:
                 5: tx_data <= 8'hBA;
                 6: tx_data <= 8'h0D;//
                 7: tx_data <= 8'h30 + temp_h;
                 8: tx_data <= 8'h30 + temp_l;
                 9: tx_data <= 8'h2E;//.
                10: tx_data <= 8'h30 + temp_s;
                11: tx_data <= 8'h0D;//
                12: tx_data <= " ";
                13: tx_data <= "C";
                14: tx_data <= 8'h0D;//\r
                15: tx_data <= 8'h0A;//\n
                16: begin tx_data <= 8'h00; send_out <= 1'b1;end
                default: tx_data <= 8'h00;
            endcase
        else
            tx_data <= 8'h00;

      这个就是用来实现温度的传送,我发现传送温度的时候,如果是英文字母的话直接传就完事了,但是如果是中文汉字的话就稍微有点麻烦,就需要它的GB2312码,对应的一个汉字两个码,一起传过去才可以显示一个完整的汉字。

      而控制这个传送过程我用了两个变量,一个是时间到整点时有效的timeout,一个是发送结束标志位send_out,两个变量同时满足timeout_flag && !send_out时才能启动发送,不然即为无效状态,不能发送,这段完整的代码我就放下面了。

module timeout_tx_ctrl(
    input Clk,
    input Rst_n,
    input timeout,
    input Tx_Done,

    input [3:0]temp_h,
    input [3:0]temp_l,
    input [3:0]temp_s,
    
    output reg Send_En,
    output reg[7:0]tx_data
);
    reg timeout_flag;
    reg send_out;
    reg [5:0]cnt;//42
    
    initial send_out = 1'b0;

    always @(posedge Clk or negedge Rst_n)begin
        if (!Rst_n)
            timeout_flag <= 1'b0;
        else if (send_out)
            timeout_flag <= 1'b0;
        else if (timeout)begin
            timeout_flag <= 1'b1;
            //send_out <= 1'b0;
        end 
        else
            timeout_flag <= timeout_flag;
    end
            
    always @(posedge Clk or negedge Rst_n)
        if (!Rst_n)
            cnt <= 6'd0;
        else if (cnt == 6'd45)
            cnt <= 6'd0;
        else if (Tx_Done)
            cnt <= cnt + 1'b1;        
        else
            cnt <= cnt;
          
    always @(posedge Clk or negedge Rst_n)
        if (!Rst_n)
            Send_En <= 1'b0;
        else if (timeout_flag && !send_out)
            Send_En <= 1'b1;       
        else 
            Send_En <= 1'b0;
        
    always @(posedge Clk or negedge Rst_n)
        if (!Rst_n) begin
            tx_data <= 8'h00;
            send_out <= 1'b0;
        end
        else if (timeout_flag)
            case (cnt)
                 0: tx_data <= "T";
                 1: tx_data <= "E";
                 2: tx_data <= "M";
                 3: tx_data <= "P"; 
                 4: tx_data <= 8'hA3;//:
                 5: tx_data <= 8'hBA;
                 6: tx_data <= 8'h0D;//
                 7: tx_data <= 8'h30 + temp_h;
                 8: tx_data <= 8'h30 + temp_l;
                 9: tx_data <= 8'h2E;//.
                10: tx_data <= 8'h30 + temp_s;
                11: tx_data <= 8'h0D;//
                12: tx_data <= " ";
                13: tx_data <= "C";
                14: tx_data <= 8'h0D;//\r
                15: tx_data <= 8'h0A;//\n
                16: begin tx_data <= 8'h00; send_out <= 1'b1;end
                default: tx_data <= 8'h00;
            endcase
        else
            tx_data <= 8'h00;
endmodule

 

传送成功的上位机模块截图:

FuhjdjTP-TPbIQYapMNlhCSUy7MJ

ps:上位机发送数据靠的是串口助手的连续发送功能,点击自动循环发送即可实现。

最最最最重要的蜂鸣器模块:

      我觉得这个是最值得我说的一个模块,因为在做这个项目之前我只知道蜂鸣器有响与不响的区分,没想到蜂鸣器还可以用来演奏音乐,很是令我惊讶,不过知道了它的工作原理之后,我发现其实很简单,就是不同频率的信号产生不同的音调来发出一首完美的曲子,然后每个音之间隔开250ms,就可以奏乐了!

这是不同音调对应的频率:

这是不同音调对应的频率

      然后产生这些不同的频率就只要使用PWM波就可以了,确实是十分的巧妙,令我惊叹不已,代码我就直接放下面了,大家可以直接欣赏:

module	song(
	input	   	clk,					//系统时钟12MHz
	input		clk1h,
	input		rst,	
	input		tone_en,			//蜂鸣器使能
	
	input		uart_done,			//蜂鸣器使能
	
	
	input[7:0]	uart_data,
	
	input[3:0]		sec_h,				//时间
	input[3:0]		min_l,
	input[3:0]		min_h,
	
	output	 fresh_flag,
	
	output		beep					//蜂鸣器输出端
);	

reg		uart_done_cnt;		//uart_done计数
reg		beep_r;				//寄存器

reg[16:0]count,count_end,count_end1;
reg[23:0]count1;
reg[7:0]state;
reg 	fresh;

//乐谱参数:D=F/2K  (D:参数,F:时钟频率,K:音高频率)
parameter 		M_1 = 16'd11464,	//中音1
				M_2 = 16'd10215,	//中音2
				M_3 = 16'd9100,	//中音3
				M_4 = 16'd8589,//中音4
				M_5 = 16'd7652,	//中音5
				M_6 = 16'd6817,	//中音6
				H_1 = 16'd5740;	//高音1			
parameter	TIME = 3_000_000;	//控制每一个音的长短(250ms)	

assign beep = beep_r;			//输出音乐
assign fresh_flag = fresh;


always@(posedge clk) 
begin
	if(tone_en && min_h == 0 && min_l == 0 && sec_h < 3)//整点才响铃,并且响铃时间为10s
	begin
		count <= count + 1'b1;		//计数器加1
		if(count == count_end)
		begin	
			count <= 17'h0;			//计数器清零
			beep_r <= !beep_r;		//输出取反
		end
	end
	else if(tone_en && !fresh)
	begin
		count <= count + 1'b1;		//计数器加1
		if(count == count_end1) 
		begin	
			count <= 17'h0;			//计数器清零
			beep_r <= !beep_r;		//输出取反
		end
	end
	// else
		// count <= 0;
end

//自动温度控制 曲谱 产生分频的系数并描述出曲谱
always @(posedge clk) 
begin
	if(count1 < TIME)             //一个节拍250mS
      count1 = count1 + 1'b1;
	else 
	begin
		count1 = 24'd0;
		if(state == 8'd36)
			state = 8'd0;
		else
			state = state + 1'b1;
			case(state)
				8'd0,8'd1:count_end=M_1;  
				8'd2,8'D3:count_end=M_5;
				8'd4,8'D5:count_end=M_6;
				8'D6:count_end=M_5;

				8'D7,8'D8:count_end=M_4;
				8'D9,8'D10:count_end=M_3;
				8'D11,8'D12:count_end=M_2;
				8'D13:count_end=M_1;

				8'D14,8'D15:count_end=M_5;
				8'D15,8'D16:count_end=M_4;
				8'D17,8'D18:count_end=M_3;
				8'D19:count_end=M_2;

				8'D16,8'D17:count_end=M_5;
				8'D18,8'D19:count_end=M_4;
				8'D20,8'D21:count_end=M_3;
				8'D22:count_end=M_2;

				8'd23,8'd24:count_end=M_1;  
				8'd25,8'D26:count_end=M_5;
				8'd27,8'D28:count_end=M_6;
				8'D29:count_end=M_5;

				8'D30,8'D31:count_end=M_4;
				8'D32,8'D33:count_end=M_3;
				8'D34,8'D35:count_end=M_2;
				8'D36:count_end=M_1;
				default: count_end = 16'h0;
			endcase
	end
end

//串口控制 曲谱 产生分频的系数并描述出曲谱
always @(posedge clk)
begin
	if(uart_done)
	begin

		case(uart_data)
			8'h1:count_end1 <=16'd22935;	//L1,
			8'h2:count_end1 <=16'd20428;	//L2,
			8'h3:count_end1 <=16'd18203;	//L3,
			8'h4:count_end1 <=16'd17181;	//L4,
			8'h5:count_end1 <=16'd15305;	//L5,
			8'h6:count_end1 <=16'd13635;	//L6,
			8'h7:count_end1 <=16'd12147;	//L7,
			8'h8:count_end1 <=16'd11464;	//M1,
			8'h9:count_end1 <=16'd10215;	//M2,
			8'ha:count_end1 <=16'd9100;	//M3,
			8'hb:count_end1 <=16'd8589;	//M4,
			8'hc:count_end1  <=16'd7652;	//M5,
			8'hd:count_end1 <=16'd6817;	//M6,
			8'he:count_end1 <=16'd6073;	//M7,
			8'hf:count_end1 <=16'd5740;	//H1,
			8'h10:count_end1 <=16'd5107;	//H2,
			8'h11:count_end1 <=16'd4549;	//H3,
			8'h12:count_end1 <=16'd4294;	//H4,
			8'h13:count_end1 <=16'd3825;	//H5,
			8'h14:count_end1 <=16'd3408;	//H6,
			8'h15:count_end1 <=16'd3036;	//H7,
			default:count_end1 <=16'd65535;//	无声
		endcase
	end
		
end

always@(posedge uart_done or posedge clk1h)
begin
	if(uart_done)
	begin
		uart_done_cnt <= 1'b1;
	end
	else
		if(uart_done_cnt)
		begin
			fresh  <= 1'b0;
			uart_done_cnt <= 1'b0;
		end	
		else
			fresh <= 1'b1;
			
end

endmodule

 

很棒,很好控制的一个代码,然后串口接收模块,包括上位机发送的内容也是根据这给代码来给出的。

 

至此,所有的模块都介绍完了,功能也都实现了。

 

遇到的难题:

  1. 在完成项目的过程中我遇到的一个问题就是如何使OLED上的信息不更新,我刚开始想的是能不能让OLED停在那个界面上不刷新,就是在显示是做手脚,但是后来苦于没有找到相应的实现的代码,遂放弃,于是在其他同学的项目里,我好到了一个很好的方法就是,找一个黑盒子给它把那个时间点的信息都装起来,然后在发送音频信息时直接显示这个黑盒子里面的东西,这个代码很好实现,也很简单。
  2. 第二个遇到的问题就是如何使蜂鸣器“唱歌”,这个我其实有那么一点知道是靠不同频率来发出不同音调的,因为毕竟声音的本质就是这样的,但是我苦于找不到如何产生这些个不同的频率。后来是在电子森林的网站上找到了这个方法,就是用PWM波来产生,确实,做工程需要多方面联动才能把这个项目做好。
  3. 还有一个就是并行运行方式,FPGA并行的运行方式导致很多单片机里的代码语言都不饿能直接用,同时也给我的编程造成了一定的困扰,有些报错实在是看不懂,请问了群友之后发现竟是并行运算与串行没转换过来,实属低级错误。

未来的计划:

      我打算再学学FPGA里面的状态机还有就是IP核的例化以及使用,这些东西真的不是很熟练,也不太懂里面的原理。另外的就是,包括以前单片机的学习,都是靠丰富的底层代码来求生的,现在我觉得该进一步学学那些底层的最基本的原理了,不然就像苏老师说的那样,以后出去工作很多都是浮于表面,没有实质性的东西是不行的,所以得抓紧学学底层的代码,学的好了真的是受益终生。我觉得我最终的目的是要学会设计,当然,这还有很长的路要走,我还要多加学习。

附件下载
project_step.jed
可编译的下载文件
STEP_CODE.rar
整个工程有点大传不上来,就传这些verilog文件吧
团队介绍
成都信息工程大学 电子工程学院 想学FPGA的小白
团队成员
黄熠昕
成都信息工程大学 电子工程学院 想学FPGA的小白
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号