基于STM32F103驱动QMI8658A输出加速度陀螺仪数据

34 阅读14分钟

简介

QMI8658A 是上海矽睿(QST)推出的一款高性能 6 轴惯性测量单元(IMU)芯片,集成了一个 3-轴加速度计和一个 3-轴陀螺仪,用于精确感知和追踪设备的运动与姿态。它支持 I³C、I²C 和 SPI 通信接口,具有 低噪声、高分辨率(16 位)、宽动态范围和低功耗 的特点,并内置了 FIFO 缓存及温度传感器,适合智能穿戴、无人机、机器人、AR/VR、游戏控制器及其他需要运动检测的消费电子与工业应用。 在这里插入图片描述

QMI8658A和QMI8658C区别

QMI8658系列有两个版本的芯片,QMI8658A是低噪音版本,QMI8658C是标准版本。

芯片QMI8658AQMI8658C
传感器类型6轴(3轴加速度+3轴陀螺仪)6轴(3轴加速度+3轴陀螺仪)
陀螺仪量程±16/32/64/128/256/512/1024/2048°/s±16/32/64/128/256/512/1024/2048°/s
加速度量程±2/4/8/16 g±2/4/8/16 g
最大输出速率1000Hz1000Hz
内置DSP有 运动协处理器,用于基本动作检测
和功能(如步数、Tap/动作事件)等
有高级DSP协处理器(AttitudeEngine),
并可进行复杂运动编码和传感器融合
姿态融合算法无内建高级 9D 融合算法支持 XKF3 9D 融合算法(需外接磁力计)
FIFO容量1536字节1536字节
通讯协议I3C, I2C ,
3-wire or 4-wire SPI
I3C, I2C
3-wire or 4-wire SPI
陀螺仪噪声(典型)13 mdps/√Hz15 mdps/√Hz
加速度计噪声(典型)150 µg/√Hz200 µg/√Hz

选型推荐: 如果你需要更低传感器本底噪声(更优的原始陀螺/加速度噪声)并侧重于低功耗动作检测与原始传感器数据处理,QMI8658A 更合适。 如果你需要板级/系统级的姿态输出(内置高性能传感器融合、AttitudeEngine 与指定的方向精度),并想减少主机处理负担,QMI8658C 是更好的选择。

QMI8658A引脚定义

上面表格是芯片支持的通讯协议,这里使用的是模块进行测试,模块的CS引脚已经拉高到3.3V,所以只能用IIC进行测试,没法使用SPI通讯。

引脚引脚定义
VCC供电电压3.3/5V
GND供电地
SCL串行时钟线
SDA串行数据线
XDA接从器件SDA,无从器件不接(或接VDDIO,GND)
XCL接从器件SCL,无从器件不接(或接VDDIO,GND)
ADO模块默认下拉接地
( AD0=0,IIC地址:0x6B;AD0=1,IIC地址:0x6A )
INT接的是芯片的INT1,INT2上拉到3.3V

以下是IIC的接线图,这里模块的布线已经把外围电路处理好了,所以可以直接使用IIC进行通讯。 在这里插入图片描述

QMI8658A寄存器表

这里提供了两个通用寄存器存放芯片ID以及版本ID。

1.地址00H的ID寄存器,读取数据为0x05才是正常。

2.地址01H的Revision ID寄存器,读取数据为0x7c才是正常。(这里版本号要看对应手册,目前官网更新最新的是D版本的手册是0x7c,有一些旧的手册版本号是0x68,这个要根据自己购买的产品确认好) 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

代码驱动

接线

STM32F103C8T6QMI8658OLED
PB10--SCL
PB11--SDA
PB8SCL--
PB9SDA--
3.3VVCCVCC
GNDGNDGND

代码

mian.c

uint8_t ID,R_ID;                        //定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ;			//定义用于存放各个数据的变量
int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	qmi8658_init();		//QMI8658初始化
    
	/*显示ID号*/
	OLED_ShowString(1, 1, "ID:");		    //显示静态字符串
	ID = QMI8658_GetID();				    //获取QMI8658的ID号
	OLED_ShowHexNum(1, 4, ID, 2);		    //OLED显示ID号
	
    /*显示REVISION_ID号*/
	OLED_ShowString(1, 8, "R_ID:");		    //显示静态字符串
	R_ID = QMI8658_GetRID();				//获取QMI8658的ID号
	OLED_ShowHexNum(1, 13, R_ID, 2);		//OLED显示ID号
    Delay_ms(2000);
    
    /*显示加速度和陀螺仪数据*/
    OLED_ShowString(1, 1, "Acc   ");
    OLED_ShowString(1, 8, "Gyro   ");
    
	while (1)
	{
		QMI8658_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);		//获取QMI8658的数据
		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);
	}
}

QMI8658.c

#include "stm32f10x.h"
#include "math.h"
#include "MyI2C.h"
#include "qmi8658.h"
#include "Delay.h"

/* 全局变量缓存区 */
qmi8658_state g_imu;

/**
  * 函    数:QMI8658写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考QMI8658手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void qmi8658_write_one_byte(uint8_t RegAddress, uint8_t Data)
{
	MyI2C_Start();						//I2C起始
	MyI2C_SendByte((QMI8658_ADDRESS <<1) | 0x00);	//发送从机地址,读写位为0,表示即将写入
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_SendByte(RegAddress);			//发送寄存器地址
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_SendByte(Data);				//发送要写入寄存器的数据
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_Stop();						//I2C终止
}

/**
  * 函    数:QMI8658读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考QMI8658手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t qmi8658_read_one_byte(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();						//I2C起始
	MyI2C_SendByte((QMI8658_ADDRESS <<1) | 0x00);	//发送从机地址,读写位为0,表示即将写入
	MyI2C_ReceiveAck();					//接收应答
	MyI2C_SendByte(RegAddress);			//发送寄存器地址
	MyI2C_ReceiveAck();					//接收应答
	
	MyI2C_Start();						//I2C重复起始
	MyI2C_SendByte((QMI8658_ADDRESS <<1) | 0x01);	//发送从机地址,读写位为1,表示即将读取
	MyI2C_ReceiveAck();					//接收应答
	Data = MyI2C_ReceiveByte();			//接收指定寄存器的数据
	MyI2C_SendAck(1);					//发送应答,给从机非应答,终止从机的数据输出
	MyI2C_Stop();						//I2C终止
	
	return Data;
}

/* 写多个连续寄存器(从 reg 开始,写 len 个字节)
 * reg: 寄存器地址
 * data: 要写入的字节
 * data 指向长度至少为 len 的缓冲区
 * 返回 0 = 成功
 */
uint8_t qmi8568_write_nbytes(uint8_t reg, uint8_t *data, uint16_t len)
{
    uint16_t i;
    MyI2C_Start();                      //I2C起始
    MyI2C_SendByte((QMI8658_ADDRESS <<1) | 0x00);    //发送从机地址,读写位为0,表示即将写入
    MyI2C_ReceiveAck();	                //接收应答
    MyI2C_SendByte(reg);			    //发送寄存器地址
    MyI2C_ReceiveAck();	                //接收应答

    for (i = 0; i < len; i++) 
    {
        MyI2C_SendByte(data[i]);
        MyI2C_ReceiveAck();
    }

    MyI2C_Stop();
    return 0;
}

/* 连续读多个寄存器(从 reg 开始,读取 len 字节)
 * out_buf: 指向接收缓冲区,至少 len 长
 */
uint8_t qmi8568_read_nbytes(uint8_t reg, uint8_t *out_buf, uint16_t len)
{
    uint16_t i;

    /* 写寄存器地址 */
    MyI2C_Start();
    MyI2C_SendByte((QMI8658_ADDRESS <<1) | 0x00);
    MyI2C_ReceiveAck();
    MyI2C_SendByte(reg);
    MyI2C_ReceiveAck();
    /* 重复起始,切换到读模式 */
    MyI2C_Start();
    MyI2C_SendByte((QMI8658_ADDRESS <<1) | 0x01);
    MyI2C_ReceiveAck();
    /* 依次读取 len 字节;对最后一个字节发送 NACK */
    for (i = 0; i < len; i++) {
        if (i == (len - 1))
        {   out_buf[i] = MyI2C_ReceiveByte(); /* 最后一个字节 NACK */
            MyI2C_SendAck(1);	
        }
        else
        {   out_buf[i] = MyI2C_ReceiveByte(); /* 发送 ACK,继续接收 */
            MyI2C_SendAck(0);
        }
    }
    MyI2C_Stop();
    return 0;
}

/**
  * 函    数:QMI8658获取ID号
  * 参    数:无
  * 返 回 值:QMI8658的ID号
  */
uint8_t QMI8658_GetID(void)
{
	return qmi8658_read_one_byte(Register_WhoAmI);		//返回WHO_AM_I寄存器的值
}

/**
  * 函    数:QMI8658获取Revision ID号
  * 参    数:无
  * 返 回 值:QMI8658的Revision ID号
  */
uint8_t QMI8658_GetRID(void)
{
	return qmi8658_read_one_byte(Register_Revision);	//返回Revision寄存器的值
}




/**
 * @brief   陀螺仪校准
 * @param   无
 * @retval  检查结果
 * @arg     0: 校准成功
 * @arg     1: 校准失败
 */
uint8_t qmi8658_calibration(void)
{
    uint8_t sta = 0;
    qmi8658_write_one_byte(Register_Ctrl7, 0x00);   /* 关闭陀螺仪、加速度计 */
    qmi8658_write_one_byte(Register_Ctrl9, 0xA2);
    Delay_ms(2000);
    sta = qmi8658_read_one_byte(Register_COD_Status);
    if(sta == 0x00)
    {
        return 0;
    }else return 1;
}

/**
 * @brief   传感器软件复位
 * @param   无
 * @retval  无
 */
void qmi8658_reset(void)
{
    qmi8658_write_one_byte(Register_Reset, 0xB0); /* 复位QMI8658 */
    Delay_ms(150);
}

/**
 * @brief   读取初始状态寄存器
 * @param   无
 * @retval  读取结果
 */
uint8_t qmi8658_read_statusint(void)
{
    return qmi8658_read_one_byte(Register_StatusInt);
}

/**
 * @brief   读取状态寄存器0
 * @param   无
 * @retval  读取结果
 */
uint8_t qmi8658_read_status0(void)
{
    return qmi8658_read_one_byte(Register_Status0);
}

/**
 * @brief   读取状态寄存器1
 * @param   无
 * @retval  读取结果
 */
uint8_t qmi8658_read_status1(void)
{
    return qmi8658_read_one_byte(Register_Status1);
}

/*****************************************************************************************************************/
/**
 * @brief   配置加速度计参数
 * @param   range       :量程
 * @param   odr         :odr输出速率
 * @param   lpfEnable   :低通滤波器 :Qmi8658Lpf_Enable 打开,Qmi8658Lpf_Disable 关闭
 * @param   stEnable    :陀螺仪自检 :Qmi8658St_Enable 自检,Qmi8658St_Disable 不自检
 * @retval  无
 */
void qmi8658_config_acc(enum qmi8658_accrange range, enum qmi8658_accodr odr, enum qmi8658_LpfConfig lpfEnable, enum qmi8658_StConfig stEnable)
{
    unsigned char ctl_dada;

    switch (range)
    {
        case Qmi8658accrange_2g:
            g_imu.ssvt_a = (1 << 14);
            break;
        case Qmi8658accrange_4g:
            g_imu.ssvt_a = (1 << 13);
            break;
        case Qmi8658accrange_8g:
            g_imu.ssvt_a = (1 << 12);
            break;
        case Qmi8658accrange_16g:
            g_imu.ssvt_a = (1 << 11);
            break;
        default:
            range = Qmi8658accrange_8g;
            g_imu.ssvt_a = (1 << 12);
            break;
    }
    if (stEnable == Qmi8658St_Enable)
    {
        ctl_dada = (unsigned char)range | (unsigned char)odr | 0x80;
    } 
    else
    {
        ctl_dada = (unsigned char)range | (unsigned char)odr;
    }
    qmi8658_write_one_byte(Register_Ctrl2, ctl_dada);
    /* set LPF & HPF */
    qmi8568_read_nbytes(Register_Ctrl5, &ctl_dada, 1);
    ctl_dada &= 0xf0;
    if (lpfEnable == Qmi8658Lpf_Enable)
    {
        ctl_dada |= A_LSP_MODE_3;
        ctl_dada |= 0x01;
    }
    else
    {
        ctl_dada &= ~0x01;
    }
    qmi8658_write_one_byte(Register_Ctrl5, ctl_dada);
}

/**
 * @brief   配置陀螺仪参数
 * @param   range       :量程
 * @param   odr         :odr输出速率
 * @param   lpfEnable   :低通滤波器 :Qmi8658Lpf_Enable 打开,Qmi8658Lpf_Disable 关闭
 * @param   stEnable    :陀螺仪自检 :Qmi8658St_Enable 自检,Qmi8658St_Disable 不自检
 * @retval  无
 */
void qmi8658_config_gyro(enum qmi8658_gyrrange range, enum qmi8658_gyrodr odr, enum qmi8658_LpfConfig lpfEnable, enum qmi8658_StConfig stEnable)
{
    /* Set the CTRL3 register to configure dynamic range and ODR */
    unsigned char ctl_dada;

    /* Store the scale factor for use when processing raw data */
    switch (range)
    {
        case Qmi8658gyrrange_16dps:
            g_imu.ssvt_g = 2048;
            break;
        case Qmi8658gyrrange_32dps:
            g_imu.ssvt_g = 1024;
            break;
        case Qmi8658gyrrange_64dps:
            g_imu.ssvt_g = 512;
            break;
        case Qmi8658gyrrange_128dps:
            g_imu.ssvt_g = 256;
            break;
        case Qmi8658gyrrange_256dps:
            g_imu.ssvt_g = 128;
            break;
        case Qmi8658gyrrange_512dps:
            g_imu.ssvt_g = 64;
            break;
        case Qmi8658gyrrange_1024dps:
            g_imu.ssvt_g = 32;
            break;
        case Qmi8658gyrrange_2048dps:
            g_imu.ssvt_g = 16;
            break;
        default:
            range = Qmi8658gyrrange_512dps;
            g_imu.ssvt_g = 64;
            break;
    }

    if (stEnable == Qmi8658St_Enable)
    {
        ctl_dada = (unsigned char)range | (unsigned char)odr | 0x80;
    }   
    else
    {
        ctl_dada = (unsigned char)range | (unsigned char)odr;
    }
    qmi8658_write_one_byte(Register_Ctrl3, ctl_dada);

    /* Conversion from degrees/s to rad/s if necessary */
    /* set LPF & HPF */
    qmi8568_read_nbytes(Register_Ctrl5, &ctl_dada, 1);
    ctl_dada &= 0x0f;
    if (lpfEnable == Qmi8658Lpf_Enable)
    {
        ctl_dada |= G_LSP_MODE_3;
        ctl_dada |= 0x10;
    }
    else
    {
        ctl_dada &= ~0x10;
    }
    qmi8658_write_one_byte(Register_Ctrl5, ctl_dada);
}


/**
 * @brief   使能陀螺仪、加速度计 
 * @param   enableFlags :
 *          QMI8658_DISABLE_ALL  : 都不使能
 *          QMI8658_ACC_ENABLE   : 使能加速度计
 *          QMI8658_GYR_ENABLE   : 使能陀螺仪
 *          QMI8658_ACCGYR_ENABLE: 使能陀螺仪、加速度计
 * @retval  无
 */
void qmi8658_enablesensors(unsigned char enableFlags)
{
#if defined(QMI8658_SYNC_SAMPLE_MODE)
    g_imu.cfg.syncsample = 1;
    qmi8658_enable_ahb_clock(0);
    qmi8658_write_one_byte(Register_Ctrl7, enableFlags | 0x80);
#else
    qmi8658_write_one_byte(Register_Ctrl7, enableFlags);    
#endif
    g_imu.cfg.ensensors = enableFlags & 0x03;

    Delay_ms(2);
}

/**
 * @brief   配置QMI8658陀螺仪和加速度计的量程、输出频率参数等
 * @param   low_power : 0: 正常模式 1:低功耗模式
 * @retval  无
 */
void qmi8658_config_reg(unsigned char low_power)
{
    qmi8658_enablesensors(QMI8658_DISABLE_ALL);
    if (low_power)
    {
        g_imu.cfg.ensensors = QMI8658_ACC_ENABLE;           
        g_imu.cfg.accrange = Qmi8658accrange_8g;
        g_imu.cfg.accodr = Qmi8658accodr_LowPower_21Hz;
        g_imu.cfg.gyrrange = Qmi8658gyrrange_1024dps;
        g_imu.cfg.gyrodr = Qmi8658gyrodr_250Hz;
    }
    else
    {
        g_imu.cfg.ensensors = QMI8658_ACCGYR_ENABLE;    /* 使能陀螺仪、加速度计 */
        g_imu.cfg.accrange = Qmi8658accrange_16g;       /* ±16g */
        g_imu.cfg.accodr = Qmi8658accodr_500Hz;         /* 500Hz采样 */
        g_imu.cfg.gyrrange = Qmi8658gyrrange_128dps;    /* ±128dps */
        g_imu.cfg.gyrodr = Qmi8658gyrodr_500Hz;         /* 500Hz采样 */
    }

    if (g_imu.cfg.ensensors & QMI8658_ACC_ENABLE)
    {
        qmi8658_config_acc(g_imu.cfg.accrange, g_imu.cfg.accodr, Qmi8658Lpf_Enable, Qmi8658St_Enable); /* 设置参数并开启加速度计自检和低通滤波器 */
    }
    if (g_imu.cfg.ensensors & QMI8658_GYR_ENABLE)
    {
        qmi8658_config_gyro(g_imu.cfg.gyrrange, g_imu.cfg.gyrodr, Qmi8658Lpf_Enable, Qmi8658St_Enable);/* 设置参数并开启陀螺仪自检和低通滤波器 */
    }
}

/**
 * @brief   初始化QMI8658
 * @param   无
 * @retval  初始化结果
 * @arg     0: 成功
 * @arg     1: 失败
 */
uint8_t qmi8658_init(void)
{
    MyI2C_Init();									//先初始化底层的I2C
    qmi8658_reset();                                /* 复位传感器 */
    
    qmi8658_write_one_byte(Register_Ctrl1, 0x60);   /* I2C驱动 */
    qmi8658_write_one_byte(Register_Ctrl7, 0x00);   /* 关闭陀螺仪、加速度计 */
    
    qmi8658_config_reg(0);                          /* 配置陀螺仪和加速度计的量程和数据输出速率等参数 */
    qmi8658_enablesensors(g_imu.cfg.ensensors);     /* 使能陀螺仪、加速度计 */

    return 0;
}

/**
  * 函    数:QMI8658获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
void QMI8658_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 = qmi8658_read_one_byte(Register_Ax_H);		//读取加速度计X轴的高8位数据
	DataL = qmi8658_read_one_byte(Register_Ax_L);		//读取加速度计X轴的低8位数据
	*AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = qmi8658_read_one_byte(Register_Ay_H);		//读取加速度计Y轴的高8位数据
	DataL = qmi8658_read_one_byte(Register_Ay_L);		//读取加速度计Y轴的低8位数据
	*AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = qmi8658_read_one_byte(Register_Az_H);		//读取加速度计Z轴的高8位数据
	DataL = qmi8658_read_one_byte(Register_Az_L);		//读取加速度计Z轴的低8位数据
	*AccZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = qmi8658_read_one_byte(Register_Gx_H);		//读取陀螺仪X轴的高8位数据
	DataL = qmi8658_read_one_byte(Register_Gx_L);		//读取陀螺仪X轴的低8位数据
	*GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = qmi8658_read_one_byte(Register_Gy_H);		//读取陀螺仪Y轴的高8位数据
	DataL = qmi8658_read_one_byte(Register_Gy_L);		//读取陀螺仪Y轴的低8位数据
	*GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = qmi8658_read_one_byte(Register_Gz_H);		//读取陀螺仪Z轴的高8位数据
	DataL = qmi8658_read_one_byte(Register_Gz_L);		//读取陀螺仪Z轴的低8位数据
	*GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
}

现象

在这里插入图片描述

总结

这里显示的是实时从寄存器获取的数据,如果要更精确的数据,需要对代码进行加算法去获取更准确的数据再开发。 需要代码的可以在评论区留言邮箱哦!