[应用方案] 超声波雷达定位基础算法及其应用方案(三)——三边定位法C语言算法实现篇

[复制链接]
9|0
是木子明呀 发表于 2026-4-28 17:52 | 显示全部楼层 |阅读模式
本帖最后由 是木子明呀 于 2026-4-28 17:55 编辑

#申请原创#         
        最近持续基于Geehy超声波雷达GURC21做超声波雷达定位基础算法及其应用方案,记录一下这个过程中自己的一些思考和总结,欢迎同行者一起交流学习。

        
        接上一篇文章超声波雷达定位基础算法及其应用方案(二)——三边定位法理论篇 - 极海MCU极海MCU官方技术支持论坛
        
        本帖主要介绍三边定位法基于C语言版本的算法实现。
        为了更好地理解算法实现的原理,方便后续快速读懂C语言代码。
        首先,我们来详细、逐步地推导两圆根轴方程中的系数。这是理解双探头定位算法从几何定义到可解方程的关键一步。
        1.两个探头参数:
        探头 A: 坐标为 (x₁, y₁),探头 B: 坐标为 (x₂, y₂),
        测得障碍物 P (x, y) 到它们的距离(即半径)为:到探头 A 的距离:r₁,到探头 B 的距离:r₂
        根据圆的定义,障碍物 P 必须同时位于两个圆上,因此满足两个方程:
        圆 A 的方程:
        (1) (x - x₁)² + (y - y₁)² = r₁²
        圆 B 的方程:
        (2) (x - x₂)² + (y - y₂)² = r₂²
        我们的目标是找到一条直线,P 点必然位于这条直线上。这条直线就是两圆的根轴。
        2.根轴方程系数的求解推导:

        推导的核心是 “相减消元法”。我们通过将两个圆的方程相减来消去二次项 x² 和 y²,从而得到一个线性方程。
        展开两个圆的方程:
        将方程(1)和(2)展开:
        (1) → x² - 2x₁*x + x₁² + y² - 2y₁*y + y₁² = r₁²
        (2) → x² - 2x₂*x + x₂² + y² - 2y₂*y + y₂² = r₂²
        方程(1)减去方程(2):[x² - 2x₁*x + x₁² + y² - 2y₁*y + y₁²] - [x² - 2x₂*x + x₂² + y² - 2y₂*y + y₂²] = r₁² - r₂²
        合并同类项,整理后得到:2(x₂ - x₁)x + 2(y₂ - y₁)y + (x₁² + y₁² - x₂² - y₂²) = r₁² - r₂²
        整理成标准线性方程形式 Ax + By + C = 0,将上式的常数项移到右边:
        2(x₂ - x₁)x + 2(y₂ - y₁)y = (r₁² - r₂²) - (x₁² + y₁² - x₂² - y₂²)
        为了更清晰,我们调整右边常数项的符号。将 -(x₁² + y₁² - x₂² - y₂²) 改写为 +(x₂² + y₂² - x₁² - y₁²):
        两圆的根轴方程为:2(x₂ - x₁)x + 2(y₂ - y₁)y = (r₁² - r₂²) + (x₂² + y₂² - x₁² - y₁²)
        现在方程已经是 Ax + By = D 的形式,其中:
        一次项系数 A = 2(x₂ - x₁)
        一次项系数 B = 2(y₂ - y₁)
        常数项 D = (r₁² - r₂²) + (x₂² + y₂² - x₁² - y₁²)
        为了方便后续求解 y = mx + c 或 x = my + c 的形式,我们也可以将常数项D除以2,得到更简洁的形式。令:K = [ (r₁² - r₂²) + (x₂² + y₂² - x₁² - y₁²) ] / 2
        则方程简化为:(x₂ - x₁)x + (y₂ - y₁)y = K
        这个形式就是C语言代码中推导所用的基础。

        
        接下来,我们利用上述根轴方程及系数,从C语言代码的角度进行求解过程推导:
        ①从简化形式出发:(x₂ - x₁)x + (y₂ - y₁)y = K
        ②令 dx = x₂ - x₁, dy = y₂ - y₁,方程写作:dx * x + dy * y = K
        ③解出 y:
        dy * y = K - dx * x
        y = (-dx/dy) * x + (K/dy)
        ④因此,在代码中:
        斜率 m_x = -dx / dy
        截距 c_x = K / dy
        其中 K 就是上面推导的 [(r₁² - r₂²) + (x₂² + y₂² - x₁² - y₁²)] / 2。
        这个推导清晰地展示了:从纯粹的几何距离约束,如何通过代数运算转化为一个线性约束正是这个线性约束,将二维交点问题降低为一维搜索问题(在一条直线上寻找满足圆方程的点),从而使求解成为可能。这也是双探头定位算法能够实现的核心数学基础。
        
        接下来,结合实际汽车上超声波雷达具体实现,附上一个C语言代码实现示例,供大家参考:
       /**
       * @file ultrasonic_general_triangulation.c
       * @brief 通用双探头超声波定位算法(探头可在任意2D位置)
       */


       #include <stdio.h>
       #include <math.h>
       #include <float.h>


       /**
       * @brief 探头与障碍物结构体定义
       */
       typedef struct { double x, y; } Probe;
       typedef struct { double x, y; } Obstacle;


       /**
       * @brief 算法返回状态码
       */
       typedef enum {
           SUCCESS = 0,
           ERROR_INVALID_RADIUS,
           ERROR_NO_INTERSECTION,
           ERROR_COINCIDENT_PROBES,
           ERROR_PARALLEL_ROOT_AXIS  // 新增:当两圆圆心连线与根轴垂直时可能出现的退化情况
       } CalcStatus;


       /**
       * @brief 计算两点间距离的平方
       */
       static inline double distance_squared(double x1, double y1, double x2, double y2) {
           double dx = x1 - x2, dy = y1 - y2;
           return dx * dx + dy * dy;
       }


       /**
       * @brief 通用的双探头定位函数
       *
       * @param p1 探头1坐标
       * @param p2 探头2坐标
       * @param r1 到探头1的距离
       * @param r2 到探头2的距离
       * @param result 计算结果存储指针(输出有效解,通常y值较大的那个)
       * @return CalcStatus
       */
       CalcStatus locateObstacleGeneral(const Probe* p1, const Probe* p2,
                                        double r1, double r2,
                                        Obstacle* result) {
           // 1. 输入有效性检查
          if (r1 <= 0.0 || r2 <= 0.0) return ERROR_INVALID_RADIUS;


           double dx = p2->x - p1->x, dy = p2->y - p1->y;
           double dist2 = dx * dx + dy * dy; // 探头间距离平方
   
           // 检查探头是否重合(或过近)
           if (dist2 < DBL_EPSILON) return ERROR_COINCIDENT_PROBES;


           // 2. 计算两圆的根轴(radical axis)线性方程:y = m*x + c
           // 通过两圆方程相减得到:(x2-x1)x + (y2-y1)y = (r1^2 - r2^2 + d^2)/2
           // 其中 d^2 = (x2-x1)^2 + (y2-y1)^2
           double right = (r1 * r1 - r2 * r2 + distance_squared(p2->x, p2->y, p1->x, p1->y)) / 2.0;


           // 3. 求解交点
           double x_sol[2], y_sol[2];
           int num_solutions = 0;


           // 判断处理哪种情况更稳定:|dx| > |dy| 时,用y表示x;否则用x表示y
           if (fabs(dx) < fabs(dy)) {
                // 情况A: 用 x 表示 y, y = m_x * x + c_x
               double m_x = -dx / dy;
               double c_x = (right - p1->x * dx - p1->y * dy) / dy;


               // 将 y = m_x * x + c_x 代入圆1方程,得到关于x的二次方程: A*x^2 + B*x + C = 0
               double A = 1 + m_x * m_x;
               double B = 2 * (m_x * (c_x - p1->y) - p1->x);
               double C = p1->x * p1->x + (c_x - p1->y) * (c_x - p1->y) - r1 * r1;


               double discriminant = B * B - 4 * A * C;
               if (discriminant < -DBL_EPSILON) return ERROR_NO_INTERSECTION;
               if (discriminant < 0) discriminant = 0; // 处理微小负值


               double sqrt_disc = sqrt(discriminant);
               x_sol[0] = (-B + sqrt_disc) / (2 * A);
               x_sol[1] = (-B - sqrt_disc) / (2 * A);
               y_sol[0] = m_x * x_sol[0] + c_x;
               y_sol[1] = m_x * x_sol[1] + c_x;
               num_solutions = (fabs(discriminant) > DBL_EPSILON) ? 2 : 1;
           } else {
               // 情况B: 用 y 表示 x, x = m_y * y + c_y
               double m_y = -dy / dx;
               double c_y = (right - p1->x * dx - p1->y * dy) / dx;


              // 将 x = m_y * y + c_y 代入圆1方程,得到关于y的二次方程
               double A = 1 + m_y * m_y;
               double B = 2 * (m_y * (c_y - p1->x) - p1->y);
               double C = p1->y * p1->y + (c_y - p1->x) * (c_y - p1->x) - r1 * r1;


               double discriminant = B * B - 4 * A * C;
               if (discriminant < -DBL_EPSILON) return ERROR_NO_INTERSECTION;
               if (discriminant < 0) discriminant = 0;


               double sqrt_disc = sqrt(discriminant);
               y_sol[0] = (-B + sqrt_disc) / (2 * A);
               y_sol[1] = (-B - sqrt_disc) / (2 * A);
               x_sol[0] = m_y * y_sol[0] + c_y;
               x_sol[1] = m_y * y_sol[1] + c_y;
               num_solutions = (fabs(discriminant) > DBL_EPSILON) ? 2 : 1;
           }


            // 4. 从可能的解中选择最合理的(以选择y值较大的点(即“前方”的障碍物)为例)
           int selected_index = 0;
           if (num_solutions == 2) {
               selected_index = (y_sol[0] > y_sol[1]) ? 0 : 1;
          }


           result->x = x_sol[selected_index];
           result->y = y_sol[selected_index];
   
           // 5. 验证解的有效性
           double err1 = fabs(distance_squared(result->x, result->y, p1->x, p1->y) - r1 * r1);
           double err2 = fabs(distance_squared(result->x, result->y, p2->x, p2->y) - r2 * r2);
           if (err1 > 1e-6 || err2 > 1e-6) {
               // 计算误差过大,可能数值不稳定
               return ERROR_NO_INTERSECTION;
           }


           return SUCCESS;
       }

        以上C语言代码中,关于x和y的坐标轴表示。其核心准则是用差值较大的那个坐标轴来表示差值较小的坐标轴。这样,在求解表达式的斜率时,我们总是在除以一个较大的数,从而获得一个绝对值小于或等于1的斜率,最大程度减少舍入误差的放大效应。

       最后,根据上述C语言代码示例,给出5个算例验证方案如下图所示:
1480269f08076355b8.png

        C语言代码实现示例如下:
       /**
       * @brief 主函数:算例演示
       */
       int main() {
           printf("=== 通用双探头超声波定位算法(任意安装位置)===\n\n");
           Obstacle obstacle;
           CalcStatus status;


           // 算例1: 经典水平情况(验证与之前算法一致性)
           Probe p1_h = {0.0, 0.0};
           Probe p2_h = {20.0, 0.0}; // 间距20cm
           status = locateObstacleGeneral(&p1_h, &p2_h, 15.0, 15.5, &obstacle); // R_a=15.0, R_b=15.5
           // 理论解应接近: x ≈ -1.8125, y ≈ 14.834 (单位:cm)


           // 算例2: 探头垂直安装(一上一下)
           Probe p1_v = {10.0, 0.0};
           Probe p2_v = {10.0, 15.0}; // 垂直间距15cm
           status = locateObstacleGeneral(&p1_v, &p2_v, 25.0, 20.0, &obstacle);


           // 算例3: 探头倾斜安装(更一般的工程情况)
           Probe p1_t = {5.0, 2.0};   // 左下
           Probe p2_t = {25.0, 8.0};  // 右上,不在同一水平线
           status = locateObstacleGeneral(&p1_t, &p2_t, 18.0, 12.0, &obstacle);


           // 算例4: 无解情况(距离过短)
           status = locateObstacleGeneral(&p1_t, &p2_t, 5.0, 5.0, &obstacle);


           // 算例5: 对称情况,障碍物在两探头中垂面附近
           // 调整距离使障碍物到两探头距离近似相等
           status = locateObstacleGeneral(&p1_t, &p2_t, 16.0, 16.2, &obstacle);
           printResult(status, &obstacle);


           return 0;

       }

        
        超声波雷达定位基础算法及其应用方案三边定位法C语言算法实现篇内容如上。
        以上所有,仅供参考,欢迎大家多多指教。







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

本版积分规则

4

主题

14

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部
0