STM32江科大----IIC
声明:本人跟随b站江科大学习,本文章是观看完视频后的一些个人总结和经验分享,也同时为了方便日后的复习,如果有错误请各位大佬指出,如果对你有帮助可以点个赞小小鼓励一下,本文章建议配合原视频使用❤️
如果你也正在学习STM32可以订阅本专栏,后续将不定期更新( ˘ ³˘)❤️
如有侵权,请私信联系删除
文章目录
- 前言
- 理论部分
- 有关IIC理论部分
- MPU6050
- 参数和简介
- 硬件电路
- 内部框图
- IIC通讯外设
- IIC外设简介
- IIC框图
- IIC基本结构(一主多从)
- 时序---主机发送
- 时序---主机接受
- 软件和硬件的波形对比(上为软件,下为硬件)
- 代码部分
- 软件模拟iic
- 硬件模拟iic
前言
- 弄清IIC时序结构以及硬件要求
- MPU6050的基本参数和大概使用方法
- 一般情况下软件实现iic比硬件更为常用,但是硬件也有其优势,比如:可节省软件资源,可实现完整的多主机通讯模型,时序波形规整,通讯速率快,一般用于对性能指标要求高,软件iic则更为灵活,比如iic总线理论上可无限开辟,而硬件iic外设有限
理论部分
有关IIC理论部分
有关iic的理论知识可以看一下我的这篇文章iic的时序结构,就不做多余赘述了,个人觉得up在32里面讲iic比51清楚,有兴趣的小伙伴还是建议看一下32的iic原视频
MPU6050
参数和简介

- 理论上陀螺仪传感器是可以直接测量处具体角度的,但这里只能测量出角加速度,最后的角度的话需要另外计算,比如对角速度积分
加速度计具有静态稳定性,不具有动态稳定性,内部工作原理其实就是F=ma,m在里面为一个已知小球重量,用类似于弹簧测力计的东西测量出小球对一个面的压力,用压力除以m,就是该面方向上的加速度陀螺仪计具有动态稳定性,不具有静态稳定性,因为静止时角速度值会因为噪声而无法完全归零,经过积分的不断累积,这个小噪声就会导致积分会产生一个角度的缓慢漂移,最后就不准确

- 16位的ADC就是2^16=65536,但是由于加速度计和陀螺仪计都是存在方向的,所以就平分65536,一半为正值,一半为负值
- 不同量程的选择,测量范围也会变化,量程越大测量范围越大,但是分辨率会变低
- 从机地址有两种表示方法,一种是将7位作为从机地址,那么110 1000就是0x68,那么要加上读写位就是0x68 | 读写位,也可以表示为11010000,那么就是加上读写位表示从机地址,也就是0xD0,那么0xD0就是写地址,0xD1就是读地址
硬件电路

- XCL和XDA用于扩展设备,例如气压计等等
- AD0用于配置从机地址最低位,也就是第8位,默认是0
- 若使用中断,则中断可引起INT电平跳变
内部框图

IIC通讯外设
IIC外设简介

- 10位地址模式:也就是前五位必须是11110作为10位寻址标志位,然后第一个字节有7个地址位和一位读写位,第二个字节有8个地址位,那么第一个字节的7个地址位中的前五位为标志位,后两位和第二个字节的8位为地址位,总共10位就是1024种情况
- SMBus是系统管理总线,主要用于电源管理系统,了解即可
IIC框图

主要分为SDA和SCL两个部分,SDA主要与DR和数据移位寄存器有关,其过程类似于串口,SCL主要和CCR,CR,SR有关,其余部分了解即可
IIC基本结构(一主多从)

- 由于高位先行,所以移位寄存器是向左移位,主要通讯过程还是看移位寄存器和DR的配合,一个SCL时钟移位一次,移位8次可将一个字节放在SDA线上,大概流程就是,先将数据写入DR,当移位寄存器是空的时候,就转到移位寄存器然后发送
(对应后面时序的数据寄存器空和移位寄存器空) - GPIO口都要配置成复用开漏输出模式,因为使用的是片上外设
时序—主机发送

- EVx(Event x)即事件x,使用事件来表示相当于一个大的标志位包含多个小标志位,有专门的函数来读取这个大标志位,也就是配置CR会产生多个不同标志位组合形成了EVx这个大标志位
时序—主机接受

软件和硬件的波形对比(上为软件,下为硬件)

大概的区别:
- 软件波形不规整,SCL高电平占空比不一致,硬件则十分规整
- 软件中SDA在SCL拉低SCL变换数据时存在较大延时,硬件则延时很小,基本上在SCL拉低的瞬间就立马变换SDA
- 图中RA应答结束时,硬件产生了一个小尖峰,而软件则产生一个峰值延迟,这是在RA结束时,从机立马就释放了SDA将SDA操纵权归还主机,让主机准备发送下一个数据0,但是这里软件主机延迟了一会才拉低SDA,将0放在SDA线上,硬件则是主机立马就拉低SDA,所以没有产生那个峰值延迟而是产生一个尖峰
代码部分

软件模拟iic
软件模拟说明白了就是实现代码模拟时序结构,达到发送字节的效果,和以前51里面的写法基本相同
MyI2C.c
#include "stm32f10x.h" // Device header
#include "Delay.h"/*引脚配置层*//*** 函 数:I2C写SCL引脚电平* 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平*/
void MyI2C_W_SCL(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平Delay_us(10); //延时10us,防止时序频率超过要求
}/*** 函 数:I2C写SDA引脚电平* 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~1* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue为1时,需要置SDA为高电平*/
void MyI2C_W_SDA(uint8_t BitValue)
{GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性Delay_us(10); //延时10us,防止时序频率超过要求
}/*** 函 数:I2C读SDA引脚电平* 参 数:无* 返 回 值:协议层需要得到的当前SDA的电平,范围0~1* 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1*/
uint8_t MyI2C_R_SDA(void)
{uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); //读取SDA电平Delay_us(10); //延时10us,防止时序频率超过要求return BitValue; //返回SDA电平
}/*** 函 数:I2C初始化* 参 数:无* 返 回 值:无* 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化*/
void MyI2C_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出/*设置默认电平*/GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}/*协议层*//*** 函 数:I2C起始* 参 数:无* 返 回 值:无*/
void MyI2C_Start(void)
{MyI2C_W_SDA(1); //释放SDA,确保SDA为高电平//其实先释放SCL也可以,但是若SCL还被未释放在高电平时,//此时SDA被释放,根据停止的时序结构就会产生一个停止信号MyI2C_W_SCL(1); //释放SCL,确保SCL为高电平MyI2C_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号MyI2C_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}/*** 函 数:I2C终止* 参 数:无* 返 回 值:无*/
void MyI2C_Stop(void)
{MyI2C_W_SDA(0); //拉低SDA,确保SDA为低电平MyI2C_W_SCL(1); //释放SCL,使SCL呈现高电平MyI2C_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}/*** 函 数:I2C发送一个字节* 参 数:Byte 要发送的一个字节数据,范围:0x00~0xFF* 返 回 值:无*/
void MyI2C_SendByte(uint8_t Byte)
{uint8_t i;for (i = 0; i < 8; i ++) //循环8次,主机依次发送数据的每一位{/*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/MyI2C_W_SDA(!!(Byte & (0x80 >> i)));//使用掩码的方式取出Byte的指定一位数据并写入到SDA线MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间读取SDAMyI2C_W_SCL(0); //拉低SCL,主机开始发送下一位数据}
}/*** 函 数:I2C接收一个字节* 参 数:无* 返 回 值:接收到的一个字节数据,范围:0x00~0xFF*/
uint8_t MyI2C_ReceiveByte(void)
{uint8_t i, Byte = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送for (i = 0; i < 8; i ++) //循环8次,主机依次接收数据的每一位{MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDAif (MyI2C_R_SDA()){Byte |= (0x80 >> i);} //读取SDA数据,并存储到Byte变量//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDA}return Byte; //返回接收到的一个字节数据
}/*** 函 数:I2C发送应答位* 参 数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答* 返 回 值:无*/
void MyI2C_SendAck(uint8_t AckBit)
{MyI2C_W_SDA(AckBit); //主机把应答位数据放到SDA线MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间,读取应答位MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
}/*** 函 数:I2C接收应答位* 参 数:无* 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答*/
uint8_t MyI2C_ReceiveAck(void)
{uint8_t AckBit; //定义应答位变量MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDAAckBit = MyI2C_R_SDA(); //将应答位存储到变量里MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块return AckBit; //返回定义应答位变量
}
MyI2C.h
#ifndef __MYI2C_H
#define __MYI2C_Hvoid MyI2C_Init(void);
void MyI2C_Start(void);
void MyI2C_Stop(void);
void MyI2C_SendByte(uint8_t Byte);
uint8_t MyI2C_ReceiveByte(void);
void MyI2C_SendAck(uint8_t AckBit);
uint8_t MyI2C_ReceiveAck(void);#endif
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0xD0 //MPU6050的I2C从机地址/*** 函 数:MPU6050写寄存器* 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述* 参 数:Data 要写入寄存器的数据,范围:0x00~0xFF* 返 回 值:无*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{MyI2C_Start(); //I2C起始MyI2C_SendByte(MPU6050_ADDRESS); //发送从机地址,读写位为0,表示即将写入MyI2C_ReceiveAck(); //接收应答MyI2C_SendByte(RegAddress); //发送寄存器地址MyI2C_ReceiveAck(); //接收应答MyI2C_SendByte(Data); //发送要写入寄存器的数据MyI2C_ReceiveAck(); //接收应答MyI2C_Stop(); //I2C终止
}/*** 函 数: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(); //先初始化底层的I2C/*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //电源管理寄存器2,保持默认值0,所有轴均不待机MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采样率分频寄存器,配置采样率MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //配置寄存器,配置DLPFMPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器,选择满量程为±2000°/sMPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度计配置寄存器,选择满量程为±16g
}/*** 函 数:MPU6050获取ID号* 参 数:无* 返 回 值:MPU6050的ID号*/
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I); //返回WHO_AM_I寄存器的值
}/*** 函 数:MPU6050获取数据* 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767* 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767* 返 回 值:无*/
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; //定义数据高8位和低8位的变量DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据*AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据*AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据*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_Hvoid MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);#endif
MPU6050_Reg
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H#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 "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"uint8_t ID; //定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ; //定义用于存放各个数据的变量int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化MPU6050_Init(); //MPU6050初始化/*显示ID号*/OLED_ShowString(1, 1, "ID:"); //显示静态字符串ID = MPU6050_GetID(); //获取MPU6050的ID号OLED_ShowHexNum(1, 4, ID, 2); //OLED显示ID号while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ); //获取MPU6050的数据OLED_ShowSignedNum(2, 1, AX, 5); //OLED显示数据OLED_ShowSignedNum(3, 1, AY, 5);OLED_ShowSignedNum(4, 1, AZ, 5);OLED_ShowSignedNum(2, 8, GX, 5);OLED_ShowSignedNum(3, 8, GY, 5);OLED_ShowSignedNum(4, 8, GZ, 5);}
}
硬件模拟iic
硬件模拟也就是使用写好的库函数控制DR,SR,CR各个寄存器,而电平的翻转由硬件控制,写入CR或者DR就可以控制时序单元的发生,时序单元发生后就可以检查对应的EVx,也就是检查SR,然后等待时序单元的发送完成,然后依次操作寄存器,等待时序单元发送,如此循环,这是发送部分;接受部分就是写入CR读取DR,产生时序单元,等待事件和时序单元的完成,如此循环
MPU6050.c
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"#define MPU6050_ADDRESS 0xD0 //MPU6050的I2C从机地址/*** 函 数:MPU6050等待事件* 参 数:同I2C_CheckEvent* 返 回 值:无*/
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{uint32_t Timeout;Timeout = 10000; //给定超时计数时间while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS) //循环等待指定事件{Timeout --; //等待时,计数值自减if (Timeout == 0) //自减到0后,等待超时{/*超时的错误处理代码,可以添加到此处*/break; //跳出等待,不等了}}
}/*** 函 数:MPU6050写寄存器* 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述* 参 数:Data 要写入寄存器的数据,范围:0x00~0xFF* 返 回 值:无*/
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始条件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6I2C_SendData(I2C2, RegAddress); //硬件I2C发送寄存器地址MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING); //等待EV8I2C_SendData(I2C2, Data); //硬件I2C发送数据MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2I2C_GenerateSTOP(I2C2, ENABLE); //硬件I2C生成终止条件
}/*** 函 数:MPU6050读寄存器* 参 数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述* 返 回 值:读取寄存器的数据,范围:0x00~0xFF*/
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{uint8_t Data;I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成起始条件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter); //硬件I2C发送从机地址,方向为发送MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED); //等待EV6I2C_SendData(I2C2, RegAddress); //硬件I2C发送寄存器地址MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED); //等待EV8_2I2C_GenerateSTART(I2C2, ENABLE); //硬件I2C生成重复起始条件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT); //等待EV5I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver); //硬件I2C发送从机地址,方向为接收MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); //等待EV6I2C_AcknowledgeConfig(I2C2, DISABLE); //在接收最后一个字节之前提前将应答失能I2C_GenerateSTOP(I2C2, ENABLE); //在接收最后一个字节之前提前申请停止条件MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED); //等待EV7Data = I2C_ReceiveData(I2C2); //接收数据寄存器I2C_AcknowledgeConfig(I2C2, ENABLE); //将应答恢复为使能,为了不影响后续可能产生的读取多字节操作return Data;
}/*** 函 数:MPU6050初始化* 参 数:无* 返 回 值:无*/
void MPU6050_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE); //开启I2C2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为复用开漏输出/*I2C初始化*/I2C_InitTypeDef I2C_InitStructure; //定义结构体变量I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; //模式,选择为I2C模式I2C_InitStructure.I2C_ClockSpeed = 50000; //时钟速度,选择为50KHzI2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; //时钟占空比,选择Tlow/Thigh = 2I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; //应答,选择使能I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //应答地址,选择7位,从机模式下才有效I2C_InitStructure.I2C_OwnAddress1 = 0x00; //自身地址,从机模式下才有效I2C_Init(I2C2, &I2C_InitStructure); //将结构体变量交给I2C_Init,配置I2C2/*I2C使能*/I2C_Cmd(I2C2, ENABLE); //使能I2C2,开始运行/*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01); //电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00); //电源管理寄存器2,保持默认值0,所有轴均不待机MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09); //采样率分频寄存器,配置采样率MPU6050_WriteReg(MPU6050_CONFIG, 0x06); //配置寄存器,配置DLPFMPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器,选择满量程为±2000°/sMPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度计配置寄存器,选择满量程为±16g
}/*** 函 数:MPU6050获取ID号* 参 数:无* 返 回 值:MPU6050的ID号*/
uint8_t MPU6050_GetID(void)
{return MPU6050_ReadReg(MPU6050_WHO_AM_I); //返回WHO_AM_I寄存器的值
}/*** 函 数:MPU6050获取数据* 参 数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767* 参 数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767* 返 回 值:无*/
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; //定义数据高8位和低8位的变量DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //读取加速度计X轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L); //读取加速度计X轴的低8位数据*AccX = (DataH << 8) | DataL; //数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H); //读取加速度计Y轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L); //读取加速度计Y轴的低8位数据*AccY = (DataH << 8) | DataL; //数据拼接,通过输出参数返回DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H); //读取加速度计Z轴的高8位数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L); //读取加速度计Z轴的低8位数据*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_Hvoid MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ);#endif
MPU6050_reg.h
#ifndef __MPU6050_REG_H
#define __MPU6050_REG_H#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 "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"uint8_t ID; //定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ; //定义用于存放各个数据的变量int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化MPU6050_Init(); //MPU6050初始化/*显示ID号*/OLED_ShowString(1, 1, "ID:"); //显示静态字符串ID = MPU6050_GetID(); //获取MPU6050的ID号OLED_ShowHexNum(1, 4, ID, 2); //OLED显示ID号while (1){MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ); //获取MPU6050的数据OLED_ShowSignedNum(2, 1, AX, 5); //OLED显示数据OLED_ShowSignedNum(3, 1, AY, 5);OLED_ShowSignedNum(4, 1, AZ, 5);OLED_ShowSignedNum(2, 8, GX, 5);OLED_ShowSignedNum(3, 8, GY, 5);OLED_ShowSignedNum(4, 8, GZ, 5);}
}相关文章:
STM32江科大----IIC
声明:本人跟随b站江科大学习,本文章是观看完视频后的一些个人总结和经验分享,也同时为了方便日后的复习,如果有错误请各位大佬指出,如果对你有帮助可以点个赞小小鼓励一下,本文章建议配合原视频使用❤️ 如…...
顺序表——C语言实现
目录 一、线性表 二、顺序表 1.实现动态顺序表 SeqList.h SeqList.c Test.c 问题 经验:free 出问题,2种可能性 解决问题 (2)尾删 (3)头插,头删 (4)在 pos 位…...
ARM 汇编启动代码详解:从中断向量表到中断处理
ARM 汇编启动代码详解:从中断向量表到中断处理 引言 在嵌入式系统开发中,ARM 处理器(如 Cortex-A 系列)的启动代码是系统初始化和运行的基础。启动代码通常包括中断向量表的创建、初始化硬件状态(如关闭缓存和 MMU&a…...
LTSPICE仿真电路:(二十六)跨阻放大器简单仿真
1.前言 由于有个机会刚好了解了下跨阻,简单做个这个仿真,实际上跨阻放大器应该要复杂的多,由于跨阻放大器实际上是将电流转换为电压,最需要注意的参数肯定是运放的偏置电流 2.跨阻放大器仿真 这篇是纯记录 这是一个将0-50uA电流…...
特辣的海藻!15
题 1.迷宫 - 蓝桥云课 2.外卖店优先级 - 蓝桥云课 3.后缀表达式 - 蓝桥云课 题 1.迷宫 - 蓝桥云课 import java.util.*;public class Main {static class Node {int x;int y;String str;public Node(int x, int y, String str) {this.x x;this.y y;this.str str;} …...
RISCV GCC 后端 -- 依赖(Dependence)简析
在命令式语言,如C/C中,其依赖关系及分类如下: 依赖(Dependence) -- Control Dependence -- Data Dependence (Reads and Writes of the same location, registers / Memories etc) -- True Dependence (Write then Rea…...
算法-- js排序
汇总 注:以下log n 是 O(log2n) 注:快速排序实际应用中通常最优,但需避免最坏情况。 1 快速排序 [快速排序的思路] 分区:从数组中任意选择一个“基准”,所有比基准小的元素放在基准前面,比基准大的元素…...
FfreeRTOS有阻塞作用的API
在 FreeRTOS 中,阻塞 API 是指那些会导致调用任务进入阻塞状态(Blocked State)的函数,即任务会暂时让出 CPU,直到某个条件满足(如超时、信号量可用、队列数据到达等)。以下是常见的阻塞 API 分类及示例: 1. 任务延迟(延时) vTaskDelay() 使任务阻塞指定的时间(以系统…...
【棒垒球规则】全国幼儿软式棒垒球比赛规则(三)·棒球1号位
棒垒球球队的组成 3.01球队的组成 球队由教练员及工作人员 2 名至 4 名、队员 9 至 12 名组成。 球衣背号不大于两位数,背号不小于 15 厘米。 上场队员名单应填写上场选手和替补选手。 3.02防守位置及名称(参照图四) a.9 名队…...
stm32week10
stm32学习 七.CAN 7.STM32 CAN外设 标识符过滤器: 每个过滤器的核心由两个32位寄存器组成:R1[31:0]和R2[31:0] FSCx:位宽设置,置0为16位,置1为32位 FBMx:模式设置,置0为屏蔽模式,…...
Linux上历史命令显示时间,修改时间戳
今天分享一个生产环境避免背锅的小技巧:设置历史命令执行的具体时间。还可以快速定位问题出现的时间点并恢复误操作导致的系统问题,用于追踪溯源。 在Linux系统中,默认情况下,history命令只会显示命令的编号和命令内容࿰…...
看雪 get_pwn3(2016 CCTF 中的 pwn3)
get_pwn3(2016 CCTF 中的 pwn3) 格式化字符串漏洞 get_pwn3(2016 CCTF 中的 pwn3) (1) motalymotaly-VMware-Virtual-Platform:~/桌面$ file pwn3 pwn3: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, …...
python全栈-JavaScript
python全栈-js 文章目录 js基础变量与常量JavaScript引入到HTML文件中JavaScript注释与常见输出方式 数据类型typeof 显示数据类型算数运算符之加法运算符运算符之算术运算符运算符之赋值运算符运算符之比较运算符运算符之布尔运算符运算符之位运算符运算符优先级类型转换 控制…...
操作系统概述(3)
批处理系统 1.单道批处理系统 单道批处理系统是成批地处理作用,并且始终只有一道作业在内存中的系统。优点:提高系统资源的利用率和系统吞吐量。缺点:系统中的资源得不到充分利用。 2.多道批处理系统 引入多道程序设计技术,是…...
SolidWorks2025三维计算机辅助设计(3D CAD)软件超详细图文安装教程(2025最新版保姆级教程)
目录 前言 一、SolidWorks下载 二、SolidWorks安装 三、启动SolidWorks 前言 SolidWorks 是一款由法国达索系统(Dassault Systmes)公司开发的三维计算机辅助设计(3D CAD)软件,广泛用于机械设计、工程仿真和产品开…...
powershell绑定按钮事件的两种方式
写一个powershell的简单GUI做本地任务,试验出2个方法: 方法1: function btn1_click {write-host $text1.Text -ForegroundColor Green -BackgroundColor Black }$btn1.Add_Click({btn1_click})方法2: $btn2_click {write-host $…...
JBDC Java数据库连接(1)
目录 JDBC概述 定义 JDBC API 实例 JDBC搭建 建立与数据库连接: 形式: 实例 获得Satement执行sql语句 Satement中的方法: 实例 实例 JDBC概述 定义 JDBC(Java DataBase Connectivity)java数据库连接是一种用于执行SQL…...
Spring Boot 3.x 集成 MongoDB 的 默认配置项及默认值,以及 常用需要修改的配置项 的详细说明
以下是 Spring Boot 3.x 集成 MongoDB 的 默认配置项及默认值,以及 常用需要修改的配置项 的详细说明: 一、默认配置项及默认值 Spring Boot 对 MongoDB 的默认配置基于 spring.data.mongodb 前缀,以下是核心配置项: 配置项默认…...
git rebase复杂场景验证
经常面临复杂的分支管理,这里对几种场景的行为做一些验证。 结论总结 git rebase br_name:等价与新建br_name分支,然后找到当前分支与br_name分支的分叉点。然后把分叉点以后的提交(当前分支)一个一个的cherry-pick过…...
【Introduction to Reinforcement Learning】翻译解读2
2.2 马尔可夫决策过程(MDPs) 马尔可夫决策过程(MDP)为顺序决策提供了框架,其中动作不仅影响即时奖励,还会影响未来结果。与多臂老虎机问题不同,MDP中的即时奖励与延迟奖励相平衡。在多臂老虎机…...
大数据(5)Spark部署核弹级避坑指南:从高并发集群调优到源码级安全加固(附万亿级日志分析实战+智能运维巡检系统)
目录 背景一、Spark核心架构拆解1. 分布式计算五层模型 二、五步军工级部署阶段1:环境核弹级校验阶段2:集群拓扑构建阶段3:黄金配置模板阶段4:高可用启停阶段5:安全加固方案 三、万亿级日志分析实战1. 案例背景&#x…...
Linux内核中TCP协议栈的实现:tcp_close函数的深度剖析
引言 TCP(传输控制协议)作为互联网协议族中的核心协议之一,负责在不可靠的网络层之上提供可靠的、面向连接的字节流服务。Linux内核中的TCP协议栈实现了TCP协议的全部功能,包括连接建立、数据传输、流量控制、拥塞控制以及连接关闭等。本文将深入分析Linux内核中tcp_close…...
从搜索丝滑过渡到动态规划的学习指南
搜索&动态规划 前言砝码称重满分代码及思路solution 1(动态规划)solution 2(BFS) 跳跃满分代码及思路solution 1(动态规划)solution 2 (BFS) 积木画满分代码及思路动态规划思路讲解solution 前言 本文主要是通过一些竞赛真题…...
(一)栈结构、队列结构
01-线性结构-数组-栈结构 线性结构(Linear List)是由n(n>0)个数据元素(结点) a[0], a[1], a[2], a[3],...,a[n-1]组成的有限序列 数组 通常数组的内存是连续的,所以在知道数组下标的情况下,访问效率是…...
AWS SNS深度解析:构建高可用、可扩展的云原生消息通信解决方案
引言 在云原生架构中,高效的消息通信是系统解耦、实时响应的核心需求。AWS Simple Notification Service(SNS)作为一款全托管的发布/订阅(Pub/Sub)服务,为开发者提供了灵活、可靠的消息分发能力。本文将从…...
MySQL基础 [五] - 表的增删查改
目录 Create(insert) Retrieve(select) where条件 编辑 NULL的查询 结果排序(order by) 筛选分页结果 (limit) Update Delete 删除表 截断表(truncate) 插入查询结果(insertselect&…...
4.7学习总结 可变参数+集合工具类Collections+不可变集合
可变参数: 示例: public class test {public static void main(String[] args) {int sumgetSum(1,2,3,4,5,6,7,8,9,10);System.out.println(sum);}public static int getSum(int...arr){int sum0;for(int i:arr){sumi;}return sum;} } 细节:…...
OpenGL学习笔记(简介、三角形、着色器、纹理、坐标系统、摄像机)
目录 简介核心模式与立即渲染模式状态机对象GLFW和GLAD Hello OpenGLTriangle 三角形顶点缓冲对象 VBO顶点数组对象 VAO元素缓冲对象 EBO/ 索引缓冲对象 IEO 着色器GLSL数据类型输入输出Uniform 纹理纹理过滤Mipmap 多级渐远纹理实际使用方式纹理单元 坐标系统裁剪空间 摄像机自…...
vmware虚拟机上Ubuntu或者其他系统无法联网的解决方法
一、检查虚拟机是否开启了网络服务 打开方式:控制面板->-管理工具--->服务 查找 VMware DHCP Service 和VMware NAT Service ,确保这两个服务已经启动。如下图,没有启动就点击启动。 二、设置网络类型 我们一般使用前两种多一些&…...
OpenVLA-OFT——微调VLA时加快推理的三大关键设计:支持动作分块的并行解码、连续动作表示以及L1回归(含输入灵活化及对指令遵循的加强)
前言 25年3.26日,这是一个值得纪念的日子,这一天,我司「七月在线」的定位正式升级为了:具身智能的场景落地与定制开发商 ,后续则从定制开发 逐步过渡到 标准产品化 比如25年q2起,在定制开发之外࿰…...

