本帖最后由 云天 于 2026-2-21 13:15 编辑
引言:当激光雷达遇见创客 你有没有想过,如何让一台机器“看见”周围的世界?传统的单点测距传感器只能告诉你“前面有东西”,而DFRobot推出的SEN0628矩阵激光测距传感器,却能以8×8的矩阵形式输出64个点的距离数据——相当于给设备装上了一双由64个像素组成的“眼睛”。 最近,我带着这双“眼睛”踏上了一场有趣的技术探索之旅:从最初的行空板K10驱动LED灯板,到在K10屏幕上绘制实时灰度图,再到最后的ESP32-E Web服务器远程可视化。这篇文章将完整记录这一路的思考、尝试与收获,希望能给正在玩传感器的朋友们一些启发。
第一阶段:行空板K10 + 8×8 LED灯板——最直观的视觉反馈
硬件组合主控:行空板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+图形化转写)
- #include <DFRobot_NeoPixel.h>
- #include <DFRobot_matrixLidarDistanceSensor_1.h>
-
- DFRobot_NeoPixel neoPixel_P0;
- DFRobot_matrixLidarDistanceSensor_I2C tof;
-
- const int THRESHOLD = 100; // 遮挡阈值
-
- void setup() {
- neoPixel_P0.begin(P0, 64);
- neoPixel_P0.setBrightness(255);
- tof.begin();
- tof.getAllDataConfig(0x33, eMatrix);
- }
-
- void loop() {
- neoPixel_P0.clear();
-
- for (int row = 0; row < 8; row++) {
- for (int col = 0; col < 8; col++) {
- int distance = tof.getFixedPointData(0x33, row, col);
- int index = row * 8 + col;
-
- // 根据距离计算亮度(近亮远暗)
- uint8_t brightness = 0;
- if (distance < 100) brightness = 255;
- else if (distance < 500) brightness = map(distance, 100, 500, 255, 50);
-
- neoPixel_P0.setRangeColor(index, index, (brightness << 16) | (brightness << 8) | brightness);
- }
- }
-
- neoPixel_P0.show();
- delay(100);
- }
复制代码 遇到的问题与解决 问题:LED亮度变化不够细腻。
解决:改用map()函数线性映射距离到亮度值,实现平滑过渡。 这个版本的体验非常直观——手靠近传感器时,对应位置的LED会像星星一样亮起来,仿佛能“触摸”到光线。 演示视频
第二阶段:行空板K10屏幕显示——从LED到像素的进化 LED灯板虽然直观,但毕竟只有单色。行空板K10本身就有一块240×240的彩色屏幕,为什么不直接在屏幕上绘制深度图呢? 硬件组合主控:行空板K10 传感器:SEN0628 显示:K10板载2.8寸彩屏
实现思路 将8×8的传感器数据映射为8×8的彩色方块网格,每个方块30×30像素。距离越近,方块颜色越偏向某种颜色(我选了蓝色);距离越远,颜色越暗。 核心代码片段
- #include "unihiker_k10.h"
- #include <DFRobot_matrixLidarDistanceSensor_1.h>
-
- UNIHIKER_K10 k10;
- DFRobot_matrixLidarDistanceSensor_I2C tof;
-
- const int THRESHOLD = 100;
- const uint32_t COLOR_ON = 0x0000FF;
- const uint32_t COLOR_OFF = 0x000000;
- const int CELL_SIZE = 30; // 240/8 = 30
-
- void setup() {
- k10.begin();
- tof.begin();
- tof.getAllDataConfig(0x33, eMatrix);
- k10.initScreen(2);
- k10.creatCanvas();
- }
-
- void loop() {
- // 清屏
- k10.canvas->canvasRectangle(0, 0, 240, 240, COLOR_OFF, COLOR_OFF, true);
-
- for (int row = 0; row < 8; row++) {
- for (int col = 0; col < 8; col++) {
- int distance = tof.getFixedPointData(0x33, row, col);
- if (distance < THRESHOLD) {
- int x = col * CELL_SIZE;
- int y = row * CELL_SIZE;
- k10.canvas->canvasRectangle(x, y, CELL_SIZE, CELL_SIZE, COLOR_OFF, COLOR_ON, true);
- }
- }
- }
-
- k10.canvas->updateCanvas();
- delay(100);
- }
复制代码 遇到的问题与解决
问题:屏幕坐标方向与传感器实际方向不一致。
解决:增加了行列反转变量,通过软件镜像修正显示方向。 这个版本的视觉冲击力更强,彩色屏幕让数据展示更加生动。我还尝试了用灰度表示距离——近白远黑,效果非常像夜视仪的画面。 演示视频
第三阶段:ESP32-E + Web可视化——随时随地查看深度图
行空板K10虽然强大,但毕竟需要近距离查看。能不能让深度图通过WiFi传输,用手机或电脑远程查看呢?于是我换上了ESP32-E,搭建了一个Web服务器。 硬件组合主控:FireBeetle 2 ESP32-E 传感器:SEN0628 显示:任意浏览器(手机/电脑)
实现思路
ESP32创建WiFi热点,提供两个URL: 前端每隔1秒请求一次数据,根据距离实时更新每个格子的灰度。 完整代码(Arduino IDE)
- #include <WiFi.h>
- #include <WebServer.h>
- #include "DFRobot_MatrixLidar.h"
-
- const char* ssid = "ESP32_Lidar";
- const char* password = "12345678";
-
- DFRobot_MatrixLidar_I2C tof(0x33);
- uint16_t distanceData[64];
- WebServer server(80);
-
- String htmlPage = R"rawliteral(
- <!DOCTYPE html>
- <html>
- <head>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>8x8 激光雷达深度图</title>
- <style>
- body { font-family: Arial; text-align: center; background: #222; color: #eee; }
- #grid-container {
- display: grid;
- grid-template-columns: repeat(8, 1fr);
- gap: 2px;
- width: min(80vw, 80vh);
- margin: 20px auto;
- background-color: #333;
- padding: 5px;
- border-radius: 5px;
- }
- .cell {
- aspect-ratio: 1 / 1;
- background-color: #000;
- border-radius: 2px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: clamp(8px, 2vw, 14px);
- color: white;
- text-shadow: 1px 1px 1px black;
- transition: background-color 0.2s ease;
- }
- .raw-data {
- margin: 20px auto;
- width: 80%;
- background: #333;
- padding: 10px;
- border-radius: 5px;
- text-align: left;
- overflow-x: auto;
- }
- #rawDataDisplay {
- font-family: monospace;
- font-size: 12px;
- color: #0f0;
- white-space: pre-wrap;
- }
- </style>
- </head>
- <body>
- <h2> 8x8 激光雷达深度图 (近白远黑)</h2>
- <div id="grid-container"></div>
- <div class="raw-data">
- <h3>原始数据 (mm):</h3>
- <pre id="rawDataDisplay">等待数据...</pre>
- </div>
-
- <script>
- const NEAR = 100;
- const FAR = 500;
- const REVERSE_COLS = true; // 左右镜像修正
-
- function map(x, in_min, in_max, out_min, out_max) {
- return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
- }
-
- function updateGrid() {
- fetch('/data')
- .then(response => response.json())
- .then(data => {
- const container = document.getElementById('grid-container');
- container.innerHTML = '';
-
- // 显示原始数据
- let rawStr = '';
- for (let row = 0; row < 8; row++) {
- for (let col = 0; col < 8; col++) {
- rawStr += data[row * 8 + col].toString().padStart(4, ' ') + ' ';
- }
- rawStr += '\n';
- }
- document.getElementById('rawDataDisplay').textContent = rawStr;
-
- // 绘制网格
- for (let r = 0; r < 8; r++) {
- for (let c = 0; c < 8; c++) {
- let col = REVERSE_COLS ? (7 - c) : c;
- let dist = data[r * 8 + col];
- if (dist <= 0 || dist > 3500) dist = FAR + 1;
-
- let gray;
- if (dist < NEAR) gray = 255;
- else if (dist > FAR) gray = 0;
- else gray = map(dist, NEAR, FAR, 255, 0);
-
- let grayHex = Math.min(255, Math.max(0, gray)).toString(16).padStart(2, '0');
- let color = `#${grayHex}${grayHex}${grayHex}`;
-
- const cell = document.createElement('div');
- cell.className = 'cell';
- cell.style.backgroundColor = color;
- cell.title = `${dist} mm`;
- cell.textContent = dist;
- container.appendChild(cell);
- }
- }
- })
- .catch(error => {
- document.getElementById('rawDataDisplay').textContent = '错误:' + error;
- });
- }
-
- window.onload = function() {
- updateGrid();
- setInterval(updateGrid, 1000);
- };
- </script>
- </body>
- </html>
- )rawliteral";
-
- void handleRoot() { server.send(200, "text/html", htmlPage); }
-
- void handleData() {
- tof.getAllData(distanceData);
- String json = "[";
- for (int i = 0; i < 64; i++) {
- json += String(distanceData[i]);
- if (i < 63) json += ",";
- }
- json += "]";
- server.send(200, "application/json", json);
- }
-
- void setup() {
- Serial.begin(115200);
-
- while (tof.begin() != 0) {
- Serial.println("传感器初始化失败,重试中...");
- delay(1000);
- }
- tof.setRangingMode(eMatrix_8X8);
-
- WiFi.softAP(ssid, password);
- Serial.print("AP IP: ");
- Serial.println(WiFi.softAPIP());
-
- server.on("/", handleRoot);
- server.on("/data", handleData);
- server.begin();
- }
-
- void loop() {
- server.handleClient();
- }
复制代码
演示视频
技术要点总结 回顾整个开发过程,有几个关键点值得记录: 1. 数据映射策略LED灯板:亮度 = map(距离, 近阈值, 远阈值, 255, 0) 屏幕显示:颜色 = 距离<阈值 ? 蓝色 : 黑色(二值化) Web灰度图:灰度 = map(距离, 100, 500, 255, 0)(线性映射)
2. 方向修正 传感器安装方向与显示方向不一致时,通过软件镜像解决: - // 水平镜像
- let col = REVERSE_COLS ? (7 - c) : c;
-
- // 垂直镜像(如需)
- let row = REVERSE_ROWS ? (7 - r) : r;
复制代码 3. 无效值处理
固件V1.3后,无效数据统一返回4000:
- 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远程可视化,这趟探索之旅让我深刻体会到:同样的数据,不同的呈现方式,带来的体验天差地别。
|