2021寒假在家练项目4——余梦雅
使用小脚丫FPGA开发板设计一个能够定时、测温、报警、控制的项目,能通过串口显示当前温度,并配置警报音乐信息。
标签
嵌入式系统
FPGA
测试
数字逻辑
显示
Doraemon
更新2021-03-05
1041

一、项目需求

使用小脚丫FPGA开发板设计一个能够定时、测温、报警、控制的项目,能通过串口显示当前温度,并配置警报音乐信息。

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

 

二、项目进展

Fobl9JdfR4r3ObJ923_ZkaRQbZT2

图2.1 项目原理图

FmmEbqnE3gCj6LX1Iey2BstExsSu

图2.2 成功展示照片

FkqCAH7Ey4fzsIYp7C_K5dbSlWJY

图2.3 串口数据演示图片

Quartus Prime Version 18.1.0 Build 625 09/12/2018 SJ Lite Edition
Revision Name fpga_alarm
Top-level Entity Name fpga_alarm
Family MAX 10
Device 10M02SCM153C8G
Timing Models Final
Total logic elements 1878 / 2,304 ( 81 % )
Total registers 361
Total pins 15 / 112 ( 13 % )
Total virtual pins 0
Total memory bits 0 / 110,592 ( 0 % )
Embedded Multiplier 9-bit elements 0 / 32 ( 0 % )
Total PLLs 0 / 1 ( 0 % )
UFM blocks 0 / 1 ( 0 % )
ADC blocks 0

图2.4 资源占用信息

(三)涉及到的关键代码

module pwm_music

(

input clk_in, //系统时钟

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

input tone_en, //蜂鸣器使能信号

input [4:0] tone, //蜂鸣器音节控制

output reg piano_out //蜂鸣器控制输出

);

/*

无源蜂鸣器可以发出不同的音节,与蜂鸣器震动的频率(等于蜂鸣器控制信号的频率)相关,

为了让蜂鸣器控制信号产生不同的频率,我们使用计数器计数(分频)实现,不同的音节控制对应不同的计数终值(分频系数)

计数器根据计数终值计数并分频,产生蜂鸣器控制信号

*/

reg [15:0] time_end;

//根据不同的音节控制,选择对应的计数终值(分频系数)

//低音1的频率为261.6Hz,蜂鸣器控制信号周期应为12MHz/261.6Hz = 45871.5,

//因为本设计中蜂鸣器控制信号是按计数器周期翻转的,所以几种终值 = 45871.5/2 = 22936

//需要计数22936个,计数范围为0 ~ (22936-1),所以time_end = 22935

always@(tone) begin

case(tone)

5'd1: time_end = 16'd22935; //L1,

5'd2: time_end = 16'd20428; //L2,

5'd3: time_end = 16'd18203; //L3,

5'd4: time_end = 16'd17181; //L4,

5'd5: time_end = 16'd15305; //L5,

5'd6: time_end = 16'd13635; //L6,

5'd7: time_end = 16'd12147; //L7,

5'd8: time_end = 16'd11464; //M1,

5'd9: time_end = 16'd10215; //M2,

5'd10: time_end = 16'd9100; //M3,

5'd11: time_end = 16'd8589; //M4,

5'd12: time_end = 16'd7652; //M5,

5'd13: time_end = 16'd6817; //M6,

5'd14: time_end = 16'd6073; //M7,

5'd15: time_end = 16'd5740; //H1,

5'd16: time_end = 16'd5107; //H2,

5'd17: time_end = 16'd4549; //H3,

5'd18: time_end = 16'd4294; //H4,

5'd19: time_end = 16'd3825; //H5,

5'd20: time_end = 16'd3408; //H6,

5'd21: time_end = 16'd3036; //H7,

default:time_end = 16'd65535;

endcase

end




reg [17:0] time_cnt;

//当蜂鸣器使能时,计数器按照计数终值(分频系数)计数

always@(posedge clk_in or negedge rst_n_in) begin

if(!rst_n_in) begin

time_cnt <= 1'b0;

end else if(!tone_en) begin

time_cnt <= 1'b0;

end else if(time_cnt>=time_end) begin

time_cnt <= 1'b0;

end else begin

time_cnt <= time_cnt + 1'b1;

end

end




//根据计数器的周期,翻转蜂鸣器控制信号

always@(posedge clk_in or negedge rst_n_in) begin

if(!rst_n_in) begin

piano_out <= 1'b0;

end else if(time_cnt==time_end) begin

piano_out <= ~piano_out; //蜂鸣器控制输出翻转,两次翻转为1Hz

end else begin

piano_out <= piano_out;

end

end

Endmodule

这个是从电子森林的简易电子琴设计中学习到的代码,并进行了修改,使之能够被其他模块直接调用。

https://www.eetree.cn/wiki/%E7%AE%80%E6%98%93%E7%94%B5%E5%AD%90%E7%90%B4%E8%AE%BE%E8%AE%A1

module time_alarm(clk_in,clk_out);//5HZ

input clk_in;

output clk_out;

reg clk_out;

reg[24:0] times;

initial

    times = 0;

always @(posedge clk_in)

    begin

 if(times < 1200000)

     times = times+1;

 else

     begin

  clk_out = ~clk_out;

  times = 0;

  end

 end

Endmodule

使用该模块将12Mhz的Clk变成5Hz的低频信号,用于计时。

module ds18b20

(

input clk_in, //系统时钟

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

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

output reg [3:0] data_temp_a,

output reg [3:0] data_temp_b,

output reg [3:0] data_temp_c

);




/*

本设计通过驱动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;

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

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

reg clk_1mhz;

reg [2:0] cnt_1mhz;

always@(data_out)

begin

data_temp_a<=data_out[10:4]/10;

data_temp_b<=data_out[10:4]%10;

data_temp_c<=data_out[3]*5+data_out[2]*2+data_out[1];

end

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

DS18B20的温度采集是比较头疼的一个地方,输出的是两个8位的字节数据,我采用的是直接将温度的各个位上数字分离,然后依次发送给其他模块。

 

三、反思和总结

反思:

我认为这个项目最大的难点在模块之间的配合和提前的思考,必须在开始敲代码之前进行全面、认真的设计和思考,否则写一个模块,想起一个功能,就加一个功能,导致最终就会出现各种问题。FPGA设计必须具备全局观念,而不能像单片机那样想到什么写什么,必须考虑互相之间的配合。

 

建议:

显示屏好丑啊,如果能更换为彩色屏幕就更好啦!

扩展板可以增加MPU6050模块,功能就更强了!

 

最后感谢能参加这次寒假一起练活动,让我在假期的生活充实和丰富了起来,学习到了Verilog的基本使用。如果有可能,我很愿意参加下一次类似的一起练活动!

附件下载
ymy.sof
ymy.pof
团队介绍
四川大学锦江学院 电气与电子信息工程学院
团队成员
余梦雅
一只野生的电子工程师
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号