I2C通信协议
概述
- 所有 I2C 设备的 SCL 连在一起,SDA 连在一起.
- 设备的 SCL 和 SDA 均要配置成开漏输出模式.此模式会有一个 “线与” 的现象,即一个或多个设备输出低电平, 总线处于低电平.只有所有设备都输出高电平,总线才输出高电平.I2C可以利用此现象执行多主机模式的时钟同步和总线仲裁.
- SCL 和 SDA 各添加一个上拉电阻, 阻值一般为 4.7KΩ 左右.
- I2C 发送数据是先发送高位再发送低位.
I2C 时序特征
- 空闲状态: SCL 和 SDA 都保持为高电平.
- 起始信号: 起始位是一个时间段, 当 SCL 为高电平, 而 SDA 由高电平跳变为低电平, 产生起始信号.
- 停止信号: 停止位是一个时间段, 当 SCL 为高电平, 而 SDA 由低电平跳变为高电平, 产生停止信号.
- 起始信号和终止信号都是主机产生的,从机不允许产生起始和终止.如果允许,就是多主机模型.
I2C 传输数据
- 发送一个字节: SCL低电平期间,主机将数据位依次放到 SDA 线上(高位先行), SCL 高电平期间, 从机读取数据位. SCL高电平期间 SDA 不允许有数据变化, 依次循环上述过程8次,即可发送一个字节.
- 接收一个字节: SCL低电平期间,从机将数据位依次放到 SDA 线上(高位先行), SCL 高电平期间, 主机读取数据位. SCL高电平期间 SDA 不允许有数据变化, 依次循环上述过程8次, 即可接收一个字节(主机在接收之前,需要释放SDA).
- 发送应答: 主机在接收完一个字节之后, 在下一个时钟发送一位数据, 数据 0 表示应答, 数据 1 表示非应答.
- 接收应答: 主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据 0 表示应答,数据 1 表示非应答(主机在接收之前, 需要释放 SDA).
I2C 时序
对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)
对于指定设备(Slave Address),在当前地址指针指示地址下,读取从机数据(Data)
I2C 程序示例
I2C程序示例
MyI2C.h
MyI2C.c
#ifndef __MYI2C_H
#define __MYI2C_H
void MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);
#endif
#include "stm32f10x.h" // Device header
#include "Delay.h"
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);
}
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
void MyI2C_Init(void)
{
// 开启 APB2 总线上的 GPIOB 端口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
// 设置 GPIO 的模式为开漏输出模式(只能输出高电平,若想输出低电平需要外部电路配合)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
// 设置GPIO的输出速度为50MH
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
/**
* 0x80 即 1000 0000
* Byte & (0x80 >> i) 即 将 Byte 的 每一位与 1 求与操作.
*/
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
MyI2C_W_SCL(0);
}
return Byte;
}
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}