STM32 I2C通信
1. 基本信息
1.1 I2C简介
- I2C (Inter IC Bus):由Philips公司开发的一种通用数据总线
- 通信线:两根通信线:SCL (Serial Clock)、SDA (Serial Data)
- 通信特性:同步,半双工
- 数据验证:带数据应答
- 设备支持:支持总线挂载多设备(一主多从、多主多从)
1.2 硬件电路
- 连接方式:所有I2C设备的SCL连在一起,SDA连在一起
- GPIO配置:设备的SCL和SDA均要配置成开漏输出模式
- 上拉电阻:SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
======================================================
2. I2C时序基本单元
2.1 起始和终止条件
- 起始条件:SCL高电平期间,SDA从高电平切换到低电平
- 终止条件:SCL高电平期间,SDA从低电平切换到高电平
2.2 数据传输
| 操作 | 描述 |
|---|---|
| 发送一个字节 | SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节 |
| 接收一个字节 | SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA) |
| 发送应答 | 主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答 |
| 接收应答 | 主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA) |
======================================================
3. I2C时序操作
| 操作类型 | 描述 |
|---|---|
| 指定地址读 | 对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data) |
| 指定地址写 | 对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data) |
| 当前地址读 | 对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data) |
======================================================
4. I2C实现
4.1 I2C软件实现
4.1.1 初始化
c
//I2C软件GPIO初始化
void MyI2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}4.1.2 基本操作函数
c
//SCL/SDA写函数
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);
}
//SDA读函数
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
Delay_us(10);
return BitValue;
}
//开启I2C
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
//停止I2C
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++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
//接收字节
uint8_t MyI2C_ReceiveByte(uint8_t AckBit)
{
uint8_t i, Byte = 0;
MyI2C_W_SDA(1);
for (i = 0; i < 8; i++)
{
MyI2C_W_SCL(1);
Byte |= (MyI2C_R_SDA() << (7 - i));
MyI2C_W_SCL(0);
}
MyI2C_SendAck(AckBit);
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;
}4.2 I2C硬件实现
4.2.1 配置步骤
- 开启I2C/GPIO时钟
- 配置GPIO
- 配置I2C
- 使能I2C
4.2.2 初始化代码
c
void HardI2C2_Init(void)
{
//使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/*配置I2C
*1.I2C模式
*2.占空比2:1
*3.主机模式,不需要自己写地址
*4.使能应答
*5.7位地址
*6.通讯频率为50MHz
*/
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE);
}4.2.3 辅助函数
c
/**
* @brief 等待I2C事件函数
* @param Event: 要等待的I2C事件
* @param Timeout: 超时时间(毫秒)
* @retval 0: 事件等待成功
* @retval 1: 事件等待超时
* @description 等待指定的I2C事件发生,若超时则返回错误
*/
uint8_t HardI2C2_WaitEvent(uint32_t Event, uint32_t Timeout)
{
uint32_t start_time = GetTick();
while (!I2C_CheckEvent(I2C2, Event)) {
if ((GetTick() - start_time) > Timeout) {
return 1;
}
}
return 0;
}
/**
* @brief 发送I2C起始条件函数
* @param DevAddr: 设备地址
* @param Direction: 通信方向(0: 写操作, 1: 读操作)
* @retval 0: 操作成功
* @retval 1: 操作失败
* @description 发送I2C起始条件并寻址设备,设置通信方向
*/
uint8_t HardI2C2_Start(uint8_t DevAddr, uint8_t Direction)
{
I2C_GenerateSTART(I2C2, ENABLE);
if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT, 1000)) {
return 1;
}
I2C_Send7bitAddress(I2C2, DevAddr, Direction ? I2C_Direction_Receiver : I2C_Direction_Transmitter);
if (Direction) {
if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED, 1000)) {
return 1;
}
} else {
if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED, 1000)) {
return 1;
}
}
return 0;
}
/**
* @brief 发送I2C停止条件函数
* @param 无
* @retval 无
* @description 发送I2C停止条件,结束通信
*/
void HardI2C2_Stop(void)
{
I2C_GenerateSTOP(I2C2, ENABLE);
delay_us(10);
}
/**
* @brief 发送一个字节函数
* @param Byte: 要发送的字节数据
* @retval 0: 发送成功
* @retval 1: 发送失败
* @description 发送一个字节数据并等待发送完成
*/
uint8_t HardI2C2_SendByte(uint8_t Byte)
{
I2C_SendData(I2C2, Byte);
if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED, 1000)) {
return 1;
}
return 0;
}
/**
* @brief 接收一个字节函数
* @param Ack: 应答标志(0: 发送应答, 1: 发送非应答)
* @retval 接收到的字节数据
* @description 接收一个字节数据并发送应答
*/
uint8_t HardI2C2_ReceiveByte(uint8_t Ack)
{
if (Ack == 0) {
I2C_AcknowledgeConfig(I2C2, ENABLE);
} else {
I2C_AcknowledgeConfig(I2C2, DISABLE);
HardI2C2_Stop();
}
if (HardI2C2_WaitEvent(I2C_EVENT_MASTER_BYTE_RECEIVED, 1000)) {
return 0;
}
return I2C_ReceiveData(I2C2);
}4.2.4 读写操作函数
c
/**
* @brief 向设备寄存器写入一个字节函数
* @param DevAddr: 设备地址
* @param RegAddr: 寄存器地址
* @param Data: 要写入的数据
* @retval 0: 写入成功
* @retval 1: 写入失败
* @description 向指定设备的指定寄存器写入一个字节数据
*/
uint8_t HardI2C2_WriteByte(uint8_t DevAddr, uint8_t RegAddr, uint8_t Data)
{
if (HardI2C2_Start(DevAddr, 0)) {
HardI2C2_Stop();
return 1;
}
if (HardI2C2_SendByte(RegAddr)) {
HardI2C2_Stop();
return 1;
}
if (HardI2C2_SendByte(Data)) {
HardI2C2_Stop();
return 1;
}
HardI2C2_Stop();
return 0;
}
/**
* @brief 从设备寄存器读取一个字节函数
* @param DevAddr: 设备地址
* @param RegAddr: 寄存器地址
* @param Data: 存储读取数据的指针
* @retval 0: 读取成功
* @retval 1: 读取失败
* @description 从指定设备的指定寄存器读取一个字节数据
*/
uint8_t HardI2C2_ReadByte(uint8_t DevAddr, uint8_t RegAddr, uint8_t *Data)
{
if (HardI2C2_Start(DevAddr, 0)) {
HardI2C2_Stop();
return 1;
}
if (HardI2C2_SendByte(RegAddr)) {
HardI2C2_Stop();
return 1;
}
if (HardI2C2_Start(DevAddr, 1)) {
HardI2C2_Stop();
return 1;
}
*Data = HardI2C2_ReceiveByte(1);
return 0;
}
/**
* @brief 从设备寄存器读取多个字节函数
* @param DevAddr: 设备地址
* @param RegAddr: 起始寄存器地址
* @param Data: 存储读取数据的缓冲区
* @param Len: 要读取的字节数
* @retval 0: 读取成功
* @retval 1: 读取失败
* @description 从指定设备的指定寄存器开始读取多个字节数据
*/
uint8_t HardI2C2_ReadMultiBytes(uint8_t DevAddr, uint8_t RegAddr, uint8_t *Data, uint8_t Len)
{
uint8_t i;
if (HardI2C2_Start(DevAddr, 0)) {
HardI2C2_Stop();
return 1;
}
if (HardI2C2_SendByte(RegAddr)) {
HardI2C2_Stop();
return 1;
}
if (HardI2C2_Start(DevAddr, 1)) {
HardI2C2_Stop();
return 1;
}
for (i = 0; i < Len; i++) {
if (i == Len - 1) {
Data[i] = HardI2C2_ReceiveByte(1);
} else {
Data[i] = HardI2C2_ReceiveByte(0);
}
}
return 0;
}