【瑞萨BLE/WI-FI模块测评】超声波双通路(二)——距离数据怎么刷到浏览器上
上一篇把硬件接线和超声波测距讲完了,板子上电后LED1和LED2两个蓝灯亮着,说明WiFi和TCP都连上了。这篇讲中间这段链路:MCU怎么把距离数据通过DA16200发到PC上,浏览器又是怎么实时显示的。
整体数据通路
先看一张图,整个WiFi链路从MCU到浏览器经过了哪些环节:

WiFi数据通路
核心设计决策:DA16200作为TCP Client主动连PC,而不是反过来让PC连模块。 原因很现实——开发板通过路由器拿到的是局域网IP,但路由器一般不开端口映射,PC主动去连板子的IP可能根本连不上。反过来板子主动往外连PC的9000端口,只要PC和板子在同一个路由器下就行,不用配任何东西。
DA16200入网和TCP连接流程
DA16200跑的是Dialog的AT固件,入网流程我是一步步对着AT手册调通的:
1. ATZ 复位模块
2. ATE0 关回显
3. AT+WFMODE=0 设为STA模式
4. AT+WFJAP=ssid,sec,pwd 连接WiFi
5. 等 +WFJAP:1 URC上报,表示拿到IP了
6. AT+TRTC=PC_IP,9000 发起TCP连接
这里有个细节:AT+TRTC返回值里会带一个CID(连接ID),后面发数据要用到。模块支持同时开多个socket,每个socket有个编号,一般就是1。
连不上的时候重试3次,每次间隔500ms。实际用下来,如果路由器正常,基本一次就成。
ESC-S帧格式——Dialog特有的坑
这个要重点说一下,因为和普通WiFi模块直接发文本不一样。
DA16200的AT固件,发TCP数据不是直接把字符串往UART里丢,而是用一个叫ESC-S的帧格式:
x1b S < 长度 >,0,0, < 实际数据 >
也就是一个转义字符0x1B,跟着字母'S',然后是连接ID、数据长度,逗号分隔,最后才是真正要发的payload。
举个实际例子,我要发一段JSON:
{"type":"telemetry","dist_cm":25,"raw_cm":26,"threshold_cm":30,"alarm":0,"enabled":1,"valid":1,"fake":0}
实际通过UART发给模块的字节流是:
x1bS1 78,0,0,{"type":"telemetry",...}
模块收到这个帧之后,才会把后面78字节的payload通过TCP socket发出去。
为什么要搞这么麻烦?因为AT命令本身也是走UART的,如果直接发TCP数据,模块怎么知道这是要发的数据还是新的AT命令?用ESC转义帧就是为了把"AT命令"和"数据"区分开。
接收方向反过来,模块收到TCP数据后会通过URC上报:+TRDTC:, <长度> rn <数据> 。MCU这边要解析这个URC,把后面的数据抠出来。 数据> 长度>
MCU端的JSON收发
遥测上行很简单,每100ms组一段JSON发出去:
snprintf(json, sizeof(json),
"{"type":"telemetry","dist_cm":%u,"raw_cm":%u,"
""threshold_cm":%u,"alarm":%u,"enabled":%u,"
""valid":%u,"fake":%u}n",
filtered_cm, raw_cm, threshold, alarm, enabled, valid, fake);
组好之后套上ESC-S头,通过SCI0的环形缓冲区发出去。发完之后留40ms时间窗口收下行数据。
下行命令的解析稍微麻烦点。模块UART里混着各种东西:AT命令的OK回复、+TRDTC: URC前缀、JSON命令本身。我写了个累加器,每次收到字节就往里塞,然后扫描里面有没有完整的{...}对象——找到一个花括号开始,匹配到对应的花括号结束,中间就是一段完整JSON。这样即使UART一次只收到半个JSON也不怕,等下一批数据到了再拼起来。
支持的下行命令就三个:
{"cmd":"set_threshold","value":30} // 改阈值
{"cmd":"set_enable","value":1} // 开关报警
{"cmd":"ack_alarm","value":0} // 消警
PC端:一个脚本同时干三件事
PC上跑的是Python FastAPI脚本,总共就一个server.py文件,但它同时开了三个服务:
1. TCP Server监听9000端口:接受MCU的TCP连接,收JSON遥测,广播给所有WebSocket客户端
2. HTTP Server监听8000端口:提供网页静态文件
3. WebSocket端点/ws:浏览器连上来,实时推数据
网页正常状态
可以看到网页上大数字显示当前距离15cm,青绿色调,表示没有报警。右边是阈值滑条和四个控制按钮。顶部几个小标签显示WiFi、TCP连接状态、当前是传感器还是假数据模式、报警状态。
收到MCU的JSON之后,PC端做的事情很简单:解析JSON,更新latest_state字典,然后广播给所有连着的WebSocket浏览器。浏览器那边收到就更新页面上的大数字。整个延迟基本就是WiFi网络延迟,人眼看不出来。
下行命令的可靠性问题
这里有个实际遇到的问题:TCP是MCU主动连过来的,PC端理论上可以直接往这个TCP连接里写数据。但实际测下来,有时候MCU正在处理AT命令或者正在发数据,下行包可能会丢。
我做了两层保障:
第一层:TCP直接推。 浏览器点了按钮,PC端立刻往所有连着的TCP连接写JSON命令。同时PC端把这个命令存下来,每收到一次MCU的遥测就检查一下——如果遥测里的状态和命令要求的一致(比如命令是set_threshold=35,遥测里threshold_cm已经变成35了),说明MCU收到了,就不再重发。
第二层:HTTP拉取兜底。 如果TCP推了25次MCU都没响应(大概2.5秒),或者干脆TCP断了,MCU每4秒会主动做一次HTTP请求:断开TCP遥测连接,用AT+TRTC连PC的8000端口,发一个GET /api/pending,看有没有待处理的命令。有就执行,然后重新连上9000继续推遥测。
这个设计有点"双重保险"的意思。平时命令走TCP秒到,万一TCP下行堵了,最多等4秒HTTP拉一次也能补上。代价是HTTP拉取会打断遥测推送(因为TCP连接要重建),所以间隔设得比较长。
网页界面和报警效果
前端就是一个单HTML文件,没框架没构建工具,直接写的原生JS。
界面做了个深色雷达风格:中间一个大数字显示距离,背景有几圈扩散的涟漪动画(纯CSS keyframes),正常状态是青绿色调,报警的时候整个页面变成暗红色,大数字变红色,顶部弹出"距离超限"的横幅还带抖动动画。
右边面板是控制区:一个滑条调阈值,下面四个按钮——下发阈值、消警、使能报警、关闭报警。滑条拖动的时候只改本地值,不会立刻发给MCU,必须点"下发阈值到MCU"才写进去。这样防止拖滑条的时候疯狂发命令。
手伸到超声波前面,距离低于30cm阈值,网页立刻变成红色报警状态:

网页报警状态
可以看到数字变成红色,顶部有红色横幅提示"距离超限",整个背景都变暗红了。手拿开距离变远,网页自动恢复青绿色。
远距离的时候数字也正常跳,我把手拿开到1米多,网页显示175cm:
远距离显示
手机和电脑连同一个WiFi,直接在手机浏览器里输PC的IP:8000也能看,响应速度和电脑上一样。这个挺实用的——调试的时候不用盯着电脑屏幕,拿手机在旁边看着数字变化就行。
跑起来需要几步
实际用的时候操作顺序是:
1. PC和开发板连同一个路由器,查PC的局域网IP
2. 改app_config.h里的WEB_SERVER_HOST成PC的IP,编译烧录
3. PC上双击start.bat,它会自动装依赖跑FastAPI
4. 板子上电,等LED1(WiFi)和LED2(TCP)亮
5. 浏览器打开http://PC_IP:8000/
整个过程不需要在PC上装任何开发环境,Python脚本跑完就一直在后台跑着。手机浏览器也能访问。
下一篇讲BLE通路:DA14531怎么用CodeLess固件透传,nRF Connect怎么连接和收发数据,以及两个通路实际用下来的对比和我的结论。
审核编辑 黄宇





