2021寒假在家练项目(4)——熊洋
基于小脚丫FPGA的综合技能训练平台,实现定时、测温、报警、控制等功能。
标签
嵌入式系统
FPGA
数字逻辑
显示
花喵~
更新2021-02-28
1058

官方例程:链接:https://pan.baidu.com/s/1QAhxnMSjTp9dQ9VdAz_UZQ
提取码:step
复制这段内容后打开百度网盘手机App,操作更方便哦

一.项目背景

      为了让高校电子/电气/电力等专业的同学们对FPGA的了解和熟练使用,将学习到的理论知识在实际的动手中深刻体会,也配合明年的全国大学生电子设计大赛,硬禾课堂和相关电子研究会联合开展了“2021寒假在家练活动”,并且设定了一些简单的规则,完成项目就能免费获得板卡,这让更多的同学们参与进来,让同学们学有成效。

二.项目要求

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

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

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

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

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

三.项目实现

(一)项目简述

      为了实现上述项目要求,本项目主要分了四个模块:温度计—Thermometer u1;OLED显示屏—OLED u2;蜂鸣器—Beeper u3;串口—uart_seg u4。这四个模块内为了实现一些各自需要的小功能,也包含了一些其他的小模块,详细内容请看下面的结构图:

Fk2O3QTDUcsCV77IOLugx2NDnSyi

整个项目的资源报告如下图:

FpJE9waYPYVmxxgoBaQyiY1IrgYN

      总体来说,因为本人初学FPGA,所以对官方给出的小脚丫的综合例程代码依赖度很高,个人对资源的占用还算满意。

(二)模块介绍

1.温度计—Thermometer u1:

下为此模块例化代码:

wire [24:0]       bcd_code;
Thermometer u1
(
.clk            (clk            ),//系统时钟
.rst_n          (rst_n          ),//系统复位,低有效
.one_wire       (one_wire       ),//ds18b20z one-wire-bus
.bcd_code       (bcd_code       ) //温度bcd码 十位[23:20],个位[19:16],小数位[14:12]
);

      该部分由于有以往单片机使用DS18B20的经验和电子森林提供的详细参考例程(温度计参考例程,也可查看文章开头链接的官方例程),所以很容易就能完成温度数据的读取。但需要特别注意的是,此模块输出的bcd码中温度的十位为[23:20],个位为[19:16],小数位为[14:12]。

2.OLED显示屏—OLED u2

下为此模块例化代码:

wire        o_clock;
wire  [24:0] temperture_out;
OLED u2
(
.clk             (clk             ),//系统时钟
.rst_n           (rst_n           ),//系统复位,低有效
.sw_set          (sw_set          ),//时间设置模式拨钮
.key             (key             ),//按键
.temperture      (bcd_code        ),//温度bcd_code
.music_end       (music_end       ),//音乐停止信号
.o_clock         (o_clock         ),//整点信号
.temperture_out  (temperture_out  ),//整点时温度
.oled_csn        (oled_csn        ),//OLCD液晶屏使能
.oled_rst        (oled_rst        ),//OLCD液晶屏复位
.oled_dcn        (oled_dcn        ),//OLCD数据指令控制
.oled_clk        (oled_clk        ),//OLCD时钟信号
.oled_dat        (oled_dat        ) //OLCD数据信号
);

       该部分引用电子森林里OLED详细参考例程(OLED参考例程,也可查看文章开头链接的官方例程)并加以修改完成。

       在原来的基础上我添加了按键消抖模块、1s时钟分频模块和实现时钟、显示等功能的部分代码。

(1)按键消抖模块

       此模块直接引用的小脚丫资料包里STEP-MAX10快速入门手册按键消抖中的代码(请查看附件中的STEP-MAX10快速入门手册),但因为需要用四个按键来调整时间,所以需要把代码中的N改为4。

 (2)1s时钟分频模块

       此模块是我阅读STEP-MAX10快速入门手册中的讲解后用自己的理解写的代码。

module clk_sec(clk_in,rst,clk_sec_out);
   input clk_in,rst;
   output reg clk_sec_out;
reg [23:0]  cnt_sec = 0;
reg [23:0] Div_sec = 12000000;
always@(posedge clk_in or negedge rst)
  begin
    if(!rst)
    begin
      cnt_sec<=0;
      clk_sec_out<=0;
    end
    else if(cnt_sec>=((Div_sec>>1)-1))
    begin
       cnt_sec<=0;
       clk_sec_out<=~clk_sec_out;
    end
    else begin
       cnt_sec<=cnt_sec+1;
    end
  end
endmodule

(3)实现计时、显示等功能

       相较于电子森林中的OLED模块代码,我增加了两个输入——temperture(温度值)和music_end(音乐停止信号,返回让OLED能更新),两个输出——temperture_out(整点时温度,给串口来显示)和o_clock(整点信号,控制串口发送数据和音乐播放)。

下为实现时钟功能的代码:

//时间设置
always@(posedge clk or negedge rst_n) begin
   if(!rst_n) begin
      min_set <= 6'd0;hour_set <= 6'd0;
   end
   else if(sw_set)begin
      case(key_pulse)             
        4'b0001:  begin min_set <= min_set + 1'd1;end
        4'b0010:  begin min_set <= min_set - 1'd1;end
        4'b0100:  begin hour_set <= hour_set + 1'd1;end
        4'b1000:  begin hour_set <= hour_set - 1'd1;end
        default: begin min_set <= min_set;hour_set <= hour_set;end  
      endcase
    end
    else begin min_set <= min;hour_set <= hour;end
end

//时间变化
always@(posedge clk_1s or negedge rst_n) begin
    if(!rst_n) begin
      sec <= 6'd0; min <= 6'd0;hour <= 6'd0;
      sec_buff <= 6'd0; min_buff <= 6'd0; hour_buff <= 6'd0;
    end
    else if(sw_set)begin
       sec_buff <= sec;
       min_buff <= min_set;
       min <= min_set;
       hour_buff <= hour_set;
       hour <= hour_set;
    end
    else if(o_clock)begin
       sec_buff <= sec_buff;
       min_buff <= min_buff;
       hour_buff <= hour_buff;
       if(sec == 6'd59) begin
          sec <= 1'd0;
          min <= min + 1'd1;
       end
       else begin
          if(hour == 6'd24) hour <= 6'd0;
          sec <= sec + 1'd1;
       end                         
       if((min == 6'd59) && (sec == 6'd59))begin
          min <= 1'd0;
          hour <= hour + 1'd1;
        end
     end
     else begin
        sec_buff <= sec;
        min_buff <= min;
        hour_buff <= hour;
     
        if(sec == 6'd59) begin
           sec <= 1'd0;
           min <= min + 1'd1;
        end
        else begin
           if(hour == 6'd24) hour <= 6'd0;
              sec <= sec + 1'd1;
           end
                      
           if((min == 6'd59) && (sec == 6'd59))begin
              min <= 1'd0;
              hour <= hour + 1'd1;
            end
        end
      sec_l <= sec_buff % 10;
      sec_h <= sec_buff / 10;
      min_l <= min_buff % 10;
      min_h <= min_buff / 10;
      hour_l <= hour_buff % 10;
      hour_h <= hour_buff / 10;
end

       为了能够达到能够停住不更新的目的,这里用了buff来缓存数据送给OLED,但不更新的同时还是需要计时,所以整点信号有效时,sec、min、hour还是要按时赋值。

下为温度计功能的代码:

reg   [24:0] temperture_buff;
always@(posedge clk or negedge rst_n) begin
   if(!rst_n) temperture_buff <= 1'b0;
   else if(o_clock) begin
      temperture_buff <= temperture_buff;
      temperture_out <= temperture_buff;
   end
   else begin temperture_buff <= temperture;end
end

       实现的思路与上述时钟功能相似,不再赘述。

3.蜂鸣器—Beeper u3:

下为此模块例化代码:

wire        music_end;
Beeper u3
(
.clk                 (clk           ),//系统时钟
.rst_n               (rst_n         ),//系统复位,低有效
.o_clock             (o_clock       ),//整点信号
.music_end           (music_end     ),//音乐停止信号
.beeper              (beeper        ),//蜂鸣器输出
.rx_data_valid       (rx_data_valid ),//接收数据有效脉冲
.rx_data_out         (rx_data_out   ) //接收到的数据
);

       相较于官方例程中的蜂鸣器模块代码(请查看文章开头链接的官方例程),我增加了三个输入——o_clock(整点信号,控制音乐播放)、rx_data_valid(接收数据有效脉冲)和rx_data_out(接收到的音调数据),一个输出——music_end(音乐停止信号,返回让OLED能更新)。

(1)rx_data_valid和rx_data_out的运用

       rx_data_valid和rx_data_out用于蜂鸣器子模块tone里,因为rx_data_valid信号很快,直接用于让音调变化达不到播放一段音乐的效果,所以此信号用来改变存储音调的“乐谱”music中的值。实现代码如下:

localparam TONE_NUM = 5'd28;//音乐音调个数
reg [7:0] music [TONE_NUM-1:0];//乐谱
reg [5:0] rx_cnt;
reg       send_end;//音乐接收完毕信号
always @(posedge clk or negedge rst_n) begin
   if(!rst_n) begin rx_cnt = 1'b0; send_end = 1'b0;end
   else if(rx_data_valid)begin
      music[rx_cnt] = rx_data_out - "0";//音调是用字符数字的方式发送的
      rx_cnt = rx_cnt + 1'b1;
      if(rx_cnt == 5'd28) begin send_end = 1'b1;end
      end
      else if(music_end) begin send_end = 1'b0; rx_cnt = 1'b0;end
      else rx_cnt = rx_cnt;
end

       其中TONE_NUM为localparam,是“乐谱”的音调个数,我用的小星星乐谱共有28个音调,全部接收完毕后产生send_end(音乐接收完毕信号);在用串口查看接收的数据时,语句“music[rx_cnt] = rx_data_out - "0";”中如果用非阻塞赋值,“乐谱”第一个存的音调会是0,这是因为rx_data_out和rx_data_valid在uart_seg中也是用的非阻塞赋值且在同一时刻,导致“乐谱”第一个存的音调是rx_data_out未变化的初始值0,而此处用阻塞赋值就能解决这个问题。

(2)音乐播放

       当“乐谱”存储完后,会发送send_end有效信号,蜂鸣器才能播放音乐。我设置的是一个音调大概响0.4秒。实现代码如下:

reg [4:0] music_cnt;//乐谱音调个数  
reg [7:0] music_tone;//当前音乐音调
always@(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
       music_tone = 1'b0;music_cnt <= 1'b0;
    end
    else if(cnt >= 24'd4999999 && o_clock && send_end)begin
       if(music_cnt == TONE_NUM) music_cnt <= music_cnt;
       else music_cnt <= music_cnt + 1'b1;

       case(music_cnt)
         TONE_NUM: begin music_tone = 1'b0; music_end = 1'b1;end
         default: music_tone = music[music_cnt];
       endcase
    end
    else if(!o_clock) begin music_tone = 1'b0; music_cnt <= 1'b0; music_end = 1'b0;end
    else begin
       music_tone = music_tone;
       music_cnt <= music_cnt;
    end        

    case(music_tone)
       4'd1: cycle <= 16'd45872;       //L1,
       4'd2: cycle <= 16'd40858;       //L2,
       4'd3: cycle <= 16'd36408;       //L3,
       4'd4: cycle <= 16'd34364;       //L4,
       4'd5: cycle <= 16'd30612;       //L5,
       4'd6: cycle <= 16'd27273;       //L6,
       4'd7: cycle <= 16'd24296;       //L7,
       default:  cycle <= 16'd0;       //cycle为0,PWM占空比为0,低电平
     endcase
end

4.串口—uart_seg u4:

下为此模块例化代码:

wire rx_data_valid;
wire [7:0]        rx_data_out;
uart_seg u4
(
.clk                            (clk               ),//系统时钟
.rst_n                          (rst_n             ),//系统复位,低有效
.o_clock                        (o_clock           ),//整点信号
.temperture                     (temperture_out    ),//整点时温度
.fpga_rx                        (fpga_rx           ),//UART接收输入
.fpga_tx                        (fpga_tx           ),//UART发送输出
.rx_data_valid                  (rx_data_valid     ),//接收数据有效脉冲
.rx_data_out                    (rx_data_out       ) //接收到的数据
);

      相较于官方例程中的蜂鸣器模块代码(请查看文章开头链接的官方例程),我增加了两个输入——o_clock(整点信号,控制串口发送数据)和temperture(整点时温度值),两个输出——rx_data_valid(接收数据有效脉冲)和rx_data_out(接收到的音调数据)。

(1)温度数据发送给上位机

实现代码如下:

reg tx_data_valid;//发送数据有效脉冲,1有效
reg [7:0] tx_data_in; //要发送的数据
reg [2:0] tx_cnt;//发送操作计数
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin tx_data_valid <= 1'b0; tx_data_in <= 1'b0; tx_cnt <= 1'b0; end
    else if((cnt >= 18'd119999) && o_clock)begin
       case(tx_cnt)
         3'd0:begin tx_data_valid <= 1'b1;tx_data_in <= "0" + temperture[23:20];end
         3'd1:begin tx_data_valid <= 1'b1;tx_data_in <= "0" + temperture[19:16];end
         3'd2:begin tx_data_valid <= 1'b1;tx_data_in <= ".";end
         3'd3:begin tx_data_valid <= 1'b1;tx_data_in <= "0" + temperture[14:12];end
         3'd4:begin tx_data_valid <= 1'b0;tx_data_in <= 1'b0;end
       endcase

       if(tx_cnt == 3'd4) tx_cnt <= tx_cnt;
       else tx_cnt <= tx_cnt + 1'b1;
    end
    else if(!o_clock) tx_cnt <= 1'b0;
    else begin tx_data_valid <= 1'b0; tx_data_in <= tx_data_in; end
end

         因为ASCII码表中数字字符的值不为数字的值,为了方便查看数据,所以tx_data_in赋值时加上了字符“0”的值。

(2)上位机发送乐谱给FPGA

       本项目用的上位机,是将乐谱的数据内嵌在了上位机中,当收到温度数据后会自动发送乐谱的数据给FPGA。因为本人对上位机了解不深,此次的上位机还不是很完善,故不分享更多内容。如果有疑问,可以用比较官方、通用的上位机,比如正点原子的XCOM,发送一下数据,也可以达到一样的效果。

四.心得体会

      刚开始时,对Verilog语言是零基础,并且对FPGA并行执行的思想理解不深,在编写代码的过程中出了很多错,并且小脚丫这次运用的OLED屏幕是用SPI时序控制且大小为128*32的,相比常见的用IIC时序控制且大小为128*64的要不同寻常许多,刚开始思考项目时,因为本身对SPI了解就不多,网上能够参考的资料也不多,怎么让屏幕亮起来都让我头大,好在后来找到了本次活动官方提供的参考代码,在一次次尝试运行的过程中对FPGA的操作运用有了了解,学有所获。但是就完成时间和自己完成度与其他同学相比来看,自己也认识到了自身的不足,还需要好好努力学习呀!很感谢拥有这次机会,也很感谢在这个过程对我有所帮助的同学和朋友。加油,未来可期!

 

附件下载
STEP_XY.pof
STEP-MAX10 快速入门.pdf
STEP_XY.sof
团队介绍
成都信息工程大学 电子工程学院
团队成员
熊洋
2019级电子信息科学与技术专业学生,正在努力成为一名优秀的程序媛~
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号