内容介绍
内容介绍
功能描述
- 实现一个可定时时钟的功能,用小脚丫FPGA核心模块的4个按键设置当前的时间,OLED显示数字钟的当前时间,精确到分钟即可,到整点的时候比如8:00,蜂鸣器报警,播放音频信号,最长可持续30秒;
- 实现温度计的功能,小脚丫通过板上的温度传感器实时测量环境温度,并同时间一起显示在OLED的屏幕上;
- 定时时钟整点报警的同时,将温度信息通过UART传递到电脑上,电脑上能够显示当前板子上的温度信息(任何显示形式都可以),要与OLED显示的温度值一致;
- PC收到报警的温度信号以后,将一段音频文件(自己制作,持续10秒钟左右)通过UART发送给小脚丫FPGA,蜂鸣器播放收到的这段音频文件,OLED屏幕上显示的时间信息和温度信息都停住不再更新;
- 音频文件播放完毕,OLED开始更新时间信息和当前的温度信息
模块划分
设计思路
温度信息获取模块
首先计数器分频产生1MHz的时钟信号,再使用1MHz时钟信号做触发完成状态机的功能。状态机分为IDLE状态、MAIN状态、INIT状态、WRITE状态、READ状态和DELAY状态。获取到16位的温度输出后,将其按照各位的含义,处理得到温度的整数部分和小数部分。最后,利用“加三移位法”将十六进制表示的数转为BCD码形式,以便在OLED上显示。
STEP FPGA驱动温度传感器DS18B20Z
module temperature_display
(
input clk_in, //系统时钟
input rst_n_in, //系统复位,低有效
inout one_wire, //DS18B20Z传感器单总线,双向管脚
output reg [15:0] data_out //DS18B20Z有效温度数据输出
);
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
蜂鸣器模块
不同的震荡频率会产生不同的音节,通过赋给蜂鸣器不同的频率会奏出一段音乐。
具体乐谱在上位机中,使用python。
STEP FPGA驱动无源蜂鸣器模块
UART收发器
使用UART在板子和PC端进行数据传送。当整点时,向上位机发送温度信息,并接收上位机传输的乐谱信息,记录计时终点。
异步收发器UART
设置模式模块
在设置时间模块和计时模块之间切换,需确保时钟的相同。
//模式切换
always @(posedge clk,negedge rst_n)begin
if(!rst_n)begin
mode <= 1'b1;
end
else if(key_pulse[0])begin//表示模式键已经按下
mode <= ~mode;
end
else begin
mode <= mode;
end
end
//设置时间
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
hh1 <= h1;
hh2 <= h2;
mm1 <= m1;
mm2 <= m2;
end
else if(!mode)begin
if(key_pulse[1])begin//时钟位增加一
hh2 <= hh2 + 4'd1;
if(hh2 == 4'd9)begin
hh2 <= 4'd0;
hh1 <= hh1 + 4'd1;
end
else if(hh2 == 4'd3 && hh1 == 4'd2)begin
hh1 <= 4'd0;
hh2 <= 4'd0;
end
end
else if(key_pulse[2])begin//分钟位增加一
mm2 <= mm2 + 4'd1;
if(mm2 == 4'd9)begin
mm2 <= 4'd0;
mm1 <= mm1 + 4'd1;
if(mm1 == 4'd5)begin
mm1 <= 4'd0;
mm2 <= 4'd0;
end
end
end
end
else begin
hh1 <= h1;
hh2 <= h2;
mm1 <= m1;
mm2 <= m2;
end
end
OLED显示
一开始不明白OLED显示屏的工作原理和编程方式,后来经过查阅资料,并利用网上的点阵字库生成器生成16*16的点阵字库数据。
OLED显示屏有4页,这里参考了电子森林的开源代码和吴子贵同学的显示方式:1、2列显示时钟数据,3、4列显示温度数据,并且将5*8点阵编程为8*8。
资源占用
上位机
借鉴吴子贵同学的乐谱及上位机代码,使用pyqt5制作简单的界面。
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'untitled.ui'
#
# Created by: PyQt5 UI code generator 5.15.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
import threading
import serial
import sys
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(600, 300)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(40, 60, 81, 31))
font = QtGui.QFont()
font.setPointSize(18)
self.label.setFont(font)
self.label.setObjectName("label")
self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
self.textBrowser.setGeometry(QtCore.QRect(140, 60, 241, 41))
self.textBrowser.setObjectName("textBrowser")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 509, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "当前温度:"))
def refreshText(self):
if (ser.isOpen()):
line = ser.read(5)
if (line):
data = line.decode('gb18030') + "℃"; # 形成温度字符串
self.textBrowser.setText(data) # 更新标签温度
ser.write(bytes.fromhex(wdata)) # 转为16进制数据发送
else:
print("open failed")
timer = threading.Timer(0.001, self.refreshText)
timer.start()
if __name__ == '__main__':
port = "COM4"
bps = 9600
# 打开串口
ser = serial.Serial(port, int(bps), timeout=0.01, bytesize=8, parity=serial.PARITY_NONE, stopbits=1)
wdata = "B9 17 B9 6C B9 17 B9 6C B9 17 B9 6C A1 1A B9 6C B9 6C A1 1A B9 6C F1 F2 F3 F4 B9 17 6C 16 6C 16 A1 1A A1 1A A1 1A F3 13 F3 13 F3 13 C5 11 C5 11 C5 11 C5 11 F3 13 F3 13 F3 13 F3 13 F3 13 F3 13 F3 13 F3 13 F3 13 6C 16 B9 17 E4 1D 8C 23 E7 27 8C 23 8C 23 8C 23 8C 23 E4 1D E4 1D E4 1D A1 1A A1 1A A1 1A 6C 16 B9 17 6C 16 6C 16 DC 0B DC 0B"
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
心得体会
- 对OLED显示屏和蜂鸣器有了新的了解。
- 与微型计算机课程相联系,对Uart收发、时钟分频模块都有了更深入的了解。
- 上位机编写较为简单,可以后期加入更多的功能,如扫描串口等。
附件下载
project.zip
烧录文件及上位机代码
团队介绍
北理电信
团队成员
杨夏巍
电信
评论
0 / 100
查看更多