IIC总线协议介绍
IIC:Inter Integrated Circuit,集成电路总线,是一种同步 串行 半双工通信总线。
IIC总线结构图
① 由时钟线SCL和数据线SDA组成,并且都接上拉电阻,确保总线空闲状态为高电平
② 总线支持多设备连接,允许多主机存在,每个设备都有一个唯一的地址
③ 连接到总线上的数目受总线的最大电容400pf限制
④ 数据传输速率:标准模式100k bit/s 快速模式400k bit/s 高速模式3.4Mbit/s
起始信号(S):当SCL为高电平时,SDA从高电平变为低电平
停止信号(P):当SCL为高电平时,SDA从低电平变为高电平
应答信号:上拉电阻影响下SDA默认为高,而从机拉低SDA就是确认收到数据即ACK,否则NACK
IIC总线协议函数
起始信号
void iic_start(void)
{ /* SCL为高电平期间, SDA从高电平往低电平跳变*/
IIC_SDA ( 1 );
IIC_SCL ( 1 );
iic_delay( );
IIC_SDA ( 0 );
iic_delay( );
IIC_SCL ( 0 );
iic_delay( ); /* 钳住总线, 准备发送/接收数据 */
}
停止信号
void iic_stop(void)
{ /* SCL为高电平期间, SDA从低电平往高电平跳变*/
IIC_SDA ( 0 );
iic_delay( );
IIC_SCL ( 1 );
iic_delay( );
IIC_SDA ( 1 ); /* 发送总线停止信号*/
iic_delay( );
}
检测应答信号
uint8_t iic_wait_ack (void) /* return 1:fail 0:succeed*/
{
IIC_SDA (1); /* 主机释放SDA线 */
iic_delay( );
IIC_SCL (1); /* 从机返回ACK*/
iic_delay( );
if ( IIC_READ_SDA ) /* SCL高电平读取SDA状态*/
{
iic_stop(); /* SDA高电平表示从机nack */
return 1;
}
IIC_SCL(0); /* SCL低电平表示结束ACK检查 */
iic_delay( );
return 0;
}
发送应答信号
void iic_ack(void)
{
IIC_SCL (0);
iic_delay( );
IIC_SDA (0); /* 数据线为低电平,表示应答 */
iic_delay( );
IIC_SCL (1);
iic_delay( );
}
发送非应答信号
void iic_nack(void)
{
IIC_SCL (0);
iic_delay( );
IIC_SDA (1); /* 数据线为高电平,表示非应答 */
iic_delay( );
IIC_SCL (1);
iic_delay( );
}
发送1字节数据
void iic_send_byte(uint8_t data)
{
for (uint8_t t = 0; t < 8; t++)
{ /* 高位先发 */
IIC_SDA((data & 0x80) >> 7);
iic_delay( );
IIC_SCL ( 1 );
iic_delay( );
IIC_SCL ( 0 );
data <<= 1; /* 左移1位, 用于下一次发送 */
}
IIC_SDA ( 1 ); /* 发送完成,主机释放SDA线 */
}
读取1字节数据
uint8_t iic_read_byte (uint8_t ack) /* 1:ack 0:nack*/
{
uint8_t receive = 0 ;
for (uint8_t t = 0; t < 8; t++)
{ /* 高位先输出,先收到的数据位要左移 */
receive <<= 1;
IIC_SCL ( 1 );
iic_delay( );
if ( IIC_READ_SDA ) receive++;
IIC_SCL ( 0 );
iic_delay( );
}
if ( !ack ) iic_nack();
else iic_ack();
return receive;
}
使用模拟IIC驱动OLED屏幕
部分代码
/*全局变量*********************/
/**
* OLED显存数组
* 所有的显示函数,都只是对此显存数组进行读写
* 随后调用OLED_Update函数或OLED_UpdateArea函数
* 才会将显存数组的数据发送到OLED硬件,进行显示
*/
uint8_t OLED_DisplayBuf[8][128];
/*********************全局变量*/
/*引脚配置*********************/
/**
* 函 数:OLED写SCL高低电平
* 参 数:要写入SCL的电平值,范围:0/1
* 返 回 值:无
* 说 明:当上层函数需要写SCL时,此函数会被调用
* 用户需要根据参数传入的值,将SCL置为高电平或者低电平
* 当参数传入0时,置SCL为低电平,当参数传入1时,置SCL为高电平
*/
void OLED_W_SCL(uint8_t BitValue)
{
/*根据BitValue的值,将SCL置高电平或者低电平*/
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, (GPIO_PinState)BitValue);
/*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
//...
}
/**
* 函 数:OLED写SDA高低电平
* 参 数:要写入SDA的电平值,范围:0/1
* 返 回 值:无
* 说 明:当上层函数需要写SDA时,此函数会被调用
* 用户需要根据参数传入的值,将SDA置为高电平或者低电平
* 当参数传入0时,置SDA为低电平,当参数传入1时,置SDA为高电平
*/
void OLED_W_SDA(uint8_t BitValue)
{
/*根据BitValue的值,将SDA置高电平或者低电平*/
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, (GPIO_PinState)BitValue);
/*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
//...
}
/**
* 函 数:OLED引脚初始化
* 参 数:无
* 返 回 值:无
* 说 明:当上层函数需要初始化时,此函数会被调用
* 用户需要将SCL和SDA引脚初始化为开漏模式,并释放引脚
*/
void OLED_GPIO_Init(void)
{
uint32_t i, j;
/*在初始化前,加入适量延时,待OLED供电稳定*/
for (i = 0; i < 1000; i ++)
{
for (j = 0; j < 1000; j ++);
}
/*将SCL和SDA引脚初始化为开漏模式*/
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStructure.Pin = GPIO_PIN_11 | GPIO_PIN_12;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);
/*释放SCL和SDA*/
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/*********************引脚配置*/
/*通信协议*********************/
/**
* 函 数:I2C起始
* 参 数:无
* 返 回 值:无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1); //释放SDA,确保SDA为高电平
OLED_W_SCL(1); //释放SCL,确保SCL为高电平
OLED_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号
OLED_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
/**
* 函 数:I2C终止
* 参 数:无
* 返 回 值:无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0); //拉低SDA,确保SDA为低电平
OLED_W_SCL(1); //释放SCL,使SCL呈现高电平
OLED_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}
/**
* 函 数:I2C发送一个字节
* 参 数:Byte 要发送的一个字节数据,范围:0x00~0xFF
* 返 回 值:无
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
/*循环8次,主机依次发送数据的每一位*/
for (i = 0; i < 8; i++)
{
/*使用掩码的方式取出Byte的指定一位数据并写入到SDA线*/
/*两个!的作用是,让所有非零的值变为1*/
OLED_W_SDA(!!(Byte & (0x80 >> i)));
OLED_W_SCL(1); //释放SCL,从机在SCL高电平期间读取SDA
OLED_W_SCL(0); //拉低SCL,主机开始发送下一位数据
}
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}
/**
* 函 数:OLED写命令
* 参 数:Command 要写入的命令值,范围:0x00~0xFF
* 返 回 值:无
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start(); //I2C起始
OLED_I2C_SendByte(0x78); //发送OLED的I2C从机地址
OLED_I2C_SendByte(0x00); //控制字节,给0x00,表示即将写命令
OLED_I2C_SendByte(Command); //写入指定的命令
OLED_I2C_Stop(); //I2C终止
}
/**
* 函 数:OLED写数据
* 参 数:Data 要写入数据的起始地址
* 参 数:Count 要写入数据的数量
* 返 回 值:无
*/
void OLED_WriteData(uint8_t *Data, uint8_t Count)
{
uint8_t i;
OLED_I2C_Start(); //I2C起始
OLED_I2C_SendByte(0x78); //发送OLED的I2C从机地址
OLED_I2C_SendByte(0x40); //控制字节,给0x40,表示即将写数量
/*循环Count次,进行连续的数据写入*/
for (i = 0; i < Count; i ++)
{
OLED_I2C_SendByte(Data[i]); //依次发送Data的每一个数据
}
OLED_I2C_Stop(); //I2C终止
}
/*********************通信协议*/