前言

本文介绍以 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由高电平变为低电平,产生一个下降沿,表示起始信号的开始。
    I2C起始信号
    使用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由低电平变为高电平,产生一个上升沿,表示停止信号。
    I2C终止信号
    使用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(非应答),通常表示接收方接收该字节没有成功。
    I2C响应信号
    使用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)
    I2C读写时序
    使用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驱动)

OLED通信时序
如上图所示:

  • 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的原理图
原理图的大致意思如下:
SSD1306内置的MCU接收到其他设备通过IIC传输过来的一个字节的内容,先对其D/C位进行解析,如果D/C为0,表示这是一个命令字节,则最终会执行控制命令。如果如果D/C为1,表示这是一个数据字节,它就会将这个数据写入GDDRAM,简化后的示意图如下:
简化后的SSD1306的原理图
OLED屏幕显示内容的逻辑就是,根据GDDRAM去扫描点阵,1表示点亮那一个像素点,0表示不点亮。最终亮和不亮的像素点构成,你想显示的内容,如字符A。

(4)GDDRAM

GDDRAM一共有64行和128列,我们取8行为一个字节,64/8=8。我们称其为8个page和128列,如图:
GDDRAM
对其进行操作的命令如下:
操作gddram部分的
OLED命令

在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);
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐