京微齐力:基于P1P060的OLED动态显示(温湿度实时数据)
目录
- 日常·唠嗑
- 实验结果
- 一、硬件解析
- 1、国产FPGA:HME-P1P060
- 2、0.96寸OLED
- 3、DTH11温湿度模块(比较枯燥,请耐心看完原理)
- 二、程序设计
- 1、DTH11数据采集(状态机)
- 2、字符显示
- 3、字符输出
- 三、波形抓取(重要)
- 四、获取工程
- 五、参考文献
日常·唠嗑
最近师傅说,搞个层次深点的网表探测demo✨✨,之前一直想试试0.96寸OLED动态显示,一直没搞,也考虑到大家毕设或者竞赛上需要用到,现在借机会在P1P060上做一个,程序参考了FPGA之旅以及正点原子的DTH11设计,在此鸣谢😁。
实验结果
从视频中,可以看到,当手指捏住传感器后,OLED屏上的温湿度数据发生变化。
基于FPGA的OLED动态显示(温湿度实时数据)
一、硬件解析
1、国产FPGA:HME-P1P060
58K逻辑单元,36K LUT-6,4个PLL,32个全局时钟,144个DSP,基本上已经满足了学生等级开发,感兴趣的,可以去米联客店铺看看。
2、0.96寸OLED
OLED的显示,这里不做介绍,我在另一篇文章有介绍过,感兴趣的可以看看:京微齐力:基于HMEP060的OLED字符显示(及FUXI工程建立演示)
3、DTH11温湿度模块(比较枯燥,请耐心看完原理)
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。
上图为DHT11的内部原理图,可以看出感湿原件、感温元件和OTP内存直接连接在内部一个八位MCU上,该MCU通过计算得出测量数值。
DATA用于FPGA与DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分为整数部分和小数部分,数据格式如下:
一次完整的数据传输为40bit,高位在前。数据格式:
8bit 湿度整数数据
+8bit湿度小数数据
+8bit 温度整数数据
+8bit温度小数数据
+8bit 校验和数据
数据传送正确时校验和数据等于“8bit湿度整数数据 + 8bit湿度小数数据 + 8bit温度整数数据 + 8bit温度小数数据”所得结果的末8位。校验和 = 数据之和,通过这个可以判断接收到的数据是否正确。
下面说一下,数据传输时序:
主机(此处指FPGA)首先发送一次开始信号,即:拉低数据线,保持t1(至少18ms)时间;然后拉高数据线保持t2(20~40us)时间,随后开始读取DHT11的响应;如果操作正确的话,DHT11会拉低数据线,保持t3(80us)时间,作为响应信号;接下来DHT11会拉高数据线,保持t4(80us)时间,随后开始输出有效数据。
DHT11共输出40bit有效数据,每1bit数据都是以50us低电平开始,高电平的持续时间作为判断数据位的条件。当数据位为0时,高电平的持续时间为26~28us;当数据位为1时,高电平的持续时间为70us。
DHT11数据位“0”时序图和数据位“1”时序图如图:
需要注意的是,DHT11的温度和湿度转换较慢,如果读取速度过快会导致DHT11无法响应的情况。
二、程序设计
1、DTH11数据采集(状态机)
DHT11驱动模块使用三段式状态机来读取DHT11的温度和湿度值,从下图可以比较直观的看到每个状态实现的功能以及跳转都下一个状态的条件。这里需要注意的一点是,由于DHT11温度和湿度转换较慢,如果读取速度过快会导致DHT11无法响应的情况,所以我们在每次读操作结束后延时两秒(这样鲁棒性会比较高,也可以不要这步)。
在时序图中,提到了18ms,26-28us,20-40us,50us,70us,80us等等,但是经过分析后,并不需要定义这么多个计时周期数,只需要定义两个即可。为什么只定义这两个就可以了呢?
在时序图中,需要FPGA判断时间的,有两个位置,一个是FPGA拉低18ms以上,另外一个是判断数据表示是数据0还是数据1。第一个很清楚就是18ms。数据0表示的数据位26-28us,为了保险起见,这里设置为35us,如果高电平的持续时间低于35us,那么就表示数据0。
//**************************************** Message ***********************************//
//技术交流:bumianzhe@126
//关注CSDN博主:“千歌叹尽执夏”
//Author: 千歌叹尽执夏
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: DHT11
// Last modified Date: 2022年9月25日18:20:00
// Last Version: V1.1
// Descriptions: 温湿度数据获取模块
//----------------------------------------------------------------------------------------
//****************************************************************************************//module DHT11(input sys_clk,input rst_n,input dht11_req, //dht11数据采集请求output dht11_done, //dht11数据采集结束output dht11_error, //dht11数据采集正确与否判断 1为错误output[7:0] tempH, //温度数据整数output[7:0] tempL, //温度数据小数output[7:0] humidityH, //温度数据整数output[7:0] humidityL, //温度数据小数inout dht11
);//时钟为50MHZ,20ns
localparam TIME18ms = 'd1000_099; //开始态的拉低18ms,900_000个时钟周期,这里适当的延长了拉低时间。
localparam TIME35us = 'd1_750; //数据传输过程中,数据0拉高的出现localparam S_IDLE ='d0; //空闲态
localparam S_START_FPGA ='d1; //FPGA请求采集数据开始
localparam S_START_DHT11 ='d2; //DHT11开始请求应答
localparam S_DATA ='d3; //数据传输
localparam S_STOP ='d4; //数据结束
localparam S_DOEN ='d5; //数据采集完成//S_delay主要是为了读取太快,防止DTH11无法响应,这里我们是动态显示,所以调快点,这个延时就不要了。
//localparam S_delay ='d6; //延时等待,延时完成后重新操作DHT11reg[2:0] state , next_state;
reg[22:0] DHT11_Cnt; //计时器
reg[5:0] DHT11Bit_Cnt; // 接收dht11传输bit数计数
reg[39:0] dht11_data;
reg dht11_d0 , dht11_d1;
wire dht11_negedge; //检测dht11的上下边沿
assign dht11_negedge = (~dht11_d0) & dht11_d1;assign dht11 = (state == S_START_FPGA && (DHT11_Cnt <= TIME18ms)) ? 1'b0 : 1'bz;
assign dht11_done = (state == S_DOEN) ? 1'b1 : 1'b0;assign tempH = dht11_data[23:16];
assign tempL = dht11_data[15:8];assign humidityH = dht11_data[39:32];
assign humidityL = dht11_data[31:24];always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)begindht11_d0 <= 1'b1;dht11_d1 <= 1'b1;endelse begindht11_d0 <= dht11;dht11_d1 <= dht11_d0;end
endalways@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)state <= S_IDLE;elsestate <= next_state;
endalways@(*) //这里用任意触发好点
begincase(state)S_IDLE:if(dht11_req == 1'b1) //数据采集请求过来进入开始态next_state <= S_START_FPGA;elsenext_state <= S_IDLE;S_START_FPGA: if((DHT11_Cnt >= TIME18ms) && dht11_negedge == 1'b1) //FPGA请求结束结束next_state <= S_START_DHT11;elsenext_state <= S_START_FPGA;S_START_DHT11:if((DHT11_Cnt > TIME35us) && dht11_negedge == 1'b1) //延时一段时间后,通过判断dht11总线的下降沿,是否结束响应next_state <= S_DATA;elsenext_state <= S_START_DHT11;S_DATA:if(DHT11Bit_Cnt == 'd39 && dht11_negedge == 1'b1) //接收到40bit数据后,进入停止态next_state <= S_STOP;elsenext_state <= S_DATA;S_STOP:if(DHT11_Cnt == TIME35us + TIME35us) //数据传输完成后,等待总线拉低50us,这里是70usnext_state <= S_DOEN;elsenext_state <= S_STOP;S_DOEN:next_state <= S_IDLE;default: next_state <= S_IDLE;endcase
end/*计数模块*/
always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)DHT11_Cnt <= 'd0;else if(state != next_state) //状态变换的时候,计时器置0DHT11_Cnt <= 'd0; else if(state == S_START_FPGA) DHT11_Cnt <= DHT11_Cnt + 1'b1;else if(state == S_START_DHT11)DHT11_Cnt <= DHT11_Cnt + 1'b1;else if(state == S_DATA && dht11_negedge == 1'b0) //数据的时候,只需要在高电平的时候计数DHT11_Cnt <= DHT11_Cnt + 1'b1;else if(state == S_STOP)DHT11_Cnt <= DHT11_Cnt + 1'b1;elseDHT11_Cnt <= 'd0;
end/*接收数据存储*/
always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)dht11_data <= 'd0;else if(state == S_DATA)if((DHT11_Cnt <= (TIME35us + 'd2500)) && dht11_negedge == 1'b1) //'d3000为低电平时间,高电平持续时间低于35us认为是数据0dht11_data <= {dht11_data[38:0],1'b0};else if(dht11_negedge == 1'b1)dht11_data <= {dht11_data[38:0],1'b1};elsedht11_data <= dht11_data;elsedht11_data <= dht11_data;
end/*接收bit计数*/
always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)DHT11Bit_Cnt <= 'd0;else if(state == S_DATA && dht11_negedge == 1'b1)DHT11Bit_Cnt <= DHT11Bit_Cnt + 1'b1;else if(state == S_DOEN) //结束后,bit计数清零DHT11Bit_Cnt <= 'd0;elseDHT11Bit_Cnt <= DHT11Bit_Cnt;
end
//例化debugware IP核,抓取传感器波形
debugware_v2_1 u_debugware_v2_1(.trig_out_0(),.data_in_0(dht11_data),.ref_clk_0(sys_clk)
);
endmodule
2、字符显示
OLED的显示(包括字符提取等),这里不做介绍,我在另一篇文章有介绍过,感兴趣的可以看看:京微齐力:基于HMEP060的OLED字符显示(及FUXI工程建立演示)
3、字符输出
代码中有注释,可以看注释
//**************************************** Message ***********************************//
//技术交流:bumianzhe@126
//关注CSDN博主:“千歌叹尽执夏”
//Author: 千歌叹尽执夏
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: OLED_Top
// Last modified Date: 2022年9月24日18:20:00
// Last Version: V1.1
// Descriptions: OLED顶层模块
//----------------------------------------------------------------------------------------
//****************************************************************************************//module OLED_Top(input sys_clk,input rst_n,//DHT11数值显示input dht11_done,input[7:0] tempH, input[7:0] tempL,input[7:0] humidityH,input[7:0] humidityL,//OLED IICoutput OLED_SCL,inout OLED_SDA);//**************************************** Message ***********************************//
本例中,OLED显示一共分为如下5个状态。在初始化完成后,屏幕显示的是杂乱无章的数据,所以在初始化完成后,要进行一次刷新,将OLED中的数据全部写0。然后进行显示固定不变的支符,例如像温度湿度这样的字符。然后进入空闲态,直到DHT11采集到数据后,就进入到数据显示状态,数据显示完成后,又回到空闲态。这是整个的一个流程。
//**********************************************************************************//localparam OLED_INIT = 'd0; //初始化
localparam OLED_Refresh = 'd1; //刷新,将oled全部写0
localparam OLED_ShowFont = 'd2; //显示字符
localparam OLED_IDLE = 'd3; //空闲
localparam OLED_ShowData = 'd4; //显示数据reg[4:0] state , next_state;//IIC相关信号
wire IICWriteReq;
wire[23:0] IICWriteData;
wire IICWriteDone;//初始化相关信号
wire init_finish;
wire[23:0] Init_data;
wire init_req;//refresh相关信号
wire refresh_finish;
wire[23:0] refresh_data;
wire refresh_req;//字符显示相关信号
wire showfont_finish;
wire[23:0] showfont_data;
wire showfont_req;//显示数据相关信号
wire showdata_finish;
wire[23:0] showdata_data;
wire showdata_req;assign init_req = (state == OLED_INIT) ? 1'b1 : 1'b0;assign refresh_req = (state == OLED_Refresh) ? 1'b1 : 1'b0;assign showfont_req = (state == OLED_ShowFont) ? 1'b1 : 1'b0;assign showdata_req = (state == OLED_ShowData) ? 1'b1 : 1'b0;always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)state <= OLED_INIT;elsestate <= next_state;
endalways@(*)
begincase(state)OLED_INIT:if(init_finish == 1'b1)next_state <= OLED_Refresh;elsenext_state <= OLED_INIT;OLED_Refresh:if(refresh_finish == 1'b1)next_state <= OLED_ShowFont;elsenext_state <= OLED_Refresh;OLED_ShowFont:if(showfont_finish == 1'b1)next_state <= OLED_IDLE;elsenext_state <= OLED_ShowFont;OLED_IDLE:if(dht11_done == 1'b1)next_state <= OLED_ShowData;elsenext_state <= OLED_IDLE;OLED_ShowData:if(showdata_finish == 1'b1)next_state <= OLED_IDLE;elsenext_state <= OLED_ShowData;default: next_state <= OLED_INIT;endcase
endOLED_Init OLED_InitHP(.sys_clk (sys_clk),.rst_n (rst_n),.init_req (init_req), //初始化请求.write_done (IICWriteDone), //一组初始化数据完成信号.init_finish (init_finish), //初始化完成输出.Init_data (Init_data) //初始化的数据
);OLED_Refresh(.sys_clk (sys_clk),.rst_n (rst_n),.refresh_req (refresh_req), //初始化请求.write_done (IICWriteDone), //一组初始化数据完成信号.refresh_finish (refresh_finish), //初始化完成输出.refresh_data (refresh_data) //初始化的数据
);OLED_ShowFont OLED_ShowFont_HP(.sys_clk (sys_clk),.rst_n (rst_n),.ShowFont_req (showfont_req), //字符显示请求.write_done (IICWriteDone), //iic一组数据写完成.ShowFont_Data (showfont_data), //字符显示数据.ShowFont_finish (showfont_finish) //字符显示完成
);OLED_ShowData OLED_ShowDataHP(.sys_clk (sys_clk),.rst_n (rst_n),.dht11_done (dht11_done),.tempH (tempH), //温度数据整数.tempL (tempL), //温度数据小数.humidityH (humidityH), //温度数据整数.humidityL (humidityL), //温度数据小数.ShowData_req (showdata_req), //字符显示请求.write_done (IICWriteDone), //iic一组数据写完成.ShowData_Data (showdata_data), //字符显示数据.ShowData_finish (showdata_finish) //字符显示完成);//数据选择
OLED_SelData OLED_SelDataHP(.sys_clk (sys_clk),.rst_n (rst_n),.init_req (init_req),.init_data (Init_data),.refresh_req (refresh_req),.refresh_data (refresh_data),.showfont_req (showfont_req),.showfont_data (showfont_data),.showdata_req (showdata_req),.showdata_data (showdata_data),.IICWriteReq (IICWriteReq), .IICWriteData (IICWriteData)
);IIC_Driver IIC_DriverHP_OLED(.sys_clk (sys_clk), /*系统时钟*/.rst_n (rst_n), /*系统复位*/.IICSCL (OLED_SCL), /*IIC 时钟输出*/.IICSDA (OLED_SDA), /*IIC 数据线*/.IICSlave ({IICWriteData[15:8],IICWriteData[23:16]}), /*从机 8bit的寄存器地址 + 8bit的从机地址*/.IICWriteReq (IICWriteReq), /*IIC写寄存器请求*/.IICWriteDone (IICWriteDone), /*IIC写寄存器完成*/.IICWriteData (IICWriteData[7:0]), /*IIC发送数据 8bit的数据*/.IICReadReq (1'b0), /*IIC读寄存器请求*/.IICReadDone (), /*IIC读寄存器完成*/.IICReadData () /*IIC读取数据*/
);endmodule
三、波形抓取(重要)
在工程中,例化了debugware IP核,对温湿度传感器的波形进行抓取,可以看到如下:
前文有说过,传感器的数据位总共有40位,考虑到后续可能抓取别的波形,所以此处debugware的数据位宽我设置到了45位,预留了5位。
这里抓到的波形是:2E 0019 034A
对应二进制即:0010_1110_0000_0000_0001_1001_0000_0011_0100_1010
根据上文数据公式:8bit 湿度整数数据+8bit湿度小数数据+8bit 温度整数数据+8bit温度小数数据
+8bit 校验和数据
所以此刻的湿度:46.00(10_1110_0000_0000) 温度:25.03(0001_1001_0000_0011)
可以看到,抓取的波形数据与实际情况完全吻合😁。
四、获取工程
直接点链接下载:京微齐力:基于FPGA的OLED动态显示(温湿度实时数据)
五、参考文献
1、开拓者FPGA 开发指南V1.5
2、FPGA之旅设计99例
京微齐力:基于P1P060的OLED动态显示(温湿度实时数据)
目录
- 日常·唠嗑
- 实验结果
- 一、硬件解析
- 1、国产FPGA:HME-P1P060
- 2、0.96寸OLED
- 3、DTH11温湿度模块(比较枯燥,请耐心看完原理)
- 二、程序设计
- 1、DTH11数据采集(状态机)
- 2、字符显示
- 3、字符输出
- 三、波形抓取(重要)
- 四、获取工程
- 五、参考文献
日常·唠嗑
最近师傅说,搞个层次深点的网表探测demo✨✨,之前一直想试试0.96寸OLED动态显示,一直没搞,也考虑到大家毕设或者竞赛上需要用到,现在借机会在P1P060上做一个,程序参考了FPGA之旅以及正点原子的DTH11设计,在此鸣谢😁。
实验结果
从视频中,可以看到,当手指捏住传感器后,OLED屏上的温湿度数据发生变化。
基于FPGA的OLED动态显示(温湿度实时数据)
一、硬件解析
1、国产FPGA:HME-P1P060
58K逻辑单元,36K LUT-6,4个PLL,32个全局时钟,144个DSP,基本上已经满足了学生等级开发,感兴趣的,可以去米联客店铺看看。
2、0.96寸OLED
OLED的显示,这里不做介绍,我在另一篇文章有介绍过,感兴趣的可以看看:京微齐力:基于HMEP060的OLED字符显示(及FUXI工程建立演示)
3、DTH11温湿度模块(比较枯燥,请耐心看完原理)
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。
上图为DHT11的内部原理图,可以看出感湿原件、感温元件和OTP内存直接连接在内部一个八位MCU上,该MCU通过计算得出测量数值。
DATA用于FPGA与DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分为整数部分和小数部分,数据格式如下:
一次完整的数据传输为40bit,高位在前。数据格式:
8bit 湿度整数数据
+8bit湿度小数数据
+8bit 温度整数数据
+8bit温度小数数据
+8bit 校验和数据
数据传送正确时校验和数据等于“8bit湿度整数数据 + 8bit湿度小数数据 + 8bit温度整数数据 + 8bit温度小数数据”所得结果的末8位。校验和 = 数据之和,通过这个可以判断接收到的数据是否正确。
下面说一下,数据传输时序:
主机(此处指FPGA)首先发送一次开始信号,即:拉低数据线,保持t1(至少18ms)时间;然后拉高数据线保持t2(20~40us)时间,随后开始读取DHT11的响应;如果操作正确的话,DHT11会拉低数据线,保持t3(80us)时间,作为响应信号;接下来DHT11会拉高数据线,保持t4(80us)时间,随后开始输出有效数据。
DHT11共输出40bit有效数据,每1bit数据都是以50us低电平开始,高电平的持续时间作为判断数据位的条件。当数据位为0时,高电平的持续时间为26~28us;当数据位为1时,高电平的持续时间为70us。
DHT11数据位“0”时序图和数据位“1”时序图如图:
需要注意的是,DHT11的温度和湿度转换较慢,如果读取速度过快会导致DHT11无法响应的情况。
二、程序设计
1、DTH11数据采集(状态机)
DHT11驱动模块使用三段式状态机来读取DHT11的温度和湿度值,从下图可以比较直观的看到每个状态实现的功能以及跳转都下一个状态的条件。这里需要注意的一点是,由于DHT11温度和湿度转换较慢,如果读取速度过快会导致DHT11无法响应的情况,所以我们在每次读操作结束后延时两秒(这样鲁棒性会比较高,也可以不要这步)。
在时序图中,提到了18ms,26-28us,20-40us,50us,70us,80us等等,但是经过分析后,并不需要定义这么多个计时周期数,只需要定义两个即可。为什么只定义这两个就可以了呢?
在时序图中,需要FPGA判断时间的,有两个位置,一个是FPGA拉低18ms以上,另外一个是判断数据表示是数据0还是数据1。第一个很清楚就是18ms。数据0表示的数据位26-28us,为了保险起见,这里设置为35us,如果高电平的持续时间低于35us,那么就表示数据0。
//**************************************** Message ***********************************//
//技术交流:bumianzhe@126
//关注CSDN博主:“千歌叹尽执夏”
//Author: 千歌叹尽执夏
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: DHT11
// Last modified Date: 2022年9月25日18:20:00
// Last Version: V1.1
// Descriptions: 温湿度数据获取模块
//----------------------------------------------------------------------------------------
//****************************************************************************************//module DHT11(input sys_clk,input rst_n,input dht11_req, //dht11数据采集请求output dht11_done, //dht11数据采集结束output dht11_error, //dht11数据采集正确与否判断 1为错误output[7:0] tempH, //温度数据整数output[7:0] tempL, //温度数据小数output[7:0] humidityH, //温度数据整数output[7:0] humidityL, //温度数据小数inout dht11
);//时钟为50MHZ,20ns
localparam TIME18ms = 'd1000_099; //开始态的拉低18ms,900_000个时钟周期,这里适当的延长了拉低时间。
localparam TIME35us = 'd1_750; //数据传输过程中,数据0拉高的出现localparam S_IDLE ='d0; //空闲态
localparam S_START_FPGA ='d1; //FPGA请求采集数据开始
localparam S_START_DHT11 ='d2; //DHT11开始请求应答
localparam S_DATA ='d3; //数据传输
localparam S_STOP ='d4; //数据结束
localparam S_DOEN ='d5; //数据采集完成//S_delay主要是为了读取太快,防止DTH11无法响应,这里我们是动态显示,所以调快点,这个延时就不要了。
//localparam S_delay ='d6; //延时等待,延时完成后重新操作DHT11reg[2:0] state , next_state;
reg[22:0] DHT11_Cnt; //计时器
reg[5:0] DHT11Bit_Cnt; // 接收dht11传输bit数计数
reg[39:0] dht11_data;
reg dht11_d0 , dht11_d1;
wire dht11_negedge; //检测dht11的上下边沿
assign dht11_negedge = (~dht11_d0) & dht11_d1;assign dht11 = (state == S_START_FPGA && (DHT11_Cnt <= TIME18ms)) ? 1'b0 : 1'bz;
assign dht11_done = (state == S_DOEN) ? 1'b1 : 1'b0;assign tempH = dht11_data[23:16];
assign tempL = dht11_data[15:8];assign humidityH = dht11_data[39:32];
assign humidityL = dht11_data[31:24];always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)begindht11_d0 <= 1'b1;dht11_d1 <= 1'b1;endelse begindht11_d0 <= dht11;dht11_d1 <= dht11_d0;end
endalways@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)state <= S_IDLE;elsestate <= next_state;
endalways@(*) //这里用任意触发好点
begincase(state)S_IDLE:if(dht11_req == 1'b1) //数据采集请求过来进入开始态next_state <= S_START_FPGA;elsenext_state <= S_IDLE;S_START_FPGA: if((DHT11_Cnt >= TIME18ms) && dht11_negedge == 1'b1) //FPGA请求结束结束next_state <= S_START_DHT11;elsenext_state <= S_START_FPGA;S_START_DHT11:if((DHT11_Cnt > TIME35us) && dht11_negedge == 1'b1) //延时一段时间后,通过判断dht11总线的下降沿,是否结束响应next_state <= S_DATA;elsenext_state <= S_START_DHT11;S_DATA:if(DHT11Bit_Cnt == 'd39 && dht11_negedge == 1'b1) //接收到40bit数据后,进入停止态next_state <= S_STOP;elsenext_state <= S_DATA;S_STOP:if(DHT11_Cnt == TIME35us + TIME35us) //数据传输完成后,等待总线拉低50us,这里是70usnext_state <= S_DOEN;elsenext_state <= S_STOP;S_DOEN:next_state <= S_IDLE;default: next_state <= S_IDLE;endcase
end/*计数模块*/
always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)DHT11_Cnt <= 'd0;else if(state != next_state) //状态变换的时候,计时器置0DHT11_Cnt <= 'd0; else if(state == S_START_FPGA) DHT11_Cnt <= DHT11_Cnt + 1'b1;else if(state == S_START_DHT11)DHT11_Cnt <= DHT11_Cnt + 1'b1;else if(state == S_DATA && dht11_negedge == 1'b0) //数据的时候,只需要在高电平的时候计数DHT11_Cnt <= DHT11_Cnt + 1'b1;else if(state == S_STOP)DHT11_Cnt <= DHT11_Cnt + 1'b1;elseDHT11_Cnt <= 'd0;
end/*接收数据存储*/
always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)dht11_data <= 'd0;else if(state == S_DATA)if((DHT11_Cnt <= (TIME35us + 'd2500)) && dht11_negedge == 1'b1) //'d3000为低电平时间,高电平持续时间低于35us认为是数据0dht11_data <= {dht11_data[38:0],1'b0};else if(dht11_negedge == 1'b1)dht11_data <= {dht11_data[38:0],1'b1};elsedht11_data <= dht11_data;elsedht11_data <= dht11_data;
end/*接收bit计数*/
always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)DHT11Bit_Cnt <= 'd0;else if(state == S_DATA && dht11_negedge == 1'b1)DHT11Bit_Cnt <= DHT11Bit_Cnt + 1'b1;else if(state == S_DOEN) //结束后,bit计数清零DHT11Bit_Cnt <= 'd0;elseDHT11Bit_Cnt <= DHT11Bit_Cnt;
end
//例化debugware IP核,抓取传感器波形
debugware_v2_1 u_debugware_v2_1(.trig_out_0(),.data_in_0(dht11_data),.ref_clk_0(sys_clk)
);
endmodule
2、字符显示
OLED的显示(包括字符提取等),这里不做介绍,我在另一篇文章有介绍过,感兴趣的可以看看:京微齐力:基于HMEP060的OLED字符显示(及FUXI工程建立演示)
3、字符输出
代码中有注释,可以看注释
//**************************************** Message ***********************************//
//技术交流:bumianzhe@126
//关注CSDN博主:“千歌叹尽执夏”
//Author: 千歌叹尽执夏
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: OLED_Top
// Last modified Date: 2022年9月24日18:20:00
// Last Version: V1.1
// Descriptions: OLED顶层模块
//----------------------------------------------------------------------------------------
//****************************************************************************************//module OLED_Top(input sys_clk,input rst_n,//DHT11数值显示input dht11_done,input[7:0] tempH, input[7:0] tempL,input[7:0] humidityH,input[7:0] humidityL,//OLED IICoutput OLED_SCL,inout OLED_SDA);//**************************************** Message ***********************************//
本例中,OLED显示一共分为如下5个状态。在初始化完成后,屏幕显示的是杂乱无章的数据,所以在初始化完成后,要进行一次刷新,将OLED中的数据全部写0。然后进行显示固定不变的支符,例如像温度湿度这样的字符。然后进入空闲态,直到DHT11采集到数据后,就进入到数据显示状态,数据显示完成后,又回到空闲态。这是整个的一个流程。
//**********************************************************************************//localparam OLED_INIT = 'd0; //初始化
localparam OLED_Refresh = 'd1; //刷新,将oled全部写0
localparam OLED_ShowFont = 'd2; //显示字符
localparam OLED_IDLE = 'd3; //空闲
localparam OLED_ShowData = 'd4; //显示数据reg[4:0] state , next_state;//IIC相关信号
wire IICWriteReq;
wire[23:0] IICWriteData;
wire IICWriteDone;//初始化相关信号
wire init_finish;
wire[23:0] Init_data;
wire init_req;//refresh相关信号
wire refresh_finish;
wire[23:0] refresh_data;
wire refresh_req;//字符显示相关信号
wire showfont_finish;
wire[23:0] showfont_data;
wire showfont_req;//显示数据相关信号
wire showdata_finish;
wire[23:0] showdata_data;
wire showdata_req;assign init_req = (state == OLED_INIT) ? 1'b1 : 1'b0;assign refresh_req = (state == OLED_Refresh) ? 1'b1 : 1'b0;assign showfont_req = (state == OLED_ShowFont) ? 1'b1 : 1'b0;assign showdata_req = (state == OLED_ShowData) ? 1'b1 : 1'b0;always@(posedge sys_clk or negedge rst_n)
beginif(rst_n == 1'b0)state <= OLED_INIT;elsestate <= next_state;
endalways@(*)
begincase(state)OLED_INIT:if(init_finish == 1'b1)next_state <= OLED_Refresh;elsenext_state <= OLED_INIT;OLED_Refresh:if(refresh_finish == 1'b1)next_state <= OLED_ShowFont;elsenext_state <= OLED_Refresh;OLED_ShowFont:if(showfont_finish == 1'b1)next_state <= OLED_IDLE;elsenext_state <= OLED_ShowFont;OLED_IDLE:if(dht11_done == 1'b1)next_state <= OLED_ShowData;elsenext_state <= OLED_IDLE;OLED_ShowData:if(showdata_finish == 1'b1)next_state <= OLED_IDLE;elsenext_state <= OLED_ShowData;default: next_state <= OLED_INIT;endcase
endOLED_Init OLED_InitHP(.sys_clk (sys_clk),.rst_n (rst_n),.init_req (init_req), //初始化请求.write_done (IICWriteDone), //一组初始化数据完成信号.init_finish (init_finish), //初始化完成输出.Init_data (Init_data) //初始化的数据
);OLED_Refresh(.sys_clk (sys_clk),.rst_n (rst_n),.refresh_req (refresh_req), //初始化请求.write_done (IICWriteDone), //一组初始化数据完成信号.refresh_finish (refresh_finish), //初始化完成输出.refresh_data (refresh_data) //初始化的数据
);OLED_ShowFont OLED_ShowFont_HP(.sys_clk (sys_clk),.rst_n (rst_n),.ShowFont_req (showfont_req), //字符显示请求.write_done (IICWriteDone), //iic一组数据写完成.ShowFont_Data (showfont_data), //字符显示数据.ShowFont_finish (showfont_finish) //字符显示完成
);OLED_ShowData OLED_ShowDataHP(.sys_clk (sys_clk),.rst_n (rst_n),.dht11_done (dht11_done),.tempH (tempH), //温度数据整数.tempL (tempL), //温度数据小数.humidityH (humidityH), //温度数据整数.humidityL (humidityL), //温度数据小数.ShowData_req (showdata_req), //字符显示请求.write_done (IICWriteDone), //iic一组数据写完成.ShowData_Data (showdata_data), //字符显示数据.ShowData_finish (showdata_finish) //字符显示完成);//数据选择
OLED_SelData OLED_SelDataHP(.sys_clk (sys_clk),.rst_n (rst_n),.init_req (init_req),.init_data (Init_data),.refresh_req (refresh_req),.refresh_data (refresh_data),.showfont_req (showfont_req),.showfont_data (showfont_data),.showdata_req (showdata_req),.showdata_data (showdata_data),.IICWriteReq (IICWriteReq), .IICWriteData (IICWriteData)
);IIC_Driver IIC_DriverHP_OLED(.sys_clk (sys_clk), /*系统时钟*/.rst_n (rst_n), /*系统复位*/.IICSCL (OLED_SCL), /*IIC 时钟输出*/.IICSDA (OLED_SDA), /*IIC 数据线*/.IICSlave ({IICWriteData[15:8],IICWriteData[23:16]}), /*从机 8bit的寄存器地址 + 8bit的从机地址*/.IICWriteReq (IICWriteReq), /*IIC写寄存器请求*/.IICWriteDone (IICWriteDone), /*IIC写寄存器完成*/.IICWriteData (IICWriteData[7:0]), /*IIC发送数据 8bit的数据*/.IICReadReq (1'b0), /*IIC读寄存器请求*/.IICReadDone (), /*IIC读寄存器完成*/.IICReadData () /*IIC读取数据*/
);endmodule
三、波形抓取(重要)
在工程中,例化了debugware IP核,对温湿度传感器的波形进行抓取,可以看到如下:
前文有说过,传感器的数据位总共有40位,考虑到后续可能抓取别的波形,所以此处debugware的数据位宽我设置到了45位,预留了5位。
这里抓到的波形是:2E 0019 034A
对应二进制即:0010_1110_0000_0000_0001_1001_0000_0011_0100_1010
根据上文数据公式:8bit 湿度整数数据+8bit湿度小数数据+8bit 温度整数数据+8bit温度小数数据
+8bit 校验和数据
所以此刻的湿度:46.00(10_1110_0000_0000) 温度:25.03(0001_1001_0000_0011)
可以看到,抓取的波形数据与实际情况完全吻合😁。
四、获取工程
直接点链接下载:京微齐力:基于FPGA的OLED动态显示(温湿度实时数据)
五、参考文献
1、开拓者FPGA 开发指南V1.5
2、FPGA之旅设计99例