项目需求
本项目为基于Lattice XO2-4000HC的一个完成定时、测温、报警、控制的小项目,具体功能如下:
- 定时时钟功能:在OLED显示屏上显示时间,时间精确到分钟。并可以通过板载按键对时间进行设置。同时在到达整点时蜂鸣器报警。
- 温度计功能:利用板载温度传感器实时监测环境温度并与时间一起显示在OLED屏幕上。
- 温度报警功能:在整点报警的同时,将当前温度信息以异步串口通信的方式传输到电脑上,并在电脑上显示温度信息。
- 控制蜂鸣器功能:通过uart发送一段音频到板子上,并通过接收到的数据控制蜂鸣器播放。在播放期间OLED上的时间和温度信息暂停更新,在播放后恢复更新。
项目实现过程
图1:顶层设计文件
代码说明:本设计代码都是采用明德杨至简设计法设计的,因为程序比较易读,便于调试,可能会比平时大家见到的代码篇幅较长,但是评价FPGA的程序好坏并不是以代码的长短来评价的吧,而是应该用生成电路所占用的资源以及电路所能跑的最快时钟来评价吧,就比如说FPGA实现乘法,你可以直接使用*,也可以用一段代码通过移位实现,综合出来的电路很多时候是直接使用*所占资源较多;我实现这个设计由于温度精确到四位小数,不管是数据处理还是显示资源使用都应该会多一点,下面是我的资源使用情况:
图2:资源占用
1、顶层模块
1.1、顶层信号
定义 | |||
clk | I | 1 | 系统时钟,12M |
rst_n | I | 1 | 系统复位,低电平有效 |
key | I | 3 | 按键输入信号 |
uart_rx | I | 1 | 串口输入信号 |
uart_tx | O | 1 | 串口输出信号 |
beep | O | 1 | 蜂鸣器驱动信号 |
res | O | 1 | OLED复位信号,低电平有效 |
cs | O | 1 | OLED片选信号 |
dc | O | 1 | OLED数据/指令指示信号 |
q | IO | 1 | ds18b20数据线 |
sclk | O | 1 | OLED时钟信号 |
sdin | O | 1 | OLED数据信号线 |
1.2、参考代码
assign q = dq_out_en ? dq_out:1'bz;
assign dq_in = q;
/**************时钟部分例化************/
key uut_key(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key ),
.key_vld(key_vld)
);
time_data uut_time_data(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key_vld ),
.times (times ),
.times_vld (times_vld ),
.time_flag (time_flag )
);
/*****************温度部分例化*************/
temp_come uut_twmp_come(
.clk (clk ),
.rst_n (rst_n ),
.rst_en (rst_en ),
.wr_en (wr_en ),
.wdata (wdata ),
.rd_en (rd_en ),
.rdy (rdy )
);
temp_byte uut_temp_byte(
.clk (clk ),
.rst_n (rst_n ),
.rst_en (rst_en ),
.wr_en (wr_en ),
.wdata (wdata ),
.rd_en (rd_en ),
.rdata (rdata ),
.data (data ),
.data_vld (data_vld ),
.rdy (rdy ),
.rst_en_bit (rst_en_bit ),
.wr_en_bit (wr_en_bit ),
.wdata_bit (wdata_bit ),
.rd_en_bit (rd_en_bit ),
.rdata_vld (rdata_vld ),
.rdy_bit (rdy_bit )
);
temp_bit uut_temp_bit(
.clk (clk ),
.rst_n (rst_n ),
.rst_en (rst_en_bit ),
.wr_en (wr_en_bit ),
.rd_en (rd_en_bit ),
.wdata (wdata_bit ),
.rdata (rdata ),
.rdata_vld (rdata_vld ),
.rdy (rdy_bit ),
.dq_out (dq_out ),
.dq_out_en (dq_out_en ),
.dq_in (dq_in )
);
temp_data uut_temp_data(
.clk (clk ),
.rst_n (rst_n ),
.din_temp (data ),
.din_temp_vld (data_vld ),
.temp_uns (temp_uns ),
.temp_uns_vld (temp_uns_vld )
);
temp_tx uut_temp_tx(
.clk (clk ),
.rst_n (rst_n ),
.din (temp_uns ),
.din_vld (time_flag ),//整点时才有效;
.uart_rdy (uart_rdy ),
.uart_tx_din (uart_tx_din ),
.uart_tx_din_vld(uart_tx_din_vld)
);
uart_tx uut_uart_tx(
.clk (clk ),
.rst_n (rst_n ),
.tx_data (uart_tx_din ),
.tx_vld (uart_tx_din_vld),
.uart_tx (uart_tx ),
.tx_rdy (uart_rdy )
);
/*************蜂鸣器例化******************/
uart_rx uut_uart_rx(
.clk (clk ),
.rst_n (rst_n ),
.uart_rx (uart_rx),
.rx_data (rx_data),
.rx_vld (rx_vld )
);
cy uut_cy(
.clk (clk ),
.rst_n (rst_n ),
.din (rx_data ),
.din_vld (rx_vld ),
.flag (beep_flag ),
.cycle (cycle ),
.beat_flag (beat_flag )
);
pwm#(.DATA_W(16)) uut_pwm(
.clk (clk ),
.rst_n (rst_n ),
.flag (beep_flag ),
.pwm (beep ),
.x (cycle ),
.duty (cycle>>1 ),
.beat_flag (beat_flag )
);
/************oled*****************/
temp_time_control uut_temp_time_control(
.clk (clk ),
.rst_n (rst_n ),
.flag (beep_flag ),
.times (times ),
.times_vld (times_vld ),
.temp_uns (temp_uns ),
.temp_uns_vld (temp_uns_vld),
.dout (dout ),
.dout_vld (dout_vld )
);
oled_control uut_oled_control(
.clk (clk ),
.rst_n (rst_n ),
.din (dout ),
.din_vld (dout_vld ),
.rdy (rdy_spi ),
.wr_en (wr_en_spi ),
.wdata (wdata_spi ),
.res (res )
);
spi uut_spi(
.clk (clk ),
.rst_n (rst_n ),
.wr_en (wr_en_spi ),
.wdata (wdata_spi ),
.cs (cs ),
.dc (dc ),
.sclk (sclk ),
.sdin (sdin ),
.rdy (rdy_spi )
);
2、时钟模块
2.1、按键消抖模块
主要经过一个计数器对输入信号进行消抖;
2.1.1、接口信号
信号 | 输入/输出 | 位宽 | 定义 |
clk | I | 1 | 系统时钟,12M |
rst_n | I | 1 | 系统复位 |
key | I | 1 | 按键输入信号 |
key_vld | O | 3 | 按键消抖输出信号 |
2.1.2、该模块可以参考至简设计系列_按键控制数字时钟
2.2、时钟产生模块
简述:由于时间需要显示,为了不增加数据处理模块,所以主要利用七个计数器实现,秒计时计数器,秒个位计数器,秒十位计数器,分个位计数器,分十位计数器,时个位计数器,时十位计数器,输出信号也是由后六个计数器拼接而成,assign times = {cnt_ss,cnt_sg,cnt_fs,cnt_fg,cnt_ms,cnt_mg};按键设置时钟思路,使用三个按键实现,第一个按键进行模式选择,正常时为计数模式,按下之后进入设置模式,再次按下又进入计时模式;第二个按键要在设置模式下才有效,用于对时钟的哪一个位设置的选择,起始设置秒的个位,按一下之后对秒的十位进行设置,依次增加循环;第三个按键用于相应位的数据设置,仅在设置模式有效,按一下相应位数据加一,溢出后回到0;比如开始计数时钟位00:00:00;我们需要设置位01:58:50;那么操作是按下按键1进入设置模式,按下按键2选中秒的十位,按5下按键3,让秒十位加5;之后再次按一下按键2选中分的个位,按八下按键3设置位分个位为8,之后依次操作,设置完之后按按键1回到计时模式正常计数;注意times = {cnt_ss,cnt_sg,cnt_fs,cnt_fg,cnt_ms,cnt_mg}必须使用组合电路,不然最后会出现bug;
2.2.1、接口信号
信号 | 输入/输出 | 位宽 | 定义 |
clk | I | 1 | 系统时钟,12M |
rst_n | I | 1 | 系统复位 |
key_in | I | 3 | 按键输入信号 |
times | O | 20 |
时钟拼接输出信号,[3:0]秒个位;[6:4]秒十位;[10:7]分个位,[13:11]分十位,[17:14]时个位,[19:18]时十位; |
times_vld | O | 1 | 时钟输出有效指示信号 |
times_flag | O | 1 | 整点标志信号 |
2.2.2、参考代码
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key0_flag <= 1'b0;
end
else if(key_in[0])begin
key0_flag <= ~key0_flag;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key2_flag <= 1'b0;
end
else if(key0_flag && key_in[2])begin
key2_flag <= 1'b1;
end
else begin
key2_flag <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = key0_flag && key_in[1];
assign end_cnt0 = add_cnt0 && cnt0==6-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = !key0_flag;
assign end_cnt1 = add_cnt1 && cnt1==TIME_1S-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_mg <= 0;
end
else if(add_cnt_mg)begin
if(end_cnt_mg)
cnt_mg <= 0;
else
cnt_mg <= cnt_mg + 1;
end
end
assign add_cnt_mg = (key0_flag && cnt0==0 && key2_flag) || (!key0_flag && end_cnt1);
assign end_cnt_mg = add_cnt_mg && cnt_mg== 10-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_ms <= 0;
end
else if(add_cnt_ms)begin
if(end_cnt_ms)
cnt_ms <= 0;
else
cnt_ms <= cnt_ms + 1;
end
end
assign add_cnt_ms = (key0_flag && cnt0==1 && key2_flag) || (!key0_flag && end_cnt_mg);
assign end_cnt_ms = add_cnt_ms && cnt_ms==6-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_fg <= 0;
end
else if(add_cnt_fg)begin
if(end_cnt_fg)
cnt_fg <= 0;
else
cnt_fg <= cnt_fg + 1;
end
end
assign add_cnt_fg = (key0_flag && cnt0==2 && key2_flag) || (!key0_flag && end_cnt_ms);
assign end_cnt_fg = add_cnt_fg && cnt_fg==10-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_fs <= 0;
end
else if(add_cnt_fs)begin
if(end_cnt_fs)
cnt_fs <= 0;
else
cnt_fs <= cnt_fs + 1;
end
end
assign add_cnt_fs = (key0_flag && cnt0==3 && key2_flag) || (!key0_flag && end_cnt_fg);
assign end_cnt_fs = add_cnt_fs && cnt_fs==6-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_sg <= 0;
end
else if(add_cnt_sg)begin
if(end_cnt_sg)
cnt_sg <= 0;
else
cnt_sg <= cnt_sg + 1;
end
end
assign add_cnt_sg = (key0_flag && cnt0==4 && key2_flag) || (!key0_flag && end_cnt_fs);
assign end_cnt_sg = add_cnt_sg && cnt_sg==x-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_ss <= 0;
end
else if(add_cnt_ss)begin
if(end_cnt_ss)
cnt_ss <= 0;
else
cnt_ss <= cnt_ss + 1;
end
end
assign add_cnt_ss = (key0_flag && cnt0==5 && key2_flag) || (!key0_flag && end_cnt_sg);
assign end_cnt_ss = add_cnt_ss && cnt_ss== 3-1;
always @(*)begin
if(cnt_ss==2)begin
x = 4;
end
else begin
x = 10;
end
end
always @(*)begin
if(rst_n==1'b0)begin
time_flag <= 1'b0;
end
else if((cnt_sg!=0 || cnt_ss!=0) && cnt_fs==0 && cnt_fg==0 && cnt_ms==0 && cnt_mg==0 && add_cnt_mg)begin
time_flag <= 1'b1;
end
else begin
time_flag <= 1'b0;
end
end
assign times = {cnt_ss,cnt_sg,cnt_fs,cnt_fg,cnt_ms,cnt_mg};
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
times_vld <= 1'b0;
end
else if(add_cnt_mg || add_cnt_ms || add_cnt_fg || add_cnt_fs || add_cnt_sg || add_cnt_ss)begin
times_vld <= 1'b1;
end
else begin
times_vld <= 1'b0;
end
end
3、温度模块
3.1、ds18b20驱动模块
此部分分为了三个模块,temp_bit负责接收上游模块的指令和数据,实现对温度传感器DS18B20的复位、读和写,也就是实现怎么写,怎么读;temp_byte模块根据要求去复位、写1字节或读一字节;最后temp_come实现什么时候复位、读或写,以及写什么;
3.1.1、temp_bit
3.1.1.1、接口信号
信号 | 输入/输出 | 位宽 | 定义 |
clk | I | 1 | 系统时钟,12M |
rst_n | I | 1 | 系统复位 |
rst_en | I | 1 | ds18b20复位使能信号 |
wr_en | I | 1 | ds18b20写使能信号 |
rd_en | I | 1 | ds18b20读使能信号 |
wdata | I | 1 | 写数据信号 |
rdata | O | 1 | 从ds18b20读到的数据 |
rdata_vld | O | 1 | 读数据有效指示信号 |
dq_out | O | 1 | 三态门输入信号 |
dq_out_en | O | 1 | 三态门使能端 |
dq_in | I | 1 | ds18b20输入本模块的数据 |
rdy | O | 1 | 模块忙闲指示信号 |
3.1.1.2、参考代码
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑always模块,描述状态转移条件判断
always@(*)begin
case(state_c)
IDLE:begin
if(idl2rst_start)begin
state_n = RST_S;
end
else if(idl2wr_start)begin
state_n = WR_S;
end
else if(idl2rd_start)begin
state_n = RD_S;
end
else begin
state_n = state_c;
end
end
RST_S:begin
if(rst2idl_start)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
WR_S:begin
if(wr2idl_start)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
RD_S:begin
if(rd2idl_start)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:begin
state_n = IDLE;
end
endcase
end
//第三段:设计转移条件
assign idl2rst_start = state_c==IDLE && rst_en;
assign idl2wr_start = state_c==IDLE && wr_en ;
assign idl2rd_start = state_c==IDLE && rd_en ;
assign rst2idl_start = state_c==RST_S&& cnt==TIME_RST-1;
assign wr2idl_start = state_c==WR_S && cnt==TIME_WR-1;
assign rd2idl_start = state_c==RD_S && cnt==TIME_RD-1;
/**********************************************************
计数器cnt:初始值为0,加一条件:不在IDLE状态,add_cnt=state_c!=IDLE;
结束条件:复位,读,写结束,end_cnt=(rst2idl_start||wr2idl_start||rd2idl_start)&&add_cnt;
**********************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = state_c!=IDLE;
assign end_cnt = add_cnt && ((state_c==RST_S && cnt==TIME_RST-1) || wr2idl_start || rd2idl_start);
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wdata_ff0 <= 1'b0;
end
else if(state_c==IDLE && rst_en==1'b0 && wr_en)begin
wdata_ff0 <= wdata;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dq_out <= 1'b1;
end
else if(state_c==RST_S)begin
if(cnt<TIME_RST_LOW)
dq_out <= 1'b0;
else
dq_out <= 1'b1;
end
else if(state_c==WR_S)begin
if(cnt<TIME_WR_INSTR)
dq_out <= 1'b0;
else
dq_out <= wdata_ff0;
end
else if(state_c==RD_S)begin
if(cnt<TIME_RD_INSTR)
dq_out <= 1'b0;
else
dq_out <= 1'b1;
end
else begin
dq_out <= 1'b1;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dq_out_en <= 1'b0;
end
else if(state_c!=IDLE)begin
if(state_c==RD_S && cnt<TIME_RD_INSTR)
dq_out_en <= 1'b1;
else if(state_c==RST_S && cnt<TIME_RST_LOW)
dq_out_en <= 1'b1;
else if(state_c==WR_S && cnt<TIME_WR_DATA)
dq_out_en <= 1'b1;
else
dq_out_en <= 1'b0;
end
else begin
dq_out_en <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdata <= 1'b0;
end
else if(state_c==RD_S && cnt==TIME_RD_GET-1)begin
rdata <= dq_in;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdata_vld <= 1'b0;
end
else if(state_c==RD_S && cnt==TIME_RD_GET-1)begin
rdata_vld <= 1'b1;
end
else begin
rdata_vld <= 1'b0;
end
end
always @(*)begin
if(rst_en || wr_en || rd_en || state_c!=IDLE)begin
rdy = 1'b0;
end
else begin
rdy = 1'b1;
end
end
3.1.2、temp_byte模块
3.1.2.1、接口信号
信号 | 输入/输出 | 位宽 | 定义 |
clk | I | 1 | 系统时钟 |
rst_n | I | 1 | 系统复位 |
rst_en | I | 1 | come模块复位使能信号 |
wr_en | I | 1 | come模块写使能信号 |
wdata | I | 8 | come给出的写数据 |
rd_en | I | 1 | come模块读使能信号 |
rdata | I | 1 | 从bit模块读取温度单bit数据 |
data | O | 8 | 输出1byte温度数据 |
data_vld | O | 1 | 输出温度数据有效指示信号 |
rdy | O | 1 | 模块忙闲指示信号 |
rst_en_bit | O | 1 | bit模块复位使能信号 |
wr_en_bit | O | 1 | bit模块写使能信号 |
wdata_bit | O | 1 | 给bit模块单bit数据 |
rd_en_bit | O | 1 | bit模块读使能信号 |
rdy_bit | I | 1 | bit模块忙闲指示信号 |
rdata_vld | I | 1 | 从bit读单bit数据有效指示信号 |
3.1.2.2、参考代码
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑always模块,描述状态转移条件判断
always@(*)begin
case(state_c)
IDLE:begin
if(idl2rst_start)begin
state_n = RST;
end
else if(idl2wr_start)begin
state_n = WR;
end
else if(idl2rd_start)begin
state_n = RD;
end
else begin
state_n = state_c;
end
end
RST:begin
if(rst2idl_start)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
WR:begin
if(wr2idl_start)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
RD:begin
if(rd2idl_start)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:begin
state_n = IDLE;
end
endcase
end
//第三段:设计转移条件
assign idl2rst_start = state_c==IDLE && rst_en;
assign idl2wr_start = state_c==IDLE && wr_en ;
assign idl2rd_start = state_c==IDLE && rd_en ;
assign rst2idl_start = state_c==RST && end_cnt;
assign wr2idl_start = state_c==WR && end_cnt;
assign rd2idl_start = state_c==RD && end_cnt;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = (state_c==RD && rdata_vld) || ((state_c==RST || state_c==WR) && rdy_bit);
assign end_cnt = add_cnt && cnt==x;
always @(*)begin
if(state_c==WR || state_c==RD)begin
x = 7;
end
else begin
x = 0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rst_en_bit <= 1'b0;
end
else if(state_c==RST && rdy_bit)begin
rst_en_bit <= 1'b1;
end
else begin
rst_en_bit <=1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_en_bit <= 1'b0;
end
else if(state_c==WR && rdy_bit)begin
wr_en_bit <= 1'b1;
end
else begin
wr_en_bit <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wdata_ff0 <= 0;
end
else if(state_c==IDLE && rst_en==1'b0 && wr_en)begin
wdata_ff0 <= wdata;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wdata_bit <= 1'b0;
end
else if(state_c==WR && rdy_bit)begin
wdata_bit <= wdata_ff0[cnt];
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_en_bit <= 1'b0;
end
else if(state_c==RD && rdy_bit)begin
rd_en_bit <= 1'b1;
end
else begin
rd_en_bit <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data <= 0;
end
else if(state_c==RD && rdata_vld)begin
data[cnt] <= rdata;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
data_vld <= 1'b0;
end
else if(state_c==RD && rdata_vld && cnt==7)begin
data_vld <= 1'b1;
end
else begin
data_vld <= 1'b0;
end
end
always @(*)begin
if(rst_en || wr_en || rd_en || state_c!=IDLE)begin
rdy = 1'b0;
end
else begin
rdy = 1'b1;
end
end
3.1.3、temp_come模块
3.1.3.1、接口信号
信号 | 输入/输出 | 位宽 | 定义 |
clk | I | 1 | 系统时钟 |
rst_n | I | 1 | 系统复位 |
rst_en | O | 1 | byte模块复位使能信号 |
wr_en | O | 1 | byte模块写使能 |
wdata | O | 8 | byte模块写数据 |
rd_en | O | 1 | byte模块读使能 |
rdy | I | 1 | byte忙闲指示信号 |
3.1.3.2、参考代码
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = (rdy && !delay_flag) || end_cnt_750ms;
assign end_cnt = add_cnt && cnt== 8-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_750ms <= 0;
end
else if(add_cnt_750ms)begin
if(end_cnt_750ms)
cnt_750ms <= 0;
else
cnt_750ms <= cnt_750ms + 1;
end
end
assign add_cnt_750ms = delay_flag && rdy;
assign end_cnt_750ms = add_cnt_750ms && cnt_750ms== TIME_750MS-1;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
delay_flag <= 1'b0;
end
else if(wdata==8'h44)begin
delay_flag <= 1'b1;
end
else if(add_cnt)begin
delay_flag <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rst_en <= 1'b0;
end
else if(add_cnt && (cnt==0 || cnt==3))begin
rst_en <= 1'b1;
end
else begin
rst_en <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_en <= 1'b0;
end
else if(add_cnt && (cnt==1 || cnt==2 || cnt==4 || cnt==5))begin
wr_en <= 1'b1;
end
else begin
wr_en <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_en <= 1'b0;
end
else if(add_cnt && (cnt==6 || cnt==7))begin
rd_en <= 1'b1;
end
else begin
rd_en <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wdata <= 0;
end
else if(add_cnt && (cnt==1 || cnt==4))begin
wdata <= 8'hCC;
end
else if(add_cnt && cnt==2)begin
wdata <= 8'h44;
end
else if(add_cnt && cnt==5)begin
wdata <= 8'hBE;
end
else begin
wdata <= 0;
end
end
3.2、温度数据处理部分
此部分包括temp_data数据处理模块和temp_tx模块,temp_data先读取temp_byte模块输出的温度信息,将补码转化位二进制原码,之后利用BCD和hex2bcd模块分别将小数以及整数部分转化位BCD码格式,转化方法为“大四加三”,用两个模块的原因在于整数、小数部分同时进行转化,由于处理数据位宽不同,故使用两个模块;之后整点指示信号times_flag有效时,temp_tx模块将温度数据以及一些字符数据转化位ASCII码发送给串口发送模块uart_tx进行发送;
3.2.1、temp_data模块
3.2.1.1、接口信号
信号 | 输入/输出 | 位宽 | 定义 |
clk | I | 1 | 系统时钟 |
rst_n | I | 1 | 系统复位 |
din_temp | I | 8 | byte温度输出信号 |
din_temp_vld | I | 1 | byte温度输出有效指示信号 |
temp_uns | O | 32 | 温度数据BCD码格式输出信号[15:0]四位小数,[27:16]三位整数,[31:28]符号位,为F表示负数,0表示正数 |
temp_uns_vld | O | 1 | 温度数据输出有效指示信号 |
3.2.1.2、参考代码
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = din_temp_vld;
assign end_cnt = add_cnt && cnt==1;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
ds_temp_lsb_req <= 8'b0;
ds_temp_lsb_req_vld <= 1'b0;
end
else if(cnt==0 && add_cnt)begin
ds_temp_lsb_req <= din_temp;
ds_temp_lsb_req_vld <= 1'b1;
end
else begin
ds_temp_lsb_req_vld <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
ds_temp_msb_req <= 8'b0;
ds_temp_msb_req_vld <= 1'b0;
end
else if(end_cnt)begin
ds_temp_msb_req <= din_temp;
ds_temp_msb_req_vld <= 1'b1;
end
else begin
ds_temp_msb_req_vld <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
ds_temp_req <= 0;
ds_temp_req_vld <= 1'b0;
end
else if(ds_temp_msb_req_vld)begin
ds_temp_req <= {ds_temp_msb_req,ds_temp_lsb_req};
ds_temp_req_vld <= 1'b1;
end
else begin
ds_temp_req_vld <= 1'b0;
end
end
assign ds_temp_req_rev = {~ds_temp_req}+1;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
temp_hex_dot <= 0;
temp_hex_dot_vld <= 1'b0;
end
else if(ds_temp_req_vld)begin
temp_hex_dot <= ((ds_temp_msb_req[7]? ds_temp_req_rev[3:0]:ds_temp_req[3:0])<<9)+((ds_temp_msb_req[7]? ds_temp_req_rev[3:0]:ds_temp_req[3:0])<<6)+((ds_temp_msb_req[7]? ds_temp_req_rev[3:0]:ds_temp_req[3:0])<<5)+((ds_temp_msb_req[7]? ds_temp_req_rev[3:0]:ds_temp_req[3:0])<<4)+(ds_temp_msb_req[7]? ds_temp_req_rev[3:0]:ds_temp_req[3:0]);
temp_hex_dot_vld <= 1'b1;
end
else begin
temp_hex_dot_vld <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
temp_hex_int <= 0;
temp_hex_int_vld <= 1'b0;
end
else if(ds_temp_req_vld)begin
temp_hex_int <= (ds_temp_msb_req[7]? ds_temp_req_rev[10:4]:ds_temp_req[10:4]);
temp_hex_int_vld <= 1'b1;
end
else begin
temp_hex_int_vld <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
temp_uns_sign <= 1'b0;
end
else if(ds_temp_msb_req_vld)begin
temp_uns_sign <= ds_temp_msb_req[7];
end
end
//将小数部分转化为BCD码;
hex2bcd uut_bcd(
.clk (clk ),
.rst_n (rst_n ),
.din (temp_hex_dot ),
.din_vld (temp_hex_dot_vld ),
.dout (temp_dot_dout ),
.dout_vld (temp_dot_dout_vld )
);
//将整数部分转化为BCD码;
bcd uut_bcd1(
.clk (clk ),
.rst_n (rst_n ),
.din ({1'b0,temp_hex_int}),
.din_vld (temp_hex_int_vld ),
.dout (temp_int_dout ),
.dout_vld (temp_int_dout_vld )
);
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
temp_uns <= 0;
end
else if(temp_int_dout_vld || temp_dot_dout_vld)begin
temp_uns <= {{4{temp_uns_sign}},temp_int_dout,temp_dot_dout};
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_vld <= 0;
end
else if(add_cnt_vld)begin
if(end_cnt_vld)
cnt_vld <= 0;
else
cnt_vld <= cnt_vld + 1;
end
end
assign add_cnt_vld =temp_int_dout_vld || temp_dot_dout_vld;
assign end_cnt_vld = add_cnt_vld && cnt_vld==2-1;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
temp_uns_vld <= 1'b0;
end
else if(end_cnt_vld)begin
temp_uns_vld <= 1'b1;
end
else begin
temp_uns_vld <= 1'b0;
end
end
3.2.1.3、BCD模块和hex2bcd模块放在附件之中,原理可以参考至简设计系列_BCD译码实现
3.2.2、temp_tx模块
3.2.2.1、接口信号
信号 | 输入/输出 | 位宽 | 定义 |
clk | I | 1 | 系统时钟信号 |
rst_n | I | 1 | 系统复位信号 |
din | I | 32 | 温度数据输入信号 |
din_vld | I | 1 | 温度数据输入有效指示信号 |
uart_tx_din | O | 8 | ASCII码输出信号 |
uart_tx_din_vld | O | 1 | ASCII码输出信号有效指示信号 |
uart_rdy | I | 1 | 串口发送模块忙闲指示信号 |
3.2.2.1、参考代码
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
din_f <= 0;
end
else if(din_vld && !flag)begin
din_f <= din;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = flag && uart_rdy;
assign end_cnt = add_cnt && cnt== 20-1;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag <= 1'b0;
end
else if(din_vld && !flag)begin
flag <= 1'b1;
end
else if(end_cnt && flag)begin
flag <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
uart_tx_din <= 1'b0;
end
else if(add_cnt)begin
case(cnt)
0 : uart_tx_din <= 8'hB5;//当
1 : uart_tx_din <= 8'hB1;
2 : uart_tx_din <= 8'hC7;//前
3 : uart_tx_din <= 8'hB0;
4 : uart_tx_din <= 8'hCE;//温
5 : uart_tx_din <= 8'hC2;
6 : uart_tx_din <= 8'hB6;//度
7 : uart_tx_din <= 8'hC8;
8 : uart_tx_din <= 8'h3A;//:
9 : uart_tx_din <= data;//+或者-
10,11,12,14,15,16,17
: uart_tx_din <= data +8'h30;//将数字转化为ASCII码
13: uart_tx_din <= 8'h2E;//.
18: uart_tx_din <= 8'h0D;//换行符
19: uart_tx_din <= 8'h0A;
default : uart_tx_din <= 8'h00;
endcase
end
end
always @(*)begin
if(add_cnt)begin
case(cnt)
9 :
begin
if({din_f[31:28]}==4'h0)
data = 8'h2B;
else if({din_f[31:28]}==4'hF)
data = 8'h2D;
else
data = 0;
end
10 : data = din_f[27:24];
11 : data = din_f[23:20];
12 : data = din_f[19:16];
14 : data = din_f[15:12];
15 : data = din_f[11:8 ];
16 : data = din_f[7 :4 ];
17 : data = din_f[3:0] ;
default : data = 0;
endcase
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
uart_tx_din_vld <= 1'b0;
end
else if(add_cnt)begin
uart_tx_din_vld <= 1'b1;
end
else begin
uart_tx_din_vld <= 1'b0;
end
end
3.3、uart_tx串口发送模块
主要功能是将并行八位数据转化为按1位起始位,8位数据位,1位结束位的格式串行输出,波特率115200,由于上游模块一个时钟可以一次输入1byte数据,而本模块多个时钟一次只能输出1bit,故需要一个忙闲指示信号rdy来控制上游模块发数据的速度;
设计思路可以参考串口发送模块
3、蜂鸣器模块
本模块串口接收数据后按所收的八位二进制数据产生不同持续时间不同频率的PWM波驱动蜂鸣器,还要让OLED停止更新数据,也就是让OLED模块的输入数据有效指示信号在这段时间内无效,所以要引入一个标志信号flag,收到第一个数据拉高,收到最后一个数据拉低即可;串口发送的八位数据作用分别如下,第一个数据和最后一个数据最高位为1,用于产生flag信号,[6:4]用于确定PWM波持续产生的总时间,[3:0]用于确定PWM波的频率;
3.1、uart_rx串口接收模块
使用两个计数器实现,1位起始位,8位数据位,1位停止位,波特率位115200,关键是uart_rx信号是外来信号,应该打两拍减小异步信号的影响,防止产生亚稳态;设计思路可以参考串口接收模块
3.2、cy模块
3.2.1、参考代码
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag <= 1'b0;
end
else if(din_vld && din[7] && !flag)begin
flag <= 1'b1;
end
else if(din_vld && din[7] && flag)begin
flag <= 1'b0;
end
end
always @(*)begin
if(rst_n==1'b0)begin
beat <= 0;
tone <= 0;
end
else if(din_vld)begin
beat <= din[6:4];
tone <= din[3:0];
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = beat_flag;
assign end_cnt = add_cnt && cnt==beat_length-1;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
beat_flag <= 1'b0;
end
else if(din_vld)begin
beat_flag <= 1'b1;
end
else if(end_cnt)begin
beat_flag <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
beat_length <= 0;
end
else if(din_vld)begin
case(beat)
0 : beat_length <= 12_000_000;//4拍;
1 : beat_length <= 6_000_000;//2拍;
2 : beat_length <= 2_760_000;//1拍;
3 : beat_length <= 1_500_000 ;//1/2拍;
4 : beat_length <= 750_000 ;//1/4拍;
default : beat_length <= 0;
endcase
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cycle <= 0;
end
else if(din_vld)begin
case(tone)
0 : cycle <= 16'd22931; //M1,
1 : cycle <= 16'd20432; //M2,
2 : cycle <= 16'd18201; //M3,
3 : cycle <= 16'd17180; //M4,
4 : cycle <= 16'd15306; //M5,
5 : cycle <= 16'd13636; //M6,
6 : cycle <= 16'd12148; //M7,
7 : cycle <= 16'd11478; //H1,
8 : cycle <= 16'd10215; //H2,
9 : cycle <= 16'd9010 ; //H3,
10: cycle <= 16'd8590 ; //H4,
11: cycle <= 16'd7653 ; //H5,
12: cycle <= 16'd6818 ; //H6,
13: cycle <= 16'd6074 ; //H7,
default:cycle <= 16'd0; //cycle为0,PWM占空比为0,低电平
endcase
end
end
3.3、PWM模块参考代码
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = flag && beat_flag;
assign end_cnt = add_cnt && cnt==x-1;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
pwm <= 1'b1;
end
else if(add_cnt && cnt==duty-1)begin
pwm <= 1'b0;
end
else if(end_cnt || !flag || !beat_flag)begin
pwm <= 1'b1;
end
end
4、OLED模块
要让OLED显示,应该要解决三个问题,下面三个模块解决这三个问题;
4.1、问题:什么时候显示,显示什么?
答:本设计要显示时间和温度并且蜂鸣器模块接收数据时显示不变,所以当温度数据有效指示信号或者时间有效指示信号有效,并且蜂鸣器模块标志信号flag无效时,将温度数据和时间数据传给下游模块对OLED进行显示,其他时刻均不传送有效信号给下游模块;这部分由temp_time_ control模块实现;
4.1.1、接口信号
信号 | 输入/输出 | 位宽 | 定义 |
clk | I | 1 | 系统时钟 |
rst_n | I | 1 | 系统复位 |
flag | I | 1 | 蜂鸣器模块标志信号 |
times | I | 20 | 时钟信号 |
times_vld | I | 1 | 时钟信号有效指示信号 |
temp_uns | I | 33 | 温度数据信号 |
temp_uns_vld | I | 1 | 温度数据有效指示信号 |
dout | O | 48 | 温度时钟拼接信号 |
dout_vld | O | 1 | 温度时钟拼接有效指示信号 |
4.1.2、参考代码
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout <= 0;
end
else if(temp_uns_vld || times_vld)begin
dout <= {times,temp_uns[27:0]};
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout_vld <= 1'b0;
end
else if((times_vld || temp_uns_vld) && !flag)begin
dout_vld <= 1'b1;
end
else begin
dout_vld <= 1'b0;
end
end
4.2、问题:OLED显示需要发送什么?比如复位,初始化,数据转化
答:系统复位之后要先进行复位100ms,之后发送初始化指令,之后对屏幕进行清屏,然后就可以进行正常显示了,需要配置的OLED寄存器信息和显示字符所需要的字模都在配置表文件中保存,通过addr进行读取,10bit位宽的信号add_wdata为保存配置信息的信号,其中add_wdata[9]表示OLED的片选CS信息,当其为0时,表示此时有效;add_wdata[8]表示数据/指令指示信号,当其为1时,表示add_wdata[7:0]为8bit数据,当其为0时,表示add_wdata[7:0]为8bit指令;配置表前30个为OLED初始化所需指令,后面依次存放0,1,2,3,4,5,6,7,8,9,时,间,“:“这些需要显示字符的字模;主要通过一个状态机,外加两个计数器实现;通过oled_control模块实现;
4.2.1、接口信号
信号 | 输入/输出 | 位宽 | 定义 |
clk | I | 1 | 系统时钟 |
rst_n | I | 1 | 系统复位 |
din | I | 48 | 输入温度时钟拼接信号 |
din_vld | I | 1 | 输入温度时钟拼接有效指示信号 |
rdy | I | 1 | SPI模块忙闲指示信号 |
wr_en | O | 1 | SPI写使能信号 |
wdata | O | 10 | SPI写数据信号 |
res | O | 1 | OLED复位信号 |
4.2.2、这个模块代码不上传了,虽然很简单,由于有配置表,所以有七百多行代码;
4.3、问题:FPGA怎么将信号发给OLED?
答:该OLED模块通过SPI进行通信,所以采用SPI格式发送数据即可,用两个计数即可以实现;用SPI模块实现即可;
spi模块参考代码:
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = flag;
assign end_cnt0 = add_cnt0 && cnt0==6-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==16-1;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag <= 1'b0;
end
else if(wr_en && !flag)begin
flag <= 1'b1;
end
else if(end_cnt1)begin
flag <= 1'b0;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wdata_ff0 <= 0;
end
else if(wr_en)begin
wdata_ff0 <= wdata;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cs <= 1'b1;
end
else if(flag&&cnt1<8)begin
cs <= wdata_ff0[9];
end
else begin
cs <= 1'b1;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dc <= 1'b1;
end
else if(flag&&cnt1<8)begin
dc <= wdata_ff0[8];
end
else begin
dc <= 1'b1;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sclk <= 1'b1;
end
else if(add_cnt0 && cnt0==0 && cnt1<8)begin
sclk <= 1'b0;
end
else if(add_cnt0 && cnt0==3)begin
sclk <= 1'b1;
end
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sdin <= 1'b0;
end
else if(add_cnt0 && cnt0==0 && cnt1<8)begin
sdin <= wdata_ff0[7-cnt1];
end
end
always @(*)begin
if(wr_en || flag)begin
rdy = 1'b0;
end
else begin
rdy = 1'b1;
end
end
总结:整个过程需要的代码其实很多网上都可以找到,但是大多都与我的编程风格差距太大,及其不便于阅读,调试,看起来跟C语言差不多,最后所有代码还是按我的风格写完了,其实与FPGA有关的代码调试在十多天之前就已经完成了,之后上位机卡了一段时间,再加上同时在学STM32和AD,所以一直没有录制视频上传;录制视频时那个温度其实PC显示和OLED显示在同一时间是相同的,由于一个手机录制,所以没有办法调节到相同时间看两处的信息,不管是温度值还是格式都是相同的,最后用PY写的上位机由于有50多M,所以没法上传,在使用时要先输入串口,输入一个串口对应数字就行,之后就可以按开始传输,把文件传输给FPGA;