【学习笔记】stm32单片机I2C

最近学了 stm32 的 I2C，今天来写学习笔记记录一下。

（一）什么是 I2C？

I2C（Inter IC Bus）是由 Philips 公司开发的一种通用数据总线
有两根通信线：SCL（Serial Clock）、SDA（Serial Data），同步，半双工，带数据应答，支持总线挂载多设备（一主多从、多主多从）。

（二）I2C 的硬件电路

（图片又又又又发不出来了，还是口头来讲吧）
I2C 有 SCL 和 SDA 两条通信线，SCL 是时钟线，SDA 是数据线。
主机和从机这两条线都连在一起，而且都设置为开漏输出弱上拉模式，其意思大概为：当没有设备发送信号时，SCL 和 SDA 都设置为高电平，当设备发送信号时，通过主动拉低发出低电平，松开则自动跳回高电平。

（三）I2C 的时序

I2C 的时序的基本单元主要包括：开始，结束，发送一个字节，接收一个字节。
开始：SDA 拉低，然后 SCL 拉低。
结束：SCL 释放为高电平，然后 SDA 释放为高电平。
发送一个字节：SDA 拉低 / 释放，这时 SCL 释放，从机于 SCL 高电平读取数据。
接收一个字节：SCL 释放至高电平，主机读取数据。

I2C 我们主要是学习两个时序，一个是指定位置写，一个是指定位置读。
指定位置写：开始 -> 发送从机地址 + 读写位（写）-> 接收应答 -> 发送寄存器地址 -> 接收应答 -> 发送数据 -> 接收应答 -> 结束
指定位置读：开始 -> 发送从机地址 + 读写位（写）-> 接收应答 -> 发送寄存器地址 -> 接收应答 -> 开始（重新开始）-> 发送从机地址 + 读写位（读）-> 接收应答 -> 发送寄存器地址 -> 接收应答 -> 接收数据 -> 发送应答 -> 结束

（四）软件 I2C

根据时序，我们可以写出软件配置 I2C 的代码：
void MyI2C_W_SCL (uint8_t Bitvalue)//SCL 置高 / 低电平
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)Bitvalue);
Delay_us(10);
}

void MyI2C_W_SDA (uint8_t Bitvalue)//SDA 置高 / 低电平
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)Bitvalue);
Delay_us(10);
}

uint8_t MyI2C_R_SDA ()// 读取 SDA 的数据
{
uint8_t Bitvalue;
Bitvalue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
Delay_us(10);
return Bitvalue;
}

void MyI2C_Init ()// 初始化 I2C
{
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);
}

void MyI2C_Start ()// 开始时序
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}

void MyI2C_Stop () 结束时序
{
MyI2C_W_SCL(0);
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}

void MyI2C_SendByte (uint8_t Byte)// 发送一个字节
{
for(int i=0;i<8;i++)
{
MyI2C_W_SCL(0);
MyI2C_W_SDA(!!(Byte & (0x80 >> i)));
MyI2C_W_SCL(1);
}
MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveByte ()// 接收一个字节
{
uint8_t Byte = 0;
MyI2C_W_SCL(0);
MyI2C_W_SDA(1);
for(int i=0;i<8;i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA())Byte|=(0x80>>i);
MyI2C_W_SCL(0);
}
return Byte;
}

void MyI2C_SendAck (uint8_t Ackbit)// 发送应答
{
MyI2C_W_SCL(0);
MyI2C_W_SDA(Ackbit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveAck ()// 接收应答
{
uint8_t Ackbit = 0;
MyI2C_W_SCL(0);
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
Ackbit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return Ackbit;
}
由这些基本时序单元的代码，我们就可以拼接出一个完整的时序：
void MPU6050_WriteReg (uint8_t regAddress,uint8_t Data)// 指定位置写
{
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(regAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
uint8_t MPU6050_ReadReg (uint8_t regAddress)// 指定位置读
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(regAddress);
MyI2C_ReceiveAck();

MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS|1);
MyI2C_ReceiveAck();
Data=MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_Stop();

return Data;
}
然后利用这两时序，我们就可以达到我们期望的目的。

（五）硬件 I2C

stm32 内部有自带的 I2C 外设，不过只用于固定的引脚，下面是配置代码：
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

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_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Ack=I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed=50000;
I2C_InitStructure.I2C_DutyCycle=I2C_DutyCycle_2;
I2C_InitStructure.I2C_Mode=I2C_Mode_I2C;
I2C_InitStructure.I2C_OwnAddress1=0x00;
I2C_Init(I2C2,&I2C_InitStructure);

I2C_Cmd(I2C2,ENABLE);
// 初始化

void MPU6050_Wait (I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)// 检查标志位，并设置超时中断
{
for(int time = 10000 ; time > 0 ; time--)
{
if(I2C_CheckEvent(I2Cx,I2C_EVENT) == SUCCESS)
return;
}
}

void MPU6050_WriteReg(uint8_t regAddress,uint8_t Data)
{
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_MODE_SELECT);

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

I2C_SendData(I2C2,regAddress);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C2,Data);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2,ENABLE);
}

uint8_t MPU6050_ReadReg(uint8_t regAddress)
{
uint8_t Data;

I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_MODE_SELECT);

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);

I2C_SendData(I2C2,regAddress);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);

I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_MODE_SELECT);

I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);
MPU6050_Wait(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);

I2C_AcknowledgeConfig(I2C2,DISABLE);
I2C_GenerateSTOP(I2C2,ENABLE);

MPU6050_Wait(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);
Data = I2C_ReceiveData(I2C2);

I2C_AcknowledgeConfig(I2C2,ENABLE);

return Data;
}
那么这些就是 I2C 使用的全部内容了，这里再提一嘴 MPU6050 模块，这个就是个陀螺仪，我们用 I2C 线与它连接，通过指定位置写将它的功能配置好，再用指定位置读读取我们想要的数据（前面的代码的有些命名还是 MPU6050，因为江协教的就是用 I2C 使用 MPU6050）

总之 I2C 部分就结束了，接下来就是 ISP 了，回头一看这排版真让人忍俊不禁，不过懒得改了，本来写这几篇的目的都只是复习一下，我表达也不贴切，等哪天我学完了巩固好了再回来改吧。（悲）