ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H7R7开发指南 V1.1 》第四十五章 QMI8658A六轴传感器实验

《STM32H7R7开发指南 V1.1 》第四十五章 QMI8658A六轴传感器实验

原创 嵌入式/内核开发 作者:正点原子 时间:2025-09-17 10:26:28 0 删除 编辑

第四十五章 QMI8658A六轴传感器实验


       本章,我们将介绍一款高性价比六轴(三轴角速度(陀螺仪)+三轴加速度)传感器:QMI8658A,该传感器可广泛用于四轴、平衡车和空中鼠标等设计,性价比极高,具有非常广泛的应用范围。正点原子STM32H7R7开发板自带了QMI8658A传感器。本章我们将使用STM32H7R7来驱动QMI8658A,读取其原始数据,并利用MPL库实现姿态解算,结合匿名四轴上位机软件和LCD显示,教大家如何使用这款功能强大的六轴传感器。

       本章分为如下几个小节:

       45.1 QMI8658A介绍

       45.2 硬件设计

       45.3 软件设计

       45.4 下载验证


        45.1 QMI8658A介绍


       45.1.1 QMI8658A简介

       QMI8658A是一款针对大众市场,高性价比的六轴传感器,具有:功耗低、噪声低、封装小的特点。

       QMI8658A内部集成有3轴陀螺仪和3轴加速度计;可以通过I3C、I2C和3线或4线SPI的接口和单片机进行数据交互,传输速率可达400kHZ/s。陀螺仪的角速度测量范围最高达±2048°/s,具有良好的动态响应特性。加速度计的测量范围最大为±16g(g为重力加速度),静态测量精度高。广泛应用于智能手机、游戏控制器、遥控器和汽车安全系统等场景。

       QMI8658A的特点包括:

       ①  陀螺仪13 mdps/Hz的低噪声,低延迟,宽带宽

       ② 150μg/Hz的加速度计噪声

       ③ 自带一个温度数字传感器

       ④ 可编程数字滤波器

       ⑤ 集成计步器,轻拍,任意运动,移动,显著运动检测

       ⑥ 自带1536字节FIFO可用于缓冲传感器数据,降低系统功耗

       ⑦ 超小封装尺寸:2.5×3.0×0.86mm(LGA)

       QMI8658A传感器的检测轴如图45.1.1.1所示:


图45.1.1.1QMI8658A检测轴及其方向


       QMI8658A的内部框图如图45.1.1.2所示:


图45.1.1.2 QMI8658A框图


       其中,SCL和SDA可以连接MCU的I2C、I3C和SPI接口,MCU通过这些接口来控制QMI8658A,我们例程中用的就是IIC接口。另外,SA0是从IIC接口(接MCU)的地址控制引脚,该引脚控制IIC地址的最低位。如果接GND,则QMI8658A的IIC地址是:0X6B,如果接VDD,则是0X6A,注意:这里的地址是不包含数据传输的最低位的(最低位用来表示读写)!! 

       在STM32开发板上,SA0是接VCC的,所以QMI8658A的IIC地址是0X6A(不含最低位),IIC通信的时序我们在之前已经介绍过(第三十六章,IIC实验),这里就不再细说了。


       45.1.2 QMI8658A寄存器

       在这里简单介绍一下本实验用到的QMI8658A比较重要的寄存器。


       l 陀螺仪配置寄存器(Gyroscope Settings)

       该寄存器地址为:0X04,各位描述如表45.1.2.1所示:


表45.1.2.1 陀螺仪配置寄存器


       该寄存器我们关心gFS[2:0]和gODR[3:0]这些位,gFS[2:0]用于设置陀螺仪的满量程范围:000,±16dps;001,±32dps;010,±64dps;011,±128dps;100,±256dps;101,±512dps;110,±1024dps;111,±2048dps;我们一般设置为011,即±128dps,因为陀螺仪的ADC为16位分辨率,所以得到灵敏度为:65536/256=256LSB(dps)。

       gODR[3:0]位用于设置陀螺仪输出数据速率,我们例程中设置为0100,即约500Hz的速率。


       l 加速度传感器配置寄存器(Accelerometer Settings)

       该寄存器地址为:0X03,各位描述如下表所示:


表45.1.2.2 加速度传感器配置寄存器


       该寄存器我们关心aFS[2:0]和aODR[3:0]这些位,aFS[2:0]用于设置加速度计的满量程范围:000,±2g;001,±4g;010,±8g;011,±16g;我们一般设置为011,即±16g,因为加速度传感器的ADC也是16位分辨率,所以得到灵敏度为:65536/32=2048LSB/g。

       aODR[3:0]位用于设置加速度计输出数据速率,我们例程中设置为0100,即约500Hz的速率。


       l 传感器数据处理寄存器(Sensor Data Processing Settings)

       该寄存器的地址为:0X06,各位描述如表45.1.2.3所示:


表45.1.2.3 传感器数据处理寄存器


       这里,我们主要关心数字低通滤波器(LPF_MODE)的设置位,即:gLPF_MODE[1:0]和aLPF_MODE[1:0],陀螺仪和加速度传感器分别根据这两个位的配置进行过滤。首先看陀螺仪gLPF_MODE[1:0]不同配置对应的过滤情况如表45.1.2.4所示:


表45.1.2.4 gLPF_MODE配置表


       再看加速度aLPF_MODE[1:0]对应的过滤情况如表45.1.2.5所示:


表45.1.2.5 aLPF_MODE配置表


       在例程中,我们将加速度传感器和陀螺仪的低通过滤器模式选择位:gLPF_MODE[1:0]和aLPF_MODE[1:0]都设置为11,即ODR的13.37%。


       l 温度传感器数据输出寄存器(Temp Sensor Output)

       该寄存器地址为:0X33-0X34,各位描述如表45.1.2.6所示:


表45.1.2.6 温度传感器数据输出寄存器


       这个寄存器可以读取温度传感器的值,可以通过读取0X33(高8位)和0X34(低8位)寄存器得到,温度换算公式为:

T= TEMP_H + (TEMP_L/256)

       其中,T为计算得到的温度值,单位为℃,TEMP_H(L)为从0X33和0X34读到的温度传感器值。

       接下来,加速度传感器数据输出寄存器,也有6个,地址为:0X35~0X3A,通过读取这6个寄存器,就可以读到加速度传感器x/y/z轴的值,比如读x轴的数据,可以通过读取0X35(高8位)和0X36(低8位)寄存器得到,其他轴以此类推。

       同样,我们看看陀螺仪数据输出寄存器,总共由6个寄存器组成,地址为:0X3B~0X40,通过读取0X3B(高8位)和0X3C(低8位)寄存器得到,其他轴以此类推。

       关于QMI8658A的基础介绍,我们就介绍到这。QMI8658A的详细资料和相关寄存器介绍,请参考光盘:7,硬件资料→2,芯片资料→C3021082_姿态传感器-陀螺仪_QMI8658A_规格书_WJ411228.pdf这个文档,供大家参考学习。


        45.2 硬件设计


       1. 例程功能

       本实验采用STM32H7R7的2个普通IO连接QMI8658A(IIC),本章实验功能简介:程序先初始化QMI8658A,然后利用imu姿态解算的计算,最后,在死循环里面不停读取:温度传感器、加速度传感器、陀螺仪、姿态解算后的欧拉角等数据,通过串口上报给上位机(温度不上报),利用上位机软件(ANO_TC匿名科创地面站v4.exe),可以实时显示QMI8658A的传感器状态曲线,并显示3D姿态,可以通过KEY0按键开启/关闭数据上传功能。同时,在LCD模块上面显示温度和欧拉角等信息。DS0来指示程序正在运行。


       2. 硬件资源


       1)LED灯  

              LED0 – PD14


       2 ) 独立按键  

       KEY0 – PE9


       3)串口1 (波特率:500000,PB14/PB15连接在板载USB转串口芯片CH340上面)


       4)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)


       5)QMI8658A传感器


       3. 原理图

       QMI8658A与STM32的连接关系,如下图所示:


图45.2.1 QMI8658A与STM32连接原理图


       从上图可以看出,QMI8658A的SCL和SDA与STM32H7R7开发板的PF1和PF0连接。图中,SA0接的VCC,所以QMI8658A的器件地址是:0X6A。


        45.3 程序设计 


       45.3.1 程序解析


       1. QMI8658A驱动代码

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。QMI8658A驱动源码包括两个文件:qmi8658.c和qmi8658.h。

       qmi8658.h的文件是一些函数的定义,我们直接看qmi8658.c文件。

       下面来看一下qmi8658_read_nbytes函数,实现QMI8658A芯片指定地址读取N字节数据,代码如下:

/**
 * @brief   从QMI8568读取N字节数据
 * @param   reg:        寄存器地址
 * @param   date:       数据存储buf
 * @param   len:        数据长度
 * @retval  读出结果
 * @retval  0, 操作成功
 *          其他, 操作失败
 */
int qmi8568_read_nbytes(uint8_t reg, uint8_t *date, uint8_t len)
{
    uint8_t i;
    
    iic_start();
    iic_send_byte((QMI8658_ADDR << 1) | 0x00);
    if (iic_wait_ack() != 0)
    {
        iic_stop();
        return 1;
    }
    iic_send_byte(reg);
    if (iic_wait_ack() != 0)
    {
        iic_stop();
        return 1;
    }
    
    iic_start();
    iic_send_byte((QMI8658_ADDR << 1) | 0x01);
    if (iic_wait_ack() != 0)
    {
        iic_stop();
        return 1;
    }
    for (i = 0; i < len; i++)
    {
        date[i] = iic_read_byte((i == (len - 1)) ? 0 : 1);
    }
    iic_stop();
    
    return 0;
}

       该函数首先调用iic_start函数产生起始信号,然后调用iic_send_byte函数发送第1个字节数据设备写地址,等待QMI8658A设备返回应答信号;收到应答信号后,继续发送第2个1字节数据内存地址reg;等待接收应答后,发送第3个1字节数据设备读地址;最后QMI8658A最 先收到的是状态字节(一个字节),随后是读取的数据(数据都是两个字节),返回应答信号,主机调用iic_stop函数产生停止信号终止数据传输,等待QMI8658A读取完毕。

       接下来看一下qmi8658_write_nbytes函数,实现从QMI8658A芯片指定地址写入数据,其定义如下:

/**
 * @brief   QMI8568写入N字节数据
 * @param   reg:        寄存器地址
 * @param   data:       写入数据
 * @param   len:        数据长度
 * @retval  写入结果
 * @arg     0:          成功
 * @arg     1:          失败
 */
uint8_t qmi8658_write_nbytes(uint8_t reg, uint8_t* data, uint8_t len)
{
    uint8_t i;
    
    iic_start();
    iic_send_byte((QMI8658_ADDR << 1) | 0x00);
    if (iic_wait_ack() != 0)
    {
        iic_stop();
        return 1;
    }
    iic_send_byte(reg);
    if (iic_wait_ack() != 0)
    {
        iic_stop();
        return 1;
    }
    for (i = 0; i < len; i++)
    {
        iic_send_byte(data[i]);
        if (iic_wait_ack() != 0)
        {
            iic_stop();
            return 1;
        }
    }
    iic_stop();
    
    return 0;
}

       该函数首先调用iic_start函数产生起始信号,然后调用iic_send_byte函数发送第1个字节数据设备写地址,等待QMI8658A设备返回应答信号;收到应答信号后,继续发送第2个1字节写入数据内存地址reg;等待接收应答后,发送第3个1字节数据设备写地址;QMI8658A设备写完数据,返回应答信号,主机调用iic_stop函数产生停止信号终止数据传输,等待QMI8658A写入完毕。

       最后,我们介绍一下读取数据函数,代码如下:

/**
 * @brief   获取传感器温度
 * @param   无
 * @retval  温度值,单位为℃
 */
float qmi8658_get_temperature(void)
{
    short temp = 0;
    float temp_f = 0;
    uint8_t buf[2];
 
    qmi8568_read_nbytes(Register_Tempearture_L, buf, 2);    /* 读取温度数据 */    
    temp = ((short)buf[1] << 8) | buf[0];
    temp_f = (float)temp / 256.0f;
    return temp_f;
}
 
 
/**
 * @brief   判断数据更新后,在读取补偿后QMI8658陀螺仪和加速度的数据(推荐使用)
 * @param   acc  : 加速度计 X,Y,Z缓存区;
 * @param   gyro : 陀螺仪 X,Y,Z缓存区;
 * @retval  无
 */
void qmi8658_read_xyz(float *acc, float *gyro)
{
    unsigned char status = 0;
    unsigned char data_ready = 0;
    int retry = 0;
 
    while (retry++ < 3)
    {
#if defined(QMI8658_SYNC_SAMPLE_MODE)
        qmi8568_read_nbytes(Register_StatusInt, &status, 1);
 
        if (status & 0x01)
        {
            delay_us(12);       /* delay 12us <=500Hz */
        }
        if ((status & 0x01) || (status & 0x03))
        {
            data_ready = 1;
            break;
        }
#else
        /* 检查加速度计和陀螺仪数据是否可用 */
        qmi8568_read_nbytes(Register_Status0, &status, 1);
        if (status & 0x03)
        {
            data_ready = 1;
            break;
        }
#endif
    }
    if (data_ready)
    {
        qmi8658_read_sensor_data(acc, gyro);
 
        g_imu.imu[0] = acc[0];
        g_imu.imu[1] = acc[1];
        g_imu.imu[2] = acc[2];
        g_imu.imu[3] = gyro[0];
        g_imu.imu[4] = gyro[1];
        g_imu.imu[5] = gyro[2];
    }
    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];
//      printf("数据还未准备好\r\n");    /* 调试使用 */
    }
}

       qmi8658_get_temperature函数:读取温度数据。

       qmi8658_read_sensor_data函数:读取补偿后加速度计和陀螺仪的x,y,z轴原始数据。

       下面看一下QMI8658A的初始化函数,其定义如下:

/**
 * @brief   初始化QMI8658
 * @param   无
 * @retval  初始化结果
 * @arg     0: 成功
 * @arg     1: 失败
 */
uint8_t qmi8658_init(void)
{
    iic_init();
 
    qmi8658_reset();                                /* 复位传感器 */
    
    if(qmi8658_check_whoami())                      /* 检查设备ID是否正确 */
    {
        return 1;
    }
    if(qmi8658_calibration())
    {
        return 1;
    }
    printf("校准成功!!\r\n");
 
    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;
}

       在初始化函数中,我们主要对该模块的加速度、陀螺仪,还有温度传感器的使能和设置。


       2. main.c代码

/**
 * @brief    通过串口1上报结算后的姿态数据给电脑
 * @param    roll  : 横滚角.单位0.1度。 -9000 -> 9000 对应 -90.00  ->  90.00度
 * @param    pitch : 俯仰角.单位 0.1度。-18000 -> 18000 对应 -180.00 -> 180.00 度
 * @param    yaw   : 航向角.单位为0.1度 -18000 -> 18000  对应 -180.00 -> 180.00度
 * @param    prs   : 气压计高度,单位:cm
 * @param    fly_mode : 飞行模式
 * @param    armed    : 锁定状态
 * @retval   无
 */
void usart1_report_imu(short roll, short pitch, short yaw, int prs, uint8_t fly_mode, uint8_t armed)
{
    uint8_t tbuf[12];
  
    tbuf[0] = (roll >> 8) & 0XFF;
    tbuf[1] = roll & 0XFF;
    tbuf[2] = (pitch >> 8) & 0XFF;
    tbuf[3] = pitch & 0XFF;
    tbuf[4] = (yaw >> 8) & 0XFF;
    tbuf[5] = yaw & 0XFF;
    tbuf[6] = (prs >> 24) & 0XFF;
    tbuf[7] = (prs >> 16) & 0XFF;
    tbuf[8] = (prs >> 8) & 0XFF;
    tbuf[9] = prs & 0XFF;
    tbuf[10] = fly_mode;
    tbuf[11] = armed;
    usart1_niming_report(0X01, tbuf, 12);   /* 状态帧,0X01 */
}
 
int main(void)
{
    uint8_t t = 0;
    uint8_t key;
    uint8_t report = 0;
    float temperature;
    float gyro[3];
    float accel[3];
    float euler_angle[3] = {0, 0, 0};
    
    sys_mpu_config();                   /* 配置MPU */
    sys_cache_enable();                 /* 使能Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
    delay_init(600);                    /* 初始化延时 */
    usart_init(500000);                 /* 初始化串口 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */
    hyperram_init();                    /* 初始化HyperRAM */
    lcd_init();                         /* 初始化LCD */
    
    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "QMI8658A TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY0:UPLOAD ON/OFF", RED);
    
    while (qmi8658_init() != 0)        /* 初始化QMI8658A */
    {
        lcd_show_string(30, 130, 200, 16, 16, "QMI8658A Error!", RED);
        delay_ms(500);
        lcd_show_string(30, 130, 200, 16, 16, "Please Check!  ", RED);
        delay_ms(500);
        LED0_TOGGLE();
    }
    
    lcd_show_string(30, 130, 200, 16, 16, "QMI8658A Ready!", RED);
    lcd_show_string(30, 150, 200, 16, 16, "UPLOAD OFF", BLUE);
    lcd_show_string(30, 170, 200, 16, 16, "Temp :    . C", BLUE);
    lcd_show_string(30, 190, 200, 16, 16, "Pitch:    . C", BLUE);
    lcd_show_string(30, 210, 200, 16, 16, "Roll :    . C", BLUE);
    lcd_show_string(30, 230, 200, 16, 16, "Yaw  :    . C", BLUE);
 
    while (1)
    {
        qmi8658_read_xyz(accel, gyro);
        key = key_scan(0);
        if (key == KEY0_PRES)
        {
            /* 切换匿名数据上报开关 */
            report = !report;
            if (report == 0)
            {
                lcd_show_string(30, 150, 200, 16, 16, "UPLOAD OFF", BLUE);
            }
            else
            {
                lcd_show_string(30, 150, 200, 16, 16, "UPLOAD ON ", BLUE);
            }
        }
        
        /* 获取并显示温度 */
        show_data(30, 170, qmi8658_get_temperature());
        
        /* 获取并显示欧拉角 */
        if (g_imu_init)
        {
            imu_get_eulerian_angles(accel, gyro, euler_angle, IMU_DELTA_T);
            show_data(30, 190, euler_angle[0]);
            show_data(30, 210, euler_angle[1]);
            show_data(30, 230, euler_angle[2]);
            
            if (report != 0)
            {
                /* 上报匿名状态帧 */
                qmi8658_send_data(accel[0], accel[1], accel[2], gyro[0], 
gyro[1], gyro[2]);  /* 发送加速度+陀螺仪原始数据 */
                usart1_report_imu((int)(euler_angle[1] * 100), 
(int)(euler_angle[0] * 100), (int)(euler_angle[2] * 100), 
0, 0, 0);          /* Pitch和Roll角位置调换 */
            }
        }
        if (++t == 100)
        {
            t = 0;
            LED0_TOGGLE();
        }
        
        delay_ms(10);
    }
}

       此部分代码除了main函数,还有几个函数,用于上报数据给上位机软件,可以通过上位机软件显示六轴传感器波形,以及3D姿态显示,有助于更好的调试QMI8658A。上位机软件使用ANO_TC匿名科创地面站v4.exe,该软件在:开发板光盘→6,软件资料→软件→5,其他软件→匿名地面站文件夹里可以找到,该软件的使用方法,见该文件夹下的:飞控通信协议v1.3-0720.pdf,这里我们不做介绍。其中,usart1_niming_report函数用于将数据打包、计算校验和,然后上报给匿名地面站上位机软件。qmi8658_send_data函数用于上报加速度和陀螺仪的原始数据,可用于波形显示传感器数据,通过传感器帧(0X02)发送。而uasrt1_report_imu函数,则用于发送匿名状态帧,可以实时3D显示QMI8658A的姿态,传感器数据等,通过状态帧(0X01)发送。

       下面我们来看main函数,利用上位机软件(ANO_TC匿名科创地面站v4.exe),可以实时显示QMI8658A的传感器状态曲线,并显示3D姿态,可以通过KEY0按键开启/关闭数据上传功能。同时,在LCD模块上面显示温度和欧拉角等信息。


        45.4 下载验证

       将程序下载到开发板后,可以看到LCD显示的内容如图45.4.1所示:


图45.4.1  程序运行效果


       屏幕显示了QMI8658A的温度、俯仰角(Pitch)、横滚角(Roll)和航向角(Yaw)的数值。然后,我们可以晃动开发板,看看各角度的变化。

       另外,模块默认是关闭状态,通过按下KEY0可以开启或关闭数据上报,在开启状态下,我们可以打开:ANO_TC匿名科创地面站v4.exe这个软件,接收STM32H7R7上传的数据,从而图形化显示飞行姿态,如图45.4.2所示:


图45.4.2 飞控状态显示


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3095916/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764724