2021寒假在家练项目4——曾子奇
本项目为基于小脚丫fpga的综合训练版制作的一个可以实现测温,定时,报警的系统
标签
FPGA
wddzg
更新2021-03-02
914

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

  1. 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
  2. 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
  3. 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
  4. PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
  5. 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息

引脚信息:FtW5A6d_MORDA_1fX5Yl9xtYWJDd

资源报告:

FtPhzDTR--IgNEDImPp2HP6lMY78

RTL视图:FpAeWbN3oewTVQfgsbILQClAQpyX

功能介绍:

sw1为蜂鸣器开关

sw2串口开关。

key1复位键,将时间复位到00:00。

Key2为分钟减,按下分钟减一

Key3为分钟加,按下分钟加一

Key4为时钟减,按下时钟减一。

核心代码

顶层模块设计

连接各个模块。

 

module top(
  input clk,rst, mindown,minup,hourdown,onoff_uart_t,uart_rxd,
  inout tone_en,
  inout one_wire,


  output                  oled_csn,   //OLCD液晶屏使能    
  output                  oled_rst,    //OLCD液晶屏复位    
  output                  oled_dcn,  //OLCD数据指令控制
  output                  oled_clk,    //OLCD时钟信号
  output                  oled_dat,   //OLCD数据信号


  output                       uart_out,   //串口输出


  output                  beep
);


wire[3:0]      temp_h;
wire[3:0]      temp_t;
wire[3:0]      temp_u;
wire[3:0]      temp_d;


wire[3:0]      six;
wire[3:0]      five;
wire[3:0]      four;
wire[3:0]      three;
wire[3:0]      two;
wire[3:0]      one;


wire[15:0]   data_out;
wire[7:0]      uart_data;


wire           clk1h;
wire      fresh_flag;


OLED12832 OLED12832_v1(
  .clk(clk),
  .rst_n(rst),


  .temp_d(temp_d),
  .temp_h(temp_h),
  .temp_t(temp_t),
  .temp_u(temp_u),


  .six(six),
  .five(five),
  .four(four),
  .three(three),
  .two(two),
  .one(one),


  .oled_clk(oled_clk),
  .oled_csn(oled_csn),
  .oled_dat(oled_dat),
  .oled_dcn(oled_dcn),
  .oled_rst(oled_rst)
);


DS18B20Z DS18B20Z_v1(
  .clk_in(clk),
  .rst_n_in(rst),


  .one_wire(one_wire),


  .data_out(data_out)
);


TempDatCvrt TempDatCvrt_v1(
  .datin(data_out),
  .clk(clk),
  .rst(rst),
  .fresh_flag(fresh_flag),


  .dat_d(temp_d),
  .dat_h(temp_h),
  .dat_t(temp_t),
  .dat_u(temp_u)
);


FreqDiv FreqDiv_v1(
  .clk(clk),
  .rst(rst),


  .clkout(clk1h)
);


clock clock_v1(
  .clk1h(clk1h),
  .rst(rst),


  .mindown(mindown),
  .minup(minup),
  .hourdown(hourdown),


  .fresh_flag(fresh_flag),


  .six(six),
  .five(five),
  .four(four),
  .three(three),
  .two(two),
  .one(one)
);


uart_tx uart_tx_v1(
  .clk_in(clk),
  .onoff_uart_t(onoff_uart_t),


  .temp_d(temp_d),
  .temp_t(temp_t),
  .temp_u(temp_u),


  .six(six),
  .five(five),
  .four(four),
  .three(three),
  .two(two),
  .one(one),


  .uart_out(uart_out)
);


song song_v1(
  .clk(clk),
  .clk1h(clk1h),
  .rst(rst),
  .tone_en(tone_en),


  .uart_done(uart_done),
  .uart_data(uart_data),


  .two(two),
  .three(three),
  .four(four),


  .fresh_flag(fresh_flag),
  .beep(beep)
);


uart_recv    uart_recv_v1(
  .sys_clk(clk),
  .sys_rst_n(rst),
  .uart_rxd(uart_rxd),


  .uart_done(uart_done),
  .uart_data(uart_data)
);


Endmodule

 

温度传感器模块

这里直接使用的是硬禾学堂提供的例程

module DS18B20Z

(

      input                    clk_in,             //系统时钟

      input                    rst_n_in,          //系统复位,低有效

      inout                    one_wire,        //DS18B20Z传感器单总线,双向管脚

      output reg [15:0]   data_out         //DS18B20Z有效温度数据输出

);




      /*

      本设计通过驱动DS18B20Z芯片获取温度数据,

      需要了解inout类型的接口如何实现双向通信,

      中间涉及各种不同的延时和寄存器指令操作,注释部分以作简要说明,更多详情需参考数据手册

      */




      localparam      IDLE    =   3'd0;

      localparam      MAIN  =   3'd1;

      localparam      INIT     =   3'd2;

      localparam      WRITE =   3'd3;

      localparam      READ   =   3'd4;

      localparam      DELAY =   3'd5;




      //计数器分频产生1MHz的时钟信号

      reg                       clk_1mhz;

      reg      [2:0]          cnt_1mhz;

      always@(posedge clk_in or negedge rst_n_in) begin

           if(!rst_n_in) begin

                 cnt_1mhz <= 3'd0;

                 clk_1mhz <= 1'b0;

           end else if(cnt_1mhz >= 3'd5) begin

                 cnt_1mhz <= 3'd0;

                 clk_1mhz <= ~clk_1mhz; //产生1MHz分频

           end else begin

                 cnt_1mhz <= cnt_1mhz + 1'b1;

           end

      end




      reg      [2:0]          cnt;

      reg                       one_wire_buffer;

      reg      [3:0]          cnt_main;

      reg      [7:0]          data_wr;

      reg      [7:0]          data_wr_buffer;

      reg      [2:0]          cnt_init;

      reg      [19:0]         cnt_delay;

      reg      [19:0]         num_delay;

      reg      [3:0]          cnt_write;

      reg      [2:0]          cnt_read;

      reg      [15:0]         temperature;

      reg      [7:0]          temperature_buffer;

      reg      [2:0]          state = IDLE;

      reg      [2:0]          state_back = IDLE;

      //使用1MHz时钟信号做触发完成下面状态机的功能

      always@(posedge clk_1mhz or negedge rst_n_in) begin

           if(!rst_n_in) begin

                 state <= IDLE;

                 state_back <= IDLE;

                 cnt <= 1'b0;

                 cnt_main <= 1'b0;

                 cnt_init <= 1'b0;

                 cnt_write <= 1'b0;

                 cnt_read <= 1'b0;

                 cnt_delay <= 1'b0;

                 one_wire_buffer <= 1'bz;

                 temperature <= 16'h0;

           end else begin

                 case(state)

                      IDLE:begin      //IDLE状态,程序设计的软复位功能,各状态异常都会跳转到此状态

                                  state <= MAIN;    //软复位完成,跳转之MAIN状态重新工作

                                  state_back <= MAIN;

                                  cnt <= 1'b0;

                                  cnt_main <= 1'b0;

                                  cnt_init <= 1'b0;

                                  cnt_write <= 1'b0;

                                  cnt_read <= 1'b0;

                                  cnt_delay <= 1'b0;

                                  one_wire_buffer <= 1'bz;

                            end

                      MAIN:begin          //MAIN状态控制状态机在不同状态间跳转,实现完整的温度数据采集

                                  if(cnt_main >= 4'd11) cnt_main <= 1'b0;

                                  else cnt_main <= cnt_main + 1'b1;

                                  case(cnt_main)

                                       4'd0: begin state <= INIT; end    //跳转至INIT状态进行芯片的复位及验证

                                       4'd1: begin data_wr <= 8'hcc;state <= WRITE; end  //主设备发出跳转ROM指令

                                       4'd2: begin data_wr <= 8'h44;state <= WRITE; end //主设备发出温度转换指令

                                       4'd3: begin num_delay <= 20'd750000;state <= DELAY;state_back <= MAIN; end //延时750ms等待转换完成




                                       4'd4: begin state <= INIT; end    //跳转至INIT状态进行芯片的复位及验证

                                       4'd5: begin data_wr <= 8'hcc;state <= WRITE; end  //主设备发出跳转ROM指令

                                       4'd6: begin data_wr <= 8'hbe;state <= WRITE; end //主设备发出读取温度指令




                                       4'd7: begin state <= READ; end  //跳转至READ状态进行单总线数据读取

                                       4'd8: begin temperature[7:0] <= temperature_buffer; end  //先读取的为低8位数据




                                       4'd9: begin state <= READ; end  //跳转至READ状态进行单总线数据读取

                                       4'd10: begin temperature[15:8] <= temperature_buffer; end    //后读取的为高8为数据




                                       4'd11: begin state <= IDLE;data_out <= temperature; end //将完整的温度数据输出并重复以上所有操作

                                       default: state <= IDLE;

                                  endcase

                            end

                      INIT:begin       //INIT状态完成DS18B20Z芯片的复位及验证功能

                                  if(cnt_init >= 3'd6) cnt_init <= 1'b0;

                                  else cnt_init <= cnt_init + 1'b1;

                                  case(cnt_init)

                                       3'd0: begin one_wire_buffer <= 1'b0; end    //单总线复位脉冲拉低

                                       3'd1: begin num_delay <= 20'd500;state <= DELAY;state_back <= INIT; end   //复位脉冲保持拉低500us时间

                                       3'd2: begin one_wire_buffer <= 1'bz; end    //单总线复位脉冲释放,自动上拉

                                       3'd3: begin num_delay <= 20'd100;state <= DELAY;state_back <= INIT; end   //复位脉冲保持释放100us时间

                                       3'd4: begin if(one_wire) state <= IDLE; else state <= INIT; end //根据单总线的存在检测结果判定是否继续

                                       3'd5: begin num_delay <= 20'd400;state <= DELAY;state_back <= INIT; end   //如果检测正常继续保持释放400us时间

                                       3'd6: begin state <= MAIN; end  //INIT状态操作完成,返回MAIN状态

                                       default: state <= IDLE;

                                  endcase

                            end

                      WRITE:begin         //按照DS18B20Z芯片单总线时序进行写操作

                                  if(cnt <= 3'd6) begin  //共需要发送8bit的数据,这里控制循环的次数

                                       if(cnt_write >= 4'd6) begin cnt_write <= 1'b1; cnt <= cnt + 1'b1; end

                                       else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end

                                  end else begin

                                       if(cnt_write >= 4'd8) begin cnt_write <= 1'b0; cnt <= 1'b0; end      //两个变量都恢复初值

                                       else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end

                                  end

                                  //对于WRITE状态中cnt_write来讲,执行过程为:0;[1~6]*8;7;8;

                                  case(cnt_write)

                                       //lock data_wr

                                       4'd0: begin data_wr_buffer <= data_wr; end //将需要写出的数据缓存

                                       //发送 1bit 数据的用时在60~120us之间,参考数据手册

                                       4'd1: begin one_wire_buffer <= 1'b0; end    //总线拉低

                                       4'd2: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end     //延时2us时间,保证15us以内

                                       4'd3: begin one_wire_buffer <= data_wr_buffer[cnt]; end   //先发送数据最低位

                                       4'd4: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end     //延时80us时间

                                       4'd5: begin one_wire_buffer <= 1'bz; end    //总线释放

                                       4'd6: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end     //延时2us时间

                                       //back to main

                                       4'd7: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end     //延时80us时间

                                       4'd8: begin state <= MAIN; end  //返回MAIN状态

                                       default: state <= IDLE;

                                  endcase

                            end

                      READ:begin          //按照DS18B20Z芯片单总线时序进行读操作

                                  if(cnt <= 3'd6) begin  //共需要接收8bit的数据,这里控制循环的次数

                                       if(cnt_read >= 3'd5) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end

                                       else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end

                                  end else begin

                                       if(cnt_read >= 3'd6) begin cnt_read <= 1'b0; cnt <= 1'b0; end //两个变量都恢复初值

                                       else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end

                                  end

                                  case(cnt_read)

                                       //读取 1bit 数据的用时在60~120us之间,总线拉低后15us时间内读取数据,参考数据手册

                                       3'd0: begin one_wire_buffer <= 1'b0; end    //总线拉低

                                       3'd1: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end //延时2us时间

                                       3'd2: begin one_wire_buffer <= 1'bz; end    //总线释放

                                       3'd3: begin num_delay <= 20'd5;state <= DELAY;state_back <= READ; end //延时5us时间

                                       3'd4: begin temperature_buffer[cnt] <= one_wire; end //读取DS18B20Z返回的总线数据,先收最低位

                                        3'd5: begin num_delay <= 20'd60;state <= DELAY;state_back <= READ; end //延时60us时间

                                       //back to main

                                       3'd6: begin state <= MAIN; end  //返回MAIN状态

                                       default: state <= IDLE;

                                  endcase

                            end

                      DELAY:begin         //延时控制

                                  if(cnt_delay >= num_delay) begin     //延时控制,延时时间由num_delay指定

                                       cnt_delay <= 1'b0;

                                       state <= state_back; //很多状态都需要延时,延时后返回哪个状态由state_back指定

                                  end else cnt_delay <= cnt_delay + 1'b1;

                            end

                 endcase

           end

      end




      assign  one_wire = one_wire_buffer;




endmodule

oled模块

核心内容依然是硬禾学堂给的例程中的代码,简单修改后就可以使用,所以不过多赘述只给出修改部分

MAIN:begin

           if(cnt_main >= 5'd6) cnt_main <= 5'd5;

           else cnt_main <= cnt_main + 1'b1;

           case(cnt_main)      //MAIN状态

               5'd0:    begin state <= INIT; end

               5'd1:    begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end

               5'd2:    begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "TEMP            ";state <= SCAN; end

               5'd3:    begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end

               5'd4:    begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "TIME            ";state <= SCAN; end

               5'd5:begin y_p <= 8'hb1; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 5; char <= {4'd0, temp_h, 4'd0, temp_t, 4'd0, temp_u, ".", 4'd0, temp_d}; state <= SCAN;                                           end
               5'd6:    begin
                                                  y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h00; num <= 5'd 8; char <= { six, 4'd0, five, ":", 4'd0,four, 4'd0, three, ":",4'd0,two, 4'd0, one}; state <= SCAN;                                              end

               default: state <= IDLE;

       endcase

end

UART串口发送温度,时间模块

这个模块我参考了李卓然同学的代码,我在他的基础上做了一定的修改后为我所用了。在这里表示感谢,基本上没有太多变化所以我就不把代码贴出来了。

UART串口接受音乐模块

这里代码是参考的CSDN https://blog.csdn.net/weifengdq/article/details/103168587,在他的基础上改编直接把收到的数据输送给蜂鸣器发声。

module uart_recv(

    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;




    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    

蜂鸣器模块

我参考了一下杨彧同学的代码,修改了一下播放的音乐

 

always @(posedge clk)

    begin

       case(tone)

                 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'd0;

       endcase

时钟分频模块

我使用的是硬禾学堂提供的例程来产生所需要的频率,篇幅已经较长而且比较简单不过多赘述

遇到的困难及解决方法:

1.串口发送和接收模块的编写。

由于第一次接触fpga和串口通信,在一开始简直是一筹莫展,但通过网上浏览大量资料和参考其他同学的代码还是完成该模块的编写。

2.oled屏出现一些多余的0。

通过网上寻找资料解决。

3.对fpga的语言不熟悉出现了许多BUG。

通过仔细查找代码和网上寻找资料的方法解决一系列BUG。

心得体会:

  • 刚开始学习时简直是一脸懵逼,别说串口通信,连oled屏我都亮不起来,好在找到了本次活动官方提供的参考代码,在一次次尝试运行的过程中对FPGA的操作运用有了了解,学有所获。也感谢分享代码的同学们让我能够拓展思路最终完成项目。
附件下载
project2.pof
烧录文件
project 2.zip
源码
XCOM V2.6.exe
上位机
团队介绍
空工大,导院
团队成员
曾子奇
2018级雷达工程学员,初学fpga
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号