陀螺仪模块MPU6050自检不过的折腾过程——软件IIC的问题解决
几天前从某多多入手了一块陀螺仪模块MPU6050, 用自己的STM32C8T6最小系统连接测试一下,结果无论如何都连不上,卡在自检初始化通不过。程序以及测试软件是从B站下载的。由于不知道是不是模块本身的问题,联系商家客服,开始说没有资料,我就说不好用,后来终于给了个链接,下载把里面的STM32的例程试用了一下,程序跑的的好好的,都能过但串口输出的x,y,z轴数据始终是0;把模块拔了竟然也能跑起来,不报错,直接忽略。
重新把原来的程序拿来用,至少数据传输过程有个校验,比商家给的靠谱多了。一步步分析到底问题卡在哪了。下面是main.c主程序:
//STM32F103C8T6+MPU6050姿态显示
//MPU6050接线: VCC->3V3
// GND->GND
// SCL->PB10
// SDA->PB11
//USART1
//TX PA9
//RX PA10
int main(void)
{
float pitch,roll,yaw;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
uart_init(115200);
delay_init();
LED_Init();
MPU_Init();
while(mpu_dmp_init())
{
delay_ms(20);
}
while(1)
{
delay_ms(2);
if(mpu_dmp_get_data(&pitch,&roll,&yaw)==0)
{
LED=~LED;
}
SEND_OULA_ANGLE((short)(roll*100),(short)(-pitch*100),(short)(-yaw*100));
}
}
由程序可以看出GPIOB的10、11分别接MUP6050的SCL,USART1接电脑串口,结构比较简单不上图了。
程序运行直接到delay_ms(20);
这里形成循环,那就循着mpu_dmp_init
一路查下去。进入inv_mpu.c下
//mpu6050,dmp初始化
//返回值:0,正常
// 其他,失败
u8 mpu_dmp_init(void)
{
u8 res=0;
MPU_IIC_Init(); //初始化IIC总线
if(mpu_init()==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;
}else return 10;
return 0;
}
在这里程序运行结果正常就返回0,否则返回1-10。由于返回不了0,所以主程序卡在初始化部分。
在if(mpu_init()==0)
前插入断点,可以看到程序能运行到这里,但进不了循环说明mpu_init()
函数返回了非0值,继续向下查mpu_init()
。下面她的是具体内容:
/**
* @brief Initialize hardware.
* Initial configuration:\n
* @return 0 if successful.
*/
int mpu_init(void)
{
unsigned char data[6], rev;
/* Reset device. */
data[0] = BIT_RESET;
if (i2c_write(st.hw->addr, st.reg->pwr_mgmt_1, 1, data))
return -1;
delay_ms(100);
//......省略n行
}
很明显程序进入到返回值-1这行,而程序正常要返回 0;
循着i2c_write()
查到#define i2c_write MPU_Write_Len
继续走MPU_Write_Len()
这个程序在mpu6050.c下,
//IIC连续写
//addr:器件地址
//reg:寄存器地址
//len:写入长度
//buf:数据区
//返回值:0,正常
// 其他,错误代码
u8 MPU_Write_Len(u8 addr,u8 reg,u8 len,u8 *buf)
{
u8 i;
MPU_IIC_Start();
MPU_IIC_Send_Byte((addr<<1)|0);//发送器件地址+写命令
if(MPU_IIC_Wait_Ack()) //等待应答
{
MPU_IIC_Stop();
return 1;
}
MPU_IIC_Send_Byte(reg); //写寄存器地址
MPU_IIC_Wait_Ack(); //等待应答
for(i=0;i<len;i++)
{
MPU_IIC_Send_Byte(buf[i]); //发送数据
if(MPU_IIC_Wait_Ack()) //等待ACK
{
MPU_IIC_Stop();
return 1;
}
}
MPU_IIC_Stop();
return 0;
}
看到程序可以通过发送器件地址+写命令这过程 ,到达了第二个写寄存器这行;这说明我的模块应该是好的,有必要搞下去了。
MPU_IIC_Send_Byte(reg); //写寄存器地址
MPU_IIC_Wait_Ack(); //等待应答
发现虽然这个过程,程序没设应答检测报错,但实际应答检测是失败的;
实际运行会卡在这里
![在这里插入图片描述](https://img-blog.csdnimg.cn/909ae727fa5e4769bc74763729b40409.png
先进入 MPU_IIC_Wait_Ack();
看一下。
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 MPU_IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
MPU_SDA_IN(); //SDA设置为输入
MPU_IIC_SDA=1;MPU_IIC_Delay();
MPU_IIC_SCL=1;MPU_IIC_Delay();
while(MPU_READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
MPU_IIC_Stop();
return 1;
}
}
MPU_IIC_SCL=0;//时钟输出0
return 0;
}
程序出错如下图:
读MPU应答信号失败,没能在发送完8位数据后检测到应答信号。反复查找也没发现问题,线路连接都正常,每次实验结果都一样,就是发送器件地址能过,而后面发送寄存器地址和发送数据就会失败。正在走投无路的时候,突然想到好久不用的逻辑分析仪。马上拿出来接好测量了一下正常和错误时的波形。
正常情况波形
发送器件地址 0xD0(0x68<<1|0),可以看到A1位置IIC的启动信号和A2位置的mpu的应答信号(低电平)。单片机在SCL为为1时读取到了应答信号。
错误情况波形
发送寄存器器件地址 0x6B,可以看到A1位置发往IIC的第8位信号 1,在A2位置竟然有个低电平的应答信号,但在单片机在SCL为为1时读取时却变回了高电平。这个信号可以确定是mup6050的应答信号,因为在这个时候单片机的SDA是输出高电平的。那可不可以改一下程序把SCL读取信号的时间提前,是不是就能读到了呢?于是改了一下程序,如下图把MPU_IIC_Delay();
移到循环前面
再如下图把MPU_IIC_Delay();
去掉。
运行结果波形见下图
让人不可思议的一幕出现了,mup6050的应答信号仍然在读取之前消失了,只持续了很短的时间。既然还是不行程序恢复回去。再分析一下正常读取的一次最后发送的是 0(D0),而错误的这次末尾发送的是 1(6B),那我在发1后补个0会怎么样呢,于是在改一下程序。
先简单在发送数据后直接加上试试,结果真的就通过了,竟然跟着下面发数据的一段也过了,那个原来也是末尾是0的数据(0x80)。既然可以,为了通用,这行移到MPU_IIC_Send_Byte();
里面
再跑一下程序,久违的开发版指示灯亮起来了,连上电脑一看,一切正常,很晚了睡觉。
躺在床上总觉得哪里不对,突然想到我下载程序的帖子后面有个人说的一句话,在一个跟我一样问题的回复跟帖中说,他的问题一样,鼓弄鼓弄结果接口烧了。啊,这不是玩笑,这个IIC的程序初始化里PB10和PB11用的是推挽模式,能好使才怪。由于这个版本的程序同我在网上随便搜的一个完全一样,所以没太怀疑有太大问题,以为可能是器件的性能有差异。
第二天接着弄,下面是原来的IIC初始化程序,
//初始化IIC
void MPU_IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//先使能外设IO PORTB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIO
GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11); //PB10,PB11 输出高
}
读写状态状态转换程序
//IO方向设置
#define MPU_SDA_IN() {GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=8<<12;}
#define MPU_SDA_OUT() {GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=3<<12;}
这个IO方向设置里面也推挽模式,和上拉/下拉模式转换,转换到输入状态时都跟了一句MPU_IIC_SDA=1
完成上拉输入设定,看了一下电路原理图,这两个引脚都接有上拉电阻,所以浮空输入更好一些。再说这种共用总线,输出模式必须改成开漏输出。既然是开漏输出,也没必要更改端口的输入输出模式了,直接在读总线时加一句MPU_IIC_SDA=1
释放总线就可以了。
所以在初始化里把PB10和PB11设置成开漏输出模式,程序中不再改变。
//初始化IIC
void MPU_IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//先使能外设IO PORTB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11; // 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIO
GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11); //PB10,PB11 输出高
}
去掉程序中的所有的MPU_SDA_OUT();
,MPU_SDA_IN();
用MPU_IIC_SDA=1;
替换就可以了,最后发个视频嘚瑟一下。
MPU6050