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

[项目] 从LED到Web:8x8激光雷达的可视化演进之旅

[复制链接]
本帖最后由 云天 于 2026-2-21 13:15 编辑

       引言:当激光雷达遇见创客
       你有没有想过,如何让一台机器“看见”周围的世界?传统的单点测距传感器只能告诉你“前面有东西”,而DFRobot推出的SEN0628矩阵激光测距传感器,却能以8×8的矩阵形式输出64个点的距离数据——相当于给设备装上了一双由64个像素组成的“眼睛”。
       最近,我带着这双“眼睛”踏上了一场有趣的技术探索之旅:从最初的行空板K10驱动LED灯板,到在K10屏幕上绘制实时灰度图,再到最后的ESP32-E Web服务器远程可视化。这篇文章将完整记录这一路的思考、尝试与收获,希望能给正在玩传感器的朋友们一些启发。

第一阶段:行空板K10 + 8×8 LED灯板——最直观的视觉反馈
从LED到Web:8x8激光雷达的可视化演进之旅图1

      硬件组合
  • 主控:行空板K10(DFR0992)
  • 传感器:SEN0628 8×8矩阵激光测距传感器
  • 显示:8×8 WS2812 LED灯板
  • 连接方式:I2C + GPIO

       实现思路
       第一次拿到传感器时,我最朴素的想法是:64个传感器点,对应64个LED,一一映射,多直观! [color=var(--dsw-alias-brand-text)]-1
       行空板K10集成了2.8寸彩屏,但我想先用最“原始”的方式感受数据——让每个LED的亮度代表对应点的距离。距离越近,LED越亮;距离越远,LED越暗。
       核心代码片段(Mind+图形化转写)
  1. #include <DFRobot_NeoPixel.h>
  2. #include <DFRobot_matrixLidarDistanceSensor_1.h>
  3. DFRobot_NeoPixel neoPixel_P0;
  4. DFRobot_matrixLidarDistanceSensor_I2C tof;
  5. const int THRESHOLD = 100;  // 遮挡阈值
  6. void setup() {
  7.     neoPixel_P0.begin(P0, 64);
  8.     neoPixel_P0.setBrightness(255);
  9.     tof.begin();
  10.     tof.getAllDataConfig(0x33, eMatrix);
  11. }
  12. void loop() {
  13.     neoPixel_P0.clear();
  14.    
  15.     for (int row = 0; row < 8; row++) {
  16.         for (int col = 0; col < 8; col++) {
  17.             int distance = tof.getFixedPointData(0x33, row, col);
  18.             int index = row * 8 + col;
  19.             
  20.             // 根据距离计算亮度(近亮远暗)
  21.             uint8_t brightness = 0;
  22.             if (distance < 100) brightness = 255;
  23.             else if (distance < 500) brightness = map(distance, 100, 500, 255, 50);
  24.             
  25.             neoPixel_P0.setRangeColor(index, index, (brightness << 16) | (brightness << 8) | brightness);
  26.         }
  27.     }
  28.    
  29.     neoPixel_P0.show();
  30.     delay(100);
  31. }
复制代码
      遇到的问题与解决
       问题:LED亮度变化不够细腻。
       解决:改用map()函数线性映射距离到亮度值,实现平滑过渡。
       这个版本的体验非常直观——手靠近传感器时,对应位置的LED会像星星一样亮起来,仿佛能“触摸”到光线。
       演示视频


第二阶段:行空板K10屏幕显示——从LED到像素的进化
从LED到Web:8x8激光雷达的可视化演进之旅图2
        LED灯板虽然直观,但毕竟只有单色。行空板K10本身就有一块240×240的彩色屏幕,为什么不直接在屏幕上绘制深度图呢?
       硬件组合
  • 主控:行空板K10
  • 传感器:SEN0628
  • 显示:K10板载2.8寸彩屏

       实现思路
       将8×8的传感器数据映射为8×8的彩色方块网格,每个方块30×30像素。距离越近,方块颜色越偏向某种颜色(我选了蓝色);距离越远,颜色越暗。
       核心代码片段
  1. #include "unihiker_k10.h"
  2. #include <DFRobot_matrixLidarDistanceSensor_1.h>
  3. UNIHIKER_K10 k10;
  4. DFRobot_matrixLidarDistanceSensor_I2C tof;
  5. const int THRESHOLD = 100;
  6. const uint32_t COLOR_ON = 0x0000FF;
  7. const uint32_t COLOR_OFF = 0x000000;
  8. const int CELL_SIZE = 30;  // 240/8 = 30
  9. void setup() {
  10.     k10.begin();
  11.     tof.begin();
  12.     tof.getAllDataConfig(0x33, eMatrix);
  13.     k10.initScreen(2);
  14.     k10.creatCanvas();
  15. }
  16. void loop() {
  17.     // 清屏
  18.     k10.canvas->canvasRectangle(0, 0, 240, 240, COLOR_OFF, COLOR_OFF, true);
  19.    
  20.     for (int row = 0; row < 8; row++) {
  21.         for (int col = 0; col < 8; col++) {
  22.             int distance = tof.getFixedPointData(0x33, row, col);
  23.             if (distance < THRESHOLD) {
  24.                 int x = col * CELL_SIZE;
  25.                 int y = row * CELL_SIZE;
  26.                 k10.canvas->canvasRectangle(x, y, CELL_SIZE, CELL_SIZE, COLOR_OFF, COLOR_ON, true);
  27.             }
  28.         }
  29.     }
  30.    
  31.     k10.canvas->updateCanvas();
  32.     delay(100);
  33. }
复制代码
      遇到的问题与解决
      问题:屏幕坐标方向与传感器实际方向不一致。
      解决:增加了行列反转变量,通过软件镜像修正显示方向。
      这个版本的视觉冲击力更强,彩色屏幕让数据展示更加生动。我还尝试了用灰度表示距离——近白远黑,效果非常像夜视仪的画面。
      演示视频


第三阶段:ESP32-E + Web可视化——随时随地查看深度图

从LED到Web:8x8激光雷达的可视化演进之旅图3
      行空板K10虽然强大,但毕竟需要近距离查看。能不能让深度图通过WiFi传输,用手机或电脑远程查看呢?于是我换上了ESP32-E,搭建了一个Web服务器。
      硬件组合
  • 主控:FireBeetle 2 ESP32-E
  • 传感器:SEN0628
  • 显示:任意浏览器(手机/电脑)

      实现思路
      ESP32创建WiFi热点,提供两个URL:
  • /:返回HTML页面,包含JavaScript绘制的8×8网格
  • /data:返回JSON格式的64个距离数据

      前端每隔1秒请求一次数据,根据距离实时更新每个格子的灰度。
      完整代码(Arduino IDE)
  1. #include <WiFi.h>
  2. #include <WebServer.h>
  3. #include "DFRobot_MatrixLidar.h"
  4. const char* ssid = "ESP32_Lidar";
  5. const char* password = "12345678";
  6. DFRobot_MatrixLidar_I2C tof(0x33);
  7. uint16_t distanceData[64];
  8. WebServer server(80);
  9. String htmlPage = R"rawliteral(
  10. <!DOCTYPE html>
  11. <html>
  12. <head>
  13.     <meta name="viewport" content="width=device-width, initial-scale=1">
  14.     <title>8x8 激光雷达深度图</title>
  15.     <style>
  16.         body { font-family: Arial; text-align: center; background: #222; color: #eee; }
  17.         #grid-container {
  18.             display: grid;
  19.             grid-template-columns: repeat(8, 1fr);
  20.             gap: 2px;
  21.             width: min(80vw, 80vh);
  22.             margin: 20px auto;
  23.             background-color: #333;
  24.             padding: 5px;
  25.             border-radius: 5px;
  26.         }
  27.         .cell {
  28.             aspect-ratio: 1 / 1;
  29.             background-color: #000;
  30.             border-radius: 2px;
  31.             display: flex;
  32.             align-items: center;
  33.             justify-content: center;
  34.             font-size: clamp(8px, 2vw, 14px);
  35.             color: white;
  36.             text-shadow: 1px 1px 1px black;
  37.             transition: background-color 0.2s ease;
  38.         }
  39.         .raw-data {
  40.             margin: 20px auto;
  41.             width: 80%;
  42.             background: #333;
  43.             padding: 10px;
  44.             border-radius: 5px;
  45.             text-align: left;
  46.             overflow-x: auto;
  47.         }
  48.         #rawDataDisplay {
  49.             font-family: monospace;
  50.             font-size: 12px;
  51.             color: #0f0;
  52.             white-space: pre-wrap;
  53.         }
  54.     </style>
  55. </head>
  56. <body>
  57.     <h2> 8x8 激光雷达深度图 (近白远黑)</h2>
  58.     <div id="grid-container"></div>
  59.     <div class="raw-data">
  60.         <h3>原始数据 (mm):</h3>
  61.         <pre id="rawDataDisplay">等待数据...</pre>
  62.     </div>
  63.     <script>
  64.         const NEAR = 100;
  65.         const FAR = 500;
  66.         const REVERSE_COLS = true;  // 左右镜像修正
  67.         
  68.         function map(x, in_min, in_max, out_min, out_max) {
  69.             return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
  70.         }
  71.         function updateGrid() {
  72.             fetch('/data')
  73.                 .then(response => response.json())
  74.                 .then(data => {
  75.                     const container = document.getElementById('grid-container');
  76.                     container.innerHTML = '';
  77.                     
  78.                     // 显示原始数据
  79.                     let rawStr = '';
  80.                     for (let row = 0; row < 8; row++) {
  81.                         for (let col = 0; col < 8; col++) {
  82.                             rawStr += data[row * 8 + col].toString().padStart(4, ' ') + ' ';
  83.                         }
  84.                         rawStr += '\n';
  85.                     }
  86.                     document.getElementById('rawDataDisplay').textContent = rawStr;
  87.                     // 绘制网格
  88.                     for (let r = 0; r < 8; r++) {
  89.                         for (let c = 0; c < 8; c++) {
  90.                             let col = REVERSE_COLS ? (7 - c) : c;
  91.                             let dist = data[r * 8 + col];
  92.                             if (dist <= 0 || dist > 3500) dist = FAR + 1;
  93.                            
  94.                             let gray;
  95.                             if (dist < NEAR) gray = 255;
  96.                             else if (dist > FAR) gray = 0;
  97.                             else gray = map(dist, NEAR, FAR, 255, 0);
  98.                            
  99.                             let grayHex = Math.min(255, Math.max(0, gray)).toString(16).padStart(2, '0');
  100.                             let color = `#${grayHex}${grayHex}${grayHex}`;
  101.                            
  102.                             const cell = document.createElement('div');
  103.                             cell.className = 'cell';
  104.                             cell.style.backgroundColor = color;
  105.                             cell.title = `${dist} mm`;
  106.                             cell.textContent = dist;
  107.                             container.appendChild(cell);
  108.                         }
  109.                     }
  110.                 })
  111.                 .catch(error => {
  112.                     document.getElementById('rawDataDisplay').textContent = '错误:' + error;
  113.                 });
  114.         }
  115.         window.onload = function() {
  116.             updateGrid();
  117.             setInterval(updateGrid, 1000);
  118.         };
  119.     </script>
  120. </body>
  121. </html>
  122. )rawliteral";
  123. void handleRoot() { server.send(200, "text/html", htmlPage); }
  124. void handleData() {
  125.     tof.getAllData(distanceData);
  126.     String json = "[";
  127.     for (int i = 0; i < 64; i++) {
  128.         json += String(distanceData[i]);
  129.         if (i < 63) json += ",";
  130.     }
  131.     json += "]";
  132.     server.send(200, "application/json", json);
  133. }
  134. void setup() {
  135.     Serial.begin(115200);
  136.    
  137.     while (tof.begin() != 0) {
  138.         Serial.println("传感器初始化失败,重试中...");
  139.         delay(1000);
  140.     }
  141.     tof.setRangingMode(eMatrix_8X8);
  142.    
  143.     WiFi.softAP(ssid, password);
  144.     Serial.print("AP IP: ");
  145.     Serial.println(WiFi.softAPIP());
  146.    
  147.     server.on("/", handleRoot);
  148.     server.on("/data", handleData);
  149.     server.begin();
  150. }
  151. void loop() {
  152.     server.handleClient();
  153. }
复制代码
       演示视频


技术要点总结
       回顾整个开发过程,有几个关键点值得记录:
       1. 数据映射策略
  • LED灯板:亮度 = map(距离, 近阈值, 远阈值, 255, 0)
  • 屏幕显示:颜色 = 距离<阈值 ? 蓝色 : 黑色(二值化)
  • Web灰度图:灰度 = map(距离, 100, 500, 255, 0)(线性映射)

       2. 方向修正
       传感器安装方向与显示方向不一致时,通过软件镜像解决:
  1. // 水平镜像
  2. let col = REVERSE_COLS ? (7 - c) : c;
  3. // 垂直镜像(如需)
  4. let row = REVERSE_ROWS ? (7 - r) : r;
复制代码
       3. 无效值处理
       固件V1.3后,无效数据统一返回4000:
  1. if (dist <= 0 || dist > 3500) dist = FAR + 1;  // 强制显示黑色
复制代码


三种方案的对比与思考




方案
优点
缺点
适用场景
LED灯板视觉冲击强,实时性好颜色单一,信息量少互动装置、艺术展示
K10屏幕彩色显示,集成度高需近距离查看手持设备、教学演示
Web可视化远程查看,数据详尽需WiFi环境物联网、远程监控

下一步的探索方向
       这次探索让我对8×8激光雷达有了更深的理解。接下来我打算尝试:
  • 手势识别:利用64个点的时序变化,识别简单的挥手、滑动等手势
  • 3D点云可视化:在Three.js中构建伪3D点云,更立体地展示深度信息
  • 多传感器级联:使用多个传感器拼接成更宽的视场角(传感器支持4个I2C地址切换)

写在最后
       从LED灯板的点点星光,到行空板K10的彩色网格,再到ESP32的Web远程可视化,这趟探索之旅让我深刻体会到:同样的数据,不同的呈现方式,带来的体验天差地别。


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

本版积分规则

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

硬件清单

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

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

mail