Document

QMI8658传感器模块IIC(I2c)通讯及基本数据读取(加速度角度转换

目录

在使用这个模块的时候官方例程会出现三个需要自己编写的通讯函数(I2C读写) 这三个函数如下

unsigned char qmi8658_write_reg(unsigned char reg, unsigned char value)
{
	unsigned char ret=0;
	unsigned int retry = 0;

	while((!ret) && (retry++ < 5))
	{
#if defined(QMI8658_USE_SPI)
		ret = qst_imu_spi_write(reg,value);
#elif defined(QST_USE_SW_I2C)
		ret = qst_sw_writereg(g_imu.slave<<1, reg, value);//这个
#else
		ret = mx_i2c1_write(g_imu.slave<<1, reg, value);
#endif
	}
	return ret;
}

unsigned char qmi8658_write_regs(unsigned char reg, unsigned char *value, unsigned char len)
{
	int i, ret;

	for(i=0; i<len; i++)
	{
#if defined(QMI8658_USE_SPI)
		ret = qst_imu_spi_write_bytes(reg, value, len);
#elif defined(QST_USE_SW_I2C)
		ret = qst_sw_writeregs(g_imu.slave<<1, reg, value, len);//这个
#else
		ret = I2C_BufferRead(g_imu.slave<<1, reg, value, len);
#endif
	}

	return ret;
}

unsigned char qmi8658_read_reg(unsigned char reg, unsigned char* buf, unsigned short len)
{
	unsigned char ret=0;
	unsigned int retry = 0;

	while((!ret) && (retry++ < 5))
	{
#if defined(QMI8658_USE_SPI)
		ret = qst_8658_spi_read(reg, buf, len);
#elif defined(QST_USE_SW_I2C)
		ret = qst_sw_readreg(g_imu.slave<<1, reg, buf, len);//这个
#else
		ret = mx_i2c1_read(g_imu.slave<<1, reg, buf, len);
#endif
	}
	return ret;
}

Step1.重写三个函数

	1.ret = qst_sw_writereg(g_imu.slave<<1, reg, value);//写字节
    2.ret = qst_sw_writeregs(g_imu.slave<<1, reg, value, len);//写多个字节
    3.ret = qst_sw_readreg(g_imu.slave<<1, reg, buf, len);//读字节

1.在重写这三个函数前需要先了解IIC通信的基本原理 下面放上正点原子官方的软件iic的代码 可以对照自己写的iic代码进行适配

static void iic_delay(void)
{
    delay_us(2);    /* 2us的延时, 读写速度在250Khz以内 */
}

/**
 * @brief       产生IIC起始信号
 * @param       无
 * @retval      无
 */
void iic_start(void)
{
    IIC_SDA(1);
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(0);     /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */
    iic_delay();
    IIC_SCL(0);     /* 钳住I2C总线,准备发送或接收数据 */
    iic_delay();
}

/**
 * @brief       产生IIC停止信号
 * @param       无
 * @retval      无
 */
void iic_stop(void)
{
    IIC_SDA(0);     /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
    iic_delay();
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(1);     /* 发送I2C总线结束信号 */
    iic_delay();
}

/**
 * @brief       等待应答信号到来
 * @param       无
 * @retval      1,接收应答失败
 *              0,接收应答成功
 */
uint8_t iic_wait_ack(void)
{
    uint8_t waittime = 0;
    uint8_t rack = 0;

    IIC_SDA(1);     /* 主机释放SDA线(此时外部器件可以拉低SDA线) */
    iic_delay();
    IIC_SCL(1);     /* SCL=1, 此时从机可以返回ACK */
    iic_delay();

    while (IIC_READ_SDA)    /* 等待应答 */
    {
        waittime++;

        if (waittime > 250)
        {
            iic_stop();
            rack = 1;
            break;
        }
    }

    IIC_SCL(0);     /* SCL=0, 结束ACK检查 */
    iic_delay();
    return rack;
}

/**
 * @brief       产生ACK应答
 * @param       无
 * @retval      无
 */
void iic_ack(void)
{
    IIC_SDA(0);     /* SCL 0 -> 1 时 SDA = 0,表示应答 */
    iic_delay();
    IIC_SCL(1);     /* 产生一个时钟 */
    iic_delay();
    IIC_SCL(0);
    iic_delay();
    IIC_SDA(1);     /* 主机释放SDA线 */
    iic_delay();
}

/**
 * @brief       不产生ACK应答
 * @param       无
 * @retval      无
 */
void iic_nack(void)
{
    IIC_SDA(1);     /* SCL 0 -> 1  时 SDA = 1,表示不应答 */
    iic_delay();
    IIC_SCL(1);     /* 产生一个时钟 */
    iic_delay();
    IIC_SCL(0);
    iic_delay();
}

/**
 * @brief       IIC发送一个字节
 * @param       data: 要发送的数据
 * @retval      无
 */
void iic_send_byte(uint8_t data)
{
    uint8_t t;
    
    for (t = 0; t < 8; t++)
    {
        IIC_SDA((data & 0x80) >> 7);    /* 高位先发送 */
        iic_delay();
        IIC_SCL(1);
        iic_delay();
        IIC_SCL(0);
        data <<= 1;     /* 左移1位,用于下一次发送 */
    }
    IIC_SDA(1);         /* 发送完成, 主机释放SDA线 */
}

/**
 * @brief       IIC读取一个字节
 * @param       ack:  ack=1时,发送ack; ack=0时,发送nack
 * @retval      接收到的数据
 */
uint8_t iic_read_byte(uint8_t ack)
{
    uint8_t i, receive = 0;
	
    for (i = 0; i < 8; i++ )    /* 接收1个字节数据 */
    {
        receive <<= 1;  /* 高位先输出,所以先收到的数据位要左移 */
        IIC_SCL(1);
        iic_delay();

        if (IIC_READ_SDA)
        {
            receive++;
        }
        
        IIC_SCL(0);
        iic_delay();
    }

    if (!ack)
    {
        iic_nack();     /* 发送nACK */
    }
    else
    {
        iic_ack();      /* 发送ACK */
    }

    return receive;
}

由于stm32的硬件iic经常会出现问题 所以一般就是用模拟的

2.根据iic读写协议编写上述三个函数 slave是设备地址 reg是寄存器地址 value是需要写入\读取的数据 除了这三个函数需要重写 其他部分可根据需要进行重写 本文将在下面部分提到部分

unsigned char qst_sw_writereg(uint8_t slave, uint8_t reg, uint8_t value )
{

	    iic_start();    /* 发送起始信号 */


    iic_send_byte(slave);   
    
    
    iic_wait_ack();             /* 每次发送完一个字节,都要等待ACK */
    iic_send_byte(reg);  /* 发送低位地址 */
    iic_wait_ack();             /* 等待ACK, 此时地址发送完成了 */
    
    /* 因为写数据的时候,不需要进入接收模式了,所以这里不用重新发送起始信号了 */
    iic_send_byte(value);        /* 发送1字节 */
    iic_wait_ack();             /* 等待ACK */
    iic_stop();                 /* 产生一个停止条件 */
		//printf("1");
		return 1;

}

unsigned char qst_sw_readreg(uint8_t slave,uint8_t reg,uint8_t* value,uint8_t len )
{uint16_t i;
	
		iic_start();  
		iic_send_byte(slave);
	if(iic_wait_ack())
	{
		return 0;
	}
	iic_send_byte(reg);
	if(iic_wait_ack())
	{
		return 0;
	}

	iic_start();
	iic_send_byte(slave+1);
	if(iic_wait_ack())
	{
		return 0;
	}

	for(i=0;i<(len-1);i++){
		*value=iic_read_byte(1);
		value++;
	}
	*value=iic_read_byte(0);
		iic_stop();//产生一个停止条件	    
		return 1;



}

unsigned char qst_sw_writeregs(uint8_t slave,uint8_t reg,uint8_t* value,uint8_t len)
{uint8_t i;
  iic_start();    /* 发送起始信号 */


    iic_send_byte(slave);   
    
    
    iic_wait_ack();             /* 每次发送完一个字节,都要等待ACK */
    iic_send_byte(reg);        /* 发送低位地址 */
    iic_wait_ack();             /* 等待ACK, 此时地址发送完成了 */
    
    /* 因为写数据的时候,不需要进入接收模式了,所以这里不用重新发送起始信号了 */
    	for(i=0; i<len; i++)
	{
		iic_send_byte(value[i]);	
		if(iic_wait_ack())
		{
			return 0;
		}
	}
    iic_wait_ack();             /* 等待ACK */
    iic_stop();                 /* 产生一个停止条件 */
		//printf("1");
		return 1;
}

Step3.官方例程部分修改(仅个人意见)

1.读取数据第一帧和第二帧会出现报错 原因官方代码给的判错标准是判断上下两帧数据是否一致 这里我直接把判断删除

void qmi8658_read_xyz(uint8_t id,float acc[3], float gyro[3])
{
	unsigned char	status;
	unsigned char data_ready = 0;

	qmi8658_read_reg(Qmi8658Register_Status0, &status, 1);
	if(status&0x03)
	{
		data_ready = 1;
		if(data_ready)
	{
		qmi8658_read_sensor_data(acc, gyro);
		qmi8658_axis_convert(acc, gyro, 0);

	}
	}

	
//	else
//	{
////		acc[0] = g_imu.imu[0];
////		acc[1] = g_imu.imu[1];
////		acc[2] = g_imu.imu[2];
////		gyro[0] = g_imu.imu[3];
////		gyro[1] = g_imu.imu[4];
////		gyro[2] = g_imu.imu[5];
////		qmi8658_log("data ready fail!\n");
//	}
}

2.如果多设备同时连接时 请求数据 官方datasheet写的是如果将AD0引脚置1,则设备地址变为6A,否则6B(没测试两台以上的设备 如果为两台以上设备 可以根据id判断?) 以下给出最简单的方法 因为例程在初始化的时候会自动检测总线上的所有设备 并进行统一的初始化 这样可以直接根据设备地址进行访问 不需要进行额外的步骤 如果有2+台设备 则需要修改 其他的需要各位自行探索!_

unsigned char qmi8658_get_id(void)
{
	unsigned char qmi8658_chip_id = 0x00;
	unsigned char qmi8658_revision_id = 0x00;
	unsigned char qmi8658_slave[2] = {QMI8658_SLAVE_ADDR_L, QMI8658_SLAVE_ADDR_H};
	int retry = 0;
	unsigned char iCount = 0;
	unsigned char firmware_id[3];
	unsigned char uuid[6];
	unsigned int uuid_low, uuid_high;

	while(iCount<2)//这里这里这里这里
	{
		g_imu.slave = qmi8658_slave[iCount];
		retry = 0;
		while((qmi8658_chip_id != 0x05)&&(retry++ < 5))
		{
			qmi8658_read_reg(Qmi8658Register_WhoAmI, &qmi8658_chip_id, 1);
			qmi8658_log("Qmi8658Register_WhoAmI = 0x%x\n", qmi8658_chip_id);
		}
		if(qmi8658_chip_id == 0x05)
		{
			qmi8658_on_demand_cali();

			g_imu.cfg.ctrl8_value = 0xc0;
			//QMI8658_INT1_ENABLE, QMI8658_INT2_ENABLE
			qmi8658_write_reg(Qmi8658Register_Ctrl1, 0x60|QMI8658_INT2_ENABLE|QMI8658_INT1_ENABLE);
			qmi8658_read_reg(Qmi8658Register_Revision, &qmi8658_revision_id, 1);			
			qmi8658_read_reg(Qmi8658Register_firmware_id, firmware_id, 3);
			qmi8658_read_reg(Qmi8658Register_uuid, uuid, 6);
			qmi8658_write_reg(Qmi8658Register_Ctrl7, 0x00);
			qmi8658_write_reg(Qmi8658Register_Ctrl8, g_imu.cfg.ctrl8_value);
			uuid_low = (unsigned int)((unsigned int)(uuid[2]<<16)|(unsigned int)(uuid[1]<<8)|(uuid[0]));
			uuid_high = (unsigned int)((unsigned int)(uuid[5]<<16)|(unsigned int)(uuid[4]<<8)|(uuid[3]));
			qmi8658_log("qmi8658_init slave=0x%x Revision=0x%x\n", g_imu.slave, qmi8658_revision_id);
			qmi8658_log("Firmware ID[0x%x 0x%x 0x%x]\n", firmware_id[2], firmware_id[1],firmware_id[0]);
			qmi8658_log("UUID[0x%x %x]\n", uuid_high ,uuid_low);

			//break;
			qmi8658_config_reg(0);
			qmi8658_enableSensors(g_imu.cfg.enSensors);
			qmi8658_dump_reg();
			printf("0x%X INIT SUCCESS!\r\n",g_imu.slave);
		}
		iCount++;
	}

	return qmi8658_chip_id;
}

数据获取的修改 比较简单 需要修改的代码量也比较少 其他读取函数也可以依照这个修改

void qmi8658_read_xyz(uint8_t id,float acc[3], float gyro[3])
{
	unsigned char	status;
	unsigned char data_ready = 0;
	//这里
    if(id  == 0)
	{
	g_imu.slave = 0x6A;
	}
	else{
	g_imu.slave = 0x6B;
	}

	qmi8658_read_reg(Qmi8658Register_Status0, &status, 1);
	if(status&0x03)
	{
		data_ready = 1;
		if(data_ready)
	{
		qmi8658_read_sensor_data(acc, gyro);
		qmi8658_axis_convert(acc, gyro, 0);

	}
	}

	

}

Step4.一些没用的tip

1.卡尔曼滤波 直接上代码 参考的其他大神的

float kalmanFilter_A(float inData)
{
  static float prevData=0;
  //其中p的初值可以随便取,但是不能为0(为0的话卡尔曼滤波器就认为已经是最优滤波器了)
  static float p=0.01, q=P_Q, r=M_R, kGain=0;
    p = p+q;
    kGain = p/(p+r);
 
    inData = prevData+(kGain*(inData-prevData));
    p = (1-kGain)*p;
 
    prevData = inData;
 
    return inData;
}


2.如何用?

#include <math.h> 
float accx,accy,accz; float gyrox,gyroy,gyroz;
float acc[3],gyro[3],angle[3];
for(uint8_t times = 15;times>0;times--)
	{
		qmi8658_read_xyz(id,acc,  gyro);
		

		if(times >5)
		{
				accx = kalmanFilter_AccX(acc[0]);
				accy =kalmanFilter_AccY(acc[1]);
				accz =kalmanFilter_AccZ(acc[2]);
				
				gyrox = kalmanFilter_GyroX(gyro[0]);
				gyroy = kalmanFilter_GyroY(gyro[1]);
				gyroz = kalmanFilter_GyroZ(gyro[2]);
			
				
		}

	}
printf("%d---X轴 : 加速度%.02F,陀螺仪%.02F  \r\n",id,accx,gyrox);
printf("%d---Y轴 : 加速度%.02F,陀螺仪%.02F  \r\n",id,accy,gyroy);
printf("%d---Z轴 : 加速度%.02F,陀螺仪%.02F  \r\n",id,accz,gyroz);

2.加速度计算角度 仅仅是角度啊 需要姿态的自己看看大佬们写的欧拉角


//3.计算角度
	angle[0] = atan( accx/(sqrt(accy*accy + accz*accz ) ) )*180/M_PI;  
	angle[1] = atan( accy/(sqrt(accx*accx + accz*accz ) ) )*180/M_PI;
	angle[2] = atan( accz/(sqrt(accy*accy + accx*accx ) ) )*180/M_PI;	




posted @ 2023-11-24 16:39  qdyaodao  阅读(4487)  评论(1)    收藏  举报
Document