简介
QMI8658A 是上海矽睿(QST)推出的一款高性能 6 轴惯性测量单元(IMU)芯片,集成了一个 3-轴加速度计和一个 3-轴陀螺仪,用于精确感知和追踪设备的运动与姿态。它支持 I³C、I²C 和 SPI 通信接口,具有 低噪声、高分辨率(16 位)、宽动态范围和低功耗 的特点,并内置了 FIFO 缓存及温度传感器,适合智能穿戴、无人机、机器人、AR/VR、游戏控制器及其他需要运动检测的消费电子与工业应用。
QMI8658A和QMI8658C区别
QMI8658系列有两个版本的芯片,QMI8658A是低噪音版本,QMI8658C是标准版本。
| 芯片 | QMI8658A | QMI8658C |
|---|---|---|
| 传感器类型 | 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 |
| 最大输出速率 | 1000Hz | 1000Hz |
| 内置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/√Hz | 15 mdps/√Hz |
| 加速度计噪声(典型) | 150 µg/√Hz | 200 µ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,这个要根据自己购买的产品确认好)
代码驱动
接线
| STM32F103C8T6 | QMI8658 | OLED |
|---|---|---|
| PB10 | -- | SCL |
| PB11 | -- | SDA |
| PB8 | SCL | -- |
| PB9 | SDA | -- |
| 3.3V | VCC | VCC |
| GND | GND | GND |
代码
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; //数据拼接,通过输出参数返回
}
现象
总结
这里显示的是实时从寄存器获取的数据,如果要更精确的数据,需要对代码进行加算法去获取更准确的数据再开发。 需要代码的可以在评论区留言邮箱哦!