一、微项目实现目标:
STM32通过两个GPIO端口,通过代码配置成I2C通讯状态,通过I2C通讯与MPU6050进行信息交互,包括配置MPU6050寄存器配置、获取MPU6050寄存器状态;
二、微项目硬件配置需求:
stm32F103C8T6核心板一块
0.96寸OLED显示,
MPU6050六轴传感器
三、前置知识:
1,I2C在STM32中,有两种方式:①软件用任意的GPIO口实现数据I2C功能;②使用STM32自带的I2C模块,配置库函数实现I2C通讯;本文采用方式①
2,I2C通讯简介
①硬件配置
两根通信线:SCL(时钟控制)、SDA(数据传送、接受);
同步,半双工,带数据应答(ACK\NACK);
②接线状态
所有SCL线的控制,始终由主机控制,产生对应的时序
SDA线的控制,可以由主机或者从机。
主机控制时刻,数据由主机进行数据发送,对应地址的从机进行数据接受,当需要从机进行回复ACK或者从机传递数据到SDA时刻,则主机将SDA数据置为高,表示释放总线;
从机发送数据,当需要主机回复ACK或者交由主机传递数据时刻,从机将SDA置为高,表示释放总线
③端口配置,SDA和SCL配置为端口开漏输出;开漏输出呈现为低电平导通,拉高时呈现高阻态
3,I2C通讯模式
①启动位:
在SCL高电平状态下,SDA位从高置为低电平
ps:在正常数据传递模式下,在SCL高电平状态,SDA位不需要变化电平,保证了数据启动位有别于正常的数据传送位;
②停止位
在SCL高电平状态下,SDA位从高置为低电平
③主机接受数据
1 - I2C中,数据的传递和接受是高位先行的原则;
2,在SCL低电平时刻,主机负责对SDA调整电平状态(按照实际放的数据位状态,进行配置)
在SCL高电平时刻,SDA不允许进行电平变化;
3-在I2C位高电平时刻,主机接受来自SDA上的数据;
4-在从机发送数据时刻,主机拉高SDA释放SDA控制权
④主机发送数据
1- 数据高位先行原则
2- 在SCL低电平时刻,主机负责SDA调整电平状态(按照实际放的数据位状态,进行配置)
在SCL高电平时刻,SDA不允许进行电平变化;
3-在I2C位高电平时刻,主机接受来自SDA上的数据;
⑤主机发送ACK、NACK
与主机发送给数据类似,发送0表示ACK,发送1表示NACK;
⑥从机发送ACK\NACK
与从机发送给数据类似,发送0表示ACK,发送1表示NACK;(从机发送数据时刻,需要主机拉高SDA,释放SDA总线)
4,I2C整体实现MPU6050指定地址写
主要需要注意的是,在外设寄存器中,通过指针操作,通过I2C访问一次寄存器后,寄存器的指针会主动递增,如果需要反复操纵一个寄存器位,则需要重新启动位,重新指定寄存器地址;
启动位+写入MPU地址+等待从机ACK+写入寄存器地址+等待从机ACK+发送数据data+等待从机ACK+停止位
5,I2C整体实现MPU6050指定地址读
启动位+写入MPU6050地址(写)+等待ACK+写入要读取的寄存器地址+等待ACK+重新启动+写入MPU6050地址(读)+等待ACK+主机释放SDA+接受从机数据+主机发送NACK不再接受数据+停止位
6,MPU6050模块介绍
①MPU6050是一个6轴姿态传感器,可以测量芯片自身X、Y、Z轴的加速度、角速度参数,通过数据融合
②一般用在飞控和平衡车中使用
③MPU6050地址数据
1101000(AD0=0)或者(AD0=1),bit0的数字主要根据MPU6050的接地引脚决定;
④MPU6050数据处理流:
传感器采集数据 产生模拟信号----ADC转化通道转为数字量---转移到寄存器中
ps:此处类似ADC的连续循环转化模式,实际在应用中,只需要不停的读取对应数据寄存器的数据即可;
⑤MPU6050的寄存器描述
#define MPU6050_SMPLRT_DIV 0x19 //分频系数
#define MPU6050_CONFIG 0x1A //配置低通滤波
#define MPU6050_GYRO_CONFIG 0x1B //配置GYRO的量程
#define MPU6050_ACCEL_CONFIG 0x1C // 配置ACCE的量程
#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
四、代码逻辑分析:
在myI2C模块中:
①开启GPIO时钟、配置PA10\PA11为开漏输出模式,并且初始化为高电平;
②配置SCL写入模块、配置SDA写入模块、配置SDA读取模块
③配置I2C启动模块
④配置I2C停止模块
⑤配置I2C发送一个字节
⑥配置I2C读取一个字节
⑦配置I2C发送ACK/NACK
⑧配置I2C接受ACK/NACK
在MPU6050模块中:
①读一个寄存器地址的数据
②向寄存器地址内写入数据
③初始化模块
包括,MYI2C模块初始化、配置初始化MPU6050寄存器
④解析数据,将数据高低自字节合并,并通过指针传递将数据传递出来
五、代码示例:
在myI2C模块中:
①开启GPIO时钟、配置PA10\PA11为开漏输出模式,并且初始化为高电平;
void myi2c_init(void)
{
//开启PB10\PB11时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//配置GPIO端口,开漏输出模式
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10 |GPIO_Pin_11;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init( GPIOB,&GPIO_InitStruct);
GPIO_SetBits(GPIOB,GPIO_Pin_10 |GPIO_Pin_11);//初始化高电平
}
②配置SCL写入模块、配置SDA写入模块、配置SDA读取模块
//SCL写入
void myi2c_W_SCL(uint8_t mybit)
{
GPIO_WriteBit( GPIOB, GPIO_Pin_10, (BitAction)mybit);//强转类型BitAction
Delay_us(10);//做10us延时
}
//SDA写入
void myi2c_W_SDA(uint8_t mybit)
{
GPIO_WriteBit( GPIOB, GPIO_Pin_11, (BitAction)mybit);//强转类型BitAction
Delay_us(10);
}
//SDA读取
uint8_t myi2c_R_SDA(void)
{
uint8_t mybit=0;
mybit= GPIO_ReadInputDataBit( GPIOB, GPIO_Pin_11);
Delay_us(10);
return mybit;
}
③配置I2C启动模块
//起始条件:SCL高电平期间,SDA从高电平切换到低电平
void myi2c_start(void)
{
myi2c_W_SDA(1);
myi2c_W_SCL(1);
myi2c_W_SDA(0);
myi2c_W_SCL(0);
}
④配置I2C停止模块
//终止条件:SCL高电平期间,SDA从低电平切换到高电平
void myi2c_stop(void)
{
myi2c_W_SDA(0);
myi2c_W_SCL(1);
myi2c_W_SDA(1);
}
⑤配置I2C发送一个字节
//发送一个字节
void myi2c_sendbyte(uint8_t mybyte)
{
uint8_t i;
for(i=0;i<8;i++)
{
myi2c_W_SDA(mybyte&(0x80>>i));//高位先行
myi2c_W_SCL(1);
myi2c_W_SCL(0);//写完一个后,置为低电平,等待SDA转化为对应数据
}
}
⑥配置I2C读取一个字节,高位先入
//接受一个字节数据
uint8_t myi2c_receivebyte(void)
{
uint8_t mybyte=0;
uint8_t i;
myi2c_W_SDA(1);//释放SDA
for(i=0;i<8;i++)
{
myi2c_W_SCL(1);
if(myi2c_R_SDA())
{
mybyte=mybyte|(0x80>>i);
}
myi2c_W_SCL(0);
}
return mybyte;
}
⑦配置I2C发送ACK/NACK
//发送应答
void myi2c_sendACK(uint8_t ackbit)
{
myi2c_W_SDA(ackbit);
myi2c_W_SCL(1);
myi2c_W_SCL(0);
}
⑧配置I2C接受ACK/NACK
//接受应答
uint8_t myi2c_receiveACK(void)
{
uint8_t re_ack;
myi2c_W_SDA(1);//释放总线
myi2c_W_SCL(1);
re_ack=myi2c_R_SDA();
myi2c_W_SCL(0);
return re_ack;
}
在MPU6050模块中:
①读一个寄存器地址的数据
启动位+写入MPU6050地址(写)+等待ACK+写入要读取的寄存器地址+等待ACK+重新启动+写入MPU6050地址(读)+等待ACK+主机释放SDA+接受从机数据+主机发送NACK不再接受数据+停止位
//读寄存器地址
uint8_t MPU6050_read(uint8_t address)
{
uint8_t read_data;
myi2c_start();
myi2c_sendbyte(MPU6050);//发送设备地址
myi2c_receiveACK();//获取ACK
myi2c_sendbyte(address);//发送读寄存器地址
myi2c_receiveACK();//获取ACK
//此处要重新start,因为读写一个寄存器后,再继续操作,寄存器会跳到下一位,所以要重新指定
myi2c_start();
myi2c_sendbyte(MPU6050|0x01);//发送设备地址,读操作
myi2c_receiveACK();//获取ACK
read_data=myi2c_receivebyte();//接受
myi2c_sendACK(1);//发送NACK,不再继续接受
myi2c_stop();
return read_data;
}
②向寄存器地址内写入数据
启动位+写入MPU地址+等待从机ACK+写入寄存器地址+等待从机ACK+发送数据data+等待从机ACK+停止位
//写寄存器地址
void MPU6050_Write(uint8_t address,uint8_t write_data)
{
myi2c_start();
myi2c_sendbyte(MPU6050);//发送设备地址
myi2c_receiveACK();//获取ACK
myi2c_sendbyte(address);//发送写寄存器地址
myi2c_receiveACK();//获取ACK
myi2c_sendbyte(write_data);//发送数据
myi2c_receiveACK();//获取ACK
myi2c_stop();
}
③初始化MPU6050模块
void MPU6050_init(void)
{
myi2c_init();
//配置MPU6050
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);
}
④解析数据,将数据高低自字节合并,并通过指针传递将数据传递出来
void MPU6050_getdata(int16_t *Ax,int16_t *Ay,int16_t *Az,
int16_t *Gx , int16_t *Gy,int16_t *Gz )
{
uint16_t dataH=0,dataL=0;
dataH =MPU6050_read(MPU6050_ACCEL_XOUT_H);
dataL =MPU6050_read(MPU6050_ACCEL_XOUT_L);
*Ax=(dataH<<8)|dataL;
dataH =MPU6050_read(MPU6050_ACCEL_YOUT_H);
dataL =MPU6050_read(MPU6050_ACCEL_YOUT_L);
*Ay=(dataH<<8)|dataL;
dataH =MPU6050_read(MPU6050_ACCEL_ZOUT_H);
dataL =MPU6050_read(MPU6050_ACCEL_ZOUT_L);
*Az=(dataH<<8)|dataL;
dataH =MPU6050_read(MPU6050_GYRO_XOUT_H);
dataL =MPU6050_read(MPU6050_GYRO_XOUT_L);
*Gx=(dataH<<8)|dataL;
dataH =MPU6050_read(MPU6050_GYRO_YOUT_H);
dataL =MPU6050_read(MPU6050_GYRO_YOUT_L);
*Gy=(dataH<<8)|dataL;
dataH =MPU6050_read(MPU6050_GYRO_ZOUT_H);
dataL =MPU6050_read(MPU6050_GYRO_ZOUT_L);
*Gz=(dataH<<8)|dataL;
}
在主函数中进行配置
#include "stm32f10x.h" // Device header
#include "delay.h"
#include "OLED.H"
#include "MPU6050.H"
int16_t ACCX,ACCY,ACCZ,GYROX,GYROY,GYROZ;
int main()
{
OLED_Init();
MPU6050_init();
OLED_ShowString(1,3,"HELLOWORLD");
while(1)
{
MPU6050_getdata(&ACCX,&ACCY,&ACCZ,&GYROX,&GYROY,&GYROZ);
OLED_ShowSignedNum(2,1,ACCX,5);
OLED_ShowSignedNum(3,1,ACCY,5);
OLED_ShowSignedNum(4,1,ACCZ,5);
OLED_ShowSignedNum(2, 8, GYROX, 5);
OLED_ShowSignedNum(3, 8, GYROY, 5);
OLED_ShowSignedNum(4, 8, GYROZ, 5);
}
}