当前位置:首页 > 消费电子 > 消费电子
[导读]扫地机路径规划在MCU上不是把ROS的move_base搬过来。低端机型RAM几KB到上百KB,跑不动激光SLAM后端;正确思路是低分辨率栅格地图+弓字形覆盖+局部A*避障+里程计闭环。全局建图留给上位机或高端芯片,下位机只做“这块地扫没扫、前面有障碍怎么绕”。


扫地机路径规划在MCU上不是把ROS的move_base搬过来。低端机型RAM几KB到上百KB,跑不动激光SLAM后端;正确思路是低分辨率栅格地图+弓字形覆盖+局部A*避障+里程计闭环。全局建图留给上位机或高端芯片,下位机只做“这块地扫没扫、前面有障碍怎么绕”。

一、地图别用float栅格

每格用1字节状态位就够了:

#define CELL_FREE   0

#define CELL_OBS    1

#define CELL_CLEAN  2

#define CELL_UNKNOWN 3


uint8_t grid[GRID_W*GRID_H];

栅格尺寸按机身直径取,家用机10~20cm一格最稳。 传感器探到障碍后在外扩1格“阴影区”,避免贴边误判死区。

坐标换算:

static inline int cell_idx(int x_cm,int y_cm){

   int gx = x_cm / CELL_CM;

   int gy = y_cm / CELL_CM;

   return gy*GRID_W + gx;

}

地图常驻RAM吃紧就分块滚动加载,只保留当前房间栅格。

二、覆盖主逻辑:弓字形

家装场景90%是矩形房间,弓字形(回字形)覆盖最省算力,不用图搜索:

typedef enum { DIR_E, DIR_S, DIR_W, DIR_N } dir_t;

static dir_t g_dir = DIR_E;

static int g_row_turn = 0;


void cover_step(pose_t *p){

   int idx = cell_idx(p->x, p->y);

   grid[idx] = CELL_CLEAN;


   if(front_cell_blocked(p, g_dir)){      // 前方障碍/边界

       turn_right_or_left(p);             // 贴边换向

       g_dir = (g_dir+1)%4;

       return;

   }

   move_forward_one_cell(p);

}

规则:

每行扫到头换向

遇到障碍走“沿边一步”再回弓字

已清扫格可踩,但不重复启动滚刷

覆盖率不够再补“未清扫格就近跳转”

纯随机清扫只放超低配机型,正经量产至少加弓字形+已扫标记。

三、局部重规划用A*,不全图搜

回充、跨房间、前方大障碍才调A*;而且只搜前方窗口,不搜全图。

typedef struct {

   int x,y,g,f,parent;

   uint8_t state;   // 0空 1开 2关

} node_t;


int astar_local(int sx,int sy,int gx,int gy){

   open_clear();

   node_put(sx,sy,0,heur(sx,sy,gx,gy));

   int expanded=0;

   while(!open_empty()){

       node_t*c=open_pop_min();

       if(c->x==gx && c->y==gy) return rebuild();

       if(++expanded > MAX_EXPAND) return -1;   // 兜底放弃

       for(int d=0;d<4;d++){

           int nx=c->x+dx[d], ny=c->y+dy[d];

           if(cell_blocked(nx,ny)) continue;

           int ng=c->g+1;

           node_t*n=node_get(nx,ny);

           if(n->state==2) continue;

           if(n->state==0 || ng<n->g){

               n->g=ng; n->f=ng+heur(nx,ny,gx,gy);

               n->parent=c->y*GRID_W+c->x;

               node_put(nx,ny,n->g,n->f);

           }

       }

   }

   return -1;

}

MCU优化要点:

静态数组,别malloc,避免碎片HardFault

启发函数用曼哈顿距离,整数运算

开放列表线性扫描或小顶堆,地图小的时候线性扫描反而快

MAX_EXPAND限节点数,超时放弃走沿边

栅格状态用位域压内存

四、传感器融合:轻量就行

别在M3/M4上跑AMCL粒子滤波,那是Linux侧干的活。 MCU层做:

编码器积分位姿(高频)

IMU补转角漂移

红外/ToF/超声波更新障碍栅格

碰撞开关触发“重标障碍+后退”

pose_t odom_step(pose_t p, int enc_l,int enc_r,float gyro_z){

   float d = (enc_l+enc_r)*0.5f*ENC_CM;

   p.head += gyro_z * DT;          // 陀螺仪补航向

   p.x += d*cosf(p.head);

   p.y += d*sinf(p.head);

   return p;

}

激光雷达每帧噪点别直接重规划,变动栅格≥几格再触发,否则主控被占满。

五、任务分层(裸机/RTOS都适用)

10ms  -> 编码器/IMU采样,里程计更新

20ms  -> 传感器融合,栅格更新

50ms  -> 覆盖状态机走一步

按需  -> 障碍挡路调astar_local

100ms -> 上报上位机位姿/电量

路径规划别进中断,别在主循环里阻塞等A*结果;算不完就沿边兜底。

六、落坑清单

全图A*每次都搜→M3直接卡死

malloc节点→跑几天HardFault

浮点欧氏距离→没FPU的核耗时翻倍

激光每帧重规划→底盘控制周期被挤

纯随机清扫无已扫标记→重复率飙

里程计不补IMU→打滑漂移撞墙

障碍不外延栅格→机身卡边

弓字形不处理凹角→角落漏扫

回充用弓字形找→绕半天,该A*

七、落地顺序

定栅格尺寸(10~20cm),建uint8地图

编码器+IMU出里程计位姿

红外/ToF写障碍栅格,外扩1格

弓字形覆盖主状态机

障碍挡路→局部A*重规划

MAX_EXPAND限节点,超时走沿边

回充/跨房走全局A*(地图块级)

变动栅格阈值防抖,别帧帧重算

MCU上扫地机路径规划的本质不是“算法多高级”,是覆盖用规则、避障用局部搜索、定位靠里程计闭环。弓字形管“扫得全”,局部A*管“绕得开”,栅格地图管“记得住”;SLAM建图、粒子定位留给Linux侧或上位机。低端STM32F1也能跑稳,前提是别贪——全图A*、粒子滤波、动态窗口法一股脑塞进MCU,都是量产翻车起点。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭