带后台的网站模板,高质量的南京网站建设,四川重大新闻事件,学做会计账的网站#xff08;一#xff09;I2C通信
#xff08;1#xff09;通信方式
I2C是一种同步半双工的通信方式#xff0c;同步指的是通信双方时钟为一个时钟#xff0c;半双工指的是在同一时间只能进行接收数据或发送数据#xff0c;其有一条时钟线#xff08;SCL#xff09;…一I2C通信
1通信方式
I2C是一种同步半双工的通信方式同步指的是通信双方时钟为一个时钟半双工指的是在同一时间只能进行接收数据或发送数据其有一条时钟线SCL和一条数据线SDA使用I2C通信要遵守一定的规定
其收发数据有几种模式
1开启传输在时钟线SCL为1时将数据线SDA由1转为0
2结束传输在时钟线SCL为1时将数据线SDA由0转为1
3传输数据在时钟线SCL为0时在数据线SDA中写入数据将时钟线SCL由0变为1发送
4接收数据先释放数据线SDA置1后将时钟线SCL由0变为1后读取数据线SDA
5接收响应I2C在主机传输8位数据时会给主机响应为1则接收失败为0则接收成功主机需要接收响应接收方式和接收数据相同
6发送响应I2C在主机接收8位数据时要给从机响应给1则从机停止继续向主机发送给0则继续向主机发送数据
这里可以注意到I2C通信中只有开始和结束时在时钟线SCL为1时操作的数据线SDA其余都是在时钟线为0时操作数据线
通信过程中其有一定的协议
1发送数据1开启传输2写入外设地址写模式3接收从机响应4写入外设寄存器的地址5接收响应6写入数据7接收响应8结束传输
2接收数据1开启传输2写入外设地址写模式3接收从机响应4写入外设寄存器的地址5接收响应6重新开启传输7写入外设地址读模式8接收响应9读取数据10发送响应11结束传输
2软件模拟
这样我们就可以用代码来模拟I2C通信我们选择时钟线SCL接在PA0口上数据线SDA接在PA1口上
#define SCL GPIO_Pin_0
#define SDA GPIO_Pin_1
1时钟打开和初始化
这里要注意的是I2C是采用开漏输出的即默认为高电平我们这里端口输出模式也要选择开漏输出
void i2c_init()
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef gpio_init;gpio_init.GPIO_Mode GPIO_Mode_Out_OD;gpio_init.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1;gpio_init.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, gpio_init);GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1);
}
2置高低电平和读取数据
为了方便给SCL和SDA高电平或低电平封装几个函数方便后面调用分别为置某个端口高电平、置某个端口低电平、读取某个端口的值
void set(uint16_t io)
{GPIO_SetBits(GPIOA, io);Delay_us(10);
}void reset(uint16_t io)
{GPIO_ResetBits(GPIOA, io);Delay_us(10);
}unsigned char read(uint16_t io)
{unsigned char result;result GPIO_ReadInputDataBit(GPIOA, io);return result;
}
3开启传输
和前面讲的一样我们要在SCL高电平的情况下把SDA由高电平拉到低电平然后拉低SCL为后面的传输数据做准备
void i2c_start()
{set(SDA);set(SCL);reset(SDA);reset(SCL);
}
由于我们不知道在开始前数据线和时钟线是否一定为高电平因此我们先把两者置高电平后再拉低
4结束传输
我们要在SCL为高电平的情况下把SDA由低电平上拉为高电平
void i2c_end()
{reset(SDA);set(SCL);set(SDA);
}
我们不知道在要结束的时候SDA是否为低电平因此我们先拉低SDA为后面的上拉做准备至于我们的时钟线SCL我们确保其在除了结束传输这一步外每一步结束时都为低电平可以注意一下其他步骤的代码
5传输一个字节
我们在时钟线SCL为低电平的时候把数据放在数据线SDA上然后把SCL拉高为高电平即完成传输循环8次传输一个字节
void i2c_send_byte(unsigned char message)
{unsigned char i;for(i 0; i 8; i){if ((message (0x80i)) 0)reset(SDA);elseset(SDA);set(SCL);reset(SCL);}
}
这里的数据传输为高位先行先传输高位我们使用“与”的方法依次提取从高位到低位的八位bit将数据message和1000 0000 的右移i位相与
6接收一个字节
先释放数据线SDA将其置1后在SCL置1后读取SDA循环八次读取一个字节
unsigned char i2c_receive_byte()
{unsigned char i;unsigned char message 0x00;set(SDA);for (i 0; i 8; i){set(SCL);if (read(SDA) 1)message | (0x80i);reset(SCL);}return message;
}
这里使用“或”的方式来接收数据如果接收到i位数据为1则将message与1000 0000 右移i位相或第i位置1其余位保持不变
7发送应答
发送应答和发送单个bit做法相同需要在SCL低电平期间将应答置于SDA中再SCL上拉发送
void i2c_send_ack(unsigned char ack)
{if (ack 0)reset(SDA);else set(SDA);set(SCL);reset(SCL);
}
8接收应答
接收应答和接收单个bit做法相同需要先释放数据线SDA置1在SCL置高电平时读取数据线的值
unsigned char i2c_receive_ack()
{unsigned char ack;set(SDA);set(SCL);ack read(SDA);reset(SCL);return ack;
}
3封装
这样I2C的几种基本通信方式就写好了我们只需要把其封装再按照发送接收的规定就可以读取改写外设寄存器最后.c和.h文件可以这样
#include stm32f10x.h // Device header
#include Delay.h#define SCL GPIO_Pin_0
#define SDA GPIO_Pin_1void i2c_init()
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef gpio_init;gpio_init.GPIO_Mode GPIO_Mode_Out_OD;gpio_init.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1;gpio_init.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, gpio_init);GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1);
}void set(uint16_t io)
{GPIO_SetBits(GPIOA, io);Delay_us(10);
}void reset(uint16_t io)
{GPIO_ResetBits(GPIOA, io);Delay_us(10);
}unsigned char read(uint16_t io)
{unsigned char result;result GPIO_ReadInputDataBit(GPIOA, io);return result;
}void i2c_start()
{set(SDA);set(SCL);reset(SDA);reset(SCL);
}void i2c_end()
{reset(SDA);set(SCL);set(SDA);
}void i2c_send_byte(unsigned char message)
{unsigned char i;for(i 0; i 8; i){if ((message (0x80i)) 0)reset(SDA);elseset(SDA);set(SCL);reset(SCL);}
}unsigned char i2c_receive_byte()
{unsigned char i;unsigned char message 0x00;set(SDA);for (i 0; i 8; i){set(SCL);if (read(SDA) 1)message | (0x80i);reset(SCL);}return message;
}void i2c_send_ack(unsigned char ack)
{if (ack 0)reset(SDA);else set(SDA);set(SCL);reset(SCL);
}unsigned char i2c_receive_ack()
{unsigned char ack;set(SDA);set(SCL);ack read(SDA);reset(SCL);return ack;
}
#ifndef __I2C_H__
#define __I2C_H__void i2c_init(void);
void i2c_start(void);
void i2c_end(void);
void i2c_send_byte(unsigned char message);
unsigned char i2c_receive_byte(void);
void i2c_send_ack(unsigned char ack);
unsigned char i2c_receive_ack(void);#endif
二MPU-6050
MPU-6050是一个可以测量加速度和角速度的陀螺仪加速度计其外设写地址为0xD0外设的读地址为0xD1其测量的加速度和角速度存在其内部寄存器中我们通过I2C访问其内部寄存器来读取测量值
经过我们前面的程序我们已经有了这几个函数1开启传输函数2结束传输函数3发送一个字节函数4接收一个字节函数5发送应答函数6接收应答函数通过这些函数我们就可以操作寄存器了
1写某个位置的一个字节
按照我们之前的说法我们要写某个寄存器我们先要开启传输发送外设写地址接收应答发送寄存器地址接收应答写入数据接收应答结束传输对应下面的每一行代码
void mpu_write(unsigned char address, unsigned char message)
{i2c_start();i2c_send_byte(mpu6050_address);ack i2c_receive_ack();i2c_send_byte(address);ack i2c_receive_ack();i2c_send_byte(message);ack i2c_receive_ack();i2c_end();Delay_us(10);
}
2读某个位置的一个字节
和前面说的一样要读取某个外设的寄存器我们需要开启传输发送外设写地址接收应答发送寄存器地址接收应答重新开启传输发送外设读地址接收应答读取数据发送应答结束传输对应代码为
uint8_t mpu_read(unsigned char address)
{uint8_t message 0x00;i2c_start();i2c_send_byte(mpu6050_address);ack i2c_receive_ack();i2c_send_byte(address);ack i2c_receive_ack();i2c_start();i2c_send_byte(mpu6050_address | 0x01);ack i2c_receive_ack();message i2c_receive_byte();i2c_send_ack(1);i2c_end();return message;
}
3初始化MPU-6050
MPU-6050默认为睡眠模式如果不初始化不会进行数据转换这里直接操作寄存器转换如下主要为停止睡眠模式、选择时钟等顺便把I2C也在此初始化
void mpu_init()
{i2c_init();mpu_write(0x6B, 0x01); //PWR_MGMT_1 - 0000 0001mpu_write(0x6C, 0x00); //PWR_MGMT_2 - 0000 0000mpu_write(0x19, 0x09); //SMPLRT_DIV - 0000 1001mpu_write(0x1A, 0x06); //CONFIG - 0000 0110mpu_write(0x1B, 0x18); //GYRO_CONFIG - 0001 1000mpu_write(0x1C, 0x18); //ACCEL_CONFIG - 0001 1000
}
4传输数据
MPU-6050中有六个数据分别为x、y、z轴加速度x、y、z轴的角速度我们需要一次返回六个变量可以用数组但这里用一个结构体来返回六个变量
typedef struct inf
{int x_acceleration;int y_acceleration;int z_acceleration;int x_angular_velocity;int y_angular_velocity;int z_angular_velocity;
} information;
这六个变量都是16位数据其高八位和低八位存在不同的寄存器中 我们可以通过把高位左移8位“或”低位的方式来读取其16位寄存器
这里记录下常见的错误如果你在读取某些有符号数据时其逼近但不超过最大值但是却从来没有为负数这可能是因为在位数小的数据强行转换为位数大的数据中出现的错误其会在位数小数据的前面自动补0而众所周知我们负数的补码是要在前面补1的比如8位有符号数据1111 1101为-3若将其强行转化为16位有符号数据则默认为0000 0000 1111 1101这就是一个很大的正数了我们要的16位-3应该为1111 1111 1111 1101
information mpu_get_inf()
{uint8_t inf_L;uint8_t inf_H;information infor;inf_H mpu_read(0x3B);inf_L mpu_read(0x3C);infor.x_acceleration (inf_H8) | inf_L;inf_H mpu_read(0x3D);inf_L mpu_read(0x3E);infor.y_acceleration (inf_H8) | inf_L;inf_H mpu_read(0x3F);inf_L mpu_read(0x40);infor.z_acceleration (inf_H8) | inf_L;inf_H mpu_read(0x43);inf_L mpu_read(0x44);infor.x_angular_velocity (inf_H8) | inf_L;inf_H mpu_read(0x45); inf_L mpu_read(0x46); infor.y_angular_velocity (inf_H8) | inf_L;inf_H mpu_read(0x47); inf_L mpu_read(0x48); infor.z_angular_velocity (inf_H8) | inf_L;if (infor.x_acceleration 0x8000){infor.x_acceleration | 0xFFFF0000;}if (infor.y_acceleration 0x8000){infor.y_acceleration | 0xFFFF0000;}if (infor.z_acceleration 0x8000){infor.z_acceleration | 0xFFFF0000;}if (infor.x_angular_velocity 0x8000){infor.x_angular_velocity | 0xFFFF0000;}if (infor.y_angular_velocity 0x8000){infor.y_angular_velocity | 0xFFFF0000;}if (infor.z_angular_velocity 0x8000){infor.z_angular_velocity | 0xFFFF0000;}return infor;
}
最后的一连串if就是来解决类型转化间的错误的
这样我们就成功读到了寄存器内的数据并返回一个包含所有数据的结构体
5封装与声明
最后的.c 和 .h代码如下
#include stm32f10x.h // Device header
#include i2c.h
#include Delay.h#define mpu6050_address 0xD0
unsigned char ack;void mpu_write(unsigned char address, unsigned char message)
{i2c_start();i2c_send_byte(mpu6050_address);ack i2c_receive_ack();i2c_send_byte(address);ack i2c_receive_ack();i2c_send_byte(message);ack i2c_receive_ack();i2c_end();Delay_us(10);
}uint8_t mpu_read(unsigned char address)
{uint8_t message 0x00;i2c_start();i2c_send_byte(mpu6050_address);ack i2c_receive_ack();i2c_send_byte(address);ack i2c_receive_ack();i2c_start();i2c_send_byte(mpu6050_address | 0x01);ack i2c_receive_ack();message i2c_receive_byte();i2c_send_ack(1);i2c_end();return message;
}void mpu_init()
{i2c_init();mpu_write(0x6B, 0x01); //PWR_MGMT_1 - 0000 0001mpu_write(0x6C, 0x00); //PWR_MGMT_2 - 0000 0000mpu_write(0x19, 0x09); //SMPLRT_DIV - 0000 1001mpu_write(0x1A, 0x06); //CONFIG - 0000 0110mpu_write(0x1B, 0x18); //GYRO_CONFIG - 0001 1000mpu_write(0x1C, 0x18); //ACCEL_CONFIG - 0001 1000
}typedef struct inf
{int x_acceleration;int y_acceleration;int z_acceleration;int x_angular_velocity;int y_angular_velocity;int z_angular_velocity;
} information;information mpu_get_inf()
{uint8_t inf_L;uint8_t inf_H;information infor;inf_H mpu_read(0x3B);inf_L mpu_read(0x3C);infor.x_acceleration (inf_H8) | inf_L;inf_H mpu_read(0x3D);inf_L mpu_read(0x3E);infor.y_acceleration (inf_H8) | inf_L;inf_H mpu_read(0x3F);inf_L mpu_read(0x40);infor.z_acceleration (inf_H8) | inf_L;inf_H mpu_read(0x43);inf_L mpu_read(0x44);infor.x_angular_velocity (inf_H8) | inf_L;inf_H mpu_read(0x45); inf_L mpu_read(0x46); infor.y_angular_velocity (inf_H8) | inf_L;inf_H mpu_read(0x47); inf_L mpu_read(0x48); infor.z_angular_velocity (inf_H8) | inf_L;if (infor.x_acceleration 0x8000){infor.x_acceleration | 0xFFFF0000;}if (infor.y_acceleration 0x8000){infor.y_acceleration | 0xFFFF0000;}if (infor.z_acceleration 0x8000){infor.z_acceleration | 0xFFFF0000;}if (infor.x_angular_velocity 0x8000){infor.x_angular_velocity | 0xFFFF0000;}if (infor.y_angular_velocity 0x8000){infor.y_angular_velocity | 0xFFFF0000;}if (infor.z_angular_velocity 0x8000){infor.z_angular_velocity | 0xFFFF0000;}return infor;
}
#ifndef __MPU_H__
#define __MPU_H__typedef struct inf
{int x_acceleration;int y_acceleration;int z_acceleration;int x_angular_velocity;int y_angular_velocity;int z_angular_velocity;
} information;//extern struct information;
void mpu_init(void);
information mpu_get_inf(void);#endif
三主函数调用
由于我们的MPU-6050初始化中已经包含了I2C的初始化我们只要引用MPU头文件即可我们在第1列显示加速度在第9列显示角速度
#include stm32f10x.h // Device header
#include mpu6050.h
#include OLED.h
#include Delay.hint main()
{information num;mpu_init();OLED_Init();while(1){num mpu_get_inf();OLED_ShowSignedNum(1, 1, num.x_acceleration, 5);OLED_ShowSignedNum(2, 1, num.y_acceleration, 5);OLED_ShowSignedNum(3, 1, num.z_acceleration, 5);OLED_ShowSignedNum(1, 9, num.x_angular_velocity, 5);OLED_ShowSignedNum(2, 9, num.y_angular_velocity, 5);OLED_ShowSignedNum(3, 9, num.z_angular_velocity, 5);Delay_ms(500);}return 0;
}
三总结
通过读取加速度和角速度我们通过I2C通信协议读写MPU-6050了解了I2C的工作原理和手动模拟I2C的工作流程解决了一些遇到的错误积累了错误处理的经验