STM32--微项目14-手撸I2C通讯、实现MPU6050六轴传感器控制

一、微项目实现目标:

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);
	}
}

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值