【IMU】6轴数据校准算法

算法概述

本算法用于惯性测量单元的自动校准,通过多次采样和优化选择,确定传感器的最佳偏移量。

算法步骤

  1. 初始化
  • converged = false(加速度计收敛标志)
  • new_offset = {0,0,0}(最优偏移量)
  • last_average = {0,0,0}(上次平均值)
  1. 主循环(重复CALIB_CYCLES次)

    FOR i = 1 to CALIB_CYCLES:
    	步骤1.1: 采集50次传感器数据,计算平均值this_avg
     	步骤1.2: 计算与上次平均值的差异this_diff = norm(last_avg - this_avg)
     	步骤1.3: 判断收敛,和最佳值更新
     	步骤2.4: 更新last_average = this_avg
    
    
    步骤1.3: 判断收敛,和最佳值更新:
    	IF this_diff < 1.0:
        - 更新滑动平均:last_average = 0.5 × this_avg + 0.5 × last_average
        - 如果首次收敛或新值更优,更新 new_offset = last_average
        - 设置 converged = true
    	else if (this_diff < best_diff):  // 未收敛但更优
        - 更新最优平均值:best_avg = 0.5×acc_avg + 0.5×acc_last_average
        - 更新最优差值:best_diff = this_diff
    

如果收敛则取new_offset作为最后的offset值,否则使用best_avg作为offset值

代码

最后得到的6轴校准代码如下,参考了《多旋翼无人机嵌入式飞控开发实战》(奚海蛟,叶贵强)。这里做了部分修改,主要是对两个参数添加了修正:

  • 异常检测:在该步骤中,(240 / Utils::getAccelerometerRange())这个边界值是一个经验值,Utils::getAccelerometerRange()获取加速度量程的半值,比如±2g量程取2.
  • 收敛判断:在两个仪器的“步骤1.3: 判断收敛,和最佳值更新”中,引入了acc_thresholdgyro_threshold,这是根据量程,采样率ODR,和均方根误差RMS-noise计算出来的。具体的计算方法在下一节说明
    uint8_t BMI2700::_Calibration()
       {
           if (!this->_calibrating)
           {
               this->_calibrating = true;
   
               bool acc_converged = false;     // 加速度聚合状态
               bool gyro_converged = false;    // 角速度聚合状态
               Vector3f acc_best_avg = {0};    // 50次测量中最优加速度
               Vector3f gyro_best_avg = {0};   // 50次测量中最优角速度
               Vector3f new_acc_offset = {0};  // 加速度最终需要修正的偏移量
               Vector3f new_gyro_offset = {0}; // 角速度最终需要修正的偏移量
   
               Vector3f acc_last_average = {0};  // 上50次测量的角速度平均值
               Vector3f gyro_last_average = {0}; // 上50次测量的角速度平均值
               Vector3l acc_raw_sum = {0};       // 加速度原始值累计和
               Vector3l gyro_raw_sum = {0};      // 角速度原始值累积和
               Vector3i acc_start = {0};         // 每50次数据读取前加速度状态
               Vector3i acc_raw = {0};           // 每50次数据读取前加速度状态
               Vector3i gyro_start = {0};        // 每50次数据读取前角速度状态
               Vector3i gyro_raw = {0};          // 每50次数据读取前角速度状态
               Vector3f acc_avg = {0};           // 50次累计加速度均值
               Vector3f gyro_avg = {0};          // 50次累计角速度均值
   
               uint8_t sum_cnt = 0;      // 50次的累加计数器
               float acc_best_diff = 0;  // 加速度最优方差
               float gyro_best_diff = 0; // 角速度最优方差
               float acc_diff_norm = 0;  // 加速度方差
               float gyro_diff_norm = 0; // 角速度方差
               float acc_threshold = Utils::calculate_acc_convergence_threshold(50);
               float gyro_threshold = Utils::calculate_gyro_convergence_threshold(50);
               this->_log("acc_threshold: %f\r\n", acc_threshold);
               this->_log("gyro_threshold: %f\r\n", gyro_threshold);
   
               this->_delay_us(1000000);
               for (int count = 0; count < 120; count++)
               {
                   /* 累计50次坐标和 */
                   acc_raw_sum.x = acc_raw_sum.y = acc_raw_sum.z = gyro_raw_sum.x = gyro_raw_sum.y = gyro_raw_sum.z = 0;
                   /* 记录起始坐标 */
                   this->read_gyro_accel(&acc_start, &gyro_start);
                   // this->_log("New acc offset: %d, %d, %d\r\n", acc_start.x,acc_start.y,acc_start.z);
                   for (sum_cnt = 0; sum_cnt < 50; sum_cnt++)
                   {
                       this->read_gyro_accel(&acc_raw, &gyro_raw);
                       acc_raw_sum.x += acc_raw.x;
                       acc_raw_sum.y += acc_raw.y;
                       acc_raw_sum.z += acc_raw.z;
                       gyro_raw_sum.x += gyro_raw.x;
                       gyro_raw_sum.y += gyro_raw.y;
                       gyro_raw_sum.z += gyro_raw.z;
                       this->_delay_us(1000);
                   }
                   float test = sqrtf3(acc_raw.x - acc_start.x, acc_raw.y - acc_start.y, acc_raw.z - acc_start.z);
                   if (test > (240 / Utils::getAccelerometerRange()))
                   {
                       continue; // 采样50次后允许加速度方差最大值(实际测量值)
                   }
   
                   acc_avg.x = acc_raw_sum.x / (float)sum_cnt;
                   acc_avg.y = acc_raw_sum.y / (float)sum_cnt;
                   acc_avg.z = acc_raw_sum.z / (float)sum_cnt;
                   gyro_avg.x = gyro_raw_sum.x / (float)sum_cnt;
                   gyro_avg.y = gyro_raw_sum.y / (float)sum_cnt;
                   gyro_avg.z = gyro_raw_sum.z / (float)sum_cnt;
   
                   acc_diff_norm = sqrtf3((acc_last_average.x - acc_avg.x), (acc_last_average.y - acc_avg.y), (acc_last_average.z - acc_avg.z));
                   this->_log("acc_diff_norm: %f\r\n", acc_diff_norm);
                   gyro_diff_norm = sqrtf3((gyro_last_average.x - gyro_avg.x), (gyro_last_average.y - gyro_avg.y), (gyro_last_average.z - gyro_avg.z));
   
                   if (count == 0)
                   {
                       acc_best_diff = acc_diff_norm;
                       acc_best_avg.x = acc_avg.x;
                       acc_best_avg.y = acc_avg.y;
                       acc_best_avg.z = acc_avg.z;
                       gyro_best_diff = gyro_diff_norm;
                       gyro_best_avg.x = gyro_avg.x;
                       gyro_best_avg.y = gyro_avg.y;
                       gyro_best_avg.z = gyro_avg.z;
                   }
                   else
                   {
                     /* 步骤1.3: 判断收敛,和最佳值更新 */
                       if (acc_diff_norm < acc_threshold) // 加速度方差
                       {
                           acc_last_average.x = (acc_avg.x * 0.5f) + (acc_last_average.x * 0.5f);
                           acc_last_average.y = (acc_avg.y * 0.5f) + (acc_last_average.y * 0.5f);
                           acc_last_average.z = (acc_avg.z * 0.5f) + (acc_last_average.z * 0.5f);
                           if (!acc_converged || (sqrtf3(acc_last_average.x, acc_last_average.y, acc_last_average.z) < sqrtf3(new_acc_offset.x, new_acc_offset.y, new_acc_offset.z)))
                           {
                               acc_converged = true;
                               new_acc_offset.x = acc_last_average.x;
                               new_acc_offset.y = acc_last_average.y;
                               new_acc_offset.z = acc_last_average.z;
                               this->_log("new_acc_offset: %f, %f, %f\r\n", new_acc_offset.x, new_acc_offset.y, new_acc_offset.z);
                           }
                       }
                       else if (acc_diff_norm < acc_best_diff)
                       {
                           acc_best_diff = acc_diff_norm;
                           acc_best_avg.x = (acc_avg.x * 0.5f) + (acc_last_average.x * 0.5f);
                           acc_best_avg.y = (acc_avg.y * 0.5f) + (acc_last_average.y * 0.5f);
                           acc_best_avg.z = (acc_avg.z * 0.5f) + (acc_last_average.z * 0.5f);
                       }
                       acc_last_average.x = acc_avg.x;
                       acc_last_average.y = acc_avg.y;
                       acc_last_average.z = acc_avg.z;
   										/* 步骤1.3: 判断收敛,和最佳值更新 */
                       if (gyro_diff_norm < gyro_threshold) // 角速度方差
                       {
                           gyro_last_average.x = (gyro_avg.x * 0.5f) + (gyro_last_average.x * 0.5f);
                           gyro_last_average.y = (gyro_avg.y * 0.5f) + (gyro_last_average.y * 0.5f);
                           gyro_last_average.z = (gyro_avg.z * 0.5f) + (gyro_last_average.z * 0.5f);
                           if (!gyro_converged || (sqrtf3(gyro_last_average.x, gyro_last_average.y, gyro_last_average.z) < sqrtf3(new_gyro_offset.x, new_gyro_offset.y, new_gyro_offset.z)))
                           {
                               gyro_converged = true;
                               new_gyro_offset.x = gyro_last_average.x;
                               new_gyro_offset.y = gyro_last_average.y;
                               new_gyro_offset.z = gyro_last_average.z;
                               this->_log("new_gyro_offset: %f, %f, %f\r\n", new_gyro_offset.x, new_gyro_offset.y, new_gyro_offset.z);
                           }
                       }
                       else if (gyro_diff_norm < gyro_best_diff)
                       {
                           gyro_best_diff = gyro_diff_norm;
                           gyro_best_avg.x = (gyro_avg.x * 0.5f) + (gyro_last_average.x * 0.5f);
                           gyro_best_avg.y = (gyro_avg.y * 0.5f) + (gyro_last_average.y * 0.5f);
                           gyro_best_avg.z = (gyro_avg.z * 0.5f) + (gyro_last_average.z * 0.5f);
                       }
                       gyro_last_average.x = gyro_avg.x;
                       gyro_last_average.y = gyro_avg.y;
                       gyro_last_average.z = gyro_avg.z;
                   }
               }
   
               if (!acc_converged)
               {
                   // xQueueSendToBack(logQueue, "acc did not converge\r\n", LOG_WAIT);
                   this->_acc_offset.x = acc_best_avg.x;
                   this->_acc_offset.y = acc_best_avg.y;
                   this->_acc_offset.z = acc_best_avg.z - this->_acc_resolution;
               }
               else
               {
                   // xQueueSendToBack(logQueue, "acc calibrate success\r\n", LOG_WAIT);
                   this->_acc_offset.x = new_acc_offset.x;
                   this->_acc_offset.y = new_acc_offset.y;
                   this->_acc_offset.z = new_acc_offset.z - this->_acc_resolution;
               }
               if (!gyro_converged)
               {
                   // xQueueSendToBack(logQueue, "gyro did not converge\r\n", LOG_WAIT);
                   this->_gyr_offset.x = gyro_best_avg.x;
                   this->_gyr_offset.y = gyro_best_avg.y;
                   this->_gyr_offset.z = gyro_best_avg.z;
               }
               else
               {
                   // xQueueSendToBack(logQueue, "gyro calibrate success\r\n", LOG_WAIT);
                   this->_gyr_offset.x = new_gyro_offset.x;
                   this->_gyr_offset.y = new_gyro_offset.y;
                   this->_gyr_offset.z = new_gyro_offset.z;
               }
               // Param_SaveAccelOffset(&mpu6050.acc_offset);
               // Param_SaveGyroOffset(&mpu6050.gyro_offset);
               this->_calibrating = false;
               return 0;
           }
           return 0;
       }

收敛阈值计算

首先明确收敛的定义。

对于一个静止放置的IMU单元,理想状态下,它的值应该是稳定于一个定量的。而现实情况是,在环境噪声,元件内部漂移,出厂时的内部校准误差等的影响下,这个值会加上一个噪声。这个噪声的大小用均方差(RMS)表示,其量纲与被测量一致,这个值通常在传感元件的参考手册中给出,并且在不同的采样频率下有不同的值,用*RMS-noise(?Hz)*表示。

也就是说,只要判断仪器的输出量的RMS与该值基本一致就可以判断其处于稳定状态。那么应该怎么确定两个值基本一致呢?用统计和概率的角度来看,噪声可以看作是以定量为中心,以*RMS-noise(?Hz)为方差的正态分布事件,既然是正态分布,那么就可以使用 3 σ 3\sigma 3σ法则。因此可以使用3倍RMS-noise(?Hz)*作为阈值。

当然,这个值的单位是物理单位,与从传感器直接得到的不同,是不能直接用作传感器读数的校准的。这两个值的转换系数可以从参考手册中得到,也称为Sensitivity,也可自己计算。在这里转换系数用 S L S B p e r G S_{LSB per G} SLSBperG表示,*RMS-noise(?Hz)*用 R M S n o i s e RMS_{noise} RMSnoise表示,转换后的阈值用 T L S B T_{LSB} TLSB表示则转换公式如下:
T L S B = R M S n o i s e × S L S B p e r G × 3 T_{LSB} = RMS_{noise}\times S_{LSB per G} \times3 TLSB=RMSnoise×SLSBperG×3
这里用量纲阐明其含义:
L S B = g × ( L S B / g ) × 3 LSB = g\times(LSB/g)\times3 LSB=g×(LSB/g)×3
单位LSB指最低有效位,在这里指在传感数据对应寄存器中的单位1

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐