一、介绍
萤火工场推出的 CEM5861G-M11 24GHz FMCW 毫米波雷达模块,区别于传统运动检测雷达,该模块通过具有微动特征识别静态人体存在,解决了行业长期存在的"静止漏检"难题。双天线设计(110°水平/垂直波束角)确保三维空间覆盖无死角。
关键性能参数:
- 工作频率:24-24.25GHz FMCW调制
- 检测能力:挂高3m:静止检测半径3m(可调),移动检测半径6m水平安装:运动检测7m,存在检测4m
- 功耗控制:平均22mA@5V,峰值80mA
- 环境适应性:-10°C~50°C工作温度
- 接口标准:UART-TTL 3.3V通信
- 支持3.3V或5V供电
二、数据帧介绍
例如有一佂数据为:55 A5 00 0E 03 81 00 00 02 00 65 00 00 00 00 05 62 5A
包头:55 A5
数据包长度: 00 0E (14,0E后面的数据开始算03 81 00 00 02 00 65 00 00 00 00 05 62 5A)
功能码:03(00是读,01是写,02是被动上报,03是主动上报)
命令码:81 00(1 为功能分类, 2 表示具体功能。)
DATA:00 02 00 65 00 00 00 00 05 62
校验和:5A(从第1字节开始到第17字的和的低八位)
这款雷达的话,同时也支持通过窗口命令发售配置雷达的一个相关的一个参数,具体的话可以去看一下数据手册,数据手册的话我放到了本文的一个附件里面想要了解的话,可以去下载。
三、上位机
最近学习上位机开发的时候发现如果我们使用串口通讯的话,使用web来开发的话,相对来说是比较方便的,他也几乎不会出现电脑的兼容性问题,电脑能够使用浏览器支持谷歌内核,一般情况下都能是打开能够正常使用,也不用下载一些奇奇怪怪的东西直接调用就可以了。
上位机的话主要是AI写了大部分(UI部分真不会设计全靠伟大的DeepSeek),然后我对AI给出的代码进行了修改,有一个不足的点是参数配置页面好像是不能用的,由于我还在学习,所以暂时还不会修改,如果你会修改的话,可以去下面下载,由于这里上传附件的时候不允许上传HTML结尾的格式我这边会暂时把后缀给改成DOCX用的时候自行把这个改成HTML就行了。
async function connectToRadar() {
try {
addLog("正在请求串口访问权限...");
port = await navigator.serial.requestPort();
addLog("串口已选择,正在打开...");
await port.open({ baudRate: 115200 });
addLog("串口已打开,波特率: 115200");
// 设置读取器 - 直接读取原始字节
reader = port.readable.getReader();
// 设置写入器
const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);
writer = textEncoder.writable.getWriter();
// 更新UI状态
connectBtn.disabled = true;
disconnectBtn.disabled = false;
statusIndicator.classList.remove('disconnected');
statusIndicator.classList.add('connected');
statusText.textContent = '已连接到雷达设备';
// 初始化数据缓冲区
dataBuffer = new Uint8Array(BUFFER_SIZE);
bufferLength = 0;
addLog("串口连接成功,开始读取数据");
addLog(`设置数据缓冲区为: ${BUFFER_SIZE} 字节`, 'debug');
// 开始读取数据
isReading = true;
readData();
} catch (error) {
addLog(`连接失败: ${error.message}`, 'error');
disconnectFromRadar();
}
}
这部分的代码的话就是我们调用web窗口的API进行读取数据,来实现读取雷达数据。
// 解析数据帧
function parseDataFrame(frame) {
// 功能码 (位置4)
const funcCodeValue = frame[4];
// 命令码 (位置5)
const cmdCodeValue = frame[5];
// 只处理目标信息帧 (功能码0x03, 命令码0x81)
if (funcCodeValue === 0x03 && cmdCodeValue === 0x81) {
// 数据区从位置7开始 (跳过保留字节)
const dataStart = 7;
// 目标ID (位置7) - 帧的第7字节
const targetIdValue = frame[dataStart];
// 目标状态 (位置8) - 帧的第8字节
const targetStatusValue = frame[dataStart + 1];
// 距离 (位置9-10) - 帧的第9~10字节 (大端格式)
const distanceHigh = frame[dataStart + 2]; // 距离高8位
const distanceLow = frame[dataStart + 3]; // 距离低8位
const distanceCm = (distanceHigh << 8) | distanceLow;
// 信号强度 (位置15-16) - 帧的第15~16字节 (大端格式)
const signalHigh = frame[dataStart + 8]; // 信号高8位
const signalLow = frame[dataStart + 9]; // 信号低8位
const signalStrength = (signalHigh << 8) | signalLow;
// 状态描述
let statusText;
switch(targetStatusValue) {
case 0: statusText = "无目标"; break;
case 1: statusText = "移动"; break;
case 2: statusText = "存在"; break;
default: statusText = "未知";
}
return {
funcCode: `0x${funcCodeValue.toString(16).toUpperCase().padStart(2, '0')}`,
cmdCode: `0x${cmdCodeValue.toString(16).toUpperCase().padStart(2, '0')}`,
rawData: Array.from(frame).map(b => b.toString(16).toUpperCase().padStart(2, '0')).join(' '),
parsedData: `目标ID: ${targetIdValue}, 状态: ${statusText}, 距离: ${distanceCm}cm, 信号强度: ${signalStrength}`,
targetId: targetIdValue,
status: statusText,
distance: distanceCm,
signal: signalStrength
};
}
return null;
}
这部分的代码就是一个解析数据的,实现的原理很简单,就是用一个数组去装了些数据,然后提取我们数据然后根据不同的一个状态来判断我们的一个状态。
三、结语
首先日常感谢小易,这款雷达的话,唯一的不足就是那个近距离那个判断的话,基本上可信度不太大的,误差很大,个人感觉有个20~30厘米吧,当然拿他来当一个门的开关的话影响不大,人的那个状态检测的话还是挺准确的。是用场景的话不大建议再近距离的一个测距更像是可以用在马桶盖的开关,电动门的开关之类的一个控制。对差点忘了说,我们这款雷达其实还有一个银角是输出低的,也是可以通过发送面临控制的可以说极大的降低了一个成本吧,如果你拿它来开门的话,只用他的一个开关量来控制的话非常简单

