尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ESP32+JY61P无线姿态采集:从串口解析到UDP传输完整方案

ESP32+JY61P无线姿态采集:从串口解析到UDP传输完整方案 把一块硬币大小的JY61P姿态传感器焊上线接上ESP32通电后几分钟内电脑屏幕上就能实时滚动出三个角度数据而传感器和电脑之间没有任何物理线缆连接。这个场景我第一次跑通的时候说实话还挺兴奋的。核心链路其实很简单JY61P负责把加速度计和陀螺仪的原始数据解算成姿态角通过串口输出ESP32通过串口读取这些数据再通过WiFi把数据发送给电脑。我最早做姿态采集用的是一根USB转TTL线直接连电脑传感器放在桌面上跑没问题但一旦想装到机械臂关节、小车底盘或者人体身上线就成了最大的枷锁。后来换了ESP32做无线传输整套系统才真正可以用起来。这篇文章就把我踩过一遍坑之后的完整方案写出来适合正在接触ESP32、想学串口数据解析、或者在做无线姿态采集项目的朋友参考。1. 为什么是JY61P ESP32一套低成本姿态采集方案的取舍1.1 JY61P能给我们什么数据JY61P是维特智能推出的一款六轴姿态传感器模块内部集成了三轴加速度计和三轴陀螺仪更关键的是模块本身完成了姿态解算直接通过串口输出三轴角度、三轴角速度和三轴加速度。也就是说你不需要再在单片机上做卡尔曼滤波、互补滤波那一套拿到数据直接就是物理量这对快速验证想法来说节省了太多时间。我以前用过MPU6050裸片读原始寄存器再自己解算调陀螺仪零偏、加速度阈值、滤波系数前前后后折腾了快两个星期最后静态数据还是有漂移。当然不是说自研解算不行而是如果项目重点是“上位机显示姿态”或者“无线传输链路”把时间花在底层滤波上并不划算。JY61P把这一层已经做完了它的输出精度对于大多数原型项目完全够用。另外JY61P的输出是串口数据非常规整每一包数据有帧头、有数据类型、有校验和解析起来很清晰非常适合拿来练串口状态机。官方手册里通常会给出量程和帧格式角度量程一般是±180°角速度默认量程根据配置不同有区别具体一定要以你手里那版模块的手册为准。网上有些教程里的参数和你的固件版本对不上照抄之后数据变成乱飞的情况并不少见。1.2 为什么中间要加一块ESP32JY61P本身是TTL串口输出直接接一个USB转TTL模块就能让电脑读数据很多人第一反应是把模块插在开发板上再用数据线连电脑。这个方案不是不行而是有两个痛点第一传感器位置被线限制住了一旦装在旋转部件上线会缠绕甚至扯断第二电脑和传感器之间的距离太近实际应用场景往往要求传感器在一两米甚至更远的地方工作。ESP32在这里的角色相当于一个“无线串口桥”。它从JY61P的串口读数据然后通过WiFi封装成UDP或TCP报文发给电脑电脑端只需要一个接收脚本就能恢复数据流。选择ESP32而不是ESP8266我当时的考量有三点ESP32有多个硬件串口可以用UART2专门接JY61P同时UART0继续做调试日志输出互不干扰。ESP8266虽然也能做但只有一个硬件串口调试起来很别扭。ESP32的算力比ESP8266强不少后续如果你想在发送之前做滑动平均、异常值剔除甚至同时挂载多个传感器都还留有余量。ESP32和JY61P都是3.3V逻辑电平直接对接不需要电平转换电路减少了硬件出错点。当然如果你手里只有ESP8266也可以照这个思路实现只是串口分配和WiFi稳定性需要额外注意。2. 硬件接线与串口参数把传感器“说”的话听清楚2.1 接线JY61P与ESP32的对应关系JY61P模块一般引出VCC、GND、TX、RX等引脚部分版本还会有SL、Key之类的功能引脚我们先只关心最低可用的四根线。我建议使用ESP32的UART2来接JY61P因为UART0默认连接的是USB转串口芯片一是烧录程序时会冲突二是你要用Serial.print打印调试信息。使用UART2的话烧录和通信互不干扰非常舒服。JY61P引脚ESP32引脚说明VCC3V3给模块供电注意共地GNDGND必须与ESP32共地TXGPIO16UART2 RX传感器的发送脚接ESP32的接收脚RXGPIO17UART2 TX传感器的接收脚接ESP32的发送脚这里有一个新手特别容易犯的错串口要交叉连接也就是JY61P的TX接ESP32的RXJY61P的RX接ESP32的TX。同向连接的话数据从传感器出来最后进了自己的RX两边都没收到任何东西表现出来就是电脑端什么都收不到。如果你手上模块的RX引脚不打算用其实可以不接因为读取姿态数据只需要传感器往ESP32发数据ESP32不用给传感器发指令。但为了之后能发指令校准我建议还是把RX线接上。供电方面JY61P支持3.3V~5V供电但接ESP32时我强烈建议直接使用3V3引脚。原因很简单ESP32的GPIO是3.3V电平不兼容5V输入如果用5V给传感器供电传感器TX引脚的输出高电平可能接近5V灌进ESP32的RX引脚长期来看有烧毁风险。虽然一些模块内部有电平处理但这种细节没必要赌。2.2 串口参数与电平匹配问题JY61P默认波特率一般是115200数据格式为8位数据位、1位停止位、无校验也就是常说的SERIAL_8N1。Arduino代码里初始化串口时要把RX和TX引脚显式传进去Serial2.begin(115200, SERIAL_8N1, 16, 17);这个写法是ESP32 Arduino core特有的第三个参数是RX引脚第四个参数是TX引脚。如果你在别的开发板上写比如STM32初始化方式会不一样。这里不要搞反顺序我第一次就写反了结果数据死活读不到。一个容易忽略的点如果你的JY61P之前被官方上位机改过配置比如改成9600波特率或者关闭了某个数据包输出那按照默认的115200去读会得到一堆乱码或者完全没有数据。遇到这种情况先通过官方上位机恢复出厂设置或者先确认当前配置再调整代码。电平匹配问题前面提过这里再强调一下ESP32的引脚不耐5VJY61P用3.3V供电时双方逻辑电平一致这是最稳妥的状态。如果你用USB转TTL模块调试JY61P也要确认模块是3.3V逻辑尤其是那些带跳线帽可以切换5V/3.3V的模块别把跳线帽拨到5V就直接插ESP32。还有一个小经验不要用一根很长的杜邦线把传感器接到ESP32上尤其是一米以上的线串口信号在115200波特率下虽然能跑但线一旦有松动或者干扰就会出现偶发的校验和错误。做原型验证时杜邦线没问题但真正要长时间跑最好把模块直接焊在扩展板上或者用带屏蔽的短导线。3. 解析JY61P数据帧读懂0x55背后的数据结构3.1 数据帧格式拆解JY61P的串口数据是持续自动上报的默认情况下会同时输出加速度包、角速度包和角度包三种数据包。每一包数据都以0x55作为帧头第二个字节用来表示包类型0x51加速度包包含三轴加速度和温度共11字节0x52角速度包包含三轴角速度和温度共11字节0x53角度包包含横滚角、俯仰角、航向角共9字节因为本项目最终只要角度数据所以我重点看0x53角度包。它的结构如下字节偏移内容说明00x55帧头10x53包类型2Roll低字节横滚角低8位3Roll高字节横滚角高8位4Pitch低字节俯仰角低8位5Pitch高字节俯仰角高8位6Yaw低字节航向角低8位7Yaw高字节航向角高8位8Sum校验和这里最关键的是数据转换。角度包里的Roll、Pitch、Yaw都是16位有符号整数低字节在前高字节在后。JY61P的角度精度是0.01°也就是底层数据把实际角度放大了100倍。比如你从字节2和字节3读出来的int16值是12345那实际角度就是123.45°。代码里一定要记得除以100.0否则你的显示角度会大得离谱。校验和的计算方法是从帧头0x55开始一直到Yaw高字节依次累加然后取低8位结果应该等于最后一个字节Sum。如果不等说明这一帧数据在传输过程中某个字节错了应该直接丢弃。注意校验和参与判断不等于数据一定正确但它能拦住大部分偶发错误对串口通信来说非常实用。3.2 一个稳定同步的串口状态机解析代码网上很多教程只拿Serial.read()傻读遇到0x55就开始攒数据这样在只解析一种包的时候勉强能跑但稍微干扰一下就会错位。JY61P默认同时输出三种包如果只盯着0x53那么0x51和0x52包里某个字节恰好是0x55时就可能被误判成帧头导致后续解析彻底乱掉。所以我在代码里用了完整的包状态机先等0x55读到后立刻读包类型根据包类型确定这一包总长度然后完整读完整个包。0x51和0x52包读进来之后不解析直接丢弃用来维持帧同步0x53包读完后做校验和通过就解析出一个角度。#include Arduino.h #define RXD2 16 #define TXD2 17 float roll 0, pitch 0, yaw 0; bool isValidFrame(byte *buf, uint8_t len) { byte sum 0; for (uint8_t i 0; i len - 1; i) sum buf[i]; return sum buf[len - 1]; } void setup() { Serial.begin(115200); Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); Serial.println(JY61P UART2 Test); } void loop() { static uint8_t state 0; static uint8_t buf[11]; static uint8_t idx 0; static uint8_t expectLen 0; while (Serial2.available()) { byte b Serial2.read(); if (state 0) { // 等待帧头 if (b 0x55) { buf[0] b; state 1; } } else if (state 1) { // 读取包类型 buf[1] b; if (b 0x51 || b 0x52) { expectLen 11; idx 2; state 2; } else if (b 0x53) { expectLen 9; idx 2; state 2; } else { // 不是已知包类型重新同步 state 0; } } else if (state 2) { // 读满整包 buf[idx] b; if (idx expectLen) { if (buf[1] 0x53 isValidFrame(buf, expectLen)) { int16_t rollRaw (buf[3] 8) | buf[2]; int16_t pitchRaw (buf[5] 8) | buf[4]; int16_t yawRaw (buf[7] 8) | buf[6]; roll rollRaw / 100.0f; pitch pitchRaw / 100.0f; yaw yawRaw / 100.0f; Serial.printf(Roll%7.2f Pitch%7.2f Yaw%7.2f\n, roll, pitch, yaw); } state 0; } } } }注意int16_t的转换。如果直接用int接收再用左移拼起来在高位字节带符号扩展时会出问题。这里把buf[3]左移8位之后强制转换为int16_tC会正确处理正负号。你看到的角度偶尔跳变到三百多度多半就是符号转换写错了。这个状态机的优点是不管前面有没有杂波只要收到一个合法帧头就能按包类型把整帧读完整然后继续下一个帧头抗干扰能力比“只找0x55然后直接当角度包”强很多。4. WiFi上行从ESP32到电脑的三种通信方案对比4.1 TCP / UDP / WebSocket怎么选ESP32把数据读到之后接下来就是如何通过WiFi送到电脑。我体验过三种方式简单说说各自的取舍TCP是最容易想到的方案面向连接数据可靠不会丢包但它的缺点是连接维护比较复杂。电脑端上位机一重启TCP连接就断了ESP32必须处理断线重连否则数据流就停了。而且TCP有确认和重传机制在WiFi环境差的时候反而可能因为重传堆积导致延迟增大。UDP则简单粗暴ESP32只管往电脑IP和端口上发数据报不管对方有没有收到。姿态数据是高频小包数据隔20毫秒就有一帧偶尔丢一帧完全没有感知下一帧马上就补上了。UDP不需要连接电脑端程序晚启动半分钟ESP32也不会报错这个特性对调试来说太舒服了。WebSocket则适合“最终要给浏览器看”的场景比如你要做一个网页版姿态仪表盘那 WebSocket 是最顺的。但成本也最高ESP32端要用WebSocket库电脑端要起一个WebSocket Server浏览器再连Server链路明显更长。我的建议是先跑通UDP这是最低成本的链路验证。后面确定需要浏览器可视化再在电脑端加一层Python WebSocket代理即可ESP32端不用改。方案可靠性实现复杂度适用场景TCP高中要求不丢包、需要确认的命令场景UDP中低高频传感器数据流、实时预览WebSocket高高浏览器页面直接显示姿态4.2 ESP32 UDP发送端代码在前面的状态机基础上加入WiFi连接和UDP发送。先把需要包含的头文件加上然后在setup里连接WiFi。#include WiFi.h #include WiFiUdp.h const char* ssid 你的WiFi名称; const char* password 你的WiFi密码; const char* host 192.168.1.100; // 电脑在局域网内的IP const uint16_t udpPort 8888; WiFiUDP udp;setup函数里在串口初始化之后连接WiFivoid setup() { Serial.begin(115200); Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); WiFi.begin(ssid, password); Serial.print(Connecting to WiFi); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(); Serial.print(ESP32 IP: ); Serial.println(WiFi.localIP()); }发送部分在原来解析到合法角度包之后将三个角度拼成字符串用UDP发出if (buf[1] 0x53 isValidFrame(buf, expectLen)) { int16_t rollRaw (buf[3] 8) | buf[2]; int16_t pitchRaw (buf[5] 8) | buf[4]; int16_t yawRaw (buf[7] 8) | buf[6]; roll rollRaw / 100.0f; pitch pitchRaw / 100.0f; yaw yawRaw / 100.0f; char packet[64]; snprintf(packet, sizeof(packet), %.2f,%.2f,%.2f, roll, pitch, yaw); udp.beginPacket(host, udpPort); udp.print(packet); udp.endPacket(); }这里我用了最简单的字符串格式电脑端split一下就能拿到三个浮点数。你也可以把三个float复制成4字节二进制再发更节省流量但调试时不方便肉眼观察。我的原则是先保证数据可读再考虑优化格式。IP地址有一个关键点电脑的IP最好在路由器后台设置为固定地址比如192.168.1.100否则电脑重启之后IP变了ESP32就找不到目标了。另外Windows防火墙可能默认拦截UDP接收第一次运行接收程序时如果收不到任何数据先检查防火墙或者临时关闭防火墙测试。5. 电脑端接收与可视化用Python把姿态数据显示出来5.1 一个极简的UDP接收程序电脑端我用Python来收UDP因为不需要装额外依赖一个脚本就能跑。环境要求就是Python 3不用装第三方库标准库socket足够。import socket udp_ip 0.0.0.0 udp_port 8888 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((udp_ip, udp_port)) print(UDP server listening on, udp_port) while True: data, addr sock.recvfrom(256) text data.decode(utf-8, errorsignore).strip() if text: parts text.split(,) if len(parts) 3: roll, pitch, yaw map(float, parts) print(fRoll{roll:7.2f} Pitch{pitch:7.2f} Yaw{yaw:7.2f})把ESP32上传到开发板接好JY61P给板子供电然后再运行这个Python脚本。你会看到屏幕上以固定的节奏刷出三行角度数。只要你转动传感器数字会跟着变化这说明整条链路已经通了传感器 → 串口 → ESP32解析 → WiFi → UDP → 电脑接收。如果你有USB线也可以同时打开Arduino IDE的串口监视器看ESP32本地打印的角度。两个数据源做对比能快速判断是传感器解析问题还是WiFi传输问题。比如串口监视器有输出但Python收不到问题一定出在WiFi或UDP环节如果串口监视器都乱码就先查传感器接线和波特率。5.2 从打印数字到可视化一个轻量的姿态可视化思路数字刷屏看久了会比较枯燥而且很难直观判断传感器跟实际姿态是否一致。我建议下一步做个简单的可视化。不要一上来就搞3D模型先做一个平面仪表盘就够用了。一种快速做法是使用Python的matplotlib动态画一个小飞机符号或者一个圆形水平仪。但matplotlib的刷新速度不太高姿态数据到50Hz以上时显示容易卡顿。另一种做法是用turtle写起来极简适合展示单轴角度。如果你只是想记录数据用来回放直接在接收程序里加三行写CSV的代码import time with open(attitude.csv, a) as f: f.write(f{time.time():.3f},{roll:.2f},{pitch:.2f},{yaw:.2f}\n)记录一段时间后用Excel打开CSV画曲线可以非常清楚地看到传感器静态漂移、动态响应和突变。这个操作对后续分析很有帮助而且几乎不占额外成本。真正做到更炫的3D姿态显示一般是用Processing或者Three.js。但那属于可视化扩展了和传感器通信链路是解耦的。你只需要让电脑端的接收程序把数据转发给一个WebSocket Server浏览器里跑Three.js就能渲染出3D模型。ESP32端完全不用改。6. 联调中的坑与我的排查经验6.1 串口读到全FF或全0这是最常见的问题。全FF通常是RX和TX接反了。串口是交叉连接JY61P的TX要接ESP32的RXJY61P的RX要接ESP32的TX。很多开发板的丝印会在引脚旁边标注但不同厂家的板子标注方式不一样插杜邦线之前最好拿万用表确认一下。全0通常是没共地或者JY61P供电异常。遇到这种问题我的排查步骤是先量JY61P的VCC和GND之间电压确认有3.3V左右。再量TX引脚有没有电平跳变。没有示波器可以用万用表虽然看不全波形但至少能确认不是一直低电平。然后把JY61P拆下来接USB转TTL用电脑串口助手看模块本身有没有输出。这样可以把问题快速限定在传感器还是ESP32侧。6.2 数据能收到但角度一直不变或跳变角度一直不变先别怀疑代码看看传感器是不是被固定住了或者你只朝一个方向转动而它刚好输出不变。如果确定在动但数值完全不动再去查代码里的变量有没有被正确更新比如是不是不小心在解析函数里用了局部变量而没有付给全局变量。角度跳变一般有两个原因。第一是符号数转换错误int16_t没写对负数变成很大的正数表现出来就是偶尔跳到三百多度又跳回来。第二是供电不稳ESP32在WiFi发射瞬间功耗会突然增大如果3V3被拉低JY61P工作状态会不正常。这时最好外接一个5V电源或者用质量好一点的USB线不要靠电脑USB口长时间供电。还有一个常见原因是你手上这块JY61P被配置成了别的量程或输出频率数据帧里的缩放系数和你代码里的除以100对不上。先通过官方上位机读一下模块配置确认量程之后再做相应换算。6.3 WiFi有延迟和丢包怎么办先分清楚延迟是WiFi本身的还是UDP丢包导致的。你可以先在电脑上ping一下ESP32的IP地址ping 192.168.1.123如果ping值稳定在几毫秒到十几毫秒说明网络环境基本健康。如果延迟到了一两百毫秒甚至更高问题大概率出在路由器或者WiFi信道拥挤上。ESP32的WiFi只支持2.4G频段而2.4G频段在办公区、居民楼里非常拥挤解决办法是去路由器后台换一个空闲信道或者尽量让ESP32和电脑靠近同一个路由器减少障碍物。如果ping也没问题但UDP丢帧比较多我建议优先怀疑供电而不是网络。ESP32在WiFi发送瞬间电流尖峰很大我用劣质USB线的时候UDP丢包率能到百分之十几而且串口数据偶尔出现校验错误。换成短线供电之后丢包直接降到肉眼看不到的程度。发送频率也要控制。JY61P的默认输出频率不低但你不一定每一帧都要发。比如控制一帧间隔50毫秒也就是20Hz对大部分姿态预览和记录已经够用。发送太多帧只会增加WiFi压力和电脑接收负担。可以在代码里加一个简单的节流static unsigned long lastSend 0; if (millis() - lastSend 20) { lastSend millis(); // 这里执行UDP发送 }6.4 帧同步把0x55当数据怎么办这是串口解析里最容易踩的坑。如果只简单判断0x55开头的字节流那么任何一包数据中如果碰巧出现0x55就可能被当成下一帧的帧头导致错位。我在3.2节里用了状态机每收到一包完整的帧再做类型判断和校验和这样即使某一帧里有0x55也会因为包长度和校验和不符合而被排除。还有一个容易忽略的点ESP32的串口接收缓冲区如果长期积压old数据没被及时读走解析程序读到的就可能是一个延迟后的数据帧。如果你在loop里做其他耗时操作比如WiFi断线重连、写入Flash会导致串口数据积压。做法是把解析放到一个优先级较高的位置或者把耗时操作放到定时器回调里。实测下来只要不在loop里做大于几十毫秒的阻塞操作115200波特率下不会积压。7. 一些能直接套用的经验最后分享几个我在这个项目上沉淀下来的习惯算给后面做类似无线传感采集的人一个参考。安装要减震JY61P对振动很敏感贴在电机旁边的时候尽量用铜柱加橡胶垫固定不要用电工胶布直接缠。不然静态角度会随着振动一直飘。数据加时间戳ESP32发UDP时可以在每帧字符串前面加上毫秒时间戳比如用millis()。电脑端拿到之后就能算真实采样间隔也能判断丢包率。这个改动很小价值却很大。电脑端固定IP路由器DHCP给电脑自动分配的IP随时可能变在路由器后台给电脑绑定IP保留ESP32里的host地址就能一直稳定。我一开始没固定IP电脑重启后ESP32就发错目标排查了半小时。保留本地串口打印即使WiFi已经通了我也强烈建议保留Serial.printf输出。调试的时候插上USB线就能看到本地数据不用总是猜是WiFi问题还是传感器问题。做到这里一套“ESP32读取JY61P姿态数据通过WiFi无线发送到电脑实时显示”的最小系统就完整了。后面想升级把数据发到Node-RED、存入数据库、接上ROS都只是在现有链路上再加一层解析的事情。我自己现在做姿态采集验证项目基本都是这一套组合换不同协议直接复用省下来的时间足够多写好几个测试用例了。
返回列表