【江科大】STM32实战教程:MPU6050软件/硬件读写代码详解

软件读写MPU6050


注意
有SCL和SDA本来需要外挂上拉电阻,但是由于STM32内部内置了,就不需要了。
由于模块内部内置了下拉电阻,所以引脚悬空就相当于是接地。
发送一个字节的编码步骤

  1. 起始条件,SDA先出现下降沿,接着SCL跟着出现下降沿。此时SDA和SCL均为低电平。
    因此发送一个数据时,开始时SCL时低电平。
  2. SCL低电平时,接着主机写数据到SDA(即控制SDA电平的转换)。
  3. 写数据完成后,保持SDA不变,将SCL置为高电平。
    MyI2C_W_SDA(Byte&Ox8o);高位先行,先写最高位
    MyI2C_W_SCL(1);/从机在DCL为高电平时读取数据
    MyI2C_WSCL(O);//读取数据结束,再将SCL的电平置为低电平
    所有的单元都要保证SCL以低电平结束。这样方便各个单元的拼接。

SCL低电平:写入数据
SCL高电平:保证数据稳定
高位先行,所以变换数据的时候先放最高位
起始位后,SCL和SDA均进入低电平
发送一个字节,即SCL低电平,写入第一个字节
写完一位,释放SCL,将SDA拉下来,

发送一个字节(MyI2C.c)

//发送一个字节
void MyI2C_SendByte(uint8_t Byte)            
{   
    uint8_t i;	//1000 0000
	for(i= 0;i<8;i++)
	{
	    MyI2C_W_SDA(Byte & (0x80 >> i) );//高位先行,首先趁着SCL低电平,将byte的最高位放到SDA上。
	                         //结果不是 0x00 就是0x80  
		MyI2C_W_SCL(1); //SCL为高电平 //从机读取数据
	    MyI2C_W_SCL(0);//SCL为低电平  //主机下一次写数据
	}
}

接收一个字节的步骤

1.将SDA,SCL已经被拉到高电平
2.从机读取SDA上的数据
3.读完后,将SCL置为低电平,即等待主机继续写数据

返回值为1,表示高电平
返回值为0,表示低电平

//接收一个字节
uint8_t  MyI2C_RecByte(void)   //主机读数据,从机写数据
{
	uint8_t i;
	uint8_t Byte = 0x00; 
	MyI2C_W_SDA(1);  //主机将SDA释放 ,为高电平,切换为输入模式
	for(i = 0;i<8;i++)
	{
	 MyI2C_W_SCL(1);  //SCL为高电平开始读SDA
	if(MyI2C_R_SDA()== 1){Byte  |= 0x80>>i;}
	//按位或  ,byte =1 ,则 byte最高位置为1  
	//如果if不成立则写入0
	MyI2C_W_SCL(0);
    }
	return Byte;
}

发送应答和接收应答与发送一个字节和接收一个字节基本一致只是只发送和接收一个数据包

//发送应答位
void MyI2C_SendACK(uint8_t ACKBit)            
{   
	MyI2C_W_SDA(ACKBit);
    MyI2C_W_SCL(1); 
	MyI2C_W_SCL(0);
}
//接收应答位
uint8_t  MyI2C_RecvACK(void)   //主机读数据,从机写数据
{
	uint8_t ACKBit; 
	MyI2C_W_SDA(1);   //即使主机释放SDA为高电平,而从机写数据到SDA
	MyI2C_W_SCL(1);  //SCL为高电平开始读SDA
	ACKBit = MyI2C_R_SDA();    //读取的
	MyI2C_W_SCL(0);
    return ACKBit;
}

注意
芯片上电后默认的时睡眠模式,睡眠模式写入寄存器时无效的

MyI2C完整代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
//利用函数将写数据为封装起来
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);
	Delay_us(10);
}
void  MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);
		Delay_us(10);
}
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}
//因为STM32中读和写不是同一个寄存器

//#define SCL_PORT GPIOB   由于I2C对频率有要求,因此我这里采用直接定义一个函数,来设置端口
//#define SCL_PIN GPIO_pin_10
//#define MyI2C_setBit()  GPIO_SetBits(GPIOA, GPIO_Pin_10|GPIO_Pin_11);
void MYI2C_Init(void)
{
	//将SCL和SDA配置位开漏输出模式
	//将SCL和SDA均置为高电平
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); 
	
	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);
	
}
//起始条件
void MyI2C_Start(void)
{
	MyI2C_W_SCL(1); //将SCL和SDA都释放
	MyI2C_W_SDA(1);
	MyI2C_W_SDA(0); //拉低SDA
	MyI2C_W_SCL(0); //拉低SCL
}
//终止条件

void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0); //先拉低SDA
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}


//发送一个字节
void MyI2C_SendByte(uint8_t Byte)            
{   
    uint8_t i;	//1000 0000
	for(i= 0;i<8;i++)
	{
	    MyI2C_W_SDA(Byte & (0x80 >> i) );//高位先行,首先趁着SCL低电平,将byte的最高位放到SDA上。
	                         //结果不是 0x00 就是0x80  
		MyI2C_W_SCL(1); //SCL为高电平 //从机读取数据
	    MyI2C_W_SCL(0);//SCL为低电平  //主机下一次写数据
	}
}
//接收一个字节
uint8_t  MyI2C_RecByte(void)   //主机读数据,从机写数据
{
	uint8_t i;
	uint8_t Byte = 0x00; 
	MyI2C_W_SDA(1);  //主机将SDA释放 ,为高电平,切换为输入模式
	for(i = 0;i<8;i++)
	{
	 MyI2C_W_SCL(1);  //SCL为高电平开始读SDA
	if(MyI2C_R_SDA()== 1){Byte  |= 0x80>>i;}
	//按位或  ,byte =1 ,则 byte最高位置为1  
	//如果if不成立则写入0
	MyI2C_W_SCL(0);
    }
	return Byte;
}
//发送应答位
void MyI2C_SendACK(uint8_t ACKBit)            
{   
	MyI2C_W_SDA(ACKBit);
    MyI2C_W_SCL(1); 
	MyI2C_W_SCL(0);
}
//接收应答位
uint8_t  MyI2C_RecvACK(void)   //主机读数据,从机写数据
{
	uint8_t ACKBit; 
	MyI2C_W_SDA(1);   //即使主机释放SDA为高电平,而从机写数据到SDA
	MyI2C_W_SCL(1);  //SCL为高电平开始读SDA
	ACKBit = MyI2C_R_SDA();    //读取的
	MyI2C_W_SCL(0);
    return ACKBit;
}

MPU6050.c

#include "stm32f10x.h"  
#include "MyI2C.h"
#include "MPU6050.h"
#include "MPU6050_Reg.h"
// Device header

#define MPU6050_ADDRESS   0xD0  //从机地址
//指定地址写
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)
{
		MyI2C_Start();
	    MyI2C_SendByte(MPU6050_ADDRESS);//发送从机地址
	    MyI2C_RecvACK();
	    MyI2C_SendByte(RegAddress);
	    MyI2C_RecvACK();
	    MyI2C_SendByte(Data);
	    MyI2C_RecvACK();
	    MyI2C_Stop();
	
}
//指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_RecvACK();
	MyI2C_SendByte(RegAddress);
	MyI2C_RecvACK();
	
	MyI2C_Start();  //重新指定读写
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//变成0xD1,读写位变1
	MyI2C_RecvACK();
	uint8_t Byte;
	Byte = MyI2C_RecByte();//返回值位接收到的数据
	MyI2C_SendACK(1);//无需应答
	MyI2C_Stop();
	return Byte;
}
void MPU6050_Init(void)
{
	MYI2C_Init();
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);//选择X轴陀螺仪时钟
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);//10分频
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);
	MPU6050_WriteReg(MPU6050_CONFIG,0x06);//低通滤波器
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);

}

uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
//结构体打包输出

//可以使用连续读取一片连续地址的寄存器
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{                                 //x,y,z加速度值和陀螺仪值
	uint8_t DataH, DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);//读取数据
	*AccX = (DataH << 8) | DataL;  //加速度x轴,16位数据,用指针返回数据
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}


将寄存器地址存起来。然后就可以直接操作了。
读取的数据/32768=x/满量程,解得X,就是具体的角速度的值

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

硬件读写MPU6050

  1. 可以将硬件2C的引脚却配置再芯片对应2C引脚上。就可以选泽软件2C和硬件2C
    软件12C和硬件2C的区别:
    软件都是通过程序手动反转引脚的电平
    而硬件就帮我们把这些事都干了。
    第一步:配置12C外设,对12C外设进行初始化
    第二步:控制外设电路,实现指定地址写的时序
    第三步:控制外设电路,实现指定地址读
    当时钟频率设置为101kHz。进入高速模式.
    占空比设置为2:1低电平:高电平=2:1
    占空比的作用;给低电平多分配一些资源,
    因为低电平写数据,也就是数据变化,而高电平读数据。
    所以给数据变化的时间延长。高电平才能读取数据。
    (一般读取速度都大于写入速度)

  2. 软件2C起始函数,发送字节的函数,内部都是延迟函数。都是阻塞式的,当函数执行完毕对应的波形也会
    生成。
    而硬件2C是非阻塞式,只管给寄存器的位置置1,或者只在DR写数据,就结束就退出函数
    所以对于这种函数,就必须在函数执行完以后,需要等待对应的标志位状态,确保函数都执行到位

主机发送

void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	I2C_GenerateSTART(I2C2, ENABLE);//生成起始条件,实际是在操作CR1寄存器,置1,产生起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//等待EV5事件的到来
	
	//发送从机地址,接收应答,直接向DR寄存器写入一个字节即可
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//I2C2、从机地址、发送(即最低地址清0)
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	I2C_SendData(I2C2, RegAddress);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);//字节正在发送中
	
	I2C_SendData(I2C2, Data);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//字节已经发送完毕transmitted
	
	I2C_GenerateSTOP(I2C2, ENABLE);//生成终止条件
}

主机接收

//指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
  //
	I2C_GenerateSTART(I2C2,ENABLE);
  MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);   //EV5:已发送起始位
  
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);  //发送的地址
  MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);   //EV61:地址发送结束。
  
	I2C_SendData(I2C2,RegAddress);                                    //指定寄存器地址
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);       //EV7:数据寄存器非空。数据已经移入数据寄存器

	
	I2C_GenerateSTART(I2C2,ENABLE);//重复起始条件
  MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5事件  已发送起始位
  
  
	I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Receiver);  //发送接收的地址
    MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED); //EV6
	
	
	
	//在发送最后一个字节之前,要提前给ACK置零和发Stop
	//如果只需要接收一个字节,则在EV6事件之后需要ACK置0且配置stop标志位,否则会多读取一个字节
	//如果是接收多个字节则直接等待EV7事件,在EV7_1事件之前ACK置0且配置stop标志位为1*/
	//如果要接收多个字节,将下面的四行套个循环体,在接收最后一个字节之前。只执行后面两行(if),如果执行到最后一个字节,那就4行全都执行。
 
	I2C_AcknowledgeConfig(I2C2,DISABLE);  //无应答
	
	I2C_GenerateSTOP(I2C2,ENABLE);  
	MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//EV7:代表数已经移位到数据寄存器
	Data = I2C_ReceiveData(I2C2);		

	I2C_AcknowledgeConfig(I2C2,ENABLE);
	return Data;
}

完整代码

#include "stm32f10x.h"                  // Device header
#include "MPU6050_Reg.h"

#define MPU6050_ADDRESS		0xD0

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


void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	I2C_GenerateSTART(I2C2, ENABLE);//生成起始条件,实际是在操作CR1寄存器,置1,产生起始条件
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//等待EV5事件的到来
	
	//发送从机地址,接收应答,直接向DR寄存器写入一个字节即可
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//I2C2、从机地址、发送(即最低地址清0)
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	I2C_SendData(I2C2, RegAddress);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);//字节正在发送中
	
	I2C_SendData(I2C2, Data);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//字节已经发送完毕transmitted
	
	I2C_GenerateSTOP(I2C2, ENABLE);//生成终止条件
}

uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	I2C_GenerateSTART(I2C2, ENABLE);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
	
	I2C_SendData(I2C2, RegAddress);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
	
	I2C_GenerateSTART(I2C2, ENABLE);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
	
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
	Data = I2C_ReceiveData(I2C2);
	
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	
	return Data;
}

void MPU6050_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	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);
	
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
	I2C_InitStructure.I2C_ClockSpeed = 50000;
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
	I2C_InitStructure.I2C_OwnAddress1 = 0x68;
	I2C_Init(I2C2, &I2C_InitStructure);
	
	I2C_Cmd(I2C2, ENABLE);
	
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}

uint8_t MPU6050_GetID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

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;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	*AccX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	*AccY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	*AccZ = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	*GyroX = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	*GyroY = (DataH << 8) | DataL;
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
	*GyroZ = (DataH << 8) | DataL;
}




作者:白糖熊

物联沃分享整理
物联沃-IOTWORD物联网 » 【江科大】STM32实战教程:MPU6050软件/硬件读写代码详解

发表评论