IIC通信协议
IIC有两根通信线,SCL和SDA;是同步半双工通信,带数据应答协议;支持总线挂载多设备(一主多从,多主多从).
所有IIC设备的SCL连接在一起,SDA连接在一起,设备的SCL和SDA均要配置成开漏输出模式.SCL和SDA各自添加一个上拉电阻,阻值一般为4.7K欧左右.
主机对SCL完全控制,在空闲状态下,主机可以主动发起对 SDA的控制;从机发送数据和应答时,主机才会转交SDA控制权给从机。 从机对于SCL时钟线任何时候只能被动读取,从机不允许控制SCL线.对于SDA线,从机不允许主动发起对SDA的控制.只有主机发送读取从机的命令后,或者从机应答的时候,从机才能短暂地获得对SDA的控制权.SCL和SDA线空闲时均为高电平状态.
IIC的时序基本单元
起止条件
起始条件:在SCL高电平期间,SDA由高电平转为低电平,产生下降沿.SDA下降沿之后主机要把SCL设置为低电平.
终止条件:在SCL高电平期间,SDA由低电平转为高电平,产生上升沿.SDA上升沿后主机要把SCL设置为高电平.
对应的逻辑代码:
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);
}
发送一个字节
在SCL低电平期间,主机会将数据位依次放到SDA上,然后释放SCL.从机在SCL高电平期间读取数据位,所以SCL高电平期间SDA不能够变化.
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);
}
}
接收一个字节
在SCL低电平期间,从机会将数据位依次放在SDA上,然后释放SCL;主机会在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化.主机在接收数据前要释放SDA(即拉高SDA),交由从机控制.
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
MyI2C_W_SDA(1);//主机释放SDA
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1);
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}
MyI2C_W_SCL(0);
}
return Byte;
}
发送/接收应答位
发送应答:主机在接收完一个字节后在下一个时钟发送一位数据,0表示应答,1表示非应答.
接收应答:主机发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,0表示应答,1表示非应答.
//发送应答
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);//释放SDA
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();//此时读取SDA的电平
MyI2C_W_SCL(0);
return AckBit;//应答位
}
指定地址写
在指定从机地址为1101000的设备在其内部0x19地址的寄存器中写入0xAA这个数据.
空闲状态可以看到SCL和SDA是高电平.在SCL高电平期间,拉低SDA产生起始条件.发送字节内容:从机地址加读写位(确定通信对象与写读).在SCL低电平期间SDA变换数据.
第二个字节送到指定设备内部(寄存器地址/指令控制字)
第三个字节:写入到寄存器的内容.
当前地址读
对于指定设备(Slave Address),在当前地址(当前指针指向的值)指针指示的地址下,读取从机数据(Data)
第一个字节:从机寻址和指定读写标志位;
第二个字节:从机发送的字节数据
指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
对于指定设备,在指定地址下读取从机数据.
发送字节进行寻址(1101000),从机应答后再发送字节(指定地址),从机寄存器指针指向0x19;然后再来个起始条件.另起一个时序,重新寻址并且指定读写标志位,开始读.
读取MPU6050
模块简介
MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等需要检测自身姿态的场景
3轴加速度计(Accelerometer):测量X、Y、Z轴的加速度
3轴陀螺仪传感器(Gyroscope):测量X、Y、Z轴的角速度
代码示例
#include "stm32f10x.h" // Device header
#include "Delay.h"
//设置SCL电平
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
Delay_us(10);
}
//设置SDA电平
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);//读取SDA线的电平
Delay_us(10);
return BitValue;//返回值
}
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);
}
//起始条件
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 ++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));//高位先行 1000 0000与上Byte最高位,1所在的位置数据保持不变 同时1往后面移动一位
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
//接收字节
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;//先定义为0
MyI2C_W_SDA(1);//主机释放SDA
for (i = 0; i < 8; i ++)
{
MyI2C_W_SCL(1);//SCL高电平读取数据
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);}// if成立该位为1 if不成立该位为0
MyI2C_W_SCL(0);//SCL拉低
}
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);//主机释放SDA 现在SDA由从机是否控制
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();//读取ACK的值 0应答 1无应答
MyI2C_W_SCL(0);
return AckBit;
}
#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 "MyI2C.h"
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0 //mpu地址
//指定地址写
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
MyI2C_Start();//IIC起始
MyI2C_SendByte(MPU6050_ADDRESS);//写入设备地址 从机地址+读写位
MyI2C_ReceiveAck();//IIC应答
MyI2C_SendByte(RegAddress);//指定寄存器地址
MyI2C_ReceiveAck();//IIC应答
MyI2C_SendByte(Data);//发送数据
MyI2C_ReceiveAck();//IIC应答
MyI2C_Stop();//IIC停止
}
//指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;//
MyI2C_Start();//IIC起始
MyI2C_SendByte(MPU6050_ADDRESS);//MPU地址
MyI2C_ReceiveAck();//接收应答位
MyI2C_SendByte(RegAddress);//指定寄存器地址
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//最后一位读写位变为1 表示读
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_SendAck(1);//从机不应答
MyI2C_Stop();
return Data;
}
//初始化MPU
void MPU6050_Init(void)
{
MyI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//配置电源管理寄存器1
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//配置电源管理寄存器2
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//采样率分频 10分频
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//滤波参数
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪配置寄存器
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度计配置寄存器
}
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t DataH, DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);//读取X轴高八位
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);//读取x轴低八位
*AccX = (DataH << 8) | DataL;//合成16位数据
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}
#ifndef __MPU6050_H
#define __MPU6050_H
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
#endif
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H
#define MPU6050_SMPLRT_DIV 0x19
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_PWR_MGMT_2 0x6C
#define MPU6050_WHO_AM_I 0x75
#endif