IIC通信时序及IIC驱动OLED
·
前言
本文介绍以 IIC通信时序以及如何使用IIC驱动OLED屏幕
文章目录
一、IIC通信时序
(1)在对应的.h文件(头文件)当中封装频繁使用的函数(以下模块会直接调用该头文件中的宏)
#ifndef __OLED_H__
#define __OLED_H__
#include "sys.h"
#define OLED_I2C_SCL_CLK() __HAL_RCC_GPIOB_CLK_ENABLE();
#define OLED_I2C_SCL_PORT GPIOB
#define OLED_I2C_SCL_PIN GPIO_PIN_8
#define OLED_I2C_SDA_CLK() __HAL_RCC_GPIOB_CLK_ENABLE();
#define OLED_I2C_SDA_PORT GPIOB
#define OLED_I2C_SDA_PIN GPIO_PIN_9
#define OLED_SCL_RESET() HAL_GPIO_WritePin(OLED_I2C_SCL_PORT, OLED_I2C_SCL_PIN, GPIO_PIN_RESET)
#define OLED_SCL_SET() HAL_GPIO_WritePin(OLED_I2C_SCL_PORT, OLED_I2C_SCL_PIN, GPIO_PIN_SET)
#define OLED_SDA_RESET() HAL_GPIO_WritePin(OLED_I2C_SDA_PORT, OLED_I2C_SDA_PIN, GPIO_PIN_RESET)
#define OLED_SDA_SET() HAL_GPIO_WritePin(OLED_I2C_SDA_PORT, OLED_I2C_SDA_PIN, GPIO_PIN_SET)
#endif
(2)起始信号
- 起始信号由主设备发起,用于通知总线上的所有设备即将开始数据传输。
- 当SCL为高电平时,SDA由高电平变为低电平,产生一个下降沿,表示起始信号的开始。

使用C语言和HAL库函数完成起始信号的代码如下:
//起始信号
void oled_i2c_start(void)
{
//初始时,将SCL(串行时钟线)和SDA(串行数据线)拉高
OLED_SCL_SET();
OLED_SDA_SET();
//SCL为高电平时,SDA由高到低产生下降沿,表示起始信号
OLED_SDA_RESET();
OLED_SCL_RESET();
}
(3)终止信号
- 停止信号也由主设备发起,用于通知总线上的所有设备,数据传输已经结束。
- 当SCL为高电平时,SDA由低电平变为高电平,产生一个上升沿,表示停止信号。

使用C语言和HAL库函数完成起始信号的代码如下:
void oled_i2c_stop(void)
{
//SCL为高电平时
OLED_SCL_SET();
//SDA由低电平到高电平,产生上升沿,表示终止信号
OLED_SDA_RESET();
OLED_SDA_SET();
}
(4)响应信号
- 每当一个字节的数据传输完成后,接收方会向发送方发送一个应答信号(ACK)或非应答信号(NACK)。
- 应答信号在SCL的第9个时钟周期(当SCL为高电平时)发送。
- 如果SDA线为低电平,则表示ACK(有效应答),表示接收方已成功接收该字节。
- 如果SDA线为高电平,则表示NACK(非应答),通常表示接收方接收该字节没有成功。

使用C语言和HAL库函数完成起始信号的代码如下:
void oled_i2c_ack(void)
{
//I2C通信是必须有响应的协议,外部调用该函数,会产生一个响应信号,但是此处我们不做处理
OLED_SCL_SET();
OLED_SCL_RESET();
}
(5)读写时序
- 发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
- 接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

使用C语言和HAL库函数完成写一个字节的代码如下:
void oled_i2c_write_byte(uint8_t data)
{
uint8_t i, tmp;
tmp = data;
//因为IIC是串行通信,是一位一位进行收发的,所以循环取出待发送数据的8位
for(i = 0; i < 8; i++)
{
if((tmp & 0x80) == 0x80) //如果这一位为1,就拉高SDA线(即将数据的这一位放在SDA线上,待从机读取)
OLED_SDA_SET();
else //如果这一位为0,就拉低SDA线(即将数据的这一位放在SDA线上,待从机读取)
OLED_SDA_RESET();
tmp = tmp << 1; //迭代,用于循环读取数据的8位
OLED_SCL_SET(); //拉高SCL线,从机读取SDA线上的数据
OLED_SCL_RESET(); //“发送完上一位数据后”,再拉低SCL线,放下一位数据
}
}
二、IIC驱动OLED屏幕
(1)OLED通讯协议(IIC驱动)

如上图所示:
- S表示IIC的起始信号
- S之后跟了8位数据,其中Slave Address 表示从机的地址共有七位(前六位固定011110,若从机有上拉电阻则为1,没有上拉电阻则为0);第8位(R/W)表示读写位,对从机写操作则为0。最终为:0111 10000,表示为16进制为:0x78
- 之后会跟一个ACK,表示从机对主机传输的第一个字节的确认。
- 紧接着的下一个字节,前两位分别为:Co,D/C#,低6位全为0。这一个字节用来配置发送的字节的“类型”。Co为1时,配置为:一个控制字节后加一个数据字节,再一个控制字节加要给数据字节,如此循环。Co为0时,配置为:仅有一个控制字节,后续全是数据字节。D/C位为0表示命令,D/C位为1时表示数据。
- 每发送一个字节后,都需要写一个ACK。
- P,最后就是一个终止信号,表示IIC通信的结束。
(2)基于IIC的时序,实现向OLED发送一个字节的命令
代码如下:
void oled_write_cmd(uint8_t cmd)
{
oled_i2c_start();
oled_i2c_write_byte(0x78);//从机不接上拉电阻,所以第7位为0,主机对从机写,所以第8为也是0,最终第一个字节发送0x78
oled_i2c_ack();
oled_i2c_write_byte(0x00);//Co设置为0,D/C也设置为0,最终第二个字节发送0x00
oled_i2c_ack();
oled_i2c_write_byte(cmd);//此处才是想给OLED发送的真正的命令
oled_i2c_ack();
oled_i2c_stop();//停止信号
}
(2)基于IIC的时序,实现向OLED发送一个字节的数据
代码如下:
void oled_write_data(uint8_t data)
{
oled_i2c_start();
oled_i2c_write_byte(0x78);
oled_i2c_ack();
oled_i2c_write_byte(0x40);//Co依然配置为0,D/C配置为1,所以发送0x40
oled_i2c_ack();
oled_i2c_write_byte(data);
oled_i2c_ack();
oled_i2c_stop();
}
(3)指定在OLED的什么位置显示,什么内容
OLED屏幕的主控芯片为SSD1306,原理图如下:

原理图的大致意思如下:
SSD1306内置的MCU接收到其他设备通过IIC传输过来的一个字节的内容,先对其D/C位进行解析,如果D/C为0,表示这是一个命令字节,则最终会执行控制命令。如果如果D/C为1,表示这是一个数据字节,它就会将这个数据写入GDDRAM,简化后的示意图如下:

OLED屏幕显示内容的逻辑就是,根据GDDRAM去扫描点阵,1表示点亮那一个像素点,0表示不点亮。最终亮和不亮的像素点构成,你想显示的内容,如字符A。
(4)GDDRAM
GDDRAM一共有64行和128列,我们取8行为一个字节,64/8=8。我们称其为8个page和128列,如图:

对其进行操作的命令如下:

在OLED屏幕上显示相关内容的代码如下:
//指定在OLED的什么位置开始显示
void oled_set_cursor(uint8_t x, uint8_t y)
{
oled_write_cmd(0xB0 + y);
oled_write_cmd((x & 0x0F) | 0x00);
oled_write_cmd(((x & 0xF0) >> 4) | 0x10);
}
//清空GDDRAM
void oled_fill(uint8_t data)
{
uint8_t i, j;
for(i = 0; i < 8; i++)
{
oled_set_cursor(0, i);
for(j = 0; j < 128; j++)
oled_write_data(data);
}
}
//在oled屏幕上显示:请输入密码
void oled_show_input(void)
{
oled_fill(0x00);
oled_show_chinese(10, 1, 0, SHOW_INPUT_PWD);
oled_show_chinese(30, 1, 1, SHOW_INPUT_PWD);
oled_show_chinese(50, 1, 2, SHOW_INPUT_PWD);
oled_show_chinese(70, 1, 3, SHOW_INPUT_PWD);
oled_show_chinese(90, 1, 4, SHOW_INPUT_PWD);
oled_show_char(110, 1, ':', 16);
}
//在oled屏幕上显示:密码正确
void oled_show_right(void)
{
oled_fill(0x00);
oled_show_chinese(20,1,0,SHOW_PWD_RIGHT);
oled_show_chinese(45,1,1,SHOW_PWD_RIGHT);
oled_show_chinese(70,1,2,SHOW_PWD_RIGHT);
oled_show_chinese(95,1,3,SHOW_PWD_RIGHT);
}
//在oled屏幕上显示:密码错误
void oled_show_wrong(void)
{
oled_fill(0x00);
oled_show_chinese(20,1,0,SHOW_PWD_WRONG);
oled_show_chinese(45,1,1,SHOW_PWD_WRONG);
oled_show_chinese(70,1,2,SHOW_PWD_WRONG);
oled_show_chinese(95,1,3,SHOW_PWD_WRONG);
}
//在oled屏幕上显示:请输入旧密码
void oled_show_old(void)
{
oled_fill(0x00);
oled_show_chinese(10,1,0,SHOW_INPUT_OLD_PWD);
oled_show_chinese(30,1,1,SHOW_INPUT_OLD_PWD);
oled_show_chinese(50,1,2,SHOW_INPUT_OLD_PWD);
oled_show_chinese(70,1,3,SHOW_INPUT_OLD_PWD);
oled_show_chinese(90,1,4,SHOW_INPUT_OLD_PWD);
oled_show_chinese(110,1,5,SHOW_INPUT_OLD_PWD);
}
//在oled屏幕上显示:请输入新密码
void oled_show_new(void)
{
oled_fill(0x00);
oled_show_chinese(10,1,0,SHOW_INPUT_NEW_PWD);
oled_show_chinese(30,1,1,SHOW_INPUT_NEW_PWD);
oled_show_chinese(50,1,2,SHOW_INPUT_NEW_PWD);
oled_show_chinese(70,1,3,SHOW_INPUT_NEW_PWD);
oled_show_chinese(90,1,4,SHOW_INPUT_NEW_PWD);
oled_show_chinese(110,1,5,SHOW_INPUT_NEW_PWD);
}
//在oled屏幕上显示:密码修改成功
void oled_show_set(void)
{
oled_fill(0x00);
oled_show_chinese(10,1,0,SHOW_SET_PWD);
oled_show_chinese(30,1,1,SHOW_SET_PWD);
oled_show_chinese(50,1,2,SHOW_SET_PWD);
oled_show_chinese(70,1,3,SHOW_SET_PWD);
oled_show_chinese(90,1,4,SHOW_SET_PWD);
oled_show_char(110,1,':',16);
}
//在oled屏幕上显示:请设定密码
void oled_show_changed(void)
{
oled_fill(0x00);
oled_show_chinese(10,1,0,SHOW_PWD_CHANGED);
oled_show_chinese(30,1,1,SHOW_PWD_CHANGED);
oled_show_chinese(50,1,2,SHOW_PWD_CHANGED);
oled_show_chinese(70,1,3,SHOW_PWD_CHANGED);
oled_show_chinese(90,1,4,SHOW_PWD_CHANGED);
oled_show_chinese(110,1,5,SHOW_PWD_CHANGED);
}
更多推荐

所有评论(0)