最近学了 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 了,回头一看这排版真让人忍俊不禁,不过懒得改了,本来写这几篇的目的都只是复习一下,我表达也不贴切,等哪天我学完了巩固好了再回来改吧。(悲)