MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)

这篇具有很好参考价值的文章主要介绍了MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

记录一下自己遇到的问题及解决方法,希望能帮助到一些人。

第一步,读取芯片的原始数据。需要注意两点:1、对HAL库提供的IIC读取写入函数进行再包装。(千万不要觉的这步多此一举,后面移植DMP时用得到)

/**
* @brief 写寄存器,这是提供给上层的接口
* @param slave_addr: 从机地址
* @param reg_addr: 寄存器地址
* @param len:写入的长度
* @param data_ptr: 指向要写入的数据
* @retval 正常为 0,不正常为非 0
*/
int Sensors_I2C_WriteRegister(unsigned char slave_addr,
							  unsigned char reg_addr,
							  unsigned short len,
							  unsigned char *data_ptr){
	HAL_StatusTypeDef status = HAL_OK;
	status = HAL_I2C_Mem_Write(&I2C_Handle,slave_addr,reg_addr,
		I2C_MEMADD_SIZE_8BIT,data_ptr,len,I2Cx_FLAG_TIMEOUT);
	if(status != HAL_OK){
		
	}
	while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY) {
	
	}
	/* 检查 SENSOR 是否就绪进行下一次读写操作 */
	while (HAL_I2C_IsDeviceReady(&I2C_Handle, slave_addr,
		I2Cx_FLAG_TIMEOUT, I2Cx_FLAG_TIMEOUT) == HAL_TIMEOUT);
	/* 等待传输结束 */
	while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY) {
		
	}
	return status;
}

/**
* @brief 读寄存器,这是提供给上层的接口
* @param slave_addr: 从机地址
* @param reg_addr: 寄存器地址
* @param len:要读取的长度
* @param data_ptr: 指向要存储数据的指针
* @retval 正常为 0,不正常为非 0
*/
int Sensors_I2C_ReadRegister(unsigned char slave_addr,
							 unsigned char reg_addr,
							 unsigned short len,
							 unsigned char *data_ptr){
	HAL_StatusTypeDef status = HAL_OK;
	status =HAL_I2C_Mem_Read(&I2C_Handle,slave_addr
		,reg_addr,I2C_MEMADD_SIZE_8BIT,data_ptr,len,I2Cx_FLAG_TIMEOUT);
	if (status != HAL_OK) {/* 检查通讯状态 */

	}
	while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY) {
		
	}
	/* 检查 SENSOR 是否就绪进行下一次读写操作 */
	while (HAL_I2C_IsDeviceReady(&I2C_Handle, slave_addr,
		I2Cx_FLAG_TIMEOUT, I2Cx_FLAG_TIMEOUT) == HAL_TIMEOUT);
	/* 等待传输结束 */
	while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY) {
		
	}
	return status;
}

2、芯片的地址(这里面有俩坑)第一就是,芯片的 I2C 设备地址可通过 AD0 引脚的电平控制,当 AD0 接地时,设 备地址为 0x68(七位地址),当 AD0 接电源时,设备地址为 0x69(七位地址)。第二就是,用HAL库需要将地址向左移一位,也就是0x68<<1,0x69<<1。

// MPU6050, Standard address 0x68
#define MPU6050_ADDRESS         0x68<<1

然后就没啥问题了,下面是我的代码

初始化MPU6050芯片

/**
	* @brief 写数据到 MPU6050 寄存器
	* @param reg_add: 寄存器地址
	* @param reg_data: 要写入的数据
	* @retval
	*/
void MPU6050_WriteReg(uint8_t reg_add,uint8_t reg_dat){
	Sensors_I2C_WriteRegister(MPU6050_ADDRESS,reg_add,1,&reg_dat);
}
/**
	* @brief 从 MPU6050 寄存器读取数据
	* @param reg_add: 寄存器地址
	* @param Read:存储数据的缓冲区
	* @param num:要读取的数据量
	* @retval
	*/
void MPU6050_ReadData(uint8_t reg_add,unsigned char* Read,uint8_t num){
	Sensors_I2C_ReadRegister(MPU6050_ADDRESS,reg_add,num,Read);
}
/**
	* @brief 初始化 MPU6050 芯片
	* @param
	* @retval
	*/
void MPU6050_Init(void){
	//在初始化之前要延时一段时间,若没有延时,则断电后再上电数据可能会出错
	HAL_Delay(100);
	//解除休眠状态
	MPU6050_WriteReg(MPU6050_RA_PWR_MGMT_1, 0x00);
	//陀螺仪采样率
	MPU6050_WriteReg(MPU6050_RA_SMPLRT_DIV , 0x07);
	MPU6050_WriteReg(MPU6050_RA_CONFIG , 0x06);
	//配置加速度传感器工作在 16G 模式
	MPU6050_WriteReg(MPU6050_RA_ACCEL_CONFIG , 0x01);
	//陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
	MPU6050_WriteReg(MPU6050_RA_GYRO_CONFIG, 0x18);
	HAL_Delay(200);
}
/**
	* @brief 读取 MPU6050 的 ID
	* @param
	* @retval 正常返回 1,异常返回 0
	*/
uint8_t MPU6050ReadID(void){
	unsigned char Re = 0;
	MPU6050_ReadData(MPU6050_RA_WHO_AM_I,&Re,1); //读器件地址
	if (Re != 0x68) {
		printf("检测不到 MPU6050 模块,请检查模块与开发板的接线,------%d\r\n",Re);
		return 0;
	} else {
		printf("MPU6050 ID = %d\r\n",Re);
		return 1;
	}
}

读取加速度、角加速度、温度的原始数据,并转化为摄氏度

/**
	* @brief 读取 MPU6050 的加速度数据
	* @param
	* @retval
	*/
void MPU6050ReadAcc(short *accData){
	uint8_t buf[6];
	MPU6050_ReadData(MPU6050_ACC_OUT, buf, 6);
	accData[0] = (buf[0] << 8) | buf[1];
	accData[1] = (buf[2] << 8) | buf[3];
	accData[2] = (buf[4] << 8) | buf[5];
}
/**
	* @brief 读取 MPU6050 的角加速度数据
	* @param
	* @retval
	*/
void MPU6050ReadGyro(short *gyroData){
	uint8_t buf[6];
	MPU6050_ReadData(MPU6050_GYRO_OUT,buf,6);
	gyroData[0] = (buf[0] << 8) | buf[1];
	gyroData[1] = (buf[2] << 8) | buf[3];
	gyroData[2] = (buf[4] << 8) | buf[5];
}
/**
	* @brief 读取 MPU6050 的原始温度数据
	* @param
	* @retval
	*/
void MPU6050ReadTemp(short *tempData){
	uint8_t buf[2];
	MPU6050_ReadData(MPU6050_RA_TEMP_OUT_H,buf,2); //读取温度值
	*tempData = (buf[0] << 8) | buf[1];
}
/**
	* @brief 读取 MPU6050 的温度数据,转化成摄氏度
	* @param
	* @retval
	*/
void MPU6050_ReturnTemp(float*Temperature){
	short temp3;
	uint8_t buf[2];
	MPU6050_ReadData(MPU6050_RA_TEMP_OUT_H,buf,2); //读取温度值
	temp3= (buf[0] << 8) | buf[1];
	*Temperature=((double) (temp3 /340.0))+36.53;
}

********然后就是重头戏了——DMP的移植!!!(真是恶心坏我了)

第一步,把官方文档中motion_driver_6.12\arm\STM32F4_MD6\Projects\eMD6\core的四个文件夹全复制过来,一个也不能少。

MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)

 然后再去官方文档motion_driver_6.12\mpl libraries\arm\Keil,解压你需要的静态库,我选的是第四个。(第四个和第五个有区别,具体问百度吧,我忘了)

MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)

 然后把解压好的静态库libmpllib.lib复制到刚才那四个文件夹中的mpl文件夹中,再把这个文件夹里面的libmpllib.a给删了。

然后打开Keil5,把刚才复制的.c文件全部加进来,包括静态库。不用分,直接搞一个group就行。

MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)

 接着把下面这三个宏定义加进去,并添加头文件路径。

MPL_LOG_NDEBUG=1
EMPL,MPU6050
EMPL_TARGET_STM32F4

MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)

 接下来就是最恶心的——修改程序

inv_mpu.c

#include "i2c.h"   
#include "main.h"
#include "log.h"
//#include "board-st_discovery.h"//*****************

//这两个文件是我创建的
#include "bsp_i2c.h"//这个里面是讲HAL库中的IIC写入读取封装成Sensors_I2C_WriteRegister、Sensors_I2C_ReadRegister 
#include "bsp_mpu6050_dmp.h"//这里面有get_tick_count函数,还有MPU6050带DMP的初始化,以及四元数的读取函数
   
#define i2c_write   Sensors_I2C_WriteRegister
#define i2c_read    Sensors_I2C_ReadRegister 
#define delay_ms    HAL_Delay//***************
#define get_ms      get_tick_count
#define log_i       printf//***************
#define log_e       printf//***************
#define min(a,b) ((a<b)?a:b)
inv_mpu_dmp_motion_driver.c

#include "i2c.h"   
#include "main.h"
//#include "board-st_discovery.h"//******************

#include "bsp_i2c.h"
#include "bsp_mpu6050_dmp.h"
   
#define i2c_write   Sensors_I2C_WriteRegister
#define i2c_read    Sensors_I2C_ReadRegister
#define get_ms      get_tick_count

创建bsp_mpu6050_dmp.h和bsp_mpu6050_dmp.c

#ifndef __BSP_MPU6050_DMP_H__
#define __BSP_MPU6050_DMP_H__

#include "stm32f4xx.h"
#include <stdio.h>
#include <i2c.h>
 
void gyro_data_ready_cb(void);

int get_tick_count(unsigned long *count);
 
uint8_t mpu_dmp_init(void);
 
uint8_t mpu_dmp_get_data(float *pitch,float *roll,float *yaw);
 
int fputcc(int ch);
 
#endif
#include "bsp_mpu6050_dmp.h"

#include "usart.h"
#include "i2c.h"
#include "gpio.h"
#include "main.h"
 
    
#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h"
#include "invensense.h"
#include "invensense_adv.h"
#include "eMPL_outputs.h"
#include "mltypes.h"
#include "mpu.h"
#include "log.h"
#include "packet.h"
 
/* Private typedef -----------------------------------------------------------*/
/* Data read from MPL. */
#define PRINT_ACCEL     (0x01)
#define PRINT_GYRO      (0x02)
#define PRINT_QUAT      (0x04)
#define PRINT_COMPASS   (0x08)
#define PRINT_EULER     (0x10)
#define PRINT_ROT_MAT   (0x20)
#define PRINT_HEADING   (0x40)
#define PRINT_PEDO      (0x80)
#define PRINT_LINEAR_ACCEL (0x100)
#define PRINT_GRAVITY_VECTOR (0x200)
 
volatile uint32_t hal_timestamp = 0;
#define ACCEL_ON        (0x01)
#define GYRO_ON         (0x02)
#define COMPASS_ON      (0x04)
 
#define MOTION          (0)
#define NO_MOTION       (1)
 
/* Starting sampling rate. */
#define DEFAULT_MPU_HZ  (100)
 
#define FLASH_SIZE      (512)
#define FLASH_MEM_START ((void*)0x1800)
 
#define PEDO_READ_MS    (1000)
#define TEMP_READ_MS    (500)
#define COMPASS_READ_MS (100)
 
#define q30  1073741824.0f
 
 
static signed char gyro_orientation[9] = { 1, 0, 0,
                                           0, 1, 0,
                                           0, 0, 1};
 
 
int get_tick_count(unsigned long *count)
{
  *count = HAL_GetTick();
  return 0;
}
 
int fputcc(int ch)
{
	printf("%c",ch);
//  HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0xFF);
  return 0;
}
 
uint8_t run_self_test(void)
{
  int result;
  long gyro[3], accel[3];
 
  result = mpu_run_self_test(gyro, accel);
  if (result == 0x07) {                   //返回0x03为MPU6050六轴,只要通过该if语句,就可以实现零偏自动校准
      /* Test passed. We can trust the gyro data here, so let's push it down
       * to the DMP.
       */
      float sens;
      unsigned short accel_sens;
      mpu_get_gyro_sens(&sens);           //读取当前陀螺仪的状态
      gyro[0] = (long)(gyro[0] * sens);
      gyro[1] = (long)(gyro[1] * sens);
      gyro[2] = (long)(gyro[2] * sens);
      dmp_set_gyro_bias(gyro);            //根据读取的状态进行校准
 
      mpu_get_accel_sens(&accel_sens);    //读取当前加速度计的状态
      accel[0] *= accel_sens;
      accel[1] *= accel_sens;
      accel[2] *= accel_sens;
      dmp_set_accel_bias(accel);          //根据读取的状态进行校准
      printf("setting bias succesfully ......\r\n");
      return 0;
    }
    else
      return 1;
}
 
//mpu6050,dmp初始化
//返回值:0,正常
//    其他,失败
uint8_t mpu_dmp_init(void)
{
	uint8_t res=0;
	struct int_param_s int_param;//这个没什么用,就是为了能给他实参调用起来
	if(mpu_init(&int_param)==0)	//初始化MPU6050
	{	 
		res=mpu_set_sensors(INV_XYZ_GYRO|INV_XYZ_ACCEL);//设置所需要的传感器
		if(res)return 1; 
		res=mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL);//设置FIFO
		if(res)return 2; 
		res=mpu_set_sample_rate(DEFAULT_MPU_HZ);	//设置采样率
		if(res)return 3; 
		res=dmp_load_motion_driver_firmware();		//加载dmp固件
		if(res)return 4; 
		res=dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation));//设置陀螺仪方向
		if(res)return 5; 
		res=dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT|DMP_FEATURE_TAP|	//设置dmp功能
		    DMP_FEATURE_ANDROID_ORIENT|DMP_FEATURE_SEND_RAW_ACCEL|DMP_FEATURE_SEND_CAL_GYRO|
		    DMP_FEATURE_GYRO_CAL);
		if(res)return 6; 
		res=dmp_set_fifo_rate(DEFAULT_MPU_HZ);	//设置DMP输出速率(最大不超过200Hz)
		if(res)return 7;   
		res = run_self_test();		//自检
		if(res)return 8;    
		res=mpu_set_dmp_state(1);	//使能DMP
		if(res)return 9;     
	}
	return 0;
}
 
 
//得到dmp处理后的数据(注意,本函数需要比较多堆栈,局部变量有点多)
//pitch:俯仰角 精度:0.1°   范围:-90.0° <---> +90.0°
//roll:横滚角  精度:0.1°   范围:-180.0°<---> +180.0°
//yaw:航向角   精度:0.1°   范围:-180.0°<---> +180.0°
//返回值:0,正常
//    其他,失败
uint8_t mpu_dmp_get_data(float *pitch,float *roll,float *yaw)
{
	float q0=1.0f,q1=0.0f,q2=0.0f,q3=0.0f;
	unsigned long sensor_timestamp;
	short gyro[3], accel[3], sensors;
	unsigned char more;
	long quat[4]; 
	if(dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors,&more))return 1;	 
	/* Gyro and accel data are written to the FIFO by the DMP in chip frame and hardware units.
	 * This behavior is convenient because it keeps the gyro and accel outputs of dmp_read_fifo and mpu_read_fifo consistent.
	**/
	/*if (sensors & INV_XYZ_GYRO )
	send_packet(PACKET_TYPE_GYRO, gyro);
	if (sensors & INV_XYZ_ACCEL)
	send_packet(PACKET_TYPE_ACCEL, accel); */
	/* Unlike gyro and accel, quaternions are written to the FIFO in the body frame, q30.
	 * The orientation is set by the scalar passed to dmp_set_orientation during initialization. 
	**/
	if(sensors&INV_WXYZ_QUAT) 
	{
		q0 = quat[0] / q30;	//q30格式转换为浮点数
		q1 = quat[1] / q30;
		q2 = quat[2] / q30;
		q3 = quat[3] / q30; 
		//计算得到俯仰角/横滚角/航向角
		*pitch = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3;	// pitch
		*roll  = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3;	// roll
		*yaw   = atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3) * 57.3;	//yaw
	}else return 2;
	return 0;
}
 

然后就是把log_stm32.c中的所有fputc(...)替换成fputcc(...)。

MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)

 然后就是,inv_mpu_dmp_motion_driver.c中,有一个__no_operation(); 直接注释掉(我也不知道这个是干啥的)

MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)

然后是修改MPU6050 的地址, 去inv_mpu.c中,直接搜“hw_s hw”就能找到

MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)

 接下来就是修改main函数,调用mpu_dmp_get_data,编译运行了

MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)

 注意还需要定义float pitch;float roll;float yaw;这仨变量。

最后,可能会出现mpu_dmp_init没问题,但mpu_dmp_get_data函数里的dmp_read_fifo函数出现了问题,解决方法见https://blog.csdn.net/liusenyon/article/details/119333722https://blog.csdn.net/liusenyon/article/details/119333722

stm32f4 移植 mpu6050 md6.12步骤_Scarlett29的博客-CSDN博客_emd 6.12z我是按照这篇文章移植的。在此特别感谢两位作者。文章来源地址https://www.toymoban.com/news/detail-404721.html

到了这里,关于MPU6050(读取原数据、移植DMP、stm32f4、HAL库、KEIL5)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 01_STM32软件+硬件I2C读取MPU6050(HAL库)

    目录 1、I2C简介 2、I2C时序单元 2.1 起始条件 2.2 终止条件 2.3 发送一个字节 2.4 接收一个字节 2.5 发送应答 2.6 接收应答 3、I2C完整时序 3.1 指定地址写一个字节 3.2 当前地址读一个字节 3.2 指定地址读一个字节 4、简单软件I2C代码(HAL) 4.1 软件I2C 4.2 软件I2C读MPU6050寄存器 5、ST

    2024年04月17日
    浏览(47)
  • 【STM32】I2C练习,HAL库读取MPU6050角度陀螺仪

    MPU-6000(6050)为全球首例整合性6轴运动处理组件,相较于多组件方案,免除了组合陀螺仪与加速器时间轴之差的问题,减少了大量的封装空间。当连接到三轴磁强计时,MPU-60X0提供完整的9轴运动融合输出到其主I2C或SPI端口(SPI仅在MPU-6000上可用)。 寄存器地址 寄存器内容 0X3B

    2024年02月16日
    浏览(50)
  • STM32MPU6050角度的读取(STM32驱动MPU6050)

    注:文末附STM32驱动MPU6050代码工程链接,需要的读者请自取。 MPU6050是一款集成了三轴陀螺仪和三轴加速度计的传感器芯片,由英国飞利浦半导体(现为恩智浦半导体)公司生产。它通过电子接口(如I2C或SPI)与微控制器进行通信,可用于测量物体的加速度和角速度,广泛应

    2024年02月20日
    浏览(51)
  • GD32F4移植STM32F4

    近期在项目中采用了GD32F407VET6替换原项目中的STM32F407VET6,网传GD的兼容性很好,之前也用F1系统的替换了一下,按照CSND各位大佬的经验一步步改进了代码,测试直接通过,现在也一直在项目中实际应用了,一直没有出问题。 所以这SMT时,嘉立创没有STM的货果断换成了GD,可换时

    2024年02月16日
    浏览(77)
  • 【STM32+cubemx】0027 HAL库开发:MPU6050陀螺仪和加速度计数据的获取和校准

    在制作平衡车或者飞行器时,不可避免地需要知道设备本身的姿态,一般我们使用陀螺仪和加速度计来获取这些信息。 陀螺仪用来测量物体的角度。传统的机械式陀螺的原理,和我们小时候玩的陀螺一样,是利用了高速旋转的物体能保持轴线稳定的特性;机械式陀螺需要的加

    2023年04月08日
    浏览(47)
  • 学习记录之STM32F103C8T6最小系统板驱动MPU6050串口打印数据

    1.使用到的工具介绍 2.MPU6050和整体和简单介绍 3.程序的介绍 1.使用到的工具介绍 硬件方面:STM32F103C8T6最小系统板核心板,MPU6050模块三维角度传感器,经典的CH340烧写和串口作用,和若干个杜邦线。 软件方面:keil5编写程序软件,烧写软件FlyMcu.exe烧写工具,sscom.exe串口调试工

    2023年04月09日
    浏览(58)
  • [GD32F4]基于GD32固件库移植cherryusb[STM32F4]

    [GD32F4]基于GD32固件库移植cherryusb[STM32F4] 使用开发板是淘宝买的不知名开发板,没什么好说的,具体的型号是GD32F450VET6。 使用的cherryusb版本是0.9.0版本。 使用的GD32官方固件库版本是:GD32F4xx_Firmware_Library_V3.0.4 cherryusb最牛的地方在于抛弃掉所有的依赖,只需要知道芯片的usb中断

    2024年02月06日
    浏览(49)
  • [Arduino ESP32] mpu6050使用笔记(含dmp)

    Arduino ESP32 I2C管脚定义:Arduino.h(第209行左右) - pins_arduino.h如图: VCC---3.3v GND--GND 简单使用() 代码位置 代码如下(直接粘的): 上传,然后卡在初始化 原因: 可能是I2Cdev库和MPU6050库不同步(I2Cdev太新了) 解决: 依次检查 (F12) main.cpp的accelgyro.initialize();== MPU6050.cpp的setClockSource

    2024年02月11日
    浏览(46)
  • F1-HAL库快速移植MPU6050

    前段时间在做平衡车,需要移植MPU6050程序。但是在网上找了挺多相关例子的,但是有时候一步步跟着做,结果还是一堆errors 或者读不出数据来,最后自己花了些时间,终于移植好了,前来分享一下。 先分享我的工程,和需要移植的MPU6050的程序 完整工程 + MPU6050移植程序 提取

    2023年04月17日
    浏览(45)
  • STM32F407硬件I2C实现MPU6050通讯(CUBEIDE)

    工程代码 https://download.csdn.net/download/weixin_52849254/87886714 I2C1通道可选择三种不同的通讯协议:I2C、SMBus-Alert-mode、SMBus-two-wire-Interface。 SMBus (System Management Bus,系统管理总线), 为系统和电源管理这样的任务提供了一条控制总线,SMBus与I2C总线之间在时序特性上存在一些差别 修改

    2024年02月09日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包