一、IIC简要介绍
IIC(Inter-Integrated Circuit)是一种由飞利浦公司开发的串行通信协议,主要用于微控制器与外设之间的低速、短距离数据传输。其核心特性如下:
1. 物理结构
- 仅需两根双向信号线:
- SDA(串行数据线):传输数据
- SCL(串行时钟线):同步通信时序
- 采用开漏输出设计,依赖上拉电阻维持高电平,支持多设备连接。
2. 通信模式
- 半双工:同一时间仅支持单向传输(主机→从机或从机→主机),通过动态切换方向实现双向通信。
- 全双工协议对比:SPI等协议需独立发送/接收线路,而IIC通过硬件简化节省资源。
3. 典型应用场景
- 适用于传感器、EEPROM等低速外设,尤其适合硬件资源受限的嵌入式系统。
- 通过主机控制SCL和地址帧中的读写位来管理通信流程。
4. 通信流程示例
- 起始条件:SCL高电平时,SDA由高→低跳变。
- 地址传输:7位设备地址+1位读写位,从机响应ACK。
- 数据交换:按字节传输,每字节后跟随ACK确认。
- 停止条件:SCL高电平时,SDA由低→高跳变。
5. 优势与局限
- 优势:硬件简单、成本低,支持多主多从拓扑。
- 局限:速率较低(标准模式100kbps),依赖上拉电阻且易受干扰。
二、连接多个IIC设备
一)、硬件连接方式
-
总线拓扑设计
- 将所有设备的SDA和SCL引脚分别并联连接,确保信号线物理上共享。
- 在总线的SDA和SCL线上各添加一个4.7kΩ~10kΩ的上拉电阻,连接到电源正极(VCC),确保空闲时为高电平。
-
地址冲突处理
- 每个IIC设备需配置唯一地址(7位或10位),通过跳线或寄存器设置。
- 部分设备(如PCF8574)支持通过硬件跳帽修改地址,避免冲突。
注:不同的IIC设备地址及设置方式不同,请参考相关设备的文档。
二)、通信流程关键点
-
主机控制时序
- 主机发送起始条件(SCL高时SDA下降沿),后接从机地址+读写位,从机通过ACK确认响应。
- 数据按字节传输,每字节后需从机返回ACK/NACK信号
三、CH592等MCU硬件IIC接口配置要点
需将SCL/SDA引脚配置为上拉输入方式。
GPIOB_ModeCfg(GPIO_Pin_12 | GPIO_Pin_13, GPIO_ModeIN_PU);
这里有点比较奇怪的地方,如果使用软件模拟IIC来驱动的话,需要将这两引脚配置为推挽输出模式。
时钟初始化
典型配置参数:400kHz通信速率,16:9占空比,7位地址模式。关键代码如下:
I2C_Init(I2C_Mode_I2C, 400000, I2C_DutyCycle_16_9, I2C_Ack_Enable, I2C_AckAddr_7bit, MASTER_ADDR);
四、4Pin IIC 驱动OLED屏介绍
这里以中景园的驱动芯片为SSD1315的OLED显示屏为例说明。
从上图中,我们很容易看出,该OLED屏的IIC地址为0x78。
该公司好象也有驱动芯片为SSD1306的OLED显示屏,两种屏的IIC地址不同,SSD1306的IIC地址是0x3C。
为从图中,我们可以看出数据格式为:
S ->SSD1315 地址->ACK->Control byte ->ACK->Data byte ->Control byte ->ACK->Data byte-> P
S:起始位
P: 停止位
Control byte: 最高位通常为0。Bit6:数据/命令选择,1表示接下来发送的是数据,0表示发送的是命令。
五、CH592 MCU 的IIC接口使用指南
对比OLED屏的介绍,我们很容易理解接下来的驱动代码。
六、实例代码
#ifdef OLED_USE_IIC
void OLED_Write_nByte(u8 * buff,u8 len,u8 cmd)
{
uint8_t mode;
cmd == OLED_CMD ? (mode = 0x00) : (mode = 0x40);
IIC_Send_n_Byte(OLED_IIC_ADDR,mode,buff,len);
}
/*******************************************************************
* @function :Write a byte of content to the OLED screen
* @parameters :dat:Content to be written
cmd:0-write command
1-write data
* @retvalue :None
********************************************************************/
void OLED_WriteByte(uint8_t dat,uint8_t cmd)
{
uint8_t mode;
cmd == OLED_CMD ? (mode = 0x40) : (mode = 0x00);
IIC_Send_OneByte(OLED_IIC_ADDR,mode,dat);
}
#else
void IIC_delay(void)
{
u8 t=3;
while(t--);
}
void I2C_Start(void)
{
OLED_SDA_Set();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Clr();
IIC_delay();
OLED_SCL_Clr();
IIC_delay();
}
void I2C_Stop(void)
{
OLED_SDA_Clr();
OLED_SCL_Set();
IIC_delay();
OLED_SDA_Set();
}
void I2C_WaitAck(void)
{
OLED_SDA_Set();
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();
IIC_delay();
}
void Send_Byte(u8 dat)
{
u8 i;
for(i=0;i<8;i++)
{
if(dat&0x80)
{
OLED_SDA_Set();
}
else
{
OLED_SDA_Clr();
}
IIC_delay();
OLED_SCL_Set();
IIC_delay();
OLED_SCL_Clr();
dat<<=1;
}
}
void OLED_Write_nByte(u8 * buff,u8 len,u8 mode)
{
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
if(mode)
Send_Byte(0x40);
else
Send_Byte(0x00);
I2C_WaitAck();
for (u8 index = 0; index < len; index ++)
{
Send_Byte(buff[index]);
I2C_WaitAck();
}
I2C_Stop();
}
void OLED_WriteByte(u8 dat,u8 mode)
{
I2C_Start();
Send_Byte(0x78);
I2C_WaitAck();
if(mode)
Send_Byte(0x40);
else
Send_Byte(0x00);
I2C_WaitAck();
Send_Byte(dat);
I2C_WaitAck();
I2C_Stop();
}
#endif
/*******************************************************************
* @function :Set coordinates in the OLED screen
* @parameters :x:x coordinates
y:y coordinates
* @retvalue :None
********************************************************************/
void OLED_Set_Pos(unsigned char x, unsigned char y)
{
#if 1
u8 cmdbuff[3];
cmdbuff[0] = YLevel+y/PAGE_SIZE;
cmdbuff[1] = (((x+2)&0xf0)>>4)|0x10;
cmdbuff[2] = (x+2)&0x0f;
OLED_Write_nByte(cmdbuff,sizeof(cmdbuff),OLED_CMD);
#else
OLED_WriteByte(YLevel+y/PAGE_SIZE,OLED_CMD);
OLED_WriteByte((((x+2)&0xf0)>>4)|0x10,OLED_CMD);
OLED_WriteByte(((x+2)&0x0f),OLED_CMD);
#endif
}
/*******************************************************************
* @name :void OLED_Display_On(void)
* @date :2018-08-27
* @function :Turn on OLED display
* @parameters :None
* @retvalue :None
********************************************************************/
//开启OLED显示
void OLED_Display_On(void)
{
u8 cmdbuff[3] = {0x8D,0x14,0xAF};
OLED_Write_nByte(cmdbuff,sizeof(cmdbuff),OLED_CMD);
// OLED_WriteByte(0X8D,OLED_CMD); //SET DCDC命令
// OLED_WriteByte(0X14,OLED_CMD); //DCDC ON
// OLED_WriteByte(0XAF,OLED_CMD); //DISPLAY ON
}
/*******************************************************************
* @name :void OLED_Display_Off(void)
* @date :2018-08-27
* @function :Turn off OLED display
* @parameters :None
* @retvalue :None
********************************************************************/
//关闭OLED显示
void OLED_Display_Off(void)
{
u8 cmdbuff[3] = {0x8D,0x10,0xAE};
OLED_Write_nByte(cmdbuff,sizeof(cmdbuff),OLED_CMD);
// OLED_WriteByte(0X8D,OLED_CMD); //SET DCDC命令
// OLED_WriteByte(0X10,OLED_CMD); //DCDC OFF
// OLED_WriteByte(0XAE,OLED_CMD); //DISPLAY OFF
}
/*******************************************************************
* @name :void OLED_Set_Pixel(unsigned char x, unsigned char y,unsigned char color)
* @date :2018-08-27
* @function :set the value of pixel to RAM
* @parameters :x:the x coordinates of pixel
y:the y coordinates of pixel
color:the color value of the point
1-white
0-black
* @retvalue :None
********************************************************************/
void OLED_Set_Pixel(unsigned char x, unsigned char y,unsigned char color)
{
if(color)
{
OLED_buffer[(y/PAGE_SIZE)*WIDTH+x]|= (1<<(y%PAGE_SIZE))&0xff;
}
else
{
OLED_buffer[(y/PAGE_SIZE)*WIDTH+x]&= ~((1<<(y%PAGE_SIZE))&0xff);
}
}
/*******************************************************************
* @name :void OLED_Display(void)
* @date :2018-08-27
* @function :Display in OLED screen
* @parameters :None
* @retvalue :None
********************************************************************/
void OLED_Display(void)
{
u8 i,n;
u8 cmdbuff[3];
cmdbuff[1] = XLevelH;
cmdbuff[2] = XLevelL;
for(i=0;i<PAGE_SIZE;i++)
{
cmdbuff[0] = YLevel+i;
OLED_Write_nByte(cmdbuff,sizeof(cmdbuff),OLED_CMD);
#if 0
OLED_WriteByte (YLevel+i,OLED_CMD); //设置页地址(0~7)
OLED_WriteByte (XLevelH,OLED_CMD); //设置显示位置—列高地址
OLED_WriteByte (XLevelL,OLED_CMD); //设置显示位置—列低地址
for(n=0;n<WIDTH;n++)
{
OLED_WriteByte(OLED_buffer[i*WIDTH+n],OLED_DATA);
}
#endif
OLED_Write_nByte(&OLED_buffer[i*WIDTH],WIDTH,OLED_DATA);
} //更新显示
}
/*******************************************************************
* @name :void OLED_Clear(unsigned dat)
* @date :2018-08-27
* @function :clear OLED screen
* @parameters :dat:0-Display full black
1-Display full white
* @retvalue :None
********************************************************************/
void OLED_Clear(uint8_t dat)
{
if(dat)
{
memset(OLED_buffer,0xff,sizeof(OLED_buffer));
}
else
{
memset(OLED_buffer,0,sizeof(OLED_buffer));
}
OLED_Display();
}
/*******************************************************************
* @name :void OLED_Init_GPIO(void)
* @date :2018-08-27
* @function :Reset OLED screen
* @parameters :None
* @retvalue :None
********************************************************************/
void OLED_Init_GPIO(void)
{
#ifdef OLED_USE_IIC
GPIOB_ModeCfg(GPIO_Pin_12 | GPIO_Pin_13, GPIO_ModeIN_PU);
I2C_Init(I2C_Mode_I2C, 400000, I2C_DutyCycle_16_9, I2C_Ack_Enable, I2C_AckAddr_7bit, MASTER_ADDR);
WaitIICBusy();
#else
GPIOB_ModeCfg(OLED_SDA_PIN | OLED_SCL_PIN, GPIO_ModeOut_PP_5mA);
#endif
}
/*******************************************************************
* @name :void OLED_Init(void)
* @date :2018-08-27
* @function :initialise OLED SH1106 control IC
* @parameters :None
* @retvalue :None
********************************************************************/
void OLED_Init(void)
{
u8 InitCmd[] = {0xAE,0x00,0x10,0x40,0x81,0xCF,0xA1,0xC8,0xA6,0xA8,0x3F,
0xD3,0x00,0xD5,0x80,0xD9,0xF1,0xDA,0x12,0xDB,0x30,0x20,0x02,0x8D,0x14};
OLED_Init_GPIO(); //初始化GPIO
#if 0
OLED_Display_Off(); //power off
OLED_WriteByte(0xAE,OLED_CMD);//--turn off oled panel
OLED_WriteByte(0x00,OLED_CMD);//---set low column address
OLED_WriteByte(0x10,OLED_CMD);//---set high column address
OLED_WriteByte(0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WriteByte(0x81,OLED_CMD);//--set contrast control register
OLED_WriteByte(0xCF,OLED_CMD); // Set SEG Output Current Brightness
OLED_WriteByte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WriteByte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WriteByte(0xA6,OLED_CMD);//--set normal display
OLED_WriteByte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
OLED_WriteByte(0x3f,OLED_CMD);//--1/64 duty
OLED_WriteByte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
OLED_WriteByte(0x00,OLED_CMD);//-not offset
OLED_WriteByte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
OLED_WriteByte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WriteByte(0xD9,OLED_CMD);//--set pre-charge period
OLED_WriteByte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WriteByte(0xDA,OLED_CMD);//--set com pins hardware configuration
OLED_WriteByte(0x12,OLED_CMD);
OLED_WriteByte(0xDB,OLED_CMD);//--set vcomh
OLED_WriteByte(0x30,OLED_CMD);//Set VCOM Deselect Level
OLED_WriteByte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WriteByte(0x02,OLED_CMD);//
OLED_WriteByte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
OLED_WriteByte(0x14,OLED_CMD);//--set(0x10) disable
// OLED_WriteByte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
// OLED_WriteByte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7)
// OLED_WriteByte(0xAF,OLED_CMD);//--turn on oled panel
#else
OLED_Write_nByte(InitCmd,sizeof(InitCmd),OLED_CMD);
#endif
OLED_Clear(0);
// OLED_WriteByte(0xAF,OLED_CMD); /*display ON*/
OLED_Set_Pos(0,0);
OLED_Display_On(); // power on
}
#ifndef __OLED_H
#define __OLED_H
#include "HAL.h"
#define PAGE_SIZE 8
#define XLevelL 0x00
#define XLevelH 0x10
#define YLevel 0xB0
#define Brightness 0xFF
#define WIDTH 128
#define HEIGHT 64
#define OLED_IIC_ADDR 0x78
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
enum{
OLED_CMD = 0,
OLED_DATA
};
#ifndef OLED_USE_IIC
#define OLED_USE_IIC
#endif
#define delay_ms(x) DelayMs(x)
#define OLED_SDA BV(12) //4 Pin OLED屏SDA
#define OLED_SCL BV(13) //7 Pin OLED屏SCL
#define OLED_SDA_OUT (R32_PB_OUT) //7 Pin OLED屏SDA
#define OLED_SCL_OUT (R32_PB_OUT) //7 Pin OLED屏SCL
#define OLED_SDA_DIR (R32_PB_DIR |= OLED_SDA) //7 Pin OLED屏SDA
#define OLED_SCL_DIR (R32_PB_DIR |= OLED_SCL) //7 Pin OLED屏SCL
#define OLED_SDA_PIN GPIO_Pin_12 //4 Pin OLED屏SDA
#define OLED_SCL_PIN GPIO_Pin_13 //7 Pin OLED屏SCL
#define OLED_SDA_Set() (OLED_SDA_OUT |= OLED_SDA)
#define OLED_SDA_Clr() (OLED_SDA_OUT &= (~OLED_SDA))
#define OLED_SCL_Set() (OLED_SCL_OUT |= OLED_SCL)
#define OLED_SCL_Clr() (OLED_SCL_OUT &= (~OLED_SCL))
void OLED_WriteByte(uint8_t data,uint8_t cmd);
void OLED_Display_On(void);
void OLED_Display_Off(void);
void OLED_Set_Pos(unsigned char x, unsigned char y);
void OLED_Reset(void);
void OLED_Init_GPIO(void);
void OLED_Init(void);
void OLED_Set_Pixel(unsigned char x, unsigned char y,unsigned char color);
void OLED_Display(void);
void OLED_Clear(uint8_t dat);
extern void IIC_Send_OneByte(uint8_t addr, uint8_t mode, uint8_t data);
extern void IIC_Send_2_Byte(uint8_t addr, uint8_t mode, uint8_t data1,uint8_t data2);
extern void IIC_Send_n_Byte(uint8_t addr, uint8_t mode, uint8_t *src, uint8_t len);
extern BOOL WaitIICBusy(void);
#endif
//mode : 0x40 command 0x00 data.
void IIC_Send_OneByte(uint8_t addr, uint8_t mode, uint8_t data)
{
while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
// if (WaitIICBusy())
// return;
I2C_GenerateSTART(ENABLE); //起始信号
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
// if (Wait_IIC_ModeSelect())
// return;
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
// if (Wait_IIC_Trans_ModeSelected())
// return;
I2C_SendData(mode);
//ACK之后直接写入数据
while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
// if (Wait_IIC_SendBuffEmpty())
// return;
I2C_SendData(data); //发送寄存器的地址
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags
// if (Wait_IIC_Trans_Finish())
// return;
I2C_GenerateSTOP(ENABLE); //停止信号
}
void IIC_Send_2_Byte(uint8_t addr, uint8_t mode, uint8_t data1,uint8_t data2)
{
// while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
if (WaitIICBusy())
return;
I2C_GenerateSTART(ENABLE); //起始信号
// while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
if (Wait_IIC_ModeSelect())
return;
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
// while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
if (Wait_IIC_Trans_ModeSelected())
return;
I2C_SendData(mode); //发送寄存器的地址
// while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
if (Wait_IIC_SendBuffEmpty())
return;
I2C_SendData(data1); //发送寄存器的地址
// while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
if (Wait_IIC_SendBuffEmpty())
return;
I2C_SendData(data2); //发送寄存器的地址
// while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags
if (Wait_IIC_Trans_Finish())
return;
I2C_GenerateSTOP(ENABLE); //停止信号
}
void IIC_Send_n_Byte(uint8_t addr, uint8_t mode, uint8_t *src, uint8_t len)
{
// while(I2C_GetFlagStatus(I2C_FLAG_BUSY)); //IIC主机判忙
if (WaitIICBusy())
return;
I2C_GenerateSTART(ENABLE); //起始信号
// while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT)); //判断BUSY, MSL and SB flags
if (Wait_IIC_ModeSelect())
return;
I2C_Send7bitAddress(addr, I2C_Direction_Transmitter); //发送地址+最低位0表示为“写”
// while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //判断BUSY, MSL, ADDR, TXE and TRA flags
if (Wait_IIC_Trans_ModeSelected())
return;
I2C_SendData(mode); //发送寄存器的地址
//ACK之后直接写入数据
for(uint8_t i=0; i<len; i++)
{
// while(!I2C_GetFlagStatus(I2C_FLAG_TXE)); //获取TxE的状态 数据寄存器为空标志位,可以向其中写数据
if (Wait_IIC_SendBuffEmpty())
return;
I2C_SendData(*(src+i)); //发送寄存器的地址
}
// while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //判断TRA, BUSY, MSL, TXE and BTF flags
if (Wait_IIC_Trans_Finish())
return;
I2C_GenerateSTOP(ENABLE); //停止信号
}
上述代码包含软件模拟实现IIC协议,切换也很简单,只需要简单修改#define OLED_USE_IIC即可。
七、常见问题排查
显示异常:检查VCOMH电压设置(0xDB指令)和预充电周期(0xD9指令)
I2C通信失败:确认总线空闲状态(I2C_FLAG_BUSY)和ACK应答机制
残影现象:增加0x20指令设置内存地址模式,确保寻址正确
完整代码:CH582、CH592、CH584硬件IIC驱动4PinOLED显示屏,代码包含有软件模拟IIC协议驱动OLED屏资源-CSDN下载