目录
I2C
I2C介绍
I2C(集成电路总线),由Philips公司(2006年迁移到NXP)在1980年代初开发的一种简单、双线双向的同步串行总线,它利用一根时钟线和一根数据线在连接总线的两个器件之间进行信息的传递,为设备之间数据交换提供了一种简单高效的方法。每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。
I2C 标准是一个具有冲突检测机制和仲裁机制的真正意义上的多主机总线,它能在多个主机同时请求控制总线时利用仲裁机制避免数据冲突并保护数据。作为嵌入式开发者,使用I2C总线通信的场景有很多,例如驱动FRAM、E2PROM、传感器等。
总结来说,I2C总线具有以下特点:
- 只需要SDA、SCL两条总线;
- 没有严格的波特率要求;
- 所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址;
- I2C是真正的多主设备总线,可提供仲裁和冲突检测;
- 传输速度分为四种模式:
- 标准模式(Standard Mode):100 Kbps
- 快速模式(Fast Mode):400 Kbps
- 高速模式(High speed mode):3.4 Mbps
- 超快速模式(Ultra fast mode):5 Mbps
- 最大主设备数:无限制;
- 最大从机数:理论上,1008个从节点,寻址模式的最大节点数为2的7次方或2的10次方,但有16个地址保留用于特殊用途。
具体详细的介绍和更有意思更生动的介绍可以看我之前发表的一篇文章:【IIC,存储器最强总结】 相信看完你就理解了个大概
I2C引脚初始化
相信你看完上面我写的文章 【IIC,存储器最强总结】 以后已经对I2C已经有了更深入的了解了。如果还没看,可能不太能连接下面的操作。下面开始配置和初始化I2C的gpio吧:
#include <driver/gpio.h>
#include <esp_log.h>
#include <esp_rom_sys.h>
#define myI2C_SCL_Pin GPIO_NUM_22
#define myI2C_SDA_Pin GPIO_NUM_21
/**
* @description: 给SCL引脚写入电平
* @param {uint32_t} BitValue 写入的电平 取值:0(低电平) 1(高电平)
* @warning 部分开发板如果频率慢的话需要加延时函数,下同
* @return {*}
*/
void myI2C_Write_SCL(uint8_t BitValue)
{
gpio_set_level(myI2C_SCL_Pin,(uint8_t)BitValue);
;
}
/**
* @description: 给SDA引脚写入电平
* @param {uint32_t} BitValue 写入的电平 取值:0(低电平) 1(高电平)
* @return {*}
*/
void myI2C_Write_SDA(uint8_t BitValue)
{
gpio_set_level(myI2C_SDA_Pin,(uint8_t)BitValue);
}
/**
* @description: I2C初始化
* @return {*}无
*/
void myI2C_Init(void)
{
gpio_config_t gpio_config_InitStructure;//初始化gpio
gpio_config_InitStructure.intr_type=GPIO_INTR_DISABLE;//中断失能
gpio_config_InitStructure.pull_down_en=GPIO_PULLDOWN_ENABLE;//下拉失能
gpio_config_InitStructure.pull_up_en=GPIO_PULLUP_DISABLE;//上拉失能
gpio_config_InitStructure.mode=GPIO_MODE_OUTPUT_OD;//开漏输出
gpio_config_InitStructure.pin_bit_mask=(1ull<<myI2C_SCL_Pin)|((1ull<<myI2C_SDA_Pin));//引脚掩码
gpio_config(&gpio_config_InitStructure);
//拉高电平,为起始信号做准备
myI2C_Write_SCL(1);
myI2C_Write_SDA(1);
}
起始信号
起始条件:SCL高电平期间,SDA从高电平切换到低电平
代码:
/**
* @description: I2C起始信号
* @tip:scl高电平期间,sda由高电平切换为低电平
* @return {*}无
*/
void myI2C_Start(void)
{
//首先恢复空闲状态,由于在指定地址读时 会有个str ,
//此时SDA的电平并不知道,
//如果先释放SCL,此时若SDA为低电平,
//然后在释放完SCL后才释放SDA,
//会导致停止条件的产生,所以先释放sda,再释放scl
myI2C_Write_SDA(1);
myI2C_Write_SCL(1);
//在scl高电平期间,sda由高电平切换为低电平,
//为保持后续的时序的连续性,将scl也拉低
myI2C_Write_SDA(0);//拉低sda
myI2C_Write_SCL(0);//拉低scl
}
停止信号
终止条件:SCL高电平期间,SDA从低电平切换到高电平
代码实现:
/**
* @description: I2C停止信号
* @tip:终止条件:SCL高电平期间,SDA从低电平切换到高电平
* @return {*}无
*/
void myI2C_Stop(void)
{
//SDA若一开始就是低电平,则可直接拉高 ,但如果SDA开始时是高电平 则需要先拉低再拉高
myI2C_Write_SDA(0);
myI2C_Write_SCL(1);//在scl高电平期间,SDA从低电平切换到高电平
myI2C_Write_SDA(1);
}
发送一个字节
代码:
/**
* @description: 发送一个字节
* @param {uint8_t} Byte 发送的字节数据 范围:0x00~0xff
* @tip:由于是高位先行,所以要使用&进行一位一位的取出
* @warning:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
* @return {*}
*/
void myI2C_SendByte(uint8_t Byte)
{
// myI2C_Write_SDA(Byte&0x80);//移出最高位,放在sda数据线上
//myI2C_Write_SCL(1);//释放scl,释放主动权,让从机读取
//myI2C_Write_SCL(0);//拉低scl
// myI2C_Write_SDA(Byte&0x40);//移出次高位,放在sda数据线上
//myI2C_Write_SCL(1);//释放scl,释放主动权,让从机读取
//myI2C_Write_SCL(0);//拉低scl
//...循环八次
uint8_t i;
for(i=0;i<8;i++)
{
myI2C_Write_SDA(Byte&(0x80>>i));//将数据放至数据线上
myI2C_Write_SCL(1);//释放scl,释放主动权,让从机读取,此时不允许数据再有变化
myI2C_Write_SCL(0);//拉低scl
}
}
接收一个字节
代码:
/**
* @description: 主机接收一个字节
* @tip:接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
* @return {*}接收的字节数据 范围:0x00~0xff
*/
uint8_t myI2C_ReceiveByte(void)
{
uint8_t byte=0x00,i=0;
myI2C_Write_SDA(1);//先释放sda,防止主机一直占用sda信号,从机无法掌握主动权
for(i=0;i<8;i++)
{
myI2C_Write_SCL(1);//拉高scl
if(myI2C_Read_SDA()==1)//如果该位为1
{
byte|=(0x80>>i);//接收数据,此时不允许数据变化
}
myI2C_Write_SCL(0);//拉低scl
}
return byte;//返回接收的字节
}
发送应答
代码:
/**
* @description: 发送应答,主机发送一位应答,确认从机是否收到一个字节的数据
* @param {uint8_t} ack 应答 取值:0或1
* @tip:发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
* @return {*}无
*/
void myI2C_SendAck(uint8_t ack)
{
myI2C_Write_SDA(ack);//发送应答
myI2C_Write_SCL(1);//拉高scl,主动权交给从机
myI2C_Write_SCL(0);//拉高scl
}
接收应答
/**
* @description: 主机接收来自从机的应答
* @tip:接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
* @return {*}ack 返回的应答位:0应答,1非应答
*/
uint8_t myI2C_ReceiveAck(void)
{
uint8_t ack=0;
myI2C_Write_SDA(1);//拉高释放,主动权交给从机,让从机发送应答
myI2C_Write_SCL(1);//拉高scl,主动权交给从机,采样
ack=myI2C_Read_SDA();//接收应答
myI2C_Write_SCL(0);//拉高scl
return ack;//返回应答
}
整体I2C软件驱动代码
注释很清晰
myI2C.c
/*
* @Author: i want to 舞动乾坤
* @Date: 2024-07-25 13:50:42
* @LastEditors: i want to 舞动乾坤
* @LastEditTime: 2024-07-25 19:46:06
* @FilePath: \i2c_software_driver\main\myI2C.c
* @Description: 软件模拟I2C
*
* Copyright (c) 2024 by i want to 舞动乾坤, All Rights Reserved.
*/
#include <driver/gpio.h>
#include <esp_log.h>
#include <esp_rom_sys.h>
#define myI2C_SCL_Pin GPIO_NUM_22
#define myI2C_SDA_Pin GPIO_NUM_21
/**
* @description: 给SCL引脚写入电平
* @param {uint32_t} BitValue 写入的电平 取值:0(低电平) 1(高电平)
* @warning 部分开发板如果频率慢的话需要加延时函数,下同
* @return {*}
*/
void myI2C_Write_SCL(uint8_t BitValue)
{
gpio_set_level(myI2C_SCL_Pin,(uint8_t)BitValue);
;
}
/**
* @description: 给SDA引脚写入电平
* @param {uint32_t} BitValue 写入的电平 取值:0(低电平) 1(高电平)
* @return {*}
*/
void myI2C_Write_SDA(uint8_t BitValue)
{
gpio_set_level(myI2C_SDA_Pin,(uint8_t)BitValue);
}
/**
* @description: 读取SCL的电平
* @tip:在 ESP32 上,GPIO 驱动程序没有直接提供获取输出引脚电平状态的函数。
* 在下方myI2C_Init()初始化函数内,myI2C_SCL_Pin和myI2C_SDA_Pin都设为了开漏输出,
* 但gpio_get_level()函数只能获取GPIO输入电平,因此,需要在该函数和myI2C_Read_SDA()函数内临时切换gpio的模式为输入模式,
* 然后读取电平,再读取完电平以后再置回开漏输出模式。
* @return {*}0:读取出来的电平为低电平 1:读取出来的电平为高电平
*/
uint8_t myI2C_Read_SCL()
{
int BitVale;
gpio_set_direction(myI2C_SCL_Pin, GPIO_MODE_INPUT);//临时设为输入模式
BitVale=gpio_get_level(myI2C_SCL_Pin);//读取电平状态
gpio_set_direction(myI2C_SCL_Pin, GPIO_MODE_INPUT_OUTPUT_OD);//切换为开漏输出模式
return BitVale;
}
/**
* @description: 读取SDA的电平
* @return {*}0:读取出来的电平为低电平 1:读取出来的电平为高电平
*/
uint8_t myI2C_Read_SDA()
{
int BitVale;
gpio_set_direction(myI2C_SDA_Pin, GPIO_MODE_INPUT);//临时设为输入模式
BitVale=gpio_get_level(myI2C_SDA_Pin);//读取电平状态
gpio_set_direction(myI2C_SDA_Pin, GPIO_MODE_INPUT_OUTPUT_OD);//切换为开漏输出模式
return BitVale;
}
/**
* @description: I2C初始化
* @return {*}无
*/
void myI2C_Init(void)
{
gpio_config_t gpio_config_InitStructure;//初始化gpio
gpio_config_InitStructure.intr_type=GPIO_INTR_DISABLE;//中断失能
gpio_config_InitStructure.pull_down_en=GPIO_PULLDOWN_ENABLE;//下拉失能
gpio_config_InitStructure.pull_up_en=GPIO_PULLUP_DISABLE;//上拉失能
gpio_config_InitStructure.mode=GPIO_MODE_OUTPUT_OD;//开漏输出
gpio_config_InitStructure.pin_bit_mask=(1ull<<myI2C_SCL_Pin)|((1ull<<myI2C_SDA_Pin));//引脚掩码
gpio_config(&gpio_config_InitStructure);
//拉高电平,为起始信号做准备
myI2C_Write_SCL(1);
myI2C_Write_SDA(1);
}
/**
* @description: I2C起始信号
* @tip:scl高电平期间,sda由高电平切换为低电平
* @return {*}无
*/
void myI2C_Start(void)
{
//首先恢复空闲状态,由于在指定地址读时 会有个str ,此时SDA的电平并不知道,
//如果先释放SCL,此时若SDA为低电平,然后在释放完SCL后才释放SDA,
//会导致停止条件的产生,所以先释放sda,再释放scl
myI2C_Write_SDA(1);
myI2C_Write_SCL(1);
//在scl高电平期间,sda由高电平切换为低电平,为保持后续的时序的连续性,将scl也拉低
myI2C_Write_SDA(0);//拉低sda
myI2C_Write_SCL(0);//拉低scl
}
/**
* @description: 发送一个字节
* @param {uint8_t} Byte 发送的字节数据 范围:0x00~0xff
* @tip:由于是高位先行,所以要使用&进行一位一位的取出
* @warning:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
* @return {*}
*/
void myI2C_SendByte(uint8_t Byte)
{
// myI2C_Write_SDA(Byte&0x80);//移出最高位,放在sda数据线上
//myI2C_Write_SCL(1);//释放scl,释放主动权,让从机读取
//myI2C_Write_SCL(0);//拉低scl
// myI2C_Write_SDA(Byte&0x40);//移出次高位,放在sda数据线上
//myI2C_Write_SCL(1);//释放scl,释放主动权,让从机读取
//myI2C_Write_SCL(0);//拉低scl
//...循环八次
uint8_t i;
for(i=0;i<8;i++)
{
myI2C_Write_SDA(Byte&(0x80>>i));//将数据放至数据线上
myI2C_Write_SCL(1);//释放scl,释放主动权,让从机读取,此时不允许数据再有变化
myI2C_Write_SCL(0);//拉低scl
}
}
/**
* @description: 主机接收一个字节
* @tip:接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
* @return {*}接收的字节数据 范围:0x00~0xff
*/
uint8_t myI2C_ReceiveByte(void)
{
uint8_t byte=0x00,i=0;
myI2C_Write_SDA(1);//先释放sda,防止主机一直占用sda信号,从机无法掌握主动权
for(i=0;i<8;i++)
{
myI2C_Write_SCL(1);//拉高scl
if(myI2C_Read_SDA()==1)//如果该位为1
{
byte|=(0x80>>i);//接收数据,此时不允许数据变化
}
myI2C_Write_SCL(0);//拉低scl
}
return byte;//返回接收的字节
}
/**
* @description: 发送应答,主机发送一位应答,确认从机是否收到一个字节的数据
* @param {uint8_t} ack 应答 取值:0或1
* @tip:发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
* @return {*}无
*/
void myI2C_SendAck(uint8_t ack)
{
myI2C_Write_SDA(ack);//发送应答
myI2C_Write_SCL(1);//拉高scl,主动权交给从机
myI2C_Write_SCL(0);//拉高scl
}
/**
* @description: 主机接收来自从机的应答
* @tip:接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
* @return {*}ack 返回的应答位:0应答,1非应答
*/
uint8_t myI2C_ReceiveAck(void)
{
uint8_t ack=0;
myI2C_Write_SDA(1);//拉高释放,主动权交给从机,让从机发送应答
myI2C_Write_SCL(1);//拉高scl,主动权交给从机,采样
ack=myI2C_Read_SDA();//接收应答
myI2C_Write_SCL(0);//拉高scl
return ack;//返回应答
}
/**
* @description: I2C停止信号
* @tip:终止条件:SCL高电平期间,SDA从低电平切换到高电平
* @return {*}无
*/
void myI2C_Stop(void)
{
//SDA若一开始就是低电平,则可直接拉高 ,但如果SDA开始时是高电平 则需要先拉低再拉高
myI2C_Write_SDA(0);
myI2C_Write_SCL(1);//在scl高电平期间,SDA从低电平切换到高电平
myI2C_Write_SDA(1);
}
myI2C.h
#ifndef _MYI2C__H
#define _MYI2C__H
void myI2C_Init(void);
void myI2C_Start(void);
void myI2C_SendByte(uint8_t Byte);
uint8_t myI2C_ReceiveByte(void);
void myI2C_SendAck(uint8_t ack);
uint8_t myI2C_ReceiveAck(void);
void myI2C_Stop(void);
#endif
MPU6050
MPU6050介绍
MPU6050是InvenSense公司推出的全球首款整合性6轴运动处理组件。下面以这个芯片为例分析六轴传感器相关的知识点,其他的三轴或六轴传感器很多特性都是与之类似的。
MPU6050是由三个陀螺仪和三个加速度传感器组成的6轴运动处理组件,是一款六轴(三轴加速度+三轴角速度(陀螺仪))传感器。
应用:
- 平衡车
- 飞行器
- 智慧医疗
- 等等
SCL、SDA
:是连接MCU的IIC接口,MCU通过这个IIC接口来控制MPU6050,此时MPU6050作为一个IIC从机设备,接单片机的I2C_SCL。
XCL、XDA
:辅助IIC用来连接其他器件,可用来连接外部从设备
,比如磁传感器,这样就可以组成一个九轴传感器
,不需要连接单片机。
AD0
:地址管脚,可以不接单片机。当MPU6050作为一个IIC从机设备的时候,有8位地址,高7位的地址是固定的,就是WHOAMI寄存器的默认——0x68,最低的一位是由AD0的连线决定的。
AD0接GND时,高8位的最后一位是0,所以iic从机地址是0x68;
AD0接VCC时,高8位的最后一位是1,所以iic从机地址是0x69。
INT:数据输出的中断引脚,可以不接单片机,准备好数据之后,通过中断告诉STM32,从而获取数据。
VCC:接3.3V或5V电源
GND:接地
具体详细看这两位大佬的:
姿态传感器——MPU6050
六轴传感器基础知识学习:MPU6050特性,四元数,姿态解算,卡尔曼滤波
这里直接把代码给写出来吧(ps:你们应该很想要)
MPU6050代码
MPU6050.c
/*
* @Author: i want to 舞动乾坤
* @Date: 2024-07-25 16:52:56
* @LastEditors: i want to 舞动乾坤
* @LastEditTime: 2024-07-25 19:24:06
* @FilePath: \i2c_software_driver\main\mpu6050.c
* @Description:
*
* Copyright (c) 2024 by i want to 舞动乾坤, All Rights Reserved.
*/
#include <stdint.h>
#include "MPU6050_REG.h"
#include "myI2C.h"//这个头文件要放在后面,不然编译会出错,暂时不知道什么原因
#define MPU6050_Address 0xD0
/**
* 函 数:MPU6050写寄存器
* 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
* 参 数:Data 要写入寄存器的数据,范围:0x00~0xFF
* 返 回 值:无
*/
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
myI2C_Start();
myI2C_SendByte(MPU6050_Address);
myI2C_ReceiveAck();
myI2C_SendByte(RegAddress);
myI2C_ReceiveAck();
myI2C_SendByte(Data);
myI2C_ReceiveAck();
myI2C_Stop();
}
/**
* 函 数:MPU6050读寄存器
* 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
* 返 回 值:读取寄存器的数据,范围:0x00~0xFF
*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
myI2C_Start(); //I2C起始
myI2C_SendByte(MPU6050_Address); //发送从机地址,读写位为0,表示即将写入
myI2C_ReceiveAck(); //接收应答
myI2C_SendByte(RegAddress); //发送寄存器地址
myI2C_ReceiveAck(); //接收应答
myI2C_Start(); //I2C重复起始
myI2C_SendByte(MPU6050_Address | 0x01); //发送从机地址,读写位为1,表示即将读取
myI2C_ReceiveAck(); //接收应答
Data = myI2C_ReceiveByte(); //接收指定寄存器的数据
myI2C_SendAck(1); //发送应答,给从机非应答,终止从机的数据输出
myI2C_Stop(); //I2C终止
return Data;
}
/**
* 函 数:MPU6050初始化
* 参 数:无
* 返 回 值:无
*/
void MPU6050_Init(void)
{
myI2C_Init();
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);//唤醒
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);//10分频
MPU6050_WriteReg(MPU6050_CONFIG,0x06);//数字低通滤波器
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);//陀螺仪寄存器
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);//加速度寄存器 最大量程
}
/**
* 函 数:MPU6050获取ID号
* 参 数:无
* 返 回 值:MPU6050的ID号
*/
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
/**
* 函 数:MPU6050获取数据
* 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
* 返 回 值:无
* 具体选择转的角速度是多少 是通过比例公式计算出来的 读取的数据/32768 = x /满量程 求x
*/
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t DataH,DataL;
//读取加速度x轴寄存器的高八位
DataH=MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
//读取加速度x轴寄存器的低八位
DataL=MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX=(DataH<<8) | DataL;//读取
//读取加速度y轴寄存器的高八位
DataH=MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
//读取加速度y轴寄存器的低八位
DataL=MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY=(DataH<<8) | DataL; //返回出去
//读取加速度z轴寄存器的高八位
DataH=MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
//读取加速度z轴寄存器的低八位
DataL=MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ=(DataH<<8) | DataL; //返回出去
//读取加速度z轴寄存器的高八位
DataH=MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
//读取加速度z轴寄存器的低八位
DataL=MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ=(DataH<<8) | DataL; //返回出去
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //读取陀螺仪X轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L); //读取陀螺仪X轴的低8位数据
*GyroX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H); //读取陀螺仪Y轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L); //读取陀螺仪Y轴的低8位数据
*GyroY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H); //读取陀螺仪Z轴的高8位数据
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L); //读取陀螺仪Z轴的低8位数据
*GyroZ = (DataH << 8) | DataL; //数据拼接,通过输出参数返回
}
MPU6050.h
#ifndef __MPU6050_H__
#define __MPU6050_H__
void MPU6050_Init(void);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);
uint8_t MPU6050_GetID(void);
#endif
MPU6050_REG.h
#ifndef __MPU6050_REG_H__
#define __MPU6050_REG_H__
//存放MPU6050常用的寄存器地址
#define MPU6050_SMPLRT_DIV 0x19 //分频值,值越小越快
#define MPU6050_CONFIG 0x1A
#define MPU6050_GYRO_CONFIG 0x1B
#define MPU6050_ACCEL_CONFIG 0x1C
#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
#endif
main.c
#include <stdio.h>
#include "MPU6050.h"
#include <esp_log.h>
#include <freeRtos/FreeRTOS.h>
#include <freeRtos/task.h>
uint8_t ID; //定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ; //定义用于存放各个数据的变量
void app_main(void)
{
MPU6050_Init();
ID=MPU6050_GetID();//获取设备ID
ESP_LOGI("MPU6050 ID","#%x\n",ID);
while(1)
{
MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);//把六个变量的地址传递过去
//显示六元组数据
ESP_LOGI("AX value is","%d\n",AX);
ESP_LOGI("AY value is","%d\n",AY);
ESP_LOGI("AZ value is","%d\n",AZ);
ESP_LOGI("GX value is","%d\n",GX);
ESP_LOGI("GY value is","%d\n",GY);
ESP_LOGI("GZ value is","%d\n",GZ);
vTaskDelay(1000/portTICK_PERIOD_MS);//20ms获取一次
}
}
整体测试
目录结构
测试结果
平衡小车例程
自制的基于STM32和FreeRTOS和PID算法的平衡小车演示视频:
我做的STM32的两轮平衡小车居然举起了两个“哑铃”
---------------完结撒花····················