OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 40|回复: 0

《ESP32-P4开发指南— V1.0》第三十四章 QMI8658A实验

[复制链接]

1238

主题

1252

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5323
金钱
5323
注册时间
2019-5-8
在线时间
1366 小时
发表于 前天 09:40 | 显示全部楼层 |阅读模式
第三十四章 QMI8658A实验

1)实验平台:正点原子DNESP32P4开发板

2)章节摘自【正点原子】ESP32-P4开发指南— V1.0

3)购买链接:https://detail.tmall.com/item.htm?id=873309579825

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子DNESP32S3开发板技术交流群:132780729


2.jpg

3.png

本章,我们将介绍一款高性价比六轴(三轴角速度(陀螺仪)+三轴加速度)传感器:QMI8658A,该传感器可广泛用于四轴、平衡车和空中鼠标等设计,性价比极高,具有非常广泛的应用范围。我们将继续使用ESP32-P4的硬件IIC接口去驱动板载QMI8658A传感器,读取其原始数据,并把原始数据转换为pitch俯仰角、roll翻滚角和yaw偏航角,最终显示在LCD上。
本章分为如下几个小节:
34.1 QMI8658A介绍
34.2 硬件设计
34.3 程序设计
34.4 下载验证


34.1 QMI8658A介绍
QMI8658A内部集成有3轴陀螺仪和3轴加速度计,可以通过I3C、I2C和3线或4线SPI的接口和单片机进行数据交互,传输速率可达400kHZ/s。陀螺仪的角速度测量范围最高达±2048°/s,具有良好的动态响应特性。加速度计的测量范围最大为±16g(g为重力加速度),静态测量精度高。
QMI8658A的特点包括:1、陀螺仪13 mdps/Hz的低噪声,低延迟,宽带宽;2、150μg/Hz的加速度计噪声;3、自带一个温度数字传感器;4、可编程数字滤波器;5、集成计步器,轻拍,任意运动,移动,显著运动检测;5、自带1536字节FIFO可用于缓冲传感器数据,降低系统功耗;6、超小封装尺寸:2.5×3.0×0.86mm(LGA)。
QMI8658A传感器的检测轴如图34.1.1所示:

第三十四章 QMI8658A实验619.png
图34.1.1QMI8658A检测轴及其方向

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

第三十四章 QMI8658A实验669.png
图34.1.2 QMI8658A框图

QMI8658A的引脚说明如下表所示。

1.png
表34.1.1 QMI8658A引脚说明

QMI8658A和主控芯片只需要连接SCL、SDA和INT,就可以实现驱动。其SCL和SDA同24C02、XL9555、AP3216C共用,连接在IO32和IO33上,INT脚连接在XL9555的IO1_4上。

34.1.1 QMI8658A寻址
要进行IIC通信,首先得知道器件地址,QMI8658A器件地址是7位的,具体格式如下表。

2.png
表34.1.1.1 QMI8658A地址格式

QMI8658A的器件地址会由SA0决定的,简单来说,由SA0引脚实际的硬件连接决定。ESP32-P4开发板上的QMI8658A电路中,SA0是连接到3V3即高电平,所以QMI8658A的器件地址为“1101011”即0x6B。读操作地址就为0xD7(0x6B << 1 | 0x1),即1101 0111;写操作地址就为0xD6(0x6B << 1 | 0x0),即1101 0110。

34.1.2 QMI8658A寄存器介绍
在这里简单介绍一下本实验用到的QMI8658A比较重要的寄存器,其他寄存器的描述和说明,请大家参考QMI8658A的数据手册。
芯片信息寄存器(Chip Information)
该寄存器地址为0x00和0x01,各位描述如表34.1.2.1所示。

第三十四章 QMI8658A实验1653.png
表34.1.2.1 芯片信息寄存器及其说明

这两个寄存器主要用来存放芯片的信息,设备标识符可从0x00地址处读到0x05,而设备的版本ID可从0x01地址处读到0x7C。这是芯片内部固定的信息,一般一开始验证与传感器是否通信成功,都是通过读取芯片固定信息去确认。
陀螺仪配置寄存器(Gyroscope Settings)
该寄存器地址为0x04,各位描述如下表所示。


第三十四章 QMI8658A实验1842.png
表34.1.2.2 陀螺仪配置寄存器及其说明

该寄存器主要关心gFS[2:0]和gODR[3:0]。gFS[2:0]用于设置陀螺仪的满量程范围,一般设置为011,即±128dps。由于陀螺仪的ADC为16位,灵敏度为:65536/256=256LSB(dps)。gODR[3:0]用于设置陀螺仪输出数据速率,设置为0100,即约500Hz的速率。
加速度传感器配置寄存器(Accelerometer Settings)
该寄存器地址为0x03,各位描述如下表所示:


第三十四章 QMI8658A实验2078.png
表34.1.2.3 加速度传感器配置寄存器及其说明

该寄存器主要关心aFS[2:0]和aODR[3:0]这些位。aFS[2:0]用于设置加速度计的满量程范围,一般设置为011,即±16g。因为加速度传感器的ADC也是16位分辨率,所以得到灵敏度为:65536/32=2048LSB/g。aODR[3:0]用于设置加速度计输出数据速率,设置为0100,即约500Hz的速率。
传感器数据处理寄存器(Accelerometer Settings)
该寄存器地址为0x03,各位描述如下表所示:


第三十四章 QMI8658A实验2326.png
表34.1.2.4 传感器数据处理寄存器及其说明

该寄存器主要关心低通滤波器(LPF_MODE)设置位,即:gLPF_MODE和aLPF_MODE,陀螺仪和加速度传感器分别根据这两个位的配置进行过滤。例程中,将加速度传感器和陀螺仪的低通过滤器模式选择位:gLPF_MODE[1:0]和aLPF_MODE[1:0]都设置为11,即ODR的13.37%。
温度传感器数据输出寄存器(Temp Sensor Output)
该寄存器地址为0x33~0x34,各位描述如下表所示:


第三十四章 QMI8658A实验2566.png
表34.1.2.5 温度传感器数据输出寄存器及其说明

这个寄存器可以读取温度传感器的值,可以通过读取0X33(高8位)和0X34(低8位)寄存器得到,温度换算公式为:
T= TEMP_H + (TEMP_L/256)
其中,T为计算得到的温度值,单位为℃。
加速度传感器数据输出寄存器(Temp Sensor Output)
该寄存器地址为0x35~0x3A,各位描述如下表所示:


第三十四章 QMI8658A实验2760.png
表34.1.2.6 加速度传感器数据输出寄存器及其说明

通过读取这6个寄存器,便可以读到加速度传感器x/y/z轴的值,比如读x轴的数据,可以通过读取0X35(低8位)和0X36(高8位)寄存器得到,其他轴以此类推。
加速度传感器数据输出寄存器(Temp Sensor Output)
该寄存器地址为0x3B~0x40,各位描述如下表所示:


第三十四章 QMI8658A实验2934.png
表34.1.2.7 陀螺仪传感器数据输出寄存器及其说明

通过读取这6个寄存器,便可以读到陀螺仪传感器x/y/z轴的值,比如读x轴的数据,可以通过读取0X3B(低8位)和0X3C(高8位)寄存器得到,其他轴以此类推。
关于QMI8658A的基础介绍,我们就介绍到这。QMI8658A的详细资料和相关寄存器介绍,请参考光盘:7,硬件资料→2,芯片资料
C3021082_姿态传感器-陀螺仪_QMI8658A_规格书_WJ411228.pdf这个文档,供大家参考学习。

34.1.3 QMI8658A时序介绍
ESP32-P4是通过IIC总线跟QMI8658A进行通信的,对QMI8658A相关寄存器进行写入配置,后面就是对相关数据寄存器进行读取。这里的时序主要就是写寄存器时序和读寄存器时序,我们一一介绍。
写寄存器时序
QMI8658A的写寄存器时序如下图所示。

第三十四章 QMI8658A实验3318.png
图34.1.3.1 QMI8658A写寄存器时序

图中,先发送QMI8658A的写操作地址0xD6(器件地址0X6B << 1 | 读写位0x0),随后发送8位寄存器地址,最后发送8位寄存器值。其中:S,表示IIC起始信号;W,表示读/写标志位(W=0表示写,W=1表示读);A,表示应答信号;P,表示IIC停止信号。
读寄存器时序图
QMI8658A的读寄存器时序如下图所示。


第三十四章 QMI8658A实验3510.png
图34.1.3.2 QMI8658A读寄存器时序

图中,同样是先发送QMI8658A的写操作地址0xD6,然后再发送寄存器地址,随后,重新发送起始信号(Sr),发送QMI8658A的读操作地址0xD7(器件地址0X6B << 1 | 读写位0x1),然后读取寄存器值。其中:Sr,表示重新发送IIC起始信号;N,表示不对QMI8658A进行应答;其他简写同上。
QMI8658A的简介,我们就介绍到这里,关于该芯片的详细说明,请大家参考其数据手册。

34.2 硬件设计

34.2.1 例程功能
开机的时候先检测QMI8658A是否存在,如检测不到QMI8658A,则在LCD屏上面显示报错信息。如果检测到QMI8658A,则显示正常,并在主循环里面循环读取加速度计、陀螺仪和温度传感器原始数据,并利用加速度计数据和陀螺仪数据姿态计算后得到欧拉角。其中程序还支持通过串口上报给上位机(温度不上报),利用上位机软件(ANO_TC匿名科创地面站v4.exe),可以实时显示QMI8658A的传感器状态曲线,并显示3D姿态,可以通过BOOT按键开启/关闭数据上传功能。LED0用来指示程序正在运行。

34.2.2 硬件资源
1)LED灯
        LED        0        - IO51
2)RGBLCD / MIPILCD(引脚太多,不罗列出来)
3)QMI8658A
        IIC_INT        - EXIO_12(XL9555)
        IIC_SDA        - IO33
        IIC_SCL        - IO32

34.2.3 原理图
QMI8658A相关原理图,如下图所示。

第三十四章 QMI8658A实验4172.png
图33.2.3.1 AP3216C原理图

这里说明一下,QMI8658A的QMI_INT脚是连接在XL9555器件的IO1_4脚上,如果大家要使用QMI8658A的中断输出功能,必须先初始化XL9555器件并配置IO1_4为输入功能,监测XL9555中断引脚是否有中断产生。若发现有中断产生,则判断是否是IO1_4导致的,从而检测到QMI8658A的中断。在本章中,并没有用到QMI8658A中断功能,所以没有对XL9555器件的IO1_4做设置。

34.3 程序设计
IIC外设驱动已经在第十九章19.3.1小节做了说明,这里就不再赘述了。

34.3.1 程序流程图

第三十四章 QMI8658A实验4485.png
图34.3.1.1 QMI8658A实验程序流程图

34.3.2 程序解析
在25_qmi8658a例程中,作者在25_qmi8658a\components\BSP路径下新增了1个文件夹QMI8658A,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1. QMI8658A驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。QMI8658A驱动源码包括四个文件:qmi8658a.c、qmi8658a.h、imu.c和imu.h。
其中qim8658a.c和qmi8658a.h主要是QMI8658A器件的驱动文件,而imu.c和imu.h主要是关于姿态解算的算法实现。
在qmi8658a.h中,定义了许多宏,包括对QMI8658A的配置,QMI8658A寄存器、寄存器相关位等,大家自行查看源码即可。
下面我们再解析qmi8658a.c的程序,首先先来看一下初始化函数qmi8658a_init,代码如下:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief           初始化QMI8658</font>
  3. <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url]             无</font>
  4. <font size="3"> * @retval           初始化结果:0成功;1:失败</font>
  5. <font size="3"> */</font>
  6. <font size="3">esp_err_t qmi8658_init(void)</font>
  7. <font size="3">{</font>
  8. <font size="3">    /* 未调用myiic_init初始化IIC */</font>
  9. <font size="3">    if (bus_handle == NULL)</font>
  10. <font size="3">    {</font>
  11. <font size="3">        ESP_ERROR_CHECK(myiic_init());</font>
  12. <font size="3">    }</font>

  13. <font size="3">    i2c_device_config_t qmi8658_i2c_dev_conf = {</font>
  14. <font size="3">        .dev_addr_length = I2C_ADDR_BIT_LEN_7,  /* 从机地址长度 */</font>
  15. <font size="3">        .scl_speed_hz    = IIC_SPEED_CLK,       /* 传输速率 */</font>
  16. <font size="3">        .device_address  = QMI8658_ADDR,        /* 从机7位的地址 */</font>
  17. <font size="3">    };</font>
  18. <font size="3">    /* I2C总线上添加qmi8658设备 */</font>
  19. <font size="3">ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &qmi8658_i2c_dev_conf, </font>
  20. <font size="3">&qmi8658_handle));</font>

  21. <font size="3">    qmi8658_reset();                /* 复位传感器 */</font>
  22. <font size="3">    </font>
  23. <font size="3">    if(qmi8658_check_whoami())      /* 检查设备ID是否正确 */</font>
  24. <font size="3">    {</font>
  25. <font size="3">        return 1;</font>
  26. <font size="3">    }</font>

  27. <font size="3">    if(qmi8658_calibration())       /* 陀螺仪校准 */</font>
  28. <font size="3">    {</font>
  29. <font size="3">        return 1;</font>
  30. <font size="3">    }</font>

  31. <font size="3">    ESP_LOGI(qmi8658_tag, "qmi8658 calibration succeed");</font>

  32. <font size="3">    qmi8658a_register_write_byte(Register_Ctrl1, 0x60); /* I2C驱动 */</font>
  33. <font size="3">    qmi8658a_register_write_byte(Register_Ctrl7, 0x00); /* 关闭陀螺仪、加速度计 */</font>
  34. <font size="3">           </font>
  35. <font size="3">    qmi8658_config_reg(0);                /* 配置陀螺仪和加速度计的量程和数据输出速率等参数 */</font>
  36. <font size="3">    qmi8658_enablesensors(g_imu.cfg.ensensors);         /* 使能陀螺仪、加速度计 */</font>

  37. <font size="3">    return 0;</font>
  38. <font size="3">}</font>
复制代码
在QMI8658A初始化函数中,首先对qmi8658_i2c_dev_conf变量的成员进行赋值,设置QMI8658A的地址长度、设备地址以及传输速率,然后调用i2c_master_bus_add_device函数对QMI8658A设备进行初始化。初始化QMI8658A后,通过调用qmi8658_reset复位传感器,调用qmi8658_check_whoami检查设备ID是否正常,调用qmi8658_calibration进行陀螺仪校准。最后就通过调用qmi8658a_register_write函数对陀螺仪和加速度计进行配置。
接下来,看一下前面提及到的向QMI8658A寄存器写入数据的函数qmi8658a_register_write,代码如下。
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief            向qmi8658a寄存器写数据</font>
  3. <font size="3"> * @param            reg:要写入的寄存器地址</font>
  4. <font size="3"> * @param             data:要写入的数据</font>
  5. <font size="3"> * @retval            ESP_OK:成功; 其他值:错误</font>
  6. <font size="3"> */</font>
  7. <font size="3">static esp_err_t qmi8658a_register_write_byte(uint8_t reg, uint8_t data)</font>
  8. <font size="3">{</font>
  9. <font size="3">    esp_err_t ret;</font>

  10. <font size="3">    uint8_t *buf = malloc(2);</font>
  11. <font size="3">    if (buf == NULL)</font>
  12. <font size="3">    {</font>
  13. <font size="3">        ESP_LOGE(qmi8658_tag, "%s memory failed", __func__);</font>
  14. <font size="3">        return ESP_ERR_NO_MEM;</font>
  15. <font size="3">    }</font>

  16. <font size="3">    buf[0] = reg;     </font>
  17. <font size="3">    buf[1] = data;</font>

  18. <font size="3">    ret = i2c_master_transmit(qmi8658_handle, buf, 2, -1);</font>

  19. <font size="3">    free(buf);</font>

  20. <font size="3">    return ret;</font>
  21. <font size="3">}</font>
复制代码
该函数的实现,主要调用i2c_master_transmit函数。在这里需要进行数据整合,把写入数据的目的地址和要写入的数据重新存放到一个buf。在这里需要注意存放顺序,写入数据的目的地址要在要写入数据的前面,这样通过i2c_master_transmit函数发送出去的数据才符合QMI8658A的写数据操作。
继续看一下读取QMI8658A寄存器数据的函数qmi8658a_register_read,代码如下。
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief            读取qmi8658a寄存器的数据</font>
  3. <font size="3"> * @param              reg:要读取的寄存器地址</font>
  4. <font size="3"> * @param            data:读取的数据</font>
  5. <font size="3"> * @param             len:数据大小</font>
  6. <font size="3"> * @retval            ESP_OK:成功;其他值:错误</font>
  7. <font size="3"> */</font>
  8. <font size="3">esp_err_t qmi8658a_register_read(const uint8_t reg, uint8_t *data, </font>
  9. <font size="3">const size_t len)</font>
  10. <font size="3">{</font>
  11. <font size="3">    return i2c_master_transmit_receive(qmi8658_handle, &#174;, 1, data, len, -1);</font>
  12. <font size="3">}</font>
复制代码
该函数的实现主要调用i2c_master_transmit_receive函数。从QMI8658A的reg内存地址处读出len长度的数据,存放到data的buf中。
程序中很多函数都是通过调用qmi8658a_register_write和qmi8658a_register_read接口完成功能的。比如qmi8658_config_acc和qmi8658_config_gyro,这两个函数是要对加速度计和陀螺仪进行配置的,具体涉及的寄存器为Ctrl2、Ctrl3和Ctrl5。
初始化好QMI8658A传感器之后,就可以对其采集到的数据进行获取,实现这个读取过程的函数为qmi8658_get_temperature和qmi8658_read_xyz,代码如下。
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief             获取传感器温度</font>
  3. <font size="3"> * @param              无</font>
  4. <font size="3"> * @retval             温度值,单位为℃</font>
  5. <font size="3"> */</font>
  6. <font size="3">float qmi8658_get_temperature(void)</font>
  7. <font size="3">{</font>
  8. <font size="3">    short temp = 0;</font>
  9. <font size="3">    float temp_f = 0;</font>
  10. <font size="3">    uint8_t buf[2];</font>

  11. <font size="3">    qmi8658a_register_read(Register_Tempearture_L, buf, 2);    /* 读取温度数据 */</font>
  12. <font size="3">        </font>
  13. <font size="3">    temp = ((short)buf[1] << 8) | buf[0];</font>
  14. <font size="3">    temp_f = (float)temp / 256.0f;</font>
  15. <font size="3">    return temp_f;</font>
  16. <font size="3">}</font>

  17. <font size="3">/**</font>
  18. <font size="3"> * @brief             判断数据更新后,在读取补偿后QMI8658陀螺仪和加速度的数据(推荐使用)</font>
  19. <font size="3"> * @param            acc:加速度计 X,Y,Z缓存区;</font>
  20. <font size="3"> * @param             gyro:陀螺仪 X,Y,Z缓存区;</font>
  21. <font size="3"> * @retval            无</font>
  22. <font size="3"> */</font>
  23. <font size="3">void qmi8658_read_xyz(float *acc, float *gyro)</font>
  24. <font size="3">{</font>
  25. <font size="3">    unsigned char status = 0;</font>
  26. <font size="3">    unsigned char data_ready = 0;</font>
  27. <font size="3">    int retry = 0;</font>

  28. <font size="3">    while (retry++ < 3)</font>
  29. <font size="3">    {</font>
  30. <font size="3">#if defined(QMI8658_SYNC_SAMPLE_MODE)</font>
  31. <font size="3">        qmi8658a_register_read(Register_StatusInt, &status, 1);</font>

  32. <font size="3">        if (status & 0x01)</font>
  33. <font size="3">        {</font>
  34. <font size="3">                        /* delay 12us <=500Hz, 12us 1000Hz, 4us 2000Hz 2us > 2000Hz */</font>
  35. <font size="3">            delay_us(12);         </font>
  36. <font size="3">        }</font>
  37. <font size="3">        if ((status & 0x01) || (status & 0x03))</font>
  38. <font size="3">        {</font>
  39. <font size="3">            data_ready = 1;</font>
  40. <font size="3">            break;</font>
  41. <font size="3">        }</font>
  42. <font size="3">#else</font>
  43. <font size="3">        /* 检查加速度计和陀螺仪数据是否可用 */</font>
  44. <font size="3">        qmi8658a_register_read(Register_Status0, &status, 1);</font>
  45. <font size="3">        if (status & 0x03)</font>
  46. <font size="3">        {</font>
  47. <font size="3">            data_ready = 1;</font>
  48. <font size="3">            break;</font>
  49. <font size="3">        }</font>
  50. <font size="3">#endif</font>
  51. <font size="3">    }</font>

  52. <font size="3">    if (data_ready)</font>
  53. <font size="3">    {</font>
  54. <font size="3">        qmi8658_read_sensor_data(acc, gyro);</font>

  55. <font size="3">        g_imu.imu[0] = acc[0];</font>
  56. <font size="3">        g_imu.imu[1] = acc[1];</font>
  57. <font size="3">        g_imu.imu[2] = acc[2];</font>
  58. <font size="3">        g_imu.imu[3] = gyro[0];</font>
  59. <font size="3">        g_imu.imu[4] = gyro[1];</font>
  60. <font size="3">        g_imu.imu[5] = gyro[2];</font>
  61. <font size="3">    }</font>
  62. <font size="3">    else</font>
  63. <font size="3">    {</font>
  64. <font size="3">        acc[0] = g_imu.imu[0];</font>
  65. <font size="3">        acc[1] = g_imu.imu[1];</font>
  66. <font size="3">        acc[2] = g_imu.imu[2];</font>
  67. <font size="3">        gyro[0] = g_imu.imu[3];</font>
  68. <font size="3">        gyro[1] = g_imu.imu[4];</font>
  69. <font size="3">        gyro[2] = g_imu.imu[5];</font>
  70. <font size="3">    }</font>
  71. <font size="3">}</font>
复制代码
qmi8658_get_temperature函数:读取温度数据。而qmi8658_read_sensor_data函数:读取补偿后加速度计和陀螺仪的x,y,z轴原始数据。
接下来,介绍一下imu.c文件,即姿态解算相关算法。如何进行姿态解算获得欧拉角,主要就是需要调用imu_get_eulerian_angles函数去实现。
imu_get_eulerian_angles函数,如下代码所示。
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief             获取欧拉角数据</font>
  3. <font size="3"> * [url=home.php?mod=space&uid=60778]@note[/url]              姿态解算融合, 核心算法,互补滤波算法,卡尔曼滤波算法</font>
  4. <font size="3"> *                    尽量保证该函数的调用频率为: IMU_DELTA_T , 否则YAW会相应的偏大/偏小</font>
  5. <font size="3"> * @param             gyro:3轴陀螺仪数据</font>
  6. <font size="3"> * @param             acc:3轴加速度数据</font>
  7. <font size="3"> * @param             rpy:欧拉角存放buf</font>
  8. <font size="3"> * @param             dt:调用频率</font>
  9. <font size="3"> * @retval           无</font>
  10. <font size="3"> */</font>
  11. <font size="3">void imu_get_eulerian_angles(float acc[3], float gyro[3], float *rpy, float dt) </font>
  12. <font size="3">{</font>
  13. <font size="3">    float normalise;</font>
  14. <font size="3">    float ex, ey, ez;</font>
  15. <font size="3">    float halfT = 0.5f * dt;</font>
  16. <font size="3">    float accBuf[3] = {0.0f};</font>

  17. <font size="3">    /* 加速度计输出有效时,利用加速度计补偿陀螺仪 */</font>
  18. <font size="3">    if (acc[0] != 0.0f || acc[1] != 0.0f || acc[2] != 0.0f) </font>
  19. <font size="3">    {</font>
  20. <font size="3">        /* 单位化加速计测量值 */</font>
  21. <font size="3">        normalise = inv_sqrt(acc[0]*acc[0] + acc[1]*acc[1] + acc[2]*acc[2]);</font>
  22. <font size="3">        acc[0] *= normalise;</font>
  23. <font size="3">        acc[1] *= normalise;</font>
  24. <font size="3">        acc[2] *= normalise;</font>

  25. <font size="3">        /* 加速计读取的方向与重力加速计方向的差值,用向量叉乘计算 */</font>
  26. <font size="3">        ex = acc[1] * rMat[2][2] - acc[2] * rMat[2][1];</font>
  27. <font size="3">        ey = acc[2] * rMat[2][0] - acc[0] * rMat[2][2];</font>
  28. <font size="3">        ez = acc[0] * rMat[2][1] - acc[1] * rMat[2][0];</font>

  29. <font size="3">        /* 误差累计,与积分常数相乘 */</font>
  30. <font size="3">        exInt += Ki * ex * dt;</font>
  31. <font size="3">        eyInt += Ki * ey * dt;</font>
  32. <font size="3">        ezInt += Ki * ez * dt;</font>

  33. <font size="3">        /* 用叉积误差来做PI修正陀螺零偏,即抵消陀螺读数中的偏移量 */</font>
  34. <font size="3">        gyro[0] += Kp * ex + exInt;</font>
  35. <font size="3">        gyro[1] += Kp * ey + eyInt;</font>
  36. <font size="3">        gyro[2] += Kp * ez + ezInt;</font>
  37. <font size="3">    }</font>

  38. <font size="3">    /* 一阶近似算法,四元数运动学方程的离散化形式和积分 */</font>
  39. <font size="3">    float q0Last = q0, q1Last = q1, q2Last = q2, q3Last = q3;</font>
  40. <font size="3">    q0 += (-q1Last * gyro[0] - q2Last * gyro[1] - q3Last * gyro[2]) * halfT;</font>
  41. <font size="3">    q1 += ( q0Last * gyro[0] + q2Last * gyro[2] - q3Last * gyro[1]) * halfT;</font>
  42. <font size="3">    q2 += ( q0Last * gyro[1] - q1Last * gyro[2] + q3Last * gyro[0]) * halfT;</font>
  43. <font size="3">    q3 += ( q0Last * gyro[2] + q1Last * gyro[1] - q2Last * gyro[0]) * halfT;</font>

  44. <font size="3">    /* 单位化四元数 */</font>
  45. <font size="3">    normalise = inv_sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);</font>
  46. <font size="3">    q0 *= normalise;</font>
  47. <font size="3">    q1 *= normalise;</font>
  48. <font size="3">    q2 *= normalise;</font>
  49. <font size="3">    q3 *= normalise;</font>

  50. <font size="3">    compute_rotation_matrix();</font>

  51. <font size="3">    /* 计算欧拉角 */</font>
  52. <font size="3">    rpy[0] = asinf(rMat[2][0]) * RAD2DEG;</font>
  53. <font size="3">    rpy[1] = atan2f(rMat[2][1], rMat[2][2]) * RAD2DEG;</font>
  54. <font size="3">    rpy[2] = atan2f(rMat[1][0], rMat[0][0]) * RAD2DEG;</font>

  55. <font size="3">    /* 使用卡尔曼滤波器来更新 YAW */</font>
  56. <font size="3">    kalman_filter(rpy[2], gyro[2], dt);</font>

  57. <font size="3">    /* 返回经过滤波的角度 */</font>
  58. <font size="3">    rpy[2] = angle;</font>
  59. <font size="3">    </font>
  60. <font size="3">    if (!isGravityCalibrated) </font>
  61. <font size="3">    {</font>
  62. <font size="3">        accBuf[2] = acc[0]*rMat[2][0] + acc[1]*rMat[2][1] + acc[2]*rMat[2][2];</font>
  63. <font size="3">        calibrate_gravity(accBuf);</font>
  64. <font size="3">    }</font>
  65. <font size="3">}</font>
复制代码
该函数得到欧拉角的过程涉及多个步骤,包括加速度计的校准、陀螺仪的误差修正、四元数的更新以及欧拉角的计算。这里牵扯的知识面有点广,大家有兴趣可以自行去学习。
其他QMI8658A驱动代码,都有比较详细的注释,自行查看学习即可。
2. CMakeLists.txt文件
本例程的功能实现主要依靠QMI8658A驱动。要在main函数中,成功调用QMI8658A文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
  1. <font size="3">set(src_dirs</font>
  2. <font size="3">                   LED</font>
  3. <font size="3">                        KEY</font>
  4. <font size="3">                        LCD</font>
  5. <font size="3">                        MYIIC</font>
  6. <font size="3">                  QMI8658A</font>
  7. <font size="3">                  UART)</font>

  8. <font size="3">set(include_dirs</font>
  9. <font size="3">                  LED</font>
  10. <font size="3">                        KEY</font>
  11. <font size="3">                        LCD</font>
  12. <font size="3">                        MYIIC</font>
  13. <font size="3">                  QMI8658A</font>
  14. <font size="3">                  UART)</font>

  15. <font size="3">set(requires</font>
  16. <font size="3">                   driver</font>
  17. <font size="3">                   esp_lcd</font>
  18. <font size="3">                   esp_common)</font>

  19. <font size="3">idf_component_register(        SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})</font>

  20. <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码
3. main.c驱动代码
在main.c里面编写如下代码。
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief       显示数据</font>
  3. <font size="3"> * @param       x: 标题起始X坐标</font>
  4. <font size="3"> * @param       y: 标题起始Y坐标</font>
  5. <font size="3"> * @param       data: 温度或俯仰角或横滚角或航向角数据</font>
  6. <font size="3"> * @retval      无</font>
  7. <font size="3"> */</font>
  8. <font size="3">static void show_data(uint16_t x, uint16_t y, float data)</font>
  9. <font size="3">{</font>
  10. <font size="3">    int16_t temp;</font>

  11. <font size="3">    temp = data *10;</font>

  12. <font size="3">    if (temp < 0)</font>
  13. <font size="3">    {</font>
  14. <font size="3">        lcd_show_char(x + 6 * 8, y, '-', 16, 0, BLUE);</font>
  15. <font size="3">        temp = -temp;</font>
  16. <font size="3">    }</font>
  17. <font size="3">    else</font>
  18. <font size="3">    {</font>
  19. <font size="3">        lcd_show_char(x + 6 * 8, y, ' ', 16, 0, BLUE);</font>
  20. <font size="3">    }</font>

  21. <font size="3">    lcd_show_num(x + 7 * 8, y, temp / 10, 3, 16, BLUE);</font>
  22. <font size="3">    lcd_show_num(x + 11 * 8, y, temp % 10, 1, 16, BLUE);</font>
  23. <font size="3">}</font>

  24. <font size="3">/**</font>
  25. <font size="3"> * @brief       传送数据给 ANO_TC匿名科创地面站v4.exe</font>
  26. <font size="3"> * @param       fun  : 功能字. 0XA0~0XAF</font>
  27. <font size="3"> * @param       data : 数据缓存区,最多28字节!!</font>
  28. <font size="3"> * @param       len  : data区有效数据个数</font>
  29. <font size="3"> * @retval      无 </font>
  30. <font size="3"> */</font>
  31. <font size="3">void usart0_niming_report(uint8_t fun, uint8_t *data, uint8_t len)</font>
  32. <font size="3">{</font>
  33. <font size="3">    unsigned char send_buf[32] = {0};</font>
  34. <font size="3">    uint8_t i;</font>

  35. <font size="3">    if (len > 28)</font>
  36. <font size="3">    {</font>
  37. <font size="3">        return;    /* 最多28字节数据 */</font>
  38. <font size="3">    }</font>

  39. <font size="3">    send_buf[len + 4] = 0;  /* 校验数置零 */</font>
  40. <font size="3">    send_buf[0] = 0XAA;     /* 帧头 */</font>
  41. <font size="3">    send_buf[1] = 0XAA;     /* 帧头 */</font>
  42. <font size="3">    send_buf[2] = fun;      /* 功能字 */</font>
  43. <font size="3">    send_buf[3] = len;      /* 数据长度 */</font>

  44. <font size="3">    for (i = 0; i < len; i++)</font>
  45. <font size="3">    {</font>
  46. <font size="3">        send_buf[4 + i] = data</font><span style="font-size: medium;">;             /* 复制数据 */
  47.     }
  48.    
  49.     for (i = 0; i < len + 4; i++)
  50.     {
  51. <span style="font-style: italic;"><span style="font-style: normal;">        send_buf[len + 4] += send_buf;      /* 计算校验和 */
  52.     }
  53.    
  54.     uart_write_bytes(USART_UX, (const char*)send_buf, len + 5);
  55. }

  56. /**
  57. * @brief       发送加速度传感器数据和陀螺仪数据
  58. * @param       aacx,aacy,aacz    : x,y,z三个方向上面的加速度值
  59. * @param       gyrox,gyroy,gyroz : x,y,z三个方向上面的陀螺仪值
  60. * @retval      无
  61. */
  62. void qmi8658a_send_data(short aacx, short aacy, short aacz, short gyrox, short gyroy, short gyroz)
  63. {
  64.     uint8_t tbuf[18];
  65.     tbuf[0] = (aacx >> 8) & 0XFF;
  66.     tbuf[1] = aacx & 0XFF;
  67.     tbuf[2] = (aacy >> 8) & 0XFF;
  68.     tbuf[3] = aacy & 0XFF;
  69.     tbuf[4] = (aacz >> 8) & 0XFF;
  70.     tbuf[5] = aacz & 0XFF;
  71.     tbuf[6] = (gyrox >> 8) & 0XFF;
  72.     tbuf[7] = gyrox & 0XFF;
  73.     tbuf[8] = (gyroy >> 8) & 0XFF;
  74.     tbuf[9] = gyroy & 0XFF;
  75.     tbuf[10] = (gyroz >> 8) & 0XFF;
  76.     tbuf[11] = gyroz & 0XFF;
  77.     /* 无磁力计数据,所以这里直接屏蔽掉.用0替代. */
  78.     tbuf[12]=0;            
  79.     tbuf[13]=0;
  80.     tbuf[14]=0;
  81.     tbuf[15]=0;
  82.     tbuf[16]=0;
  83.     tbuf[17]=0;
  84.     usart0_niming_report(0X02, tbuf, 18);       /* 自定义帧,0X02 */
  85. }

  86. /**
  87. * @brief       通过串口1上报结算后的姿态数据给电脑
  88. * @param       roll: 横滚角.单位0.1度。 -9000 -> 9000 对应 -90.00  ->  90.00度
  89. * @param       pitch: 俯仰角.单位 0.1度。-18000 -> 18000 对应 -180.00 -> 180.00 度
  90. * @param       yaw: 航向角.单位为0.1度 -18000 -> 18000  对应 -180.00 -> 180.00 度
  91. * @param       prs: 气压计高度,单位:cm
  92. * @param       fly_mode : 飞行模式
  93. * @param       armed: 锁定状态
  94. * @retval      无
  95. */
  96. void usart0_report_imu(short roll, short pitch, short yaw, int prs, uint8_t fly_mode, uint8_t armed)
  97. {
  98.     uint8_t tbuf[12];
  99.   
  100.     tbuf[0] = (roll >> 8) & 0XFF;
  101.     tbuf[1] = roll & 0XFF;
  102.     tbuf[2] = (pitch >> 8) & 0XFF;
  103.     tbuf[3] = pitch & 0XFF;
  104.     tbuf[4] = (yaw >> 8) & 0XFF;
  105.     tbuf[5] = yaw & 0XFF;
  106.     tbuf[6] = (prs >> 24) & 0XFF;
  107.     tbuf[7] = (prs >> 16) & 0XFF;
  108.     tbuf[8] = (prs >> 8) & 0XFF;
  109.     tbuf[9] = prs & 0XFF;
  110.     tbuf[10] = fly_mode;
  111.     tbuf[11] = armed;
  112.     usart0_niming_report(0X01, tbuf, 12);   /* 状态帧,0X01 */
  113. }

  114. void app_main(void)
  115. {
  116.     esp_err_t ret;
  117.     uint8_t t = 0;
  118.     uint8_t key;
  119.     uint8_t report = 0;
  120.     float gyro[3];
  121.     float accel[3];
  122.     float euler_angle[3] = {0, 0, 0};

  123.     ret = nvs_flash_init();     /* 初始化NVS */
  124.     if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
  125.     {
  126.         ESP_ERROR_CHECK(nvs_flash_erase());
  127.         ESP_ERROR_CHECK(nvs_flash_init());
  128.     }

  129.     led_init();             /* LED初始化 */
  130.     key_init();             /* KEY初始化 */
  131.     lcd_init();             /* LCD屏初始化 */
  132.     uart0_init(115200);     /* 初始化UART */

  133.     lcd_show_string(30, 50,  200, 16, 16, "ESP32-P4", RED);
  134.     lcd_show_string(30, 70,  200, 16, 16, "QMI8658A TEST", RED);
  135.     lcd_show_string(30, 90,  200, 16, 16, "ATOM@ALIENTEK", RED);
  136.     lcd_show_string(30, 110, 200, 16, 16, "BOOT:UPLOAD ON/OFF", RED);

  137.     while (qmi8658_init() != 0)     /* 初始化QMI8658A */
  138.     {
  139.         lcd_show_string(30, 130, 200, 16, 16, "QMI8658A Error!", RED);
  140.         vTaskDelay(pdMS_TO_TICKS(200));
  141.         lcd_fill(30, 130, 239, 156, WHITE);
  142.         vTaskDelay(pdMS_TO_TICKS(200));
  143.     }
  144.     lcd_show_string(30, 130, 200, 16, 16, "QMI8658A Ready!", RED);

  145.     lcd_show_string(30, 150, 200, 16, 16, "UPLOAD OFF", BLUE);
  146.     lcd_show_string(30, 170, 200, 16, 16, " Temp:    . C", BLUE);
  147.     lcd_show_string(30, 190, 200, 16, 16, "Pitch:    . C", BLUE);
  148.     lcd_show_string(30, 210, 200, 16, 16, " Roll:    . C", BLUE);
  149.     lcd_show_string(30, 230, 200, 16, 16, "  Yaw:    . C", BLUE);

  150.     while (1)
  151.     {
  152.         qmi8658_read_xyz(accel, gyro);
  153.         key = key_scan(0);

  154.         if (key == BOOT_PRES)       /* 0.2秒左右更新一次三轴原始值 */
  155.         {
  156.             /* 切换匿名数据上报开关 */
  157.             report = !report;

  158.             if (report == 0)
  159.             {
  160.                 lcd_show_string(30, 150, 200, 16, 16, "UPLOAD OFF", BLUE);
  161.             }
  162.             else
  163.             {
  164.                 lcd_show_string(30, 150, 200, 16, 16, "UPLOAD ON ", BLUE);
  165.             }
  166.         }

  167.         /* 获取并显示温度 */
  168.         show_data(30, 170, qmi8658_get_temperature());

  169.         /* 获取并显示欧拉角 */
  170.         if (g_imu_init)
  171.         {
  172.             imu_get_eulerian_angles(accel, gyro, euler_angle, IMU_DELTA_T);
  173.             show_data(30, 190, euler_angle[0]);
  174.             show_data(30, 210, euler_angle[1]);
  175.             show_data(30, 230, euler_angle[2]);
  176.             
  177.             if (report != 0)
  178.             {
  179.                 /* 上报匿名状态帧 */
  180.                 qmi8658a_send_data(accel[0], accel[1], accel[2], gyro[0],
  181.                 gyro[1], gyro[2]);  /* 发送加速度+陀螺仪原始数据 */
  182.                 /* Pitch和Roll角位置调换 */
  183.                 usart0_report_imu((int)(euler_angle[1] * 100),
  184. (int)(euler_angle[0]*100), (int)(euler_angle[2]*100), 0, 0, 0);
  185.             }
  186.         }
  187.         
  188.         t++;
  189.         if (t == 100)
  190.         {
  191.             t = 0;
  192.             LED0_TOGGLE();
  193.         }
  194.         
  195.         vTaskDelay(pdMS_TO_TICKS(10));
  196.     }
  197. }</span></span></span>
复制代码
这部分代码除了app_main函数,还有几个函数,用于上报数据给上位机软件,利用上位机软件显示传感器波形以及3D姿态显示,有助于更好的调试QMI8658A。上位机软件使用ANO_TC匿名科创地面站v4.exe,该软件在:开发板光盘→6,软件资料→软件→5,其他软件→匿名地面站文件夹里可以找到,该软件的使用方法,见该文件夹下的:飞控通信协议v1.3-0720.pdf,这里我们不做介绍。其中,usart0_niming_report函数用于将数据打包、计算校验和,然后上报给匿名地面站上位机软件。qmi8658a_send_data函数用于上报加速度和陀螺仪的原始数据,可用于波形显示传感器数据,通过传感器帧(0X02)发送。而uasrt0_report_imu函数,则用于发送匿名状态帧,可以实时3D显示QMI8658A的姿态,传感器数据等,通过状态帧(0X01)发送。
在app_main函数中,设置一个while循环去初始化QMI8658A,若QMI8658A成功被初始化,程序便可往下执行。在while循环中会调用qmi8658_read_xyz和qmi8658_get_temperature函数读取加速度计、陀螺仪和温度,然后imu_get_eulerian_angles函数会根据加速度计和陀螺仪的值进行运算得出欧拉角。若按下BOOT按键便可以开启数据上传功能,通过上位机(ANO_TC匿名科创地面站)可以实时显示QMI8658A的传感器状态曲线,并显示3D姿态。

34.4 下载验证
将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如下图所示:

第三十四章 QMI8658A实验19411.png
图34.4.1 QMI8658A实验程序运行效果图

屏幕显示了QMI8658A的温度、俯仰角(Pitch)、横滚角(Roll)和航向角(Yaw)的数值。然后,我们可以晃动开发板,看看各角度的变化。
另外,模块默认是关闭状态,通过按下BOOT可以开启或关闭数据上报,在开启状态下,我们可以打开:ANO_TC匿名科创地面站v4.exe这个软件,接收ESP32-P4上传的数据,从而图形化显示飞行姿态,如图34.4.2所示:


第三十四章 QMI8658A实验19623.png
图34.4.2 飞控状态显示
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-1-16 02:51

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表