(一)I2C硬件电路
stm32内部有I2C的硬件电路,我们可以使用stm32的标准库函数来实现I2C,这可以为我们减少对软件资源的占用
I2C硬件电路常用的标准库函数
void I2C_Init(I2C_TypeDef* I2Cx, I2C_InitTypeDef* I2C_InitStruct);
//通过结构体初始化I2C
void I2C_StructInit(I2C_InitTypeDef* I2C_InitStruct);
//给初始化结构体赋默认值
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
//使能I2C,初始化之后要使能其才开始工作
void I2C_GenerateSTART(I2C_TypeDef* I2Cx, FunctionalState NewState);
//开始I2C传输
void I2C_GenerateSTOP(I2C_TypeDef* I2Cx, FunctionalState NewState);
//结束I2C传输
void I2C_SendData(I2C_TypeDef* I2Cx, uint8_t Data);
//发送一个字节数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);
//接收一个字节数据
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);
//发送7位地址,在最开始I2C寻址调用这个函数
ErrorStatus I2C_CheckEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT);
//接收事件,在使用硬件开始传输或发送数据等操作之后都会有对应的事件需要接收判断后再执行下一个操作
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);
//设置响应为,相当于软件实现I2C中的ack
我们只要使用这些库函数,按照其规定的流程进行数据的发送和接收即可,不需要我们手动在软件中模拟I2C的高低电平跳变
(二)使用库函数实现I2C数据的发送与接收
(1)发送
I2C发送流程如图,在开始传输完成后,其会产生EV5事件,我们在事件产生后即可发送地址位,成功发送后会产生EV6事件,接着发送寄存器地址,因为这里使用的是一个寄存器和一个移位寄存器来发送数据,所以我们在寄存器的数据写入到移位寄存器中正在发送(EV8事件)即可将新的数据写入寄存器,在最后一个数据位我们没有数据写入寄存器,且移位寄存器发送完毕,会产生EV8_2事件,表示发送完成,然后即可停止传输
这里我们发送一个字节后的接收响应由硬件自动接收,不需要我们手动接收响应
由于我们会频繁接收各种事件,我们可以把接收事件的函数封装成一个简单的函数,且让其超时退出,不让程序卡死
接收事件函数
void mpu_wait_flag(int event)
{
unsigned int time = 10000;
while (I2C_CheckEvent(I2C2, event) != SUCCESS)
{
time--;
if (time == 0)
{
break;
}
}
}
按照流程,我们可以这样编写发送函数
发送函数
void mpu_write(unsigned char address, unsigned char inf)
{
I2C_GenerateSTART(I2C2, ENABLE);
mpu_wait_flag(I2C_EVENT_MASTER_MODE_SELECT); //EV5
I2C_Send7bitAddress(I2C2, mpu_address, I2C_Direction_Transmitter);
mpu_wait_flag(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV6
I2C_SendData(I2C2, address);
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_TRANSMITTING); //EV8
I2C_SendData(I2C2, inf);
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_TRANSMITTED); //EV8_2
I2C_GenerateSTOP(I2C2, ENABLE);
}
第一行我们先开始传输,第二行我们接收EV5事件,第三行我们发送7位地址,并且第三个参数为读模式还是写模式,我们选择写模式,第四行我们等待EV6事件,紧接着第五行发送要读取的寄存器地址,第六行等待寄存器地址移动到移位寄存器正在发送事件(EV8),最后第七行写要发送的数据,等待发送完成事件,最后停止传输;
(2)接收
接收数据的流程如图所示
这里一样,我们在开始之后会产生EV5事件,在传递MPU地址后会产生EV6事件,接着读取数据,这里由于我们在读取事件之后硬件电路会立刻把我们之前预设的ack响应发给从机,因此我们需要在读取最后一个数据之前把ack置0,我们这里只读取一个字节的数据,也就是要在发送MPU地址之后,读取数据之前先把ack置0,对应代码如下
unsigned char mpu_read(unsigned char address)
{
unsigned char inf;
I2C_GenerateSTART(I2C2, ENABLE);
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_Send7bitAddress(I2C2, mpu_address, I2C_Direction_Transmitter);
mpu_wait_flag(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, address);
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTART(I2C2, ENABLE);
mpu_wait_flag(I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, mpu_address, I2C_Direction_Receiver);
mpu_wait_flag(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
I2C_AcknowledgeConfig(I2C2, DISABLE); //ack=0
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_RECEIVED);
inf = I2C_ReceiveData(I2C2);
I2C_GenerateSTOP(I2C2, ENABLE);
I2C_AcknowledgeConfig(I2C2, ENABLE);
return inf;
}
第一段对应我们软件I2C的给寄存器地址操作,我们仍需要先写入寄存器地址,然后在第二段我们重新开始传输,接收EV5事件后发送MPU地址,这里第三个参数选择读模式,等待地址发送完成,之后值得注意的是我们先执行的不是读取数据,而是先把应答位置非应答,再去读取数据,读取完成后我们即可停止传输
(三)MPU-6050
经过上面两个函数的编写,我们已经可以调用上面两个函数来实现在指定外设寄存器上写数据和读数据的操作,我们即可进行MPU的初始化和读取转换数据所在的寄存器,这里就和之前使用软件模拟I2C的代码没有什么区别,因为软件我们只是模拟了I2C的波形,最终也封装成了写字节和读字节的函数
(1)初始化
(1)打开时钟和初始化GPIO
这里因为我们使用内部的I2C硬件电路,因此我们打开时钟我们要把I2C的时钟打开,stm32F03C8有两个I2C,这里使用的是I2C2,对应的SCL、SDA引脚为PB10、PB11
void mpu_rcc_init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
}
初始化GPIO,这里要用复用开漏输出
void mpu_gpio_init()
{
GPIO_InitTypeDef gpio_init;
gpio_init.GPIO_Pin = SCL | SDA;
gpio_init.GPIO_Mode = GPIO_Mode_AF_OD;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
}
(2)I2C初始化
使用I2C硬件电路还要对I2C进行初始化,可以看下初始化结构体
(1)第一个参数是选择时钟速度,数值越大电平翻转频率越高,低于100KHz的是正常模式,高于100KHz的是高速模式,这里选择100KHz;
(2)第二个参数是选择模式,可以选泽I2C模式、SMBus设备模式或SMBus主控模式,这里选择I2C模式;
(3)第三个参数是选择高低电平的占空比,这里只有在高速模式下有效,有16:9和2:1两种可选,这里不是高速模式,可以随便选择;
(4)第四个参数是选择自身地址,自身可以作为从机,设置自身地址让其他主机呼叫,随便选择,只要不和MPU或其他I2C上的地址冲突即可;
(5)第五个参数是应答位设置,可以选择应答和非应答,给应答;
(6)第六个参数是选择7位地址模式还是10位地址模式,这里选择7为地址模式;
这样我们就可以初始化I2C了
void mpu_i2c_init()
{
I2C_InitTypeDef i2c_init;
i2c_init.I2C_ClockSpeed = 100000;
i2c_init.I2C_Mode = I2C_Mode_I2C;
i2c_init.I2C_DutyCycle = I2C_DutyCycle_2;
i2c_init.I2C_OwnAddress1 = 0x00;
i2c_init.I2C_Ack = I2C_Ack_Enable;
i2c_init.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C2, &i2c_init);
I2C_Cmd(I2C2, ENABLE);
}
最后不要忘了使能I2C,否则其不工作
(3)MPU初始化
我们把时钟、gpio、I2C的初始化都集成于此,我们还要像软件那样配置寄存器来关闭MPU睡眠模式和选择时钟等功能,具体代码如下
void mpu_init()
{
mpu_rcc_init();
mpu_gpio_init();
mpu_i2c_init();
mpu_write(0x6B, 0x01); //PWR_MGMT_1 -> 0000 0001
mpu_write(0x6C, 0x00); //PWR_MGMT_2 -> 0000 0000}
mpu_write(0x19, 0x09); //SMPLRT_DIV -> 0000 1001
mpu_write(0x1A, 0x06); //CONFIG -> 0000 0110
mpu_write(0x1B, 0x18); //GYRO_CONFIG -> 0001 1000
mpu_write(0x1C, 0x18); //ACCEL_CONFIG -> 0001 1000
}
(2)读取参数
最后我们选择将x轴、y轴和z轴的加速度和角速度放在一个结构体中返回,我们还要注意16位整数转为32位整数中的符号问题
结构体定义
typedef struct infs
{
int x_acceleration;
int y_acceleration;
int z_acceleration;
int x_angular_velocity;
int y_angular_velocity;
int z_angular_velocity;
} information;
16位整数转32为整数
int mpu_16_to_32(int original)
{
int final;
if (original & 0x8000)
{
final = original | 0xFFFF0000;
}
return final;
}
我们只要读取MPU对应数据的寄存器即可,这里和软件模拟的操作一样
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 = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x3D);
inf_L = mpu_read(0x3E);
infor.y_acceleration = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x3F);
inf_L = mpu_read(0x40);
infor.z_acceleration = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x43);
inf_L = mpu_read(0x44);
infor.x_angular_velocity = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x45);
inf_L = mpu_read(0x46);
infor.y_angular_velocity = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x47);
inf_L = mpu_read(0x48);
infor.z_angular_velocity = mpu_16_to_32((inf_H<<8) | inf_L);
return infor;
}
(3)封装与声明
最后的.c 和 .h文件如下
#include "stm32f10x.h" // Device header
#define SCL GPIO_Pin_10
#define SDA GPIO_Pin_11
#define mpu_address 0xD0
void mpu_wait_flag(int event)
{
unsigned int time = 10000;
while (I2C_CheckEvent(I2C2, event) != SUCCESS)
{
time--;
if (time == 0)
{
break;
}
}
}
void mpu_write(unsigned char address, unsigned char inf)
{
I2C_GenerateSTART(I2C2, ENABLE);
mpu_wait_flag(I2C_EVENT_MASTER_MODE_SELECT); //EV5
I2C_Send7bitAddress(I2C2, mpu_address, I2C_Direction_Transmitter);
mpu_wait_flag(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //EV6
I2C_SendData(I2C2, address);
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_TRANSMITTING); //EV8
I2C_SendData(I2C2, inf);
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_TRANSMITTED); //EV8_2
I2C_GenerateSTOP(I2C2, ENABLE);
}
unsigned char mpu_read(unsigned char address)
{
unsigned char inf;
I2C_GenerateSTART(I2C2, ENABLE);
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_Send7bitAddress(I2C2, mpu_address, I2C_Direction_Transmitter);
mpu_wait_flag(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, address);
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTART(I2C2, ENABLE);
mpu_wait_flag(I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, mpu_address, I2C_Direction_Receiver);
mpu_wait_flag(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
I2C_AcknowledgeConfig(I2C2, DISABLE);
mpu_wait_flag(I2C_EVENT_MASTER_BYTE_RECEIVED);
inf = I2C_ReceiveData(I2C2);
I2C_GenerateSTOP(I2C2, ENABLE);
I2C_AcknowledgeConfig(I2C2, ENABLE);
return inf;
}
void mpu_rcc_init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
}
void mpu_gpio_init()
{
GPIO_InitTypeDef gpio_init;
gpio_init.GPIO_Pin = SCL | SDA;
gpio_init.GPIO_Mode = GPIO_Mode_AF_OD;
gpio_init.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
}
void mpu_i2c_init()
{
I2C_InitTypeDef i2c_init;
i2c_init.I2C_ClockSpeed = 100000;
i2c_init.I2C_Mode = I2C_Mode_I2C;
i2c_init.I2C_DutyCycle = I2C_DutyCycle_2;
i2c_init.I2C_OwnAddress1 = 0x00;
i2c_init.I2C_Ack = I2C_Ack_Enable;
i2c_init.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_Init(I2C2, &i2c_init);
I2C_Cmd(I2C2, ENABLE);
}
void mpu_init()
{
mpu_rcc_init();
mpu_gpio_init();
mpu_i2c_init();
mpu_write(0x6B, 0x01); //PWR_MGMT_1 -> 0000 0001
mpu_write(0x6C, 0x00); //PWR_MGMT_2 -> 0000 0000}
mpu_write(0x19, 0x09); //SMPLRT_DIV -> 0000 1001
mpu_write(0x1A, 0x06); //CONFIG -> 0000 0110
mpu_write(0x1B, 0x18); //GYRO_CONFIG -> 0001 1000
mpu_write(0x1C, 0x18); //ACCEL_CONFIG -> 0001 1000
}
typedef struct infs
{
int x_acceleration;
int y_acceleration;
int z_acceleration;
int x_angular_velocity;
int y_angular_velocity;
int z_angular_velocity;
} information;
int mpu_16_to_32(int original)
{
int final;
if (original & 0x8000)
{
final = original | 0xFFFF0000;
}
return final;
}
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 = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x3D);
inf_L = mpu_read(0x3E);
infor.y_acceleration = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x3F);
inf_L = mpu_read(0x40);
infor.z_acceleration = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x43);
inf_L = mpu_read(0x44);
infor.x_angular_velocity = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x45);
inf_L = mpu_read(0x46);
infor.y_angular_velocity = mpu_16_to_32((inf_H<<8) | inf_L);
inf_H = mpu_read(0x47);
inf_L = mpu_read(0x48);
infor.z_angular_velocity = mpu_16_to_32((inf_H<<8) | inf_L);
return infor;
}
#ifndef __MPU_H__
#define __MPU_H__
typedef struct infs
{
int x_acceleration;
int y_acceleration;
int z_acceleration;
int x_angular_velocity;
int y_angular_velocity;
int z_angular_velocity;
} information;
void mpu_init(void);
information mpu_get_inf(void);
#endif
(四)主函数调用
我们只要在主函数开始时对MPU初始化,然后在循环中调用读取寄存器值的函数即可,和软件模拟实现的程序一样
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "MPU.h"
#include "Delay.h"
int main()
{
mpu_init();
OLED_Init();
information inf;
while(1)
{
inf = mpu_get_inf();
OLED_ShowSignedNum(1, 1, inf.x_acceleration, 5);
OLED_ShowSignedNum(2, 1, inf.y_acceleration, 5);
OLED_ShowSignedNum(3, 1, inf.z_acceleration, 5);
OLED_ShowSignedNum(1, 8, inf.x_acceleration, 5);
OLED_ShowSignedNum(2, 8, inf.y_acceleration, 5);
OLED_ShowSignedNum(3, 8, inf.z_acceleration, 5);
Delay_ms(500);
}
return 0;
}
(五)总结
在学习软件模拟I2C后,这里学习的是通过硬件I2C,通过库函数来实现I2C通信,读取MPU寄存器中的数据,我们对I2C的理解更加深入,对stm32的了解也更加具体