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