FPGA使用IIC协议完成OLED的驱动---附完整工程
本次使用开发环境:
软件:Quartus II 64-Bit
开发板及芯片型号:至芯 --EP4CE6E22C8N 1.2V
完整工程因为会失效的风险请移步到公粽号:发拉不拉电,回复“OLED显示”获取最新网盘链接

协议核心概述
- 物理层特性
- 双线制:仅需 SCL(时钟线)和 SDA(数据线)两根线,均需外接上拉电阻,空闲时保持高电平
- 多主多从:支持多个主机和从机,通过设备地址(通常 7 位)区分不同外设,地址后接 1 位读写标志位(0 写 / 1 读)
- 半双工通信:数据线 SDA 同一时刻只能单向传输,由主机控制时序
- 关键时序状态
- 起始条件(Start):SCL 高电平时,SDA 由高→低跳变,标志通信开始
- 停止条件(Stop):SCL 高电平时,SDA 由低→高跳变,标志通信结束
- 数据传输:SCL 低电平时更新 SDA 数据,SCL 高电平时保持稳定,数据按高位优先传输
- 应答机制(ACK/NACK):每传输 8 位数据后,接收方在第 9 个 SCL 周期拉低 SDA(ACK)或保持高电平(NACK)
- 状态机驱动
- 通过状态机划分 IIC 操作阶段,典型状态包括:空闲态(IDLE)、起始态(START)、写数据(WR_DATA)、读数据(RD_DATA)、应答(ACK)、停止态(STOP)
- 时钟分频与时序控制
- 将 FPGA 系统时钟(如 50MHz)分频至 IIC 速率(如 100kHz 或 400kHz),需计算分频参数(例如 50MHz/100kHz=500 分频)
- 在 SCL 低电平中点(如分频计数器的 65/250 位置)更新 SDA 数据,确保时序稳定
- 数据收发逻辑及数据帧结构
示例状态机跳转逻辑:
Verilogcase(state) IDLE: 检测到读写请求→跳转START; START: 生成起始信号→跳转WR_DATA; WR_DATA: 发送8位数据→跳转ACK; ACK: 检测应答→根据操作类型跳转STOP或继续传输; STOP: 结束通信→返回IDLE; endcase - 写操作:主机依次发送设备地址(+ 写标志)、寄存器地址、数据,每字节后检测 ACK
- 读操作:主机发送设备地址(+ 写标志)→寄存器地址→重新发送起始信号→设备地址(+ 读标志)→接收数据并发送 ACK/NACK
1. 基础知识
1. I2C(IIC)通讯协议
I2C 通讯协议(Inter - Intergrated Circuit)是由 Philips 公司开发的一种简单、双向二线同步串行总线,只需要两根先就可以在连接于总线上的器件之间传送信息。
I2C 通讯协议和通信接口在很多工程中与广泛的应用,如数据采集领域的串行 AD,图像处理领域的摄像头配置,工业控制领域的 X 射线管配置等等。除此之外,由于 I2C 协议占用的引脚特别少,硬件实现简单,可扩展性强,现在被广泛使用在系统内多个集成电路(IC)间的通讯。同时也通常用于连接低速设备,如传感器、存储器和其他外设。它使用两根线(SCL 和 SDA)来实现双向通信,具有地址定向性和主从模式。
优点:
- 多设备支持:I2C 支持多个设备连接到同一总线上,每个设备都有唯一的地址。
- 简单:I2C 协议相对简单,易于实现和调试。
- 低功耗:在空闲状态时,显示高阻态,I2C 总线上的器件可以进入低功耗模式,节省能量。(空闲状态下,IIC 设备端口处于高阻态,所以 IIC 总线空闲时刻也就是高电平
- 仲裁:总线上可以连接多个从机,也可以连接多个主机,当多个主机同时使用总线时,为了防止数据发生冲突,会使用仲裁的方式来决定哪个设备使用总线
- 速度:IIC 具有 3 个传输模式,在标准模式下,传输速度为 100kb/s。在快速模式下,速度为 400kb/s。最后是高速模式,速度可以达到 3.4MB/s。当前的大多数 IIC 设备仅支持标准模式和快速模式。
缺点:
- 速度较慢:I2C 通信速度较低,适用于低速设备。
- 受限制:I2C 的总线长度和设备数量受到限制,过长的总线可能导致通信问题。
- 冲突:当多个设备尝试同时发送数据时,可能会发生冲突,需要额外的冲突检测和处理机制。
应用案例:
就其应用而言,连接方面,I2C 在需要简单且经济的通信环境中表现出色。它尤其擅长在小型传感器、LCD 屏幕和 RTC(实时时钟)模块中使用。此外,I2C 由于其在紧凑电路中的效率,在温度控制设备、电池管理系统和 LED 控制器中很有用。但是,在需要快速或长距离数据传输的项目中,最好选择其他协议。
读写等详细的通信过程可参考下文:
老宇哥带你玩转 ESP32:07 I2C 协议,看这一篇就够了 (点击阅读)
2. I2C 物理层
I2C_SCL:串行时钟线,用于同步通讯数据;
I2C_SDA: 双向串行数据线,传输通讯数据;
3. I2C 协议层
在这个示意图中,包含 4 个状态:
1: 总线空闲状态
2: 起始信号
3: 数据读写状态
4: 停止信号
在 SCL 为高电平的时候,进行数据的写入,低电平的时候,进行数据的更新。
当我们的从机设备正确接受到我们发送的信号的时候,会把 SDA 数据线拉低,称为响应位,表示向主机发送一个单比特的应答信号。
为什么数据在传输过程中不会被误判为停止信号?
IIC 将 SCL 处于高时 SDA 拉低的动作作为开始信号,SCL 处于高时 SDA 拉高的动作作为结束信号,这里我存在疑惑,iic 协议的各种状态时根据这两根线的时序进行判断的,scl 时时钟信号,再空闲时一直保持高电平,再准备开始信号时 sda 从高电平拉低后,准备发送地址信号,此时 scl 开始时钟震荡,那么会不会出现 scl 在震荡到高电平时,sda 正好发送的一个高电平,此时是不是也同样满足停止信号呐?
在这里有一个误区,首先只有在 scl 时钟信号为低电平时,sda 才会进行更新数据,可以理解为 scl 为高电平时,sda 的状态才是有效的,那么我们的开始和停止状态并不是单一的在 scl 和 sda 都为高时,而是 scl 为高,sda 从高电平变换为低电平时为开始,从低电平变换为高电平时为停止状态。在正常的数据传输过程中,scl 在高电平时,sda 禁止变换电平,在开始和停止状态中,时变换电平的。所以,我们的数据传输过程中,数据电平和时钟电平的状态是不会影响我们开始和停止状态的,下面是两个示意图,讲述的内容是一致的。
我们怎么检测这个开始或者停止信号呐?
相信大家在学习 FPGA 的时候会常常遇到一些工程中会对信号进行打拍的处理,这个的目的是将信号与其他的信号进行对齐和防止亚稳态,每次打一个拍后信号会延迟一个时钟周期。当我们将需要检测的信号对齐之后,我们开始对信号的上升沿或者下降沿进行检测,使用时序逻辑,以系统时钟为标准,如果打拍的两个信号前后不一样,那么就是检测到了变化沿,例如 reg1 在这个时钟下为高电平,reg2 为低电平,那么我们的信号则是下降沿。
关于器件的地址的内容
器件地址被定义为 7Bit 的数据,器件厂商在出厂的时候就将地址设置好了,用户不能进行自己更改,但是有的厂商如上图所示,其中高 4 位是固定的,但是后 3 位没有被写入,这样的情况是允许用户自行设置的。
可以在这里看到,根据上面的两行图像,可以确定这个芯片的 I2C 地址为 10100_000,因为 A0、A1、A2 都是低电平。
最后一位为读写控制位,0 表示写入,1 表示读取。
举例:如果要让这个芯片处于读取状态,使用 I2C 协议的地址则为:10100_000_1
4. 数据传输流程
I²C 协议中判断数据接收设备的基本步骤:
- 地址分配:每个连接到 I²C 总线的设备都有一个唯一的地址。这个地址在设备制造时被设定,并且通常通过硬件方式设置。
- 开始条件:在数据传输开始之前,主机设备会发送一个开始条件,这通常涉及到 SDA(数据线)和 SCL(时钟线)的特定电平变化。
- 发送地址:主机设备随后会在 SDA 线上发送目标设备的 7 位地址,加上一个额外的读 / 写位(R/W 位)。这个位指示了接下来的操作是读取(1)还是写入(0)。
- 确认应答:当地址和 R/W 位被发送后,目标设备需要在下一个时钟脉冲的开始时将 SDA 线拉低,以表示它已经接收到了地址并且准备好进行通信。这个过程称为应答(ACK)。
- 数据传输:一旦接收到应答,主机设备就会开始发送数据。如果是写入操作,主机发送数据;如果是读取操作,从设备发送数据。
- 非应答(NACK):如果目标设备不是预期的接收者,或者由于其他原因无法接收数据,它会在下一个时钟脉冲的开始时不拉低 SDA 线,表示非应答。
- 停止条件:数据传输完成后,主机设备会发送一个停止条件,这涉及到 SDA 和 SCL 线的特定电平变化,以结束数据传输。
- 多主机环境:在多主机环境中,如果有多个主机尝试同时控制总线,它们会使用仲裁机制来决定哪个主机获得控制权。仲裁是基于发送的数据位,如果两个主机尝试发送不同的位,发送 0 的主机会放弃控制权。
2. IIC 驱动 7Pin 0.96 寸 OLED 显示屏实例
1. 硬件修改
在驱动显示屏时,显示屏默认使用 SPI 协议驱动,要改为 IIC 协议驱动需要将硬件电路进行简单的修改,具体就是:
- 将背面 R3 电阻移动到 R4 的 位置
- 短接 R8 电阻
改造好之后,不能象原生的 IIC 屏幕那样接 4 根线即可,必须将 7 个管脚都要接线,否则可能没有任何显示。管脚处理:
1、CS 脚接地。
2、DC 脚的处理:在 IIC 通信中 DC 的高低电平是用来选择 IIC 通信地址的;当 DC 接地时 IIC 从机地址为:0x78, 当 DC 接高电平时 IIC 地址为 0x7A; 测试程序中所用的为 0x78; 通常直接将 DC 接地
3、关于 RES 的处理。RES 这个脚是 OLED 屏的复位脚;大家在用 OLED 屏的时候会发现;所
有 OLED 本身都会有一个复位脚;因为 OLED 在被操作之前需要在将寄存作一次复位;然后才能对期进行初始货操作;否则 OLED 可能会出现水稳定的情况。RES 处理方案:
1> 简单的验证办法:将 RES 接电源正;这样可以把屏点亮;但是会不稳定,在快速测试时可以这么操作
2> 将 RES 脚与开发板的复位脚连接;通过开发板的复位来对 OLED 进行复位
3> 通过一个 IO 脚来对 OLED 进行复位,这个操作放在对屏初始化之前;先将 RES 拉低延迟 200ms 左右;然后再拉高一直处于高电平状态
4> 通过一个 RC 复位电路来控制 RES
4、D0 为 IIC 时钟线或者 SCK 为时钟线,
5、D1 为 IIC 数据线或者 SDA 为数据线;
6、GND 为接地线,
7、VDD 为电源线;
2.OLED 上的驱动部分:
OLED 屏幕有三种刷新方式分别为页地址模式,水平地址模式和垂直地址模式,。 水平地址模式和垂直地址模式可以在一页 (一列) 写完后自动换页 (列) 所以在水平地址寻址或者垂直地址寻址模式下,只要源源不断的发送数据即可
页地址模式
水平地址模式
垂直地址模式
3. OLED 关键命令介绍
0xAE/0xAF: 对应着开启 OLED 显示和关闭 OLED 显示 0x20-0x22: 对应着上面的三种 OLED 数据存储模式,默认为 0x22,模式一 0x00-0x0F: 设置列地址的低四位,默认为 0x00, 0x10-0x1F: 设置列地址的高四位,默认为 0x10, 0xB0-0xB7: 设置 page,第四位表示 page。
IIC 数据格式 :OLED 地址 + 命令 / 数据 + 值。
OLED 地址,就是 IIC 协议中的从机地址,我这里是 0x78。
命令 / 数据中,0x00 表示接下来的值代表命令,0x40 表示接下的值表示数据,存入 GRMA。
值,具体的命令或者数据
always@(*) begin case(Init_index) 'd0: Init_data_reg <= {8'h78,8'h00,8'hAE}; //OLED地址 + 命令 + 值。** 'd1: Init_data_reg <= {8'h78,8'h00,8'h00}; 'd2: Init_data_reg <= {8'h78,8'h00,8'h10}; 'd3: Init_data_reg <= {8'h78,8'h00,8'h40}; 'd4: Init_data_reg <= {8'h78,8'h00,8'hB0}; 'd5: Init_data_reg <= {8'h78,8'h00,8'h81}; 'd6: Init_data_reg <= {8'h78,8'h00,8'hFF}; 'd7: Init_data_reg <= {8'h78,8'h00,8'hA1}; 'd8: Init_data_reg <= {8'h78,8'h00,8'hA6}; 'd9: Init_data_reg <= {8'h78,8'h00,8'hA8}; 'd10: Init_data_reg <= {8'h78,8'h00,8'h3F}; 'd11: Init_data_reg <= {8'h78,8'h00,8'hC8}; 'd12: Init_data_reg <= {8'h78,8'h00,8'hD3}; 'd13: Init_data_reg <= {8'h78,8'h00,8'h00}; 'd14: Init_data_reg <= {8'h78,8'h00,8'hD5}; 'd15: Init_data_reg <= {8'h78,8'h00,8'h80}; 'd16: Init_data_reg <= {8'h78,8'h00,8'hD8}; 'd17: Init_data_reg <= {8'h78,8'h00,8'h05}; 'd18: Init_data_reg <= {8'h78,8'h00,8'hD9}; 'd19: Init_data_reg <= {8'h78,8'h00,8'hF1}; 'd20: Init_data_reg <= {8'h78,8'h00,8'hDA}; 'd21: Init_data_reg <= {8'h78,8'h00,8'h12}; 'd22: Init_data_reg <= {8'h78,8'h00,8'hDB}; 'd23: Init_data_reg <= {8'h78,8'h00,8'h30}; 'd24: Init_data_reg <= {8'h78,8'h00,8'h8D}; 'd25: Init_data_reg <= {8'h78,8'h00,8'h14}; 'd26: Init_data_reg <= {8'h78,8'h00,8'hAF}; default: Init_data_reg <= {8'h78,8'h00,8'hAE}; endcase end 3. IIC 驱动 4Pin OLED 显示器显示 DHT11 温湿度数据
3.1 字模设计
3.2 程序设计
程序如下
module main( input sys_clk, input rst_n, inout dht11, output OLED_SCL, inout OLED_SDA ); wire dht11_done; reg dht11_req; wire[7:0] tempH; wire[7:0] tempL; wire[7:0] humidityH; wire[7:0] humidityL; localparam S_DELAY = 'd55_000_000; reg[35:0] delay; always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) delay <= 'd0; else if(dht11_done == 1'b1) delay <= 'd0; else if(delay == S_DELAY) delay <= delay; else delay <= delay + 1'b1; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) dht11_req <= 1'b0; else if(delay == S_DELAY) dht11_req <= 1'b1; else dht11_req <= 1'b0; end DHT11 DHT11HP( .sys_clk (sys_clk), .rst_n (rst_n), .dht11_req (dht11_req), //dht11数据采集请求 .dht11_done (dht11_done), //dht11数据采集结束 .dht11_error (), //dht11数据采集正确与否判断 1为错误 .tempH (tempH), //温度数据整数 .tempL (tempL), //温度数据小数 .humidityH (humidityH), //温度数据整数 .humidityL (humidityL), //温度数据小数 .dht11 (dht11) ); OLED_Top OLED_TopHP( .sys_clk (sys_clk), .rst_n (rst_n), .dht11_done (dht11_done), .tempH (tempH), //温度数据整数 .tempL (tempL), //温度数据小数 .humidityH (humidityH), //温度数据整数 .humidityL (humidityL), //温度数据小数 //OLED IIC .OLED_SCL (OLED_SCL), .OLED_SDA (OLED_SDA) ); endmodule IIC_Driver 模块如下:
`timescale 1ns/1ps module IIC_Driver( input sys_clk, /*系统时钟*/ input rst_n, /*系统复位*/ output IICSCL, /*IIC 时钟输出*/ inout IICSDA, /*IIC 数据线*/ input[15:0] IICSlave, /*从机 8bit的寄存器地址 + 8bit的从机地址*/ input IICWriteReq, /*IIC写寄存器请求*/ output IICWriteDone, /*IIC写寄存器完成*/ input[7:0] IICWriteData, /*IIC发送数据 8bit的数据*/ input IICReadReq, /*IIC读寄存器请求*/ output IICReadDone, /*IIC读寄存器完成*/ output[7:0] IICReadData /*IIC读取数据*/ ); /*IIC 状态*/ localparam IIC_IDLE = 6'b000_001; /*空闲态*/ localparam IIC_START = 6'b000_010; /*起始态*/ localparam IIC_WRDATA = 6'b000_100; /*写数据态*/ localparam IIC_RDDATA = 6'b001_000; /*读数据态*/ localparam IIC_ACK = 6'b010_000; /*应答态*/ localparam IIC_STOP = 6'b100_000; /*停止态*/ localparam IIC_Pre = 'd100; /*iiC分频*/ reg[5:0] state , next_state; reg[21:0] IICCnt; /*IIC计数器*/ reg[3:0] IICBitCnt; /*IIC数据发送个数计数*/ reg[1:0] IICACKStopCnt; /*IIC ack stop应答计数*/ reg[2:0] IICSendBytes; /*IIC 发送字节计数*/ reg[15:0] IICSlaveReg; /*从机地址+寄存器数据*/ reg[7:0] IICReadDataReg; /*读取到的数据*/ reg IICWriteReqReg; reg iictx; /*iic发送数据引脚*/ reg iicCLK; /*iic时钟信号引脚*/ assign IICSDA = (state == IIC_RDDATA || (state == IIC_ACK)) ? 1'bz : iictx; /*iic为读数据或者应答的时候,输出为高阻态*/ assign IICSCL = iicCLK; assign IICReadData = IICReadDataReg; assign IICReadDone = (state != next_state && state == IIC_STOP) ? 1'b1 : 1'b0; assign IICWriteDone = (state != next_state && state == IIC_STOP) ? 1'b1 : 1'b0; always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) IICWriteReqReg <= 1'b0; else if(IICWriteDone == 1'b1) IICWriteReqReg <= 1'b0; else if(IICWriteReq == 1'b1) IICWriteReqReg <= 1'b1; else IICWriteReqReg <= IICWriteReqReg; end always @(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) state <= IIC_IDLE; else state <= next_state; end /*状态机*/ always @(*) begin case(state) IIC_IDLE: if(IICWriteReq == 1'b1 || IICReadReq == 1'b1) next_state <= IIC_START; else next_state <= IIC_IDLE; IIC_START: if(IICCnt == (IIC_Pre * 'd2)) next_state <= IIC_WRDATA; else next_state <= IIC_START; IIC_WRDATA: if(IICBitCnt == 'd8 /*&& IICCnt == IIC_Pre /4 */&& iicCLK == 1'b0) next_state <= IIC_ACK; else next_state <= IIC_WRDATA; IIC_RDDATA: if(IICBitCnt == 'd8 && IICCnt == IIC_Pre /4 && iicCLK == 1'b0) next_state <= IIC_ACK; else next_state <= IIC_RDDATA; IIC_ACK: if(IICACKStopCnt == 'd1 /*&& IICCnt == IIC_Pre /4 */&& iicCLK == 1'b0) if(IICSendBytes == 'd2) if(/*IICWriteReq*/IICWriteReqReg == 1'b1) /*三个字节发送完成,进入停止态*/ next_state <= IIC_STOP; else next_state <= IIC_RDDATA; else if(IICSendBytes == 'd2 && IICReadReq == 1'b1) next_state <= IIC_START; else if(IICSendBytes == 'd4) next_state <= IIC_STOP; else next_state <= IIC_WRDATA; else next_state <= IIC_ACK; IIC_STOP: if(IICACKStopCnt == 'd1 && IICCnt == IIC_Pre/4 && iicCLK == 1'b1) next_state <= IIC_IDLE; else next_state <= IIC_STOP; default: next_state <= IIC_IDLE; endcase end /*IIC 发送字节计数*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) IICSendBytes <= 'd0; else if(state == IIC_IDLE) IICSendBytes <= 'd0; else if(state == IIC_ACK) if(next_state != state) IICSendBytes <= IICSendBytes + 1'b1; else IICSendBytes <= IICSendBytes; else IICSendBytes <= IICSendBytes; end /*IIC分频计数*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) IICCnt <= 'd0; else if(IICCnt == IIC_Pre && state != IIC_START) IICCnt <= 'd0; else if(IICCnt == (IIC_Pre *'d2) && state != IIC_START) IICCnt <= 'd0; else if(state != next_state) IICCnt <= 'd0; else if(state == IIC_START) IICCnt <= IICCnt + 1'b1; else if(state == IIC_WRDATA ) IICCnt <= IICCnt + 1'b1; else if(state == IIC_RDDATA) IICCnt <= IICCnt + 1'b1; else if(state == IIC_ACK) IICCnt <= IICCnt + 1'b1; else if(state == IIC_STOP) IICCnt <= IICCnt + 1'b1; end /*IIC发送bit计数*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) IICBitCnt <= 'd0; else if(state == IIC_IDLE || state == IIC_ACK) IICBitCnt <= 'd0; else if(state == IIC_WRDATA && IICCnt == (IIC_Pre /2)) if(iicCLK == 1'b1) IICBitCnt <= IICBitCnt + 1'b1; else IICBitCnt <= IICBitCnt; else if(state == IIC_RDDATA && IICCnt == (IIC_Pre /2)) if(iicCLK == 1'b1) IICBitCnt <= IICBitCnt + 1'b1; else IICBitCnt <= IICBitCnt; else IICBitCnt <= IICBitCnt; end /*IIC ack stop应答计数*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) IICACKStopCnt <= 'd0; else if(state != next_state) IICACKStopCnt <= 'd0; else if((state == IIC_ACK || state == IIC_STOP) && IICCnt == (IIC_Pre /2)) if(iicCLK == 1'b1) IICACKStopCnt <= IICACKStopCnt + 1'b1; else IICACKStopCnt <= IICACKStopCnt; else if(state == IIC_ACK || state == IIC_STOP) IICACKStopCnt <= IICACKStopCnt; else IICACKStopCnt <= 'd0; end /*IIC从机信息控制*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) IICSlaveReg <= 'd0; else if((IICWriteReq == 1'b1 || IICReadReq == 1'b1) && state == IIC_IDLE) /*请求来时,保存信息*/ IICSlaveReg <= IICSlave; else if(state == IIC_ACK && state != next_state) /*每发送完成一字节,就调换数据,始终发送的是低8位*/ if(IICSendBytes == 'd2) IICSlaveReg <= {IICSlaveReg[7:0],IICSlaveReg[15:8]} + 1'b1; else IICSlaveReg <= {IICSlaveReg[7:0],IICSlaveReg[15:8]}; else IICSlaveReg <= IICSlaveReg; end /*IIC 时钟控制*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) iicCLK <= 1'b1; else if(state == IIC_START && IICCnt == (IIC_Pre*2)) /*开始结束时,拉低时钟线*/ iicCLK <= 1'b0; else if(state == IIC_START && IICCnt > IIC_Pre) iicCLK <= 1'b1; else if(state == IIC_START && IICCnt == IIC_Pre) iicCLK <= iicCLK; else if(state == IIC_WRDATA && IICCnt == IIC_Pre) /*发送数据,依次取反时钟线*/ iicCLK <= ~iicCLK; else if(state == IIC_RDDATA && IICCnt == IIC_Pre) /*接收数据,依次取反时钟线*/ iicCLK <= ~iicCLK; else if(state == IIC_ACK && IICCnt == IIC_Pre) iicCLK <= ~iicCLK; else if(state == IIC_STOP && IICCnt == IIC_Pre) /*在停止态时,IICCnt时,直接拉高iicCLK*/ iicCLK <= 1'b1; end /*iic 发送控制*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) iictx <= 1'b1; else if(state == IIC_START && IICCnt == (IIC_Pre/2)) /*开始,iic数据线拉低*/ iictx <= 1'b0; else if(state == IIC_START && IICCnt == IIC_Pre/4) /*开始,iic数据线拉低*/ iictx <= 1'b1; else if(state == IIC_WRDATA && IICCnt == IIC_Pre / 2) /*iic发送数据*/ if(iicCLK == 1'b0 && IICSendBytes == 'd2 && (IICWriteReq == 1'b1 || IICWriteReqReg == 1'b1)) iictx <= IICWriteData['d7-IICBitCnt]; else if(iicCLK == 1'b0) iictx <= IICSlaveReg['d7-IICBitCnt]; else iictx <= iictx; else if(state == IIC_ACK) iictx <= 1'b0; else if(state == IIC_STOP && IICCnt == (IIC_Pre)) if(iicCLK == 1'b1) iictx <= 1'b1; else iictx <= iictx; else if(state == IIC_IDLE) iictx <= 1'b1; end /*iic 读取数据控制*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) IICReadDataReg <= 'd0; else if(state == IIC_RDDATA && IICCnt == IIC_Pre / 2) if(iicCLK == 1'b1) IICReadDataReg <= {IICReadDataReg[6:0],IICSDA}; else IICReadDataReg <= IICReadDataReg; else IICReadDataReg <= IICReadDataReg; end endmodule DHT11 模块如下:
/*dht11温湿度数据获取模块*/ 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; //数据采集完成 reg[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) begin if(rst_n == 1'b0) begin dht11_d0 <= 1'b1; dht11_d1 <= 1'b1; end else begin dht11_d0 <= dht11; dht11_d1 <= dht11_d0; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) state <= S_IDLE; else state <= next_state; end always@(*) begin case(state) S_IDLE: if(dht11_req == 1'b1) //数据采集请求过来进入开始态 next_state <= S_START_FPGA; else next_state <= S_IDLE; S_START_FPGA: if((DHT11_Cnt >= TIME18ms) && dht11_negedge == 1'b1) //FPGA请求结束结束 next_state <= S_START_DHT11; else next_state <= S_START_FPGA; S_START_DHT11: if((DHT11_Cnt > TIME35us) && dht11_negedge == 1'b1) //延时一段时间后,通过判断dht11总线的下降沿,是否结束响应 next_state <= S_DATA; else next_state <= S_START_DHT11; S_DATA: if(DHT11Bit_Cnt == 'd39 && dht11_negedge == 1'b1) //接收到40bit数据后,进入停止态 next_state <= S_STOP; else next_state <= S_DATA; S_STOP: if(DHT11_Cnt == TIME35us + TIME35us) //数据传输完成后,等待总线拉低50us,这里是70us next_state <= S_DOEN; else next_state <= S_STOP; S_DOEN: next_state <= S_IDLE; default: next_state <= S_IDLE; endcase end /*计数模块*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) DHT11_Cnt <= 'd0; else if(state != next_state) //状态变换的时候,计时器置0 DHT11_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; else DHT11_Cnt <= 'd0; end /*接收数据存储*/ always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) dht11_data <= 'd0; else if(state == S_DATA) if((DHT11_Cnt <= (TIME35us + 'd2500)) && dht11_negedge == 1'b1) //'d3000为低电平时间,高电平持续时间低于35us认为是数据0 dht11_data <= {dht11_data[38:0],1'b0}; else if(dht11_negedge == 1'b1) dht11_data <= {dht11_data[38:0],1'b1}; else dht11_data <= dht11_data; else dht11_data <= dht11_data; end /*接收bit计数*/ always@(posedge sys_clk or negedge rst_n) begin if(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; else DHT11Bit_Cnt <= DHT11Bit_Cnt; end endmodule 下面是 oled 的程序
//中文16*16 数字和字母8*16 module OLED_FontData( input sys_clk, input rst_n, input font_row, input[5:0] font_sel, input[8:0] index, output reg[7:0] data ); reg[7:0] data0[15:0]; //F reg[7:0] data1[15:0]; //G reg[7:0] data2[15:0]; //P reg[7:0] data3[15:0]; //A reg[7:0] data4[31:0]; //之 reg[7:0] data5[31:0]; //旅 reg[7:0] data6[31:0]; //温 reg[7:0] data7[31:0]; //度 reg[7:0] data8[31:0]; //湿 reg[7:0] data9[31:0]; //度 reg[7:0] data10[15:0]; //. reg[7:0] data11[31:0]; //℃ reg[7:0] data12[15:0]; //R reg[7:0] data13[15:0]; //H always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 'd0) data <= 'd0; else if(font_sel == 'd0) data <= data0[index + 'd8 * font_row]; else if(font_sel == 'd1) data <= data1[index + 'd8 * font_row]; else if(font_sel == 'd2) data <= data2[index + 'd8 * font_row]; else if(font_sel == 'd3) data <= data3[index + 'd8 * font_row]; else if(font_sel == 'd4) data <= data4[index + 'd16 * font_row]; else if(font_sel == 'd5) data <= data5[index + 'd16 * font_row]; else if(font_sel == 'd6) data <= data6[index + 'd16 * font_row]; else if(font_sel == 'd7) data <= data7[index + 'd16 * font_row]; else if(font_sel == 'd8) data <= data8[index + 'd16 * font_row]; else if(font_sel == 'd9) data <= data9[index + 'd16 * font_row]; else if(font_sel == 'd10) data <= data10[index + 'd8 * font_row]; else if(font_sel == 'd11) data <= data11[index + 'd16 * font_row]; else if(font_sel == 'd12) data <= data12[index + 'd8 * font_row]; else if(font_sel == 'd13) data <= data13[index + 'd8 * font_row]; else if(font_sel == 'd14) data <= data10[index + 'd8 * font_row]; else data <= data; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data0[0] = 8'h08; data0[1] = 8'hF8; data0[2] = 8'h88; data0[3] = 8'h88; data0[4] = 8'hE8; data0[5] = 8'h08; data0[6] = 8'h10; data0[7] = 8'h00; data0[8] = 8'h20; data0[9] = 8'h3F; data0[10] = 8'h20; data0[11] = 8'h00; data0[12] = 8'h03; data0[13] = 8'h00; data0[14] = 8'h00; data0[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data1[0] = 8'h08; data1[1] = 8'hF8; data1[2] = 8'h08; data1[3] = 8'h08; data1[4] = 8'h08; data1[5] = 8'h08; data1[6] = 8'hF0; data1[7] = 8'h00; data1[8] = 8'h20; data1[9] = 8'h3F; data1[10] = 8'h21; data1[11] = 8'h01; data1[12] = 8'h01; data1[13] = 8'h01; data1[14] = 8'h00; data1[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data2[0] = 8'hC0; data2[1] = 8'h30; data2[2] = 8'h08; data2[3] = 8'h08; data2[4] = 8'h08; data2[5] = 8'h38; data2[6] = 8'h00; data2[7] = 8'h00; data2[8] = 8'h07; data2[9] = 8'h18; data2[10] = 8'h20; data2[11] = 8'h20; data2[12] = 8'h22; data2[13] = 8'h1E; data2[14] = 8'h02; data2[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data3[0] = 8'h00; data3[1] = 8'h00; data3[2] = 8'hC0; data3[3] = 8'h38; data3[4] = 8'hE0; data3[5] = 8'h00; data3[6] = 8'h00; data3[7] = 8'h00; data3[8] = 8'h20; data3[9] = 8'h3C; data3[10] = 8'h23; data3[11] = 8'h02; data3[12] = 8'h02; data3[13] = 8'h27; data3[14] = 8'h38; data3[15] = 8'h20; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data4[0] = 8'h10; data4[1] = 8'h0C; data4[2] = 8'h04; data4[3] = 8'h84; data4[4] = 8'h14; data4[5] = 8'h64; data4[6] = 8'h05; data4[7] = 8'h06; data4[8] = 8'hF4; data4[9] = 8'h04; data4[10] = 8'h04; data4[11] = 8'h04; data4[12] = 8'h04; data4[13] = 8'h14; data4[14] = 8'h0C; data4[15] = 8'h00; data4[16] = 8'h04; data4[17] = 8'h84; data4[18] = 8'h84; data4[19] = 8'h44; data4[20] = 8'h47; data4[21] = 8'h24; data4[22] = 8'h14; data4[23] = 8'h0C; data4[24] = 8'h07; data4[25] = 8'h0C; data4[26] = 8'h14; data4[27] = 8'h24; data4[28] = 8'h44; data4[29] = 8'h84; data4[30] = 8'h04; data4[31] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data5[0] = 8'h02; data5[1] = 8'hFA; data5[2] = 8'h82; data5[3] = 8'h82; data5[4] = 8'hFE; data5[5] = 8'h80; data5[6] = 8'h40; data5[7] = 8'h20; data5[8] = 8'h50; data5[9] = 8'h4C; data5[10] = 8'h43; data5[11] = 8'h4C; data5[12] = 8'h50; data5[13] = 8'h20; data5[14] = 8'h40; data5[15] = 8'h00; data5[16] = 8'h08; data5[17] = 8'h18; data5[18] = 8'h48; data5[19] = 8'h84; data5[20] = 8'h44; data5[21] = 8'h3F; data5[22] = 8'h40; data5[23] = 8'h44; data5[24] = 8'h58; data5[25] = 8'h41; data5[26] = 8'h4E; data5[27] = 8'h60; data5[28] = 8'h58; data5[29] = 8'h47; data5[30] = 8'h40; data5[31] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data6[0] = 8'h10; data6[1] = 8'h60; data6[2] = 8'h02; data6[3] = 8'h8C; data6[4] = 8'h00; data6[5] = 8'h00; data6[6] = 8'hFE; data6[7] = 8'h92; data6[8] = 8'h92; data6[9] = 8'h92; data6[10] = 8'h92; data6[11] = 8'h92; data6[12] = 8'hFE; data6[13] = 8'h00; data6[14] = 8'h00; data6[15] = 8'h00; data6[16] = 8'h04; data6[17] = 8'h04; data6[18] = 8'h7E; data6[19] = 8'h01; data6[20] = 8'h40; data6[21] = 8'h7E; data6[22] = 8'h42; data6[23] = 8'h42; data6[24] = 8'h7E; data6[25] = 8'h42; data6[26] = 8'h7E; data6[27] = 8'h42; data6[28] = 8'h42; data6[29] = 8'h7E; data6[30] = 8'h40; data6[31] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data7[0] = 8'h00; data7[1] = 8'h00; data7[2] = 8'hFC; data7[3] = 8'h24; data7[4] = 8'h24; data7[5] = 8'h24; data7[6] = 8'hFC; data7[7] = 8'h25; data7[8] = 8'h26; data7[9] = 8'h24; data7[10] = 8'hFC; data7[11] = 8'h24; data7[12] = 8'h24; data7[13] = 8'h24; data7[14] = 8'h04; data7[15] = 8'h00; data7[16] = 8'h40; data7[17] = 8'h30; data7[18] = 8'h8F; data7[19] = 8'h80; data7[20] = 8'h84; data7[21] = 8'h4C; data7[22] = 8'h55; data7[23] = 8'h25; data7[24] = 8'h25; data7[25] = 8'h25; data7[26] = 8'h55; data7[27] = 8'h4C; data7[28] = 8'h80; data7[29] = 8'h80; data7[30] = 8'h80; data7[31] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data8[0] = 8'h10; data8[1] = 8'h60; data8[2] = 8'h02; data8[3] = 8'h8C; data8[4] = 8'h00; data8[5] = 8'hFE; data8[6] = 8'h92; data8[7] = 8'h92; data8[8] = 8'h92; data8[9] = 8'h92; data8[10] = 8'h92; data8[11] = 8'h92; data8[12] = 8'hFE; data8[13] = 8'h00; data8[14] = 8'h00; data8[15] = 8'h00; data8[16] = 8'h04; data8[17] = 8'h04; data8[18] = 8'h7E; data8[19] = 8'h01; data8[20] = 8'h44; data8[21] = 8'h48; data8[22] = 8'h50; data8[23] = 8'h7F; data8[24] = 8'h40; data8[25] = 8'h40; data8[26] = 8'h7F; data8[27] = 8'h50; data8[28] = 8'h48; data8[29] = 8'h44; data8[30] = 8'h40; data8[31] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data9[0] = 8'h00; data9[1] = 8'h00; data9[2] = 8'hFC; data9[3] = 8'h24; data9[4] = 8'h24; data9[5] = 8'h24; data9[6] = 8'hFC; data9[7] = 8'h25; data9[8] = 8'h26; data9[9] = 8'h24; data9[10] = 8'hFC; data9[11] = 8'h24; data9[12] = 8'h24; data9[13] = 8'h24; data9[14] = 8'h04; data9[15] = 8'h00; data9[16] = 8'h40; data9[17] = 8'h30; data9[18] = 8'h8F; data9[19] = 8'h80; data9[20] = 8'h84; data9[21] = 8'h4C; data9[22] = 8'h55; data9[23] = 8'h25; data9[24] = 8'h25; data9[25] = 8'h25; data9[26] = 8'h55; data9[27] = 8'h4C; data9[28] = 8'h80; data9[29] = 8'h80; data9[30] = 8'h80; data9[31] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data10[0] = 8'h00; data10[1] = 8'h00; data10[2] = 8'h00; data10[3] = 8'h00; data10[4] = 8'h00; data10[5] = 8'h00; data10[6] = 8'h00; data10[7] = 8'h00; data10[8] = 8'h00; data10[9] = 8'h30; data10[10] = 8'h30; data10[11] = 8'h00; data10[12] = 8'h00; data10[13] = 8'h00; data10[14] = 8'h00; data10[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data11[0] = 8'h06; data11[1] = 8'h09; data11[2] = 8'h09; data11[3] = 8'hE6; data11[4] = 8'hF8; data11[5] = 8'h0C; data11[6] = 8'h04; data11[7] = 8'h02; data11[8] = 8'h02; data11[9] = 8'h02; data11[10] = 8'h02; data11[11] = 8'h02; data11[12] = 8'h04; data11[13] = 8'h1E; data11[14] = 8'h00; data11[15] = 8'h00; data11[16] = 8'h00; data11[17] = 8'h00; data11[18] = 8'h00; data11[19] = 8'h07; data11[20] = 8'h1F; data11[21] = 8'h30; data11[22] = 8'h20; data11[23] = 8'h40; data11[24] = 8'h40; data11[25] = 8'h40; data11[26] = 8'h40; data11[27] = 8'h40; data11[28] = 8'h20; data11[29] = 8'h10; data11[30] = 8'h00; data11[31] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data12[0] = 8'h08; data12[1] = 8'hF8; data12[2] = 8'h88; data12[3] = 8'h88; data12[4] = 8'h88; data12[5] = 8'h88; data12[6] = 8'h70; data12[7] = 8'h00; data12[8] = 8'h20; data12[9] = 8'h3F; data12[10] = 8'h20; data12[11] = 8'h00; data12[12] = 8'h03; data12[13] = 8'h0C; data12[14] = 8'h30; data12[15] = 8'h20; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data13[0] = 8'h08; data13[1] = 8'hF8; data13[2] = 8'h08; data13[3] = 8'h00; data13[4] = 8'h00; data13[5] = 8'h08; data13[6] = 8'hF8; data13[7] = 8'h08; data13[8] = 8'h20; data13[9] = 8'h3F; data13[10] = 8'h21; data13[11] = 8'h01; data13[12] = 8'h01; data13[13] = 8'h21; data13[14] = 8'h3F; data13[15] = 8'h20; end end endmodule 下面是 OLED 模块初始化程序
//oled_init 初始化模块 module OLED_Init( input sys_clk, input rst_n, input init_req, //初始化请求 input write_done, //一组初始化数据完成信号 output init_finish, //初始化完成输出 output[23:0] Init_data //初始化的数据 ); localparam RST_T = 1'b0; //复位有效 reg[23:0] Init_data_reg; reg[4:0] Init_index; assign Init_data = Init_data_reg; assign init_finish = (Init_index >= 'd26 && write_done == 1'b1) ? 1'b1 : 1'b0;//初始化完成信号 always@(posedge sys_clk or negedge rst_n) begin if(rst_n == RST_T) Init_index <= 'd0; else if(Init_index == 'd26 && write_done == 1'b1 ) Init_index <= 'd0; else if(write_done == 1'b1 && init_req == 1'b1) Init_index <= Init_index + 1'b1; else Init_index <= Init_index; end always@(*) begin case(Init_index) 'd0: Init_data_reg <= {8'h78,8'h00,8'hAE}; 'd1: Init_data_reg <= {8'h78,8'h00,8'h00}; 'd2: Init_data_reg <= {8'h78,8'h00,8'h10}; 'd3: Init_data_reg <= {8'h78,8'h00,8'h40}; 'd4: Init_data_reg <= {8'h78,8'h00,8'hB0}; 'd5: Init_data_reg <= {8'h78,8'h00,8'h81}; 'd6: Init_data_reg <= {8'h78,8'h00,8'hFF}; 'd7: Init_data_reg <= {8'h78,8'h00,8'hA1}; 'd8: Init_data_reg <= {8'h78,8'h00,8'hA6}; 'd9: Init_data_reg <= {8'h78,8'h00,8'hA8}; 'd10: Init_data_reg <= {8'h78,8'h00,8'h3F}; 'd11: Init_data_reg <= {8'h78,8'h00,8'hC8}; 'd12: Init_data_reg <= {8'h78,8'h00,8'hD3}; 'd13: Init_data_reg <= {8'h78,8'h00,8'h00}; 'd14: Init_data_reg <= {8'h78,8'h00,8'hD5}; 'd15: Init_data_reg <= {8'h78,8'h00,8'h80}; 'd16: Init_data_reg <= {8'h78,8'h00,8'hD8}; 'd17: Init_data_reg <= {8'h78,8'h00,8'h05}; 'd18: Init_data_reg <= {8'h78,8'h00,8'hD9}; 'd19: Init_data_reg <= {8'h78,8'h00,8'hF1}; 'd20: Init_data_reg <= {8'h78,8'h00,8'hDA}; 'd21: Init_data_reg <= {8'h78,8'h00,8'h12}; 'd22: Init_data_reg <= {8'h78,8'h00,8'hDB}; 'd23: Init_data_reg <= {8'h78,8'h00,8'h30}; 'd24: Init_data_reg <= {8'h78,8'h00,8'h8D}; 'd25: Init_data_reg <= {8'h78,8'h00,8'h14}; 'd26: Init_data_reg <= {8'h78,8'h00,8'hAF}; default: Init_data_reg <= {8'h78,8'h00,8'hAE}; endcase end endmodule OLED_NumData 的模块:
/*数字数据0-9*/ module OLED_NumData( input sys_clk, input rst_n, input font_row, input[4:0] font_sel, input[4:0] index, output reg[7:0] data ); /*0-9*/ reg[7:0] data0[15:0]; reg[7:0] data1[15:0]; reg[7:0] data2[15:0]; reg[7:0] data3[15:0]; reg[7:0] data4[15:0]; reg[7:0] data5[15:0]; reg[7:0] data6[15:0]; reg[7:0] data7[15:0]; reg[7:0] data8[15:0]; reg[7:0] data9[15:0]; always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) data <= 'd0; else if(font_sel == 'd0) data <= data0[index + 'd8 * font_row]; else if(font_sel == 'd1) data <= data1[index + 'd8 * font_row]; else if(font_sel == 'd2) data <= data2[index + 'd8 * font_row]; else if(font_sel == 'd3) data <= data3[index + 'd8 * font_row]; else if(font_sel == 'd4) data <= data4[index + 'd8 * font_row]; else if(font_sel == 'd5) data <= data5[index + 'd8 * font_row]; else if(font_sel == 'd6) data <= data6[index + 'd8 * font_row]; else if(font_sel == 'd7) data <= data7[index + 'd8 * font_row]; else if(font_sel == 'd8) data <= data8[index + 'd8 * font_row]; else if(font_sel == 'd9) data <= data9[index + 'd8 * font_row]; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data0[0] = 8'h00; data0[1] = 8'hE0; data0[2] = 8'h10; data0[3] = 8'h08; data0[4] = 8'h08; data0[5] = 8'h10; data0[6] = 8'hE0; data0[7] = 8'h00; data0[8] = 8'h00; data0[9] = 8'h0F; data0[10] = 8'h10; data0[11] = 8'h20; data0[12] = 8'h20; data0[13] = 8'h10; data0[14] = 8'h0F; data0[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data1[0] = 8'h00; data1[1] = 8'h00; data1[2] = 8'h10; data1[3] = 8'h10; data1[4] = 8'hF8; data1[5] = 8'h00; data1[6] = 8'h00; data1[7] = 8'h00; data1[8] = 8'h00; data1[9] = 8'h00; data1[10] = 8'h20; data1[11] = 8'h20; data1[12] = 8'h3F; data1[13] = 8'h20; data1[14] = 8'h20; data1[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data2[0] = 8'h00; data2[1] = 8'h70; data2[2] = 8'h08; data2[3] = 8'h08; data2[4] = 8'h08; data2[5] = 8'h08; data2[6] = 8'hF0; data2[7] = 8'h00; data2[8] = 8'h00; data2[9] = 8'h30; data2[10] = 8'h28; data2[11] = 8'h24; data2[12] = 8'h22; data2[13] = 8'h21; data2[14] = 8'h30; data2[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data3[0] = 8'h00; data3[1] = 8'h30; data3[2] = 8'h08; data3[3] = 8'h08; data3[4] = 8'h08; data3[5] = 8'h88; data3[6] = 8'h70; data3[7] = 8'h00; data3[8] = 8'h00; data3[9] = 8'h18; data3[10] = 8'h20; data3[11] = 8'h21; data3[12] = 8'h21; data3[13] = 8'h22; data3[14] = 8'h1C; data3[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data4[0] = 8'h00; data4[1] = 8'h00; data4[2] = 8'h80; data4[3] = 8'h40; data4[4] = 8'h30; data4[5] = 8'hF8; data4[6] = 8'h00; data4[7] = 8'h00; data4[8] = 8'h00; data4[9] = 8'h06; data4[10] = 8'h05; data4[11] = 8'h24; data4[12] = 8'h24; data4[13] = 8'h3F; data4[14] = 8'h24; data4[15] = 8'h24; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data5[0] = 8'h00; data5[1] = 8'hF8; data5[2] = 8'h88; data5[3] = 8'h88; data5[4] = 8'h88; data5[5] = 8'h08; data5[6] = 8'h08; data5[7] = 8'h00; data5[8] = 8'h00; data5[9] = 8'h19; data5[10] = 8'h20; data5[11] = 8'h20; data5[12] = 8'h20; data5[13] = 8'h11; data5[14] = 8'h0E; data5[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data6[0] = 8'h00; data6[1] = 8'hE0; data6[2] = 8'h10; data6[3] = 8'h88; data6[4] = 8'h88; data6[5] = 8'h90; data6[6] = 8'h00; data6[7] = 8'h00; data6[8] = 8'h00; data6[9] = 8'h0F; data6[10] = 8'h11; data6[11] = 8'h20; data6[12] = 8'h20; data6[13] = 8'h20; data6[14] = 8'h1F; data6[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data7[0] = 8'h00; data7[1] = 8'h18; data7[2] = 8'h08; data7[3] = 8'h08; data7[4] = 8'h88; data7[5] = 8'h68; data7[6] = 8'h18; data7[7] = 8'h00; data7[8] = 8'h00; data7[9] = 8'h00; data7[10] = 8'h00; data7[11] = 8'h3E; data7[12] = 8'h01; data7[13] = 8'h00; data7[14] = 8'h00; data7[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data8[0] = 8'h00; data8[1] = 8'h70; data8[2] = 8'h88; data8[3] = 8'h08; data8[4] = 8'h08; data8[5] = 8'h88; data8[6] = 8'h70; data8[7] = 8'h00; data8[8] = 8'h00; data8[9] = 8'h1C; data8[10] = 8'h22; data8[11] = 8'h21; data8[12] = 8'h21; data8[13] = 8'h22; data8[14] = 8'h1C; data8[15] = 8'h00; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin data9[0] = 8'h00; data9[1] = 8'hF0; data9[2] = 8'h08; data9[3] = 8'h08; data9[4] = 8'h08; data9[5] = 8'h10; data9[6] = 8'hE0; data9[7] = 8'h00; data9[8] = 8'h00; data9[9] = 8'h01; data9[10] = 8'h12; data9[11] = 8'h22; data9[12] = 8'h22; data9[13] = 8'h11; data9[14] = 8'h0F; data9[15] = 8'h00; end end endmodule OLED 刷新模块:
//刷新模块,将oled屏幕全部变为黑色 module OLED_Refresh( input sys_clk, input rst_n, input refresh_req, //初始化请求 input write_done, //一组初始化数据完成信号 output refresh_finish, //初始化完成输出 output[23:0] refresh_data //初始化的数据 ); reg[23:0] refresh_data_reg; reg[10:0] refresh_index; reg[2:0] page; assign refresh_data = refresh_data_reg; assign refresh_finish = (page == 'd7 && refresh_index == 'd130 && write_done == 1'b1) ? 1'b1 : 1'b0; always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) refresh_index <= 'd0; else if(refresh_index == 'd130 && write_done == 1'b1) refresh_index <= 'd0; else if(write_done == 1'b1) refresh_index <= refresh_index + 1'b1; else refresh_index <= refresh_index; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) page <= 'd0; else if(refresh_index == 'd130 && write_done == 1'b1) page <= page + 1'b1; else page <= page; end always@(*) begin case(refresh_index) 'd0: refresh_data_reg <= {8'h78,8'h00,8'hB0 + page}; 'd1: refresh_data_reg <= {8'h78,8'h00,8'h00}; 'd2: refresh_data_reg <= {8'h78,8'h00,8'h10}; default: refresh_data_reg <= {8'h78,8'h40,8'h00}; endcase end endmodule OLED_SelData 模块
module OLED_SelData( input sys_clk, input rst_n, input init_req, input[23:0] init_data, input refresh_req, input[23:0] refresh_data, input showfont_req, input[23:0] showfont_data, input showdata_req, input[23:0] showdata_data, output IICWriteReq, output[23:0] IICWriteData ); reg IICWriteReqReg; reg[23:0] IICWriteDataReg; assign IICWriteReq = init_req | showfont_req | refresh_req | showdata_req; assign IICWriteData = (init_req == 1'b1) ? init_data : (refresh_req == 1'b1) ? refresh_data : (showfont_req == 1'b1) ? showfont_data : showdata_data; endmodule OLED_ShowData 模块:
module OLED_ShowData( input sys_clk, input rst_n, input dht11_done, input[7:0] tempH, input[7:0] tempL, input[7:0] humidityH, input[7:0] humidityL, input ShowData_req, //字符显示请求 input write_done, //iic一组数据写完成 output[23:0] ShowData_Data, //字符显示数据 output ShowData_finish //字符显示完成 ); // //reg[3:0] tempHH; //tempH的高位 //reg[3:0] tempHL; //tempH的低位 //reg[3:0] tempLH; //reg[3:0] tempLL; // //reg[3:0] humidityHH; //reg[3:0] humidityHL; //reg[3:0] humidityLH; //reg[3:0] humidityLL; reg[7:0] tempHREG; reg[7:0] tempLREG; reg[7:0] humidityHREG; reg[7:0] humidityLREG; reg[4:0] font; reg[4:0] font_sel; reg[4:0] font_index; reg font_row; reg[7:0] show_x; reg[3:0] show_y; reg[23:0] showfont_data_reg; wire onefont_finish; wire[7:0] fontdata; assign onefont_finish = (font_row == 1'b1 && font_index == 'd10 && write_done == 1'b1) ? 1'b1 : 1'b0; assign ShowData_finish = (onefont_finish == 1'b1 && font_sel == 'd7) ? 1'b1 : 1'b0; assign ShowData_Data = showfont_data_reg; always@(*) begin case(font_index) 'd0: showfont_data_reg <= {8'h78,8'h00,8'hB0 + show_y + font_row}; 'd1: showfont_data_reg <= {8'h78,8'h00,8'h00 + show_x[3:0]}; 'd2: showfont_data_reg <= {8'h78,8'h00,8'h10 + show_x[7:4]}; default: showfont_data_reg <= {8'h78,8'h40,fontdata}; //fontdata endcase end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) font_index <= 'd0; else if(write_done == 1'b1 && font_index == 'd10) font_index <= 'd0; else if(write_done == 1'b1 && ShowData_req == 1'b1) font_index <= font_index + 1'b1; else font_index <= font_index; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) font_row <= 1'b0; else if(onefont_finish == 1'b1) font_row <= 1'b0; else if(write_done == 1'b1 && font_index == 'd10) font_row <= 1'b1; else font_row <= font_row; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) font_sel <= 'd0; else if(ShowData_finish == 1'b1) font_sel <= 'd0; else if(onefont_finish == 1'b1) font_sel <= font_sel + 1'b1; else font_sel <= font_sel; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) begin show_x <= 'd0; show_y <= 'd0; end else if(font_sel == 'd0) begin show_x <= 'd54; show_y <= 'd3; end else if(font_sel == 'd1) begin show_x <= 'd62; show_y <= 'd3; end else if(font_sel == 'd2) begin show_x <= 'd79; show_y <= 'd3; end else if(font_sel == 'd3) begin show_x <= 'd88; show_y <= 'd3; end else if(font_sel == 'd4) begin show_x <= 'd54; show_y <= 'd5; end else if(font_sel == 'd5) begin show_x <= 'd62; show_y <= 'd5; end else if(font_sel == 'd6) begin show_x <= 'd79; show_y <= 'd5; end else if(font_sel == 'd7) begin show_x <= 'd88; show_y <= 'd5; end else begin show_x <= 'd0; show_y <= 'd0; end end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) font <= 'd0; else if(font_sel == 'd0) font <= tempHREG / 10; else if(font_sel == 'd1) font <= tempHREG % 10; else if(font_sel == 'd2) font <= tempLREG / 10; else if(font_sel == 'd3) font <= tempLREG % 10; else if(font_sel == 'd4) font <= humidityHREG / 10; else if(font_sel == 'd5) font <= humidityHREG % 10; else if(font_sel == 'd6) font <= humidityLREG / 10; else if(font_sel == 'd7) font <= humidityLREG % 10; else font <= font; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) tempHREG <= 'd0; else if(dht11_done == 1'b1) tempHREG <= tempH; else tempHREG <= tempHREG; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) tempLREG <= 'd0; else if(dht11_done == 1'b1) tempLREG <= tempL; else tempLREG <= tempLREG; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) humidityHREG <= 'd0; else if(dht11_done == 1'b1) humidityHREG <= humidityH; else humidityHREG <= humidityHREG; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) humidityLREG <= 'd0; else if(dht11_done == 1'b1) humidityLREG <= humidityL; else humidityLREG <= humidityLREG; end OLED_NumData OLED_NumDataHP( .sys_clk (sys_clk), .rst_n (rst_n), .font_row (font_row), .font_sel (font), .index (font_index - 'd3), .data (fontdata) ); endmodule OLED_ShowFont 模块:
module OLED_ShowFont( input sys_clk, input rst_n, input ShowFont_req, //字符显示请求 input write_done, //iic一组数据写完成 output[23:0] ShowFont_Data, //字符显示数据 output ShowFont_finish //字符显示完成 ); reg[8:0] showfont_index; reg[23:0] showfont_data_reg; wire[7:0] fontdata; reg[5:0] font_index; //当前显示第几个字符 reg[1:0] font_size; //1 :16*16 0 : 8*16 reg[7:0] show_x; reg[3:0] show_y; reg font_row; wire onefont_finish; assign onefont_finish = ((showfont_index == ('d10 + 'd8 * font_size)) && (font_row == 1'b1) && write_done == 1'b1) ? 1'b1 : 1'b0; assign ShowFont_finish = (onefont_finish == 1'b1 && font_index == 'd14) ? 1'b1 : 1'b0; assign ShowFont_Data = showfont_data_reg; always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) showfont_index <= 'd0; else if(onefont_finish == 1'b1) showfont_index <= 'd0; else if(font_size == 'd0 && showfont_index == 'd10 && write_done == 1'b1) showfont_index <= 'd0; else if(font_size == 'd1 && showfont_index == 'd18 && write_done == 1'b1) showfont_index <= 'd0; else if(write_done == 1'b1 && ShowFont_req == 1'b1) showfont_index <= showfont_index + 1'b1; else showfont_index <= showfont_index; end always@(*) begin case(showfont_index) 'd0: showfont_data_reg <= {8'h78,8'h00,8'hB0 + show_y + font_row}; 'd1: showfont_data_reg <= {8'h78,8'h00,8'h00 + show_x[3:0]}; 'd2: showfont_data_reg <= {8'h78,8'h00,8'h10 + show_x[7:4]}; default: showfont_data_reg <= {8'h78,8'h40,fontdata}; //fontdata endcase end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) font_row <= 1'b0; else if(onefont_finish == 1'b1) font_row <= 1'b0; else if(font_size == 'd0 && showfont_index == 'd10 && write_done == 1'b1) font_row <= 1'b1; else if(font_size == 'd1 && showfont_index == 'd18 && write_done == 1'b1) font_row <= 1'b1; else font_row <= font_row; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) font_size <= 'd0; else if(font_index <= 'd3) font_size <= 'd0; else if(font_index == 'd10 || font_index == 'd12 || font_index == 'd13 || font_index == 'd14) font_size <= 'd0; else font_size <= 'd1; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) font_index <= 'd0; else if(ShowFont_finish == 1'b1) font_index <= 'd0; else if(onefont_finish == 1'b1) font_index <= font_index + 1'b1; else font_index <= font_index; end always@(posedge sys_clk or negedge rst_n) begin if(rst_n == 1'b0) //F begin show_x <= 'd8; show_y <= 'd0; end else if(onefont_finish == 1'b1 && font_index == 'd0) //P begin show_x <= 'd24; show_y <= 'd0; end else if(onefont_finish == 1'b1 && font_index == 'd1) //G begin show_x <= 'd40; show_y <= 'd0; end else if(onefont_finish == 1'b1 && font_index == 'd2) //A begin show_x <= 'd56; show_y <= 'd0; end else if(onefont_finish == 1'b1 && font_index == 'd3) //之 begin show_x <= 'd77; show_y <= 'd0; end else if(onefont_finish == 1'b1 && font_index == 'd4) //旅 begin show_x <= 'd100; show_y <= 'd0; end else if(onefont_finish == 1'b1 && font_index == 'd5) //温 begin show_x <= 'd10; show_y <= 'd3; end else if(onefont_finish == 1'b1 && font_index == 'd6) //度 begin show_x <= 'd30; show_y <= 'd3; end else if(onefont_finish == 1'b1 && font_index == 'd7) //湿 begin show_x <= 'd10; show_y <= 'd5; end else if(onefont_finish == 1'b1 && font_index == 'd8) //度 begin show_x <= 'd30; show_y <= 'd5; end else if(onefont_finish == 1'b1 && font_index == 'd9) //. begin show_x <= 'd70; show_y <= 'd3; end else if(onefont_finish == 1'b1 && font_index == 'd10) //℃ begin show_x <= 'd100; show_y <= 'd3; end else if(onefont_finish == 1'b1 && font_index == 'd11) //R begin show_x <= 'd100; show_y <= 'd5; end else if(onefont_finish == 1'b1 && font_index == 'd12) //H begin show_x <= 'd108; show_y <= 'd5; end else if(onefont_finish == 1'b1 && font_index == 'd13) //. begin show_x <= 'd70; show_y <= 'd5; end else begin show_x <= show_x; show_y <= show_y; end end OLED_FontData OLED_FontData_HP( .sys_clk (sys_clk), .rst_n (rst_n), .font_row (font_row), .font_sel (font_index), .index (showfont_index - 'd3), .data (fontdata) ); endmodule OLED_Top.v 模块:
//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 IIC output OLED_SCL, inout OLED_SDA ); 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) begin if(rst_n == 1'b0) state <= OLED_INIT; else state <= next_state; end always@(*) begin case(state) OLED_INIT: if(init_finish == 1'b1) next_state <= OLED_Refresh; else next_state <= OLED_INIT; OLED_Refresh: if(refresh_finish == 1'b1) next_state <= OLED_ShowFont; else next_state <= OLED_Refresh; OLED_ShowFont: if(showfont_finish == 1'b1) next_state <= OLED_IDLE; else next_state <= OLED_ShowFont; OLED_IDLE: if(dht11_done == 1'b1) next_state <= OLED_ShowData; else next_state <= OLED_IDLE; OLED_ShowData: if(showdata_finish == 1'b1) next_state <= OLED_IDLE; else next_state <= OLED_ShowData; default: next_state <= OLED_INIT; endcase end OLED_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 参考链接
[1] STM32 7 针 0.96 寸 OLED 显示屏(硬件 SPI+DMA)无需内核响应 超高刷新率!_0.96oled 速率 - ZEEKLOG 博客
[2] 7 脚 spi OLED 屏幕改造成 IIC 屏幕_7 针 spioled 改 4 针 iic-ZEEKLOG 博客
[3] FPGA 之旅设计 99 例之第九例 ----- 驱动 0.96 寸 OLED 屏_fpga oled 12864-ZEEKLOG 博客
[4] IIC 通讯协议手册
[5] 一次讲透 I2C 总线时序图,单片机 IIC 通信协议底层【洋桃电子大百科 P28】_哔哩哔哩_bilibili