8浏览
查看: 8|回复: 0

[项目] 双ESP32无线协作:打造360°激光扫描避障小车

[复制链接]
本帖最后由 云天 于 2026-3-4 22:25 编辑

       让你的小车拥有“眼睛”,通过旋转扫描感知四周,自主决策避开障碍。

【项目简介】

       大家好!这次我制作了一台基于 “TOF激光测距传感器” 的智能避障小车。它使用两块ESP32开发板——“FireBeetle ESP32-E” 负责采集距离数据,“Romeo ESP32-S3” 负责电机控制和决策。为了让感知范围覆盖360°,我让传感器安装在28BYJ-48步进电机驱动模块上,在行进中不断旋转扫描,实时检测四个方向(前、右、后、左)的障碍距离,然后根据距离阈值自动前进、转向或后退。

【项目的亮点】
       50米高精度TOF激光测距:采用新一代dTOF(直接飞行时间)技术,测距范围 0.05~50米,精度高达 ±3cm,抗环境光干扰能力达100K LUX,为小车提供精准、稳定的障碍物感知,无论室内外都能可靠工作。
       无线通信:采用 **ESP-NOW** 协议,无需Wi-Fi路由器,两块ESP32直接交换数据。
       持续扫描:28BYJ-48步进电机驱动模块步进电机单向旋转,每90°采集一次数据,保证数据实时更新。
       可校准:上电后可通过串口命令校准车头方向,方便安装调试。
       开源代码:Arduino IDE编写,注释清晰,易于二次开发。

【硬件清单】

组件
型号
数量
备注
主控板(接收端)Romeo ESP32-S3 (DFR0994)1四路电机驱动、ESP32-S3核心
传感器板(发送端)FireBeetle ESP32-E1低功耗、I2C接口
TOF激光测距传感器SEN0648 (50m)1采用IIC模式,测距精度±3cm
步进电机驱动模块28BYJ-48步进电机驱动模块 (DFR1199)1STEP/DIR控制,5V供电
直流减速电机N20 微型电机4带轮子,用于四轮驱动
电源7.4V 锂电池组2给Romeo供电,为电机及步进电机供电
车架亚克力车架根据车架自行准备

双ESP32无线协作:打造360°激光扫描避障小车图1

(TOF激光测距传感器与28BYJ-48步进电机驱动模块)

       “注意”:TOF传感器需预先通过上位机配置为 “IIC模式”,并记住设置的ID(假设ID=0,则IIC地址为0x08)。


【硬件接线】

       FireBeetle ESP32-E ↔ TOF传感器 (SEN0648)

TOF传感器
FireBeetle ESP32-E
VCC (黑线)5V
GND (红线)GND
SDAGPIO21
SCLGPIO22

(FireBeetle默认I2C引脚为21/22,可根据实际修改)

       Romeo ESP32-S3 ↔ 步进电机驱动模块 (DFR1199)

驱动模块
Romeo ESP32-S3
STEPGPIO4
DIRGPIO5
5V5V (可从Romeo板载5V取电)
GNDGND

       Romeo ESP32-S3 ↔ 四个N20直流电机
       采用PH/EN控制模式,EN引脚输出PWM,PH引脚控制方向。参考下表(可根据实际车轮布局调整):

电机
EN引脚
PH引脚
对应车轮
M1GPIO12GPIO13右前
M2GPIO14GPIO21左前
M3GPIO9GPIO10右后
M4GPIO47GPIO11左后

       电源连接
       Romeo的 “VIN” 或 “VM” 输入7~12V(我用了7.4V锂电池)。
       步进电机驱动模块的5V可从Romeo的5V输出引脚取电(注意总电流不超过2A)。
       FireBeetle的5V也可从Romeo的5V输出取电,方便共地。
【硬件组装】
       1.组装车底座与供电电池
双ESP32无线协作:打造360°激光扫描避障小车图5

       2.组装主控板Romeo ESP32-S3
双ESP32无线协作:打造360°激光扫描避障小车图2

       3.组装8BYJ-48步进电机驱动模块
双ESP32无线协作:打造360°激光扫描避障小车图4

       4.组装TOF激光测距传感器
双ESP32无线协作:打造360°激光扫描避障小车图3


双ESP32无线协作:打造360°激光扫描避障小车图6


       5.“雷达”车
双ESP32无线协作:打造360°激光扫描避障小车图7


双ESP32无线协作:打造360°激光扫描避障小车图8


双ESP32无线协作:打造360°激光扫描避障小车图9

【工作原理】

       整体流程如下图所示:

       1. “FireBeetle端”:上电后持续通过IIC读取TOF传感器的距离数据(每秒约5次),并通过ESP-NOW广播。
       2. “Romeo端”:
       上电后等待用户通过串口发送 `c` 命令校准方向(将当前传感器指向设为“前方”)。
       进入**扫描模式**:步进电机以恒定速度单向(顺时针)旋转,每转过90°,等待接收FireBeetle发来的最新距离数据,并存入对应的方向数组(索引0:前, 1:右, 2:后, 3:左)。
       同时小车以低速(20%速度)前进,实现“边前进边扫描”。
       每完成一圈(四个方向都采集一次),调用决策函数:
              若前方距离 > 1.5米 → 继续前进。
              否则,比较左右方向距离,转向较空旷的一侧(原地旋转90°)。
              若左右也受阻,但后方 > 1.5米 → 后退3秒。
              全部受阻 → 随机转向90°。
       动作完成后,回到扫描模式,循环执行。

【软件实现Arduino IDE】

FireBeetle 发送端代码

  1. #include <Wire.h>
  2. #include <esp_now.h>
  3. #include <WiFi.h>
  4. // ==================== TOF 传感器相关 ====================
  5. #define deviceaddress 0x08
  6. typedef struct {
  7.   uint8_t id;
  8.   uint8_t interface_mode;
  9.   uint32_t uart_baudrate;
  10.   uint32_t system_time;
  11.   float dis;
  12.   uint16_t dis_status;
  13.   uint16_t signal_strength;
  14.   uint8_t range_precision;
  15. } tof_parameter;
  16. tof_parameter tof0;
  17. int16_t i2c_readN(uint8_t registerAddress, uint8_t *buf, size_t len) {
  18.   Wire.beginTransmission(deviceaddress);
  19.   Wire.write(registerAddress);
  20.   if (Wire.endTransmission(false) != 0) return -1;
  21.   Wire.requestFrom(deviceaddress, len);
  22.   int i = 0;
  23.   while (Wire.available() && i < len) buf[i++] = Wire.read();
  24.   return i;
  25. }
  26. bool recdData() {
  27.   uint8_t pdata[48];
  28.   if (i2c_readN(0x00, pdata, 32) != 32) return false;
  29.   if (i2c_readN(0x20, &pdata[32], 16) != 16) return false;
  30.   tof0.interface_mode = pdata[0x0c] & 0x07;
  31.   tof0.id = pdata[0x0d];
  32.   tof0.uart_baudrate = (uint32_t)(pdata[0x10] | (pdata[0x11] << 8) | (pdata[0x12] << 16) | (pdata[0x13] << 24));
  33.   tof0.system_time   = (uint32_t)(pdata[0x20] | (pdata[0x21] << 8) | (pdata[0x22] << 16) | (pdata[0x23] << 24));
  34.   tof0.dis           = (float)((uint32_t)(pdata[0x24] | (pdata[0x25] << 8) | (pdata[0x26] << 16) | (pdata[0x27] << 24))) / 1000.0f;
  35.   tof0.dis_status    = (uint16_t)(pdata[0x28] | (pdata[0x29] << 8));
  36.   tof0.signal_strength = (uint16_t)(pdata[0x2a] | (pdata[0x2b] << 8));
  37.   tof0.range_precision = pdata[0x2c];
  38.   return true;
  39. }
  40. // ==================== ESP-NOW ====================
  41. typedef struct {
  42.   float distance;
  43.   uint16_t status;
  44.   uint16_t signal;
  45. } sensor_data_t;
  46. sensor_data_t sendData;
  47. // Romeo ESP32-S3 MAC 地址
  48. uint8_t romeoMac[] = {0xEC, 0xDA, 0x3B, 0x65, 0xD2, 0xA0};
  49. esp_now_peer_info_t peerInfo;
  50. void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  51.   Serial.print("Send Status: ");
  52.   Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Fail");
  53. }
  54. void setup() {
  55.   Serial.begin(115200);
  56.   Wire.begin(21, 22);   // 根据实际接线修改 SDA, SCL
  57.   WiFi.mode(WIFI_STA);
  58.   if (esp_now_init() != ESP_OK) {
  59.     Serial.println("ESP-NOW init failed");
  60.     return;
  61.   }
  62.   esp_now_register_send_cb(OnDataSent);
  63.   memcpy(peerInfo.peer_addr, romeoMac, 6);
  64.   peerInfo.channel = 0;
  65.   peerInfo.encrypt = false;
  66.   if (esp_now_add_peer(&peerInfo) != ESP_OK) {
  67.     Serial.println("Add peer failed");
  68.     return;
  69.   }
  70.   Serial.println("FireBeetle TOF Sender Ready");
  71. }
  72. void loop() {
  73.   if (recdData()) {
  74.     sendData.distance = tof0.dis;
  75.     sendData.status = tof0.dis_status;
  76.     sendData.signal = tof0.signal_strength;
  77.     esp_now_send(romeoMac, (uint8_t*)&sendData, sizeof(sendData));
  78.     Serial.printf("Sent: %.2f m, status=%d\n", tof0.dis, tof0.dis_status);
  79.   } else {
  80.     Serial.println("Sensor read failed");
  81.   }
  82.   delay(200);
  83. }
复制代码

Romeo 接收端代码

  1. #include <esp_now.h>
  2. #include <WiFi.h>
  3. // ==================== 引脚定义 ====================
  4. #define STEP_PIN  4
  5. #define DIR_PIN   5
  6. #define M_RF_EN   12
  7. #define M_RF_PH   13
  8. #define M_LF_EN   14
  9. #define M_LF_PH   21
  10. #define M_RR_EN   9
  11. #define M_RR_PH   10
  12. #define M_LR_EN   47
  13. #define M_LR_PH   11
  14. // ==================== 步进电机参数 ====================
  15. const int STEPS_PER_REV = 2048;          // 实测一圈步数
  16. const float STEP_ANGLE = 360.0 / STEPS_PER_REV; // 每步角度 ≈ 0.17578°
  17. // ==================== 扫描参数 ====================
  18. const float SCAN_STEP = 90.0;             // 每次转动角度(90°)
  19. const int NUM_DIRECTIONS = 4;              // 方向数量
  20. float distances[NUM_DIRECTIONS];           // 存储四个方向的距离(索引0:前, 1:右, 2:后, 3:左)
  21. float currentAngle = 0.0;                  // 当前TOF指向的角度(相对于车头前方,0°=前,顺时针增加)
  22. int scanCount = 0;                         // 记录当前圈内已采集的方向数(0~3)
  23. bool scanning = true;                       // 是否正在扫描(true:扫描中, false:执行动作中)
  24. unsigned long actionStartTime = 0;          // 动作开始时间
  25. // 安全阈值(米)
  26. const float SAFE_DIST = 1.5;
  27. // ==================== ESP-NOW ====================
  28. typedef struct {
  29.   float distance;
  30.   uint16_t status;
  31.   uint16_t signal;
  32. } sensor_data_t;
  33. sensor_data_t receivedData;
  34. bool newDataAvailable = false;
  35. // FireBeetle MAC 地址
  36. uint8_t firebeetleMac[] = {0x7C, 0x9E, 0xBD, 0xD3, 0x63, 0x40};
  37. // ==================== 电机 PWM ====================
  38. const int pwmFreq = 2000;
  39. const int pwmResolution = 8;
  40. const int pwmChannels[4] = {0, 1, 2, 3};
  41. const int enPins[4] = {M_RF_EN, M_LF_EN, M_RR_EN, M_LR_EN};
  42. const int phPins[4] = {M_RF_PH, M_LF_PH, M_RR_PH, M_LR_PH};
  43. const int SCAN_SPEED = 10;   // 扫描时的前进速度(0-100)
  44. // ==================== 校准相关 ====================
  45. long headingOffsetSteps = 0;      // 车身正方向对应的绝对步数偏移
  46. long currentStepPosition = 0;     // 当前电机所处的绝对步数(相对于上电时的位置)
  47. bool calibrated = false;
  48. // ==================== 函数声明 ====================
  49. void stepperRotateRelative(float deltaAngle);
  50. void setMotor(int idx, int speedPercent, bool forward);
  51. void moveForward(int speed);
  52. void moveBackward(int speed);
  53. void turnLeft(int speed, bool pivot);
  54. void turnRight(int speed, bool pivot);
  55. void stopMotors();
  56. void checkSerialCommand();
  57. void analyzeAndAct();
  58. // ==================== ESP-NOW 回调 ====================
  59. void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
  60.   memcpy(&receivedData, incomingData, sizeof(receivedData));
  61.   newDataAvailable = true;
  62. }
  63. // ==================== 初始化 ====================
  64. void setup() {
  65.   Serial.begin(115200);
  66.   Serial.println("Send 'c' to calibrate forward direction (0°).");
  67.   // 电机引脚初始化
  68.   for (int i = 0; i < 4; i++) {
  69.     pinMode(enPins[i], OUTPUT);
  70.     pinMode(phPins[i], OUTPUT);
  71.     digitalWrite(phPins[i], LOW);
  72.     digitalWrite(enPins[i], LOW);
  73.   }
  74.   for (int i = 0; i < 4; i++) {
  75.     ledcSetup(pwmChannels[i], pwmFreq, pwmResolution);
  76.     ledcAttachPin(enPins[i], pwmChannels[i]);
  77.   }
  78.   // 步进电机引脚
  79.   pinMode(STEP_PIN, OUTPUT);
  80.   pinMode(DIR_PIN, OUTPUT);
  81.   digitalWrite(STEP_PIN, LOW);
  82.   digitalWrite(DIR_PIN, LOW);
  83.   // Wi-Fi 和 ESP-NOW
  84.   WiFi.mode(WIFI_STA);
  85.   if (esp_now_init() != ESP_OK) {
  86.     Serial.println("ESP-NOW init failed");
  87.     return;
  88.   }
  89.   esp_now_register_recv_cb(OnDataRecv);
  90.   esp_now_peer_info_t peerInfo;
  91.   memcpy(peerInfo.peer_addr, firebeetleMac, 6);
  92.   peerInfo.channel = 0;
  93.   peerInfo.encrypt = false;
  94.   if (esp_now_add_peer(&peerInfo) != ESP_OK) {
  95.     Serial.println("Add peer failed (may already exist)");
  96.   }
  97.   Serial.println("Romeo Controller Ready");
  98.   delay(10000);
  99. }
  100. // ==================== 主循环 ====================
  101. void loop() {
  102.   checkSerialCommand();  // 监听串口校准命令
  103.   if (scanning) {
  104.     // 扫描时小车以恒定速度前进
  105.     moveForward(SCAN_SPEED);
  106.     // 1. 相对转动 SCAN_STEP 度(持续顺时针)
  107.     stepperRotateRelative(SCAN_STEP);
  108.     // 2. 等待接收数据(最多 500ms)
  109.     unsigned long startWait = millis();
  110.     while (!newDataAvailable && (millis() - startWait < 500)) {
  111.       delay(10);
  112.     }
  113.     // 3. 根据当前角度计算理论方向索引(0前,1右,2后,3左)
  114.     int rawIndex = (int)(currentAngle / SCAN_STEP + 0.5) % NUM_DIRECTIONS;
  115.     // 由于电机实际转向可能与预期相反,交换左右索引(1和3)
  116.     int dirIndex = rawIndex;
  117.     if (rawIndex == 1) dirIndex = 3;
  118.     else if (rawIndex == 3) dirIndex = 1;
  119.     // 前(0)和后(2)保持不变
  120.     // 4. 记录距离
  121.     if (newDataAvailable) {
  122.       newDataAvailable = false;
  123.       distances[dirIndex] = receivedData.distance;
  124.       Serial.printf("Angle %.0f° -> Dir %d (raw %d): %.2f m\n", currentAngle, dirIndex, rawIndex, receivedData.distance);
  125.     } else {
  126.       // 超时,假设无障碍(设为较大值)
  127.       distances[dirIndex] = 10.0;
  128.       Serial.printf("Angle %.0f° -> Dir %d (raw %d): timeout, assume clear\n", currentAngle, dirIndex, rawIndex);
  129.     }
  130.     scanCount++;
  131.     if (scanCount >= NUM_DIRECTIONS) {
  132.       // 完成一圈扫描
  133.       scanning = false;
  134.       scanCount = 0;
  135.       Serial.println("One full circle completed. Analyzing...");
  136.       analyzeAndAct();
  137.     }
  138.   } else {
  139.     // 非扫描状态:执行动作(移动中),等待动作完成
  140.     if (millis() - actionStartTime > 3000) {
  141.       scanning = true;
  142.       stopMotors(); // 可选,停止电机准备下一轮扫描
  143.     }
  144.   }
  145. }
  146. // ==================== 数据分析与动作执行 ====================
  147. void analyzeAndAct() {
  148.   // 打印四个方向距离
  149.   Serial.print("Front: "); Serial.print(distances[0]); Serial.print(" m, ");
  150.   Serial.print("Right: "); Serial.print(distances[1]); Serial.print(" m, ");
  151.   Serial.print("Back: "); Serial.print(distances[2]); Serial.print(" m, ");
  152.   Serial.print("Left: "); Serial.println(distances[3]); Serial.print(" m");
  153.   // 决策逻辑:
  154.   // 1. 如果前方距离 > 安全阈值,前进
  155.   if (distances[0] > SAFE_DIST) {
  156.     Serial.println("Action: Move Forward");
  157.     moveForward(10);
  158.     actionStartTime = millis();
  159.   }
  160.   // 2. 否则,检查左右哪个更远,就转向哪个方向
  161.   else if (distances[1] > distances[3] && distances[1] > SAFE_DIST) {
  162.     Serial.println("Action: Turn Right");
  163.     turnRight(20, true);   // 原地右转
  164.     delay(2000);
  165.     stopMotors();
  166.     actionStartTime = millis();
  167.   }
  168.   else if (distances[3] > distances[1] && distances[3] > SAFE_DIST) {
  169.     Serial.println("Action: Turn Left");
  170.     turnLeft(20, true);
  171.     delay(2000);
  172.     stopMotors();
  173.     actionStartTime = millis();
  174.   }
  175.   // 3. 如果左右都不行,但后方可行,后退
  176.   else if (distances[2] > SAFE_DIST) {
  177.     Serial.println("Action: Move Backward");
  178.     moveBackward(15);
  179.     actionStartTime = millis();
  180.   }
  181.   // 4. 所有方向都危险,原地旋转随机方向
  182.   else {
  183.     Serial.println("Action: All blocked, spin randomly");
  184.     if (random(0, 2) == 0) turnLeft(20, true);
  185.     else turnRight(20, true);
  186.     delay(2000);
  187.     stopMotors();
  188.     actionStartTime = millis();
  189.   }
  190. }
  191. // ==================== 串口命令处理 ====================
  192. void checkSerialCommand() {
  193.   if (Serial.available()) {
  194.     char c = Serial.read();
  195.     if (c == 'c' || c == 'C') {
  196.       headingOffsetSteps = currentStepPosition;
  197.       currentStepPosition = 0;
  198.       currentAngle = 0.0;
  199.       calibrated = true;
  200.       Serial.println("Calibration OK! Current direction is now FORWARD (0°).");
  201.     }
  202.   }
  203. }
  204. // ==================== 步进电机相对转动 ====================
  205. void stepperRotateRelative(float deltaAngle) {
  206.   long deltaSteps = round(deltaAngle / STEP_ANGLE);
  207.   if (deltaSteps == 0) return;
  208.   bool clockwise = (deltaSteps > 0);  // 正角度为顺时针
  209.   long stepsToMove = abs(deltaSteps);
  210.   digitalWrite(DIR_PIN, clockwise ? HIGH : LOW);
  211.   for (long i = 0; i < stepsToMove; i++) {
  212.     digitalWrite(STEP_PIN, HIGH);
  213.     delayMicroseconds(1000);
  214.     digitalWrite(STEP_PIN, LOW);
  215.     delayMicroseconds(1000);
  216.   }
  217.   // 更新当前位置和角度
  218.   if (clockwise) {
  219.     currentStepPosition += stepsToMove;
  220.     currentAngle += deltaAngle;
  221.   } else {
  222.     currentStepPosition -= stepsToMove;
  223.     currentAngle -= deltaAngle;
  224.   }
  225.   // 归一化到 0~360 度
  226.   while (currentAngle >= 360.0) currentAngle -= 360.0;
  227.   while (currentAngle < 0.0) currentAngle += 360.0;
  228. }
  229. // ==================== 直流电机控制 ====================
  230. void setMotor(int idx, int speedPercent, bool forward) {
  231.   if (idx < 0 || idx >= 4) return;
  232.   speedPercent = constrain(speedPercent, 0, 100);
  233.   int pwmVal = map(speedPercent, 0, 100, 0, 255);
  234.   digitalWrite(phPins[idx], forward ? HIGH : LOW);
  235.   ledcWrite(pwmChannels[idx], pwmVal);
  236. }
  237. void moveForward(int speed) {
  238.   for (int i = 0; i < 4; i++) setMotor(i, speed, true);
  239. }
  240. void moveBackward(int speed) {
  241.   for (int i = 0; i < 4; i++) setMotor(i, speed, false);
  242. }
  243. void turnLeft(int speed, bool pivot) {
  244.   if (pivot) {
  245.     setMotor(0, speed, true);   // 右前
  246.     setMotor(1, speed, false);  // 左前
  247.     setMotor(2, speed, true);   // 右后
  248.     setMotor(3, speed, false);  // 左后
  249.   } else {
  250.     setMotor(0, speed, true);
  251.     setMotor(1, speed / 2, true);
  252.     setMotor(2, speed, true);
  253.     setMotor(3, speed / 2, true);
  254.   }
  255. }
  256. void turnRight(int speed, bool pivot) {
  257.   if (pivot) {
  258.     setMotor(0, speed, false);  // 右前
  259.     setMotor(1, speed, true);   // 左前
  260.     setMotor(2, speed, false);  // 右后
  261.     setMotor(3, speed, true);   // 左后
  262.   } else {
  263.     setMotor(0, speed / 2, true);
  264.     setMotor(1, speed, true);
  265.     setMotor(2, speed / 2, true);
  266.     setMotor(3, speed, true);
  267.   }
  268. }
  269. void stopMotors() {
  270.   for (int i = 0; i < 4; i++) ledcWrite(pwmChannels[i], 0);
  271. }
复制代码


【调试过程与踩坑记录】

       步进电机一圈步数实测
       按照28BYJ-48的减速比1/64,理论上输出轴一圈需4096步。但实际运行时,发现转动90°只转了180°。经过排查,发现驱动模块工作在**全步/双拍模式**,实际一圈只需2048步。因此代码中将 `STEPS_PER_REV` 设为2048,问题解决。

       左右方向相反
       当传感器转到右侧时,程序却显示“左”方向距离。原因可能是步进电机实际转向与预期相反。通过软件映射:在 `loop` 中计算 `rawIndex` 后,交换索引1和3(即 `if(rawIdx==1) dirIdx=3; else if(rawIdx==3) dirIdx=1;`),轻松修正。

       转向延时确定
       为了精确原地旋转90°,我通过实验调节了 `turnRight` 后的 `delay` 时间。最终发现速度15%下,延时2000ms正好转90°。这个参数与地面摩擦、电池电压有关,可根据实际情况微调。

       前进时扫描
       最初扫描时小车静止,但我想让它在前进中感知环境。只需在 `if(scanning)` 开头加上 `moveForward(SCAN_SPEED);`,即可实现边前进边旋转扫描。

       ESP-NOW 添加对等设备失败
       虽然串口打印“Add peer failed”,但通信正常。这是因为之前可能已经添加过该设备,忽略即可。

【最终效果】

       经过调试,小车能在室内环境中自主导航:
              当前方1米内无障碍时,保持直行;
              遇到障碍时,扫描左右,选择较空旷的方向转向;
              若四面受阻,则随机转向或后退;
              整个过程中,步进电机持续旋转,数据实时更新。



【总结与拓展】

       本项目展示了如何利用两块ESP32通过ESP-NOW协同工作,实现一个功能完整的激光扫描避障小车。你可以在此基础上进行更多拓展:

       “增加扫描分辨率”:将 `SCAN_STEP` 改为45°或30°,获取更精细的环境信息。
       “加入PID速度控制”:让前进和转向更平滑。
       “远程监控”:通过Wi-Fi将距离数据及摄像头图像发送到手机或电脑。
       “路径规划”:结合多个方向数据,实现沿墙走或探索算法。

       希望这个项目能给你带来启发!如有任何问题,欢迎在评论区交流讨论。



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

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2026 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail