云计算百科
云计算领域专业知识百科平台

stm32-I2C通信协议、软件I2C

I2C

通信简介

硬件电路

对于一主多从,SCL全程由主机控制,从机不能控制;主机要读操作或者从机要应答的时候从机才能暂时控制SDA。

设置为开漏输出模式以及外置上拉电阻:如果设置为推挽输出模式,都能主动输出高低电平,可能就会出现一方输出高电平,一方输出低电平,最终导致短路。为了避免这个情况,就有了“线与”的需求:一方输出低电平,总线就是低电平;所有设备都释放总线,总线才是高电平,类似于逻辑与。线与保证了设备不能强行拉高总线。

开漏输出模式+外置上拉电阻可以实现线与,配置为开漏输出模式时,设备只能输出低电平,拉低总线;不能拉高总线,只能释放。当总线被释放时,处于浮空状态,为了避免这种不稳定,要外置上拉电阻将总线拉高。所以最后就达到了有设备拉低总线,总线就是低电平;所有设备都释放总线,总线才会被拉至高电平,实现了线与。

软件时序

基本单元

空闲时期两根线都是高电平。起始条件下SDA拉低后SCL也会拉低;终止条件下SCL线拉高,SDA再拉高。起始和终止信号都只能是主机发送。

发送一个字节

起始之后的第一个字节由主机发送。SCL低电平时主机控制SDA,要发送数据1就释放,发送数据0就拉低。随后主机释放SCL,从机一般在SCL上升沿期间就读取了SDA上的数据。SCL高电平期间SDA不能变化,是为了避免误判成起始条件和终止条件。将数据先高位后低位发送8位,即发送了一个字节。

接收一个字节

主机要接收数据,所以要先释放SDA,好让从机操作。SCL依旧是主机控制,SCL低电平时从机控制SDA,然后主机释放SCL并在SCL高电平期间读取SDA上的数据。SCL高电平期间SDA依然不能变化,随后主机再次拉低SCL,从机一般在SCL下降沿的时候就已经在SDA放上了数据。循环8次接收先高位后低位,就接收了一个字节。

发送应答

主机接收完一个字节之后要向从机发送应答即一位数据位,从机发送完最后一个数据位之后会释放SDA,SCL低电平期间如果主机跟着拉低了SDA,随后释放SCL,代表发送了数据0表示应答;如果从机释放SDA之后,SCL低电平期间主机没有拉低SDA,SDA仍然被释放,释放SCL后表示发送数据1,即没应答,从机就不会再继续发送数据了。

接收应答

主机发送完字节后马上释放SDA,随后SCL高电平时读取SDA,如果是数据0表示从机应答,数据1表示没应答。

指定地址写

主机发送起始之后,第一个字节主机应该发送从机地址位+读/写位,从机地址可以查手册,有7位和10位,7位最简单、应用更广泛,一般地址高位是固定的,低位可以根据从机的引脚接高低电平改变,能够避免出现相同类型从机地址冲突的情况。最后一位读/写位=0表示主机要写,=1表示主机要读。随后从机应答。不应答主机就会停止。

第二个字节内容可以由从机规定,可以是寄存器地址或者指令控制字等。

以上数据帧就完成了在指定从机的指定寄存器写入了一个字节。如果想要连续写入字节,只需要在从机应答之后继续写。但注意,在一个寄存器地址下进行了一次写/读操作之后寄存器指针会指向下一个地址,你下一次的读写就到这个地址里了。

当前地址读

起始条件之后主机发送从机地址+读,从机应答之后就马上要向主句发数据了,主机没有指定寄存器地址,从机就会发送当前寄存器指针指向的地址下的内容。

读也是读一个字节,如果想要读多个字节,就在从机发送完之后主机发送数据0表示应答,从机就会继续发,如果不想要数据了,发数据1表示非应答,从机就会停止。同样注意,读多个字节时寄存器指针也在移动。

指定地址读

起始条件之后主机发送从机地址+写,第二个字节主机指定寄存器地址,从机的寄存器指针就指向指定的地址了,从机应答之后主机再次发送起始条件,然后发送从机地址+读,从机就会发送这个寄存器地址下的内容了。

软件I2C读写MPU6050

I2C.c文件

#include "stm32f10x.h" // Device header
//使用软件I2C,就不使用库函数了,在函数里手动改变电平模拟时序
//初始化函数里初始化GPIO口为开漏输出模式,并将SCL,SDA置高电平
void IIC_init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitSturcture;
GPIO_InitSturcture.GPIO_Mode=GPIO_Mode_Out_OD;//虽然叫开漏输出,但是也可以输入
GPIO_InitSturcture.GPIO_Pin=GPIO_Pin_10|GPIO_Pin_11;
GPIO_InitSturcture.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitSturcture);
GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);
}

//接下来写六个时序基本单元的函数
void IIC_W_SCL(uint8_t bitvalue)//读SCL,先把改变电平的函数变一下,语义更清楚,更方便
{
GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)bitvalue);//W就是写,调用函数再给高低电平即可
}

void IIC_W_SDA(uint8_t bitvalue)//写SDA
{
GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)bitvalue);
}

uint8_t IIC_R_SDA(void)//读SDA
{
uint8_t bitvalue;
bitvalue=GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
return bitvalue;
}

//起始条件
void IIC_START(void)
{
IIC_W_SDA(1);//先拉高SDA,为了兼容重复起始条件
IIC_W_SCL(1);//把两个都拉高,确保是空闲状态
IIC_W_SDA(0);
IIC_W_SCL(0);
}

//终止条件
void IIC_STOP(void)
{
IIC_W_SDA(0);
IIC_W_SCL(1);//不用拉低SCL,是因为每次应答之后SCL默认是低电平
IIC_W_SDA(1);

}

//发送一个字节
void IIC_send(uint8_t byte)
{
uint8_t i;
for(i=0;i<8;i++)
{
IIC_W_SDA(byte&(0x80>>i));//处理终止条件外,所有单元开始时SCL都是低电平,循环8次取出每一位
IIC_W_SCL(1);//发送
IIC_W_SCL(0);//拉低为下一次做准备
}
}

//接收一个字节
uint8_t IIC_receive(void)
{
uint8_t i,byte=0x00;
IIC_W_SDA(1);//释放SDA
for(i=0;i<8;i++)
{
IIC_W_SCL(1);//接收数据位
if(IIC_R_SDA()==1)
{
byte|=(0x80>>i);//如果接收到1,就通过与在byte对应位置写1,如果是0就不变,因为byte默认是0x00,就相当于写0
}
IIC_W_SCL(0);
}
return byte;
}

//发送应答
void IIC_sengACK(uint8_t bit)
{
IIC_W_SDA(bit);
IIC_W_SCL(1);
IIC_W_SCL(0);
}

//接收应答
uint8_t IIC_receACK(void)
{
uint8_t ACK;
IIC_W_SDA(1);//释放SDA
IIC_W_SCL(1);//接收应答
ACK=IIC_R_SDA();
IIC_W_SCL(0);
return ACK;
}

MPU6050.c文件

#include "stm32f10x.h" // Device header
#include "IIC.h"
#include "MPU6050_REG.h"
//指定地址写时序
void mpu6050_write(uint8_t regaddress,uint8_t data)
{
IIC_START();
IIC_send(0XD0);//MPU6050的地址+写 1101 000 0
IIC_receACK();
IIC_send(regaddress);//寄存器地址
IIC_receACK();
IIC_send(data);//写入数据
IIC_receACK();
IIC_STOP();
}

//指定地址读
uint8_t mpu6050_read(uint8_t regaddress)
{
uint8_t byte;
IIC_START();
IIC_send(0XD0);//MPU6050的地址+写 1101 000 0
IIC_receACK();
IIC_send(regaddress);//寄存器地址
IIC_receACK();
IIC_START();//重复起始条件
IIC_send(0XD1);//MPU6050的地址+读 1101 000 1
IIC_receACK();
byte=IIC_receive();
IIC_sengACK(1);
IIC_STOP();
return byte;
}

void mpu6050_init(void)//配置mpu6050的寄存器
{
IIC_init();
mpu6050_write(MPU6050_PWR_MGMT_1, 0x01);
mpu6050_write(MPU6050_PWR_MGMT_2, 0x00);
mpu6050_write(MPU6050_SMPLRT_DIV, 0x09);
mpu6050_write(MPU6050_CONFIG, 0x06);
mpu6050_write(MPU6050_GYRO_CONFIG, 0x18);
mpu6050_write(MPU6050_ACCEL_CONFIG, 0x18);
}

uint8_t MPU6050_GetID(void)
{
return mpu6050_read(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)//寄存器中的值为16位
{
uint8_t DataH, DataL;

DataH = mpu6050_read(MPU6050_ACCEL_XOUT_H);//获取高八位
DataL = mpu6050_read(MPU6050_ACCEL_XOUT_L);//获取低八位
*AccX = (DataH << 8) | DataL;

DataH = mpu6050_read(MPU6050_ACCEL_YOUT_H);
DataL = mpu6050_read(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;

DataH = mpu6050_read(MPU6050_ACCEL_ZOUT_H);
DataL = mpu6050_read(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;

DataH = mpu6050_read(MPU6050_GYRO_XOUT_H);
DataL = mpu6050_read(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;

DataH = mpu6050_read(MPU6050_GYRO_YOUT_H);
DataL = mpu6050_read(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;

DataH = mpu6050_read(MPU6050_GYRO_ZOUT_H);
DataL = mpu6050_read(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}

MPU6050寄存器.h文件

#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H

#defineMPU6050_SMPLRT_DIV0x19
#defineMPU6050_CONFIG0x1A
#defineMPU6050_GYRO_CONFIG0x1B
#defineMPU6050_ACCEL_CONFIG0x1C

#defineMPU6050_ACCEL_XOUT_H0x3B
#defineMPU6050_ACCEL_XOUT_L0x3C
#defineMPU6050_ACCEL_YOUT_H0x3D
#defineMPU6050_ACCEL_YOUT_L0x3E
#defineMPU6050_ACCEL_ZOUT_H0x3F
#defineMPU6050_ACCEL_ZOUT_L0x40
#defineMPU6050_TEMP_OUT_H0x41
#defineMPU6050_TEMP_OUT_L0x42
#defineMPU6050_GYRO_XOUT_H0x43
#defineMPU6050_GYRO_XOUT_L0x44
#defineMPU6050_GYRO_YOUT_H0x45
#defineMPU6050_GYRO_YOUT_L0x46
#defineMPU6050_GYRO_ZOUT_H0x47
#defineMPU6050_GYRO_ZOUT_L0x48

#defineMPU6050_PWR_MGMT_10x6B
#defineMPU6050_PWR_MGMT_20x6C
#defineMPU6050_WHO_AM_I0x75

#endif

主函数

#include "stm32f10x.h"// Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
uint8_t id;
int16_t AX,AY,AZ,GX,GY,GZ;
int main()
{
OLED_Init();
mpu6050_init();
OLED_ShowString(1,1,"ID:");
id=MPU6050_GetID();
OLED_ShowHexNum(1,4,id,2);
while(1)
{
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
OLED_ShowSignedNum(2, 1, AX, 5);
OLED_ShowSignedNum(3, 1, AY, 5);
OLED_ShowSignedNum(4, 1, AZ, 5);
OLED_ShowSignedNum(2, 8, GX, 5);
OLED_ShowSignedNum(3, 8, GY, 5);
OLED_ShowSignedNum(4, 8, GZ, 5);
}
}

赞(0)
未经允许不得转载:网硕互联帮助中心 » stm32-I2C通信协议、软件I2C
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!