省级大创项目 · 面向药液有效性检测的智能组网监测设备
隶属于省级大创项目"面向药品有效性的射频微传感测试系统",以 GPS 定位数据为示范载荷,完成了从 STM32L0 设备端、CT02 4G LTE 模组到 MQTT 物联网平台的全栈数据链路打通,构建了完整的 IOT-MQTT 数据环路。平台侧基于 Node.js 集成 Aedes Broker、Express、Socket.IO 和 SQLite;设备侧以 DMA+IDLE 中断机制实现高可靠 AT 固件守护,并配合 TJC 串口屏与低功耗休眠策略,兼顾现场交互与续航需求。
摘要
本工程隶属于省级大学生创新创业训练计划项目"面向药品有效性的射频微传感测试系统"。该项目旨在利用射频微传感技术实现药液组分的非接触式检测,并通过物联网体系将传感器分析结果实时上报至远程平台,以提升药品安全监管与药液质量控制的智能化水平。本报告所述工作作为该项目的物联网工程 Demo,当前以 GPS 定位数据为示范载荷,完成了从 STM32L0 设备端、CT02 4G LTE 模组到 MQTT 物联网平台的全栈数据链路打通,构建了完整的 IOT-MQTT 数据环路。
在平台侧,本工程基于 Node.js 开发环境,集成 Aedes MQTT Broker、Express Web 服务、REST API、Socket.IO 实时通道和 SQLite 本地数据库,实现了设备接入认证、主题解析、GPS 数据识别、状态持久化、命令队列与 ACK 闭环、离线命令补发以及 Web 控制台实时可视化等功能。在设备侧,STM32L0 微控制器通过 USART2 DMA 循环接收与 IDLE 中断排空机制,构建了可靠的 CT02 AT 固件守护程序,完成 MQTT 连接维护、GPS 冷启动与定位质量过滤、周期性上报、平台下行命令解析与执行等任务;同时通过 USART1 驱动 TJC 串口屏实现本地状态显示与参数配置,并配合 PA4 外部中断与 PA5 屏幕供电控制实现休眠/运行状态切换,兼顾现场交互与低功耗需求。
本工程的意义在于:当前阶段以 GPS 数据验证并固化了端到端的物联网通信架构、平台协议规范、设备端守护机制以及上下行命令闭环流程;待后续射频微传感器完成标定后,仅需将设备端的上报载荷由 GPS 坐标替换为药液组分分析数据,即可直接复用现有 MQTT 主题体系、平台解析逻辑和 Web 可视化框架,实现传感器分析结果的远程实时监控与历史追溯,为项目整体目标的达成提供了可直接迁移的工程基础与验证平台。
项目背景与总体架构
本工程的 MQTT 物联网平台是一个集成式物联网服务端,运行设备是一台高性能个人闲置笔记本(CPU:Intel Core i5-13420H,操作系统:Linux Ubuntu 24.04),使用 FRP 内网穿透技术将此上的 6010/6011 端口与阿里云服务器(IP: 120.26.111.75,带宽 200 Mb/s)的对应端口进行配置连接,并通过实际的 4G LTE/GPS 设备(大夏龙雀 AT 固件,型号:CT02)试验验证了其功能的完备与有效性。
本工程设计的 MQTT Node.js 进程中,同时启动 MQTT Broker、HTTP Web 服务、REST API、Socket.IO 实时通道和 SQLite 本地数据库。设备端通过 MQTT 协议接入,浏览器端通过 HTTP 和 WebSocket 访问,平台后端在两者之间完成数据解析、状态维护、命令下发和实时刷新。平台运行阶段,Aedes 库创建 MQTT Broker,监听在 127.0.0.1:6010 端口;Express 创建 Web 服务,监听在 127.0.0.1:6011 端口;Socket.IO 挂载在同一个 HTTP 服务上,用于把设备上线、离线、GPS 更新和命令 ACK 实时推送到浏览器;SQLite 数据库访问代码负责持久化设备、消息、命令任务和离线命令队列。部署阶段,PM2 持久化守护配置代码负责服务自动重启、日志写入和运行环境参数加载。
| 层次 | 实现模块 | 主要职责 |
|---|---|---|
| 服务启动与集成 | 主服务启动代码(.JS) | 创建 Aedes Broker、Express 服务、Socket.IO 通道,处理 MQTT 事件、REST 路由、命令队列和定时任务 |
| 配置读取 | 平台参数配置代码(.JS) | 从环境变量和默认值读取 MQTT 端口、Web 端口、账号密码、离线 TTL、命令超时、高德地图 Key 和数据库路径 |
| 数据持久化 | SQLite 数据持久化代码(.JS) | 初始化 SQLite 表结构,保存设备状态、消息记录、GPS 轨迹、命令任务和离线待发命令 |
| 协议解析 | MQTT 协议解析代码(.JS) | 解析 ct02/... 主题、JSON、GPSST 文本、NMEA 报文、状态报文和 requestId |
| 前端页面 | 控制台页面结构代码(.HTML) | 定义设备表、GPS 控制、地图轨迹和消息流四个主要视图 |
| 前端逻辑 | 浏览器交互逻辑代码(.JS) | 调用 REST API,监听 Socket.IO 事件,渲染设备、命令结果、消息列表和 GPS 轨迹 |
| 样式与部署 | 样式与进程配置 | 提供控制台界面样式,以及 PM2 进程守护、日志和环境变量配置 |
MQTT 服务器平台设计
MQTT 协议的基本原理
MQTT(Message Queuing Telemetry Transport)是一种面向物联网场景的轻量级应用层通信协议,通常运行在 TCP 连接之上。它的设计目标不是传输大文件或复杂网页内容,而是在网络带宽有限、设备算力有限、连接可能不稳定的条件下,尽量用较小的协议开销完成设备状态、传感器数据和控制命令的传递。因此,MQTT 常用于 GPS 定位器、环境传感器、工业采集终端和远程控制节点等场景。
MQTT 最重要的思想是发布/订阅模型。传统 HTTP 通信通常是客户端主动请求、服务器返回响应;MQTT 则把通信双方都看作客户端,中间由 Broker 负责消息转发。发布者只需要把消息发送到某个主题,订阅者只需要声明自己关心哪些主题,二者不需要知道彼此的 IP 地址、连接状态和具体数量。这样可以降低设备端耦合度:一个设备发布 GPS 数据时,不必直接连接网页、数据库或其他业务程序;平台中新增一个订阅者时,也不需要修改设备端固件。
MQTT 通信中的三个基本元素是客户端、主题和负载。客户端可以是传感设备、后端服务、调试工具或网关程序;主题用于描述消息的分类路径,常用斜杠分层,例如 factory/line1/temperature;负载则是主题下携带的具体内容,可以是 JSON、普通文本、二进制数据或设备厂商自定义格式。MQTT 协议本身并不规定负载的业务结构,只负责把负载作为消息体传递给订阅者,因此具体字段含义、单位、数据有效性和命令语义都需要由上层应用协议定义。
| 机制 | 基本含义 |
|---|---|
| Broker | MQTT 消息中心,负责接收客户端连接、维护订阅关系,并把发布消息转发给匹配的订阅者 |
| Topic | 消息主题,用分层字符串描述消息类别,订阅端可按完整主题或通配规则接收消息 |
| Payload | 消息负载,协议层只把它视为字节序列,实际格式由业务系统自行约定 |
| QoS | 服务质量等级,包括 0、1、2 三级,分别对应最多一次、至少一次和只有一次的投递语义 |
| Retain | 保留消息机制,Broker 可保存某主题最后一条保留消息,使新订阅者立即获得当前状态 |
| Keep Alive | 心跳保活机制,客户端周期性与 Broker 确认连接仍然有效,用于发现异常断线 |
| ClientID | 客户端标识,Broker 用它区分不同连接,并可结合会话机制恢复订阅或状态 |
| Will Message | 遗嘱消息,客户端异常掉线时由 Broker 代为发布,常用于通知其他系统设备离线 |
后端运行设计
后端运行设计可以按实现职责拆成六个部分:服务启动与参数装配、MQTT 接入与身份约束、协议解析、状态与数据持久化、命令队列与 ACK 闭环、定时维护与进程守护。这样划分后,平台不是一个单纯的 MQTT 转发器,而是一个能够保存状态、追踪命令、判断在线情况并实时驱动网页刷新的物联网服务端。
服务启动与参数装配:主服务启动代码(.JS)是后端的统一入口。它在同一个 Node.js 进程中创建MQTT Broker、HTTP 服务、REST 路由、Socket.IO 实时通道和 SQLite 访问对象,因此设备通信、网页访问和数据库读写不需要由多个独立程序手工拼接。平台参数配置代码(.JS)负责把运行参数集中化以方便迁移、开源与调试。配置值优先来自运行环境变量,缺省时使用工程内置默认值。
MQTT 接入与设备身份约束:MQTT 接入模块由 Aedes Broker 实现。设备连接时需要提交用户名和密码,认证成功后才允许建立会话。会话建立完成后,平台把 MQTT ClientID 作为设备 ID 写入设备状态表,并记录 IP 地址、上线时间和连接次数。设备发布消息时,平台还会进行主题身份校验。按照工程约定,主题格式为 ct02/设备ID/通道名,其中设备 ID 必须与 MQTT ClientID 一致。这个约束可以防止不同设备之间串号,也让数据库中的设备记录、消息记录和命令记录始终围绕同一个设备 ID 归档。
协议解析与 GPS 数据识别:MQTT 协议解析代码(.JS)先处理主题,再处理 payload。不符合 ct02/设备ID/通道名 格式的主题会被作为普通消息或协议异常记录。payload 进入后端后先统一转成 UTF-8 字符串,再尝试按 JSON 解析;如果不是 JSON,也会继续按 CT02 文本格式和 NMEA 语句进行识别。GPS 识别是该模块的重点:对于 JSON 数据,平台优先读取根字段或 GPS 对象中的经纬度、高度和定位有效标志;对于 CT02 模块直接输出的 +GPSST 文本,平台会解析定位状态、卫星信号、经度、高度和纬度;对于 NMEA 报文,平台兼容 RMC 与 GGA 两类定位语句,并把度分格式转换为十进制度。只有定位有效且经纬度均为合法数值时,后端才把该消息作为 GPS 轨迹点写入数据库并广播 gps.update。
状态记录与数据持久化:SQLite 数据持久化代码承担平台"记忆"的作用。数据库主要包括四类表:设备表、消息表、命令任务表和离线命令队列表。平台每收到一条业务消息,都会先更新设备最近活跃时间,再把消息写入消息表。消息如果为有效 GPS 坐标,还会同步更新设备表中的最后经纬度和最后定位时间。前端查询设备列表时读取设备表,查询消息流时读取消息表,查询轨迹时只读取带有有效经纬度的消息记录。因此,设备状态、历史消息和地图轨迹三类页面数据都来自同一套持久化记录,避免了内存状态与历史记录不一致的问题。
命令队列与 ACK 闭环:命令下发模块由 REST 接口、MQTT 发布逻辑、命令任务表和离线命令队列共同组成。浏览器提交 GPS 控制命令后,后端为命令生成或补齐 requestId,同时补齐时间戳,并把该命令纳入统一的任务跟踪流程。因此,这一部分不是单纯的"发出一条 MQTT 消息",不是类似于 UDP 的"只发不问",而是围绕命令全周期建立了一套可查询、可追踪、可补发、可判定超时的控制链。
REST API 是网页对后端发起的普通 HTTP 请求接口。POST /api/devices/:id/commands 用于接收前端提交的控制命令:若设备在线,则立即写入命令任务并通过 ct02/设备ID/down 主题下发,返回 deliveryMode=immediate;若设备离线但允许排队,则把命令写入离线命令队列并返回 deliveryMode=queued;若设备离线且禁止排队,则接口直接返回拒绝结果。设备恢复在线后,队列刷新逻辑会查找该设备尚未过期且重试次数未超限的命令,并按创建时间顺序重新发布。ACK 闭环依赖 requestId:设备执行命令后向 /ack 通道发布应答,后端从 ACK 内容中提取 requestId,再更新对应命令任务的 ACK 时间、ACK 内容和任务状态,并广播 command.ack。如果超过命令超时时间仍未收到 ACK,定时任务会把命令标记为 timeout 并通知前端。
实时推送、定时维护与进程守护:实时推送模块只负责把后端已经产生的状态事件转换为浏览器可见的增量通知。后端收到设备上线、离线、消息入库、GPS 更新、命令发送、命令 ACK、命令超时、离线命令入队和命令过期等事件后,都会通过 Socket.IO 广播给前端。后端还包含三类周期性维护任务:命令超时扫描、离线队列扫描(补发+清理)、设备在线兜底扫描。PM2 持久化守护配置代码则负责生产环境中的进程启动、自动重启、日志输出和环境变量注入,使平台能够在服务器上长期运行。
前端运行原理(Web 控制台)
前端 Web 控制台是浏览器侧的人机交互层,不直接作为 MQTT 客户端连接 6010 端口,而是通过 Web 服务访问后端已经整理好的设备状态、消息记录、GPS 轨迹和命令任务。前端的核心作用是把后端的 MQTT 事件、数据库记录和命令状态转化成可查看、可筛选、可操作的页面模块。
页面结构可以按"状态显示、命令输入、轨迹显示、消息审计"四类功能进行划分。页面顶部显示 MQTT 平台状态、WebSocket 连接状态和在线设备数量;设备在线状态区域显示设备 ID、在线状态、首次接入、最后在线、连接次数、断线原因和最后坐标;GPS 控制与命令区域把常用 gps.control 动作封装成按钮和输入框;GPS 轨迹区域把后端返回的轨迹点渲染到高德地图或坐标列表;消息流区域显示上行、下行消息的原始 payload 和 JSON 解析结果。
前端启动流程不是一次性静态页面展示,而是一个"加载配置、获取快照、建立实时监听、按事件局部 Socket.IO 刷新"的过程。浏览器加载 app.js 后,先应用主题设置,再请求 /api/frontend-config 读取 GPS 控制模板、命令超时参数、离线队列参数和地图配置;随后初始化地图显示能力,调用 /api/devices 取得设备列表,并根据当前选中设备继续读取消息、轨迹和实际上报间隔。初始快照加载完成后,前端建立 Socket.IO 连接,监听后端广播的设备、消息、GPS 和命令事件。这样做的好处是:页面第一次打开时依赖 REST 接口取得完整状态,之后主要靠 Socket.IO 事件触发局部刷新,避免用户频繁手动刷新。
下行控制链路从页面操作开始。用户在设备选择器中确定目标设备,点击"查看 GPS 状态、开启 GPS、关闭 GPS、立即抓取快照、设置上报间隔"等按钮后,前端根据配置接口返回的模板生成 gps.control 负载,并通过 REST 命令提交接口发送给后端。后端决定命令是立即发布还是进入离线队列,并把命令任务返回给页面;如果设备在线,后端继续向 MQTT 下行主题发布命令;如果设备离线,页面显示已排队状态。随后设备返回 ACK 或后端判定超时、过期时,Socket.IO 事件会把任务状态推给浏览器,命令结果框随之更新。
上行显示链路则从设备消息开始。CT02 或 STM32L0 设备把 GPS 或状态数据发布到 MQTT 主题后,后端完成主题解析、payload 解析、数据库写入和事件广播。前端收到 message.in 时刷新消息流,收到 gps.update 时刷新轨迹和实际上报间隔,收到 device.online/offline 时刷新设备表。Web 控制台采用的是"REST 接口提供当前状态,Socket.IO 提供实时触发,浏览器状态负责当前选择和局部渲染"的设计。
STM32L0 串口守护程序、GPS-AT 固件及显示设计
STM32L0 端程序承担的是现场设备侧的网关与本地交互功能。本 STM32L0 工程以两路串口为核心:一路是面向 CT02 GPS/4G 模块的 AT 指令守护通道,负责蜂窝网络、MQTT 连接、GPS 定位与上下行命令;另一路是面向 TJC 串口屏的显示与本地控制通道,负责将平台状态、定位间隔和运行状态同步到屏幕,同时接收屏幕侧的本地设置。除这两路串口外,系统还使用 PA3 片内 ADC 完成锂电池电量检测,并通过 PA4 外部中断和 PA5 屏幕供电控制脚实现"休眠/运行"状态切换。
从程序职责上看,USART2 是设备与远端 MQTT 平台之间的主数据链路,USART1 只服务于本地人机界面,两者不复用同一串口;这样的划分可以避免屏幕刷新或本地按键操作阻塞 CT02 模块的周期性 GPS 上报,也便于在省电状态下把屏幕整体下电而不影响后台定位发送。
总体接口与模块分工
| 接口 | 连接对象 | 主要功能 |
|---|---|---|
| USART2 PA2/PA15 | CT02 GPS/4G 模块 | 执行 AT 初始化、MQTT 连接、订阅下行主题、GPS 定位控制、周期性上报定位与状态;采用 DMA 接收、空闲中断等方式提高串口守护可靠性。 |
| USART1 PA9/PA10 | TJC 串口屏 | 发送屏幕变量刷新命令,显示平台地址、上报间隔、电池/运行状态等信息;接收屏幕侧设置帧并映射到设备本地控制逻辑。 |
| ADC PA3 | 锂电池分压采样 | 对 3.7V 单节锂电池经两个 100k 电阻分压后的电压进行 12 位 ADC 采样,换算电池电压并估算剩余电量。 |
| EXTI PA4 | 外部按键 | 检测下降沿中断,触发设备从省电状态进入一分钟运行窗口。 |
| GPIO PA5 | 屏幕供电光耦开关 | 控制屏幕供电,输出 1 表示打开屏幕,输出 0 表示关闭屏幕。 |
休眠/运行状态省电设计
在总体接口划分之后,需要进一步说明本设备的省电策略。这里的"休眠"不是让 STM32L0 和 CT02 整体进入深度掉电,而是把本地人机交互部分从后台通信链路中剥离出来:当现场无人操作时,屏幕供电被关闭,USART1 屏幕刷新和屏幕输入解析暂停;CT02 所在的 USART2 守护链路仍然保持运行,继续维护 MQTT 连接、GPS 定位缓存和周期性上报。上述省电设计的核心不是牺牲平台侧数据连续性,而是减少屏幕和本地交互在长期待机中的无效耗电。
系统将运行状态划分为省电休眠状态和一分钟运行状态。省电休眠状态面向长期无人值守,PA5 输出低电平,屏幕供电光耦关闭,屏幕背光和屏幕逻辑电源不再消耗主电池能量;主循环中不再主动排队发送 USART1 显示命令,也不依赖屏幕侧设置帧完成后台通信。与此同时,USART2 CT02 守护任务仍按周期推进,GPS 样本缓存、MQTT 持续联通有效、下行主题监听和自动上报逻辑保持有效。
一分钟运行状态面向现场查看和临时配置。PA4 按键触发下降沿后,外部中断只做轻量事件置位,真正的状态切换放在主循环或调度函数中完成。进入运行状态时,PA5 输出高电平,屏幕重新上电;系统记录一个一分钟后的超时时间点,并恢复 USART1 接收解析、屏幕变量刷新和本地设置处理。在这一分钟内,用户可以通过 HMI 串口屏幕,查看平台地址、MQTT 连接状态、GPS 坐标、上报次数、电池状态和上报间隔,也可以通过屏幕修改定位上报周期。若运行状态下再次检测到 PA4 按键事件,系统不需要重新初始化全部外设,只刷新一分钟运行窗口,使连续操作期间屏幕保持点亮。
| 状态 | 本地显示与交互 | 后台通信与定位 | 主要耗电特征 |
|---|---|---|---|
| 省电休眠状态 | PA5=0,屏幕断电;USART1 显示刷新和屏幕输入解析暂停;本地用户暂不操作。 | USART2 CT02 守护继续运行;GPS 缓存、MQTT 连接维护、下行监听和周期上报不中断。 | 去掉屏幕逻辑电源和背光电流,只保留 MCU 基础运行、CT02 通信定位、电池分压采样等后台耗电;100k/100k 分压支路满电时约为 21 µA。 |
| 一分钟运行状态 | PA5=1,屏幕上电;USART1 恢复变量刷新和设置帧解析;允许查看状态和修改上报间隔。 | USART2 任务不因屏幕打开而暂停,仍按原周期处理 GPS/MQTT 业务。 | 在休眠态基础上叠加屏幕供电、背光和 USART1 刷新带来的电流,瞬时功耗更高,但只维持一分钟窗口。 |
USART2 CT02 GPS/4G 模块串口守护
CT02 模块通过 USART2 接入 STM32L0,是设备端与远端 MQTT 平台通信的核心通道。本部分不是把串口字节简单转发给服务器,而是在 MCU 内部实现了一套面向 CT02 AT 固件的守护程序:它负责串口收发、AT 命令排队、MQTT 连接确认、下行主题订阅、GPS 启动、定位质量过滤、周期上报、下行控制执行以及异常重连。CT02 承担 4G、GPS 和 MQTT AT 固件能力,STM32L0 承担"什么时候发命令、如何判断回复、如何把模块输出变成平台协议"的设备侧控制逻辑。
USART2 守护程序在工程实现上可以抽象为主函数调度层、守护接口层、串口传输层、连接服务层、业务动作层和数据解析层。主函数调度层负责按周期驱动任务,串口传输层负责 USART2 收发和 AT 命令时序,连接服务层负责把 CT02 维持在 MQTT 在线且已订阅下行主题的状态,业务动作层负责执行 GPS 启停、状态查询、间隔设置和快照上报,数据解析层则把模块输出转换为平台可识别的 GPS 样本和控制结果。
| 程序模块 | 主要职责 |
|---|---|
| 主函数与外设调度模块 | 初始化 USART2、DMA、ADC 和 USART1;在主循环中周期驱动 CT02 守护任务;实现 USART2 DMA 接收启动、IDLE 中断排空、错误恢复和 1.5 秒链路看门狗;同时把 CT02 上报样本转交给 USART1 屏幕刷新。 |
| 守护接口与状态上下文模块 | 定义守护上下文、GPS 样本、AT 状态、发布状态、动作状态、主题缓存、下行队列和统计计数器;同时定义缓冲区大小、重连退避、下行重组超时等关键参数。 |
| CT02 守护核心调度模块 | 提供守护初始化、串口绑定、接收字节入队和周期调度入口;每个周期按"收字节、驱动 AT、驱动发布、驱动动作、驱动服务状态机"的顺序推进。 |
| USART2 传输与 AT 命令模块 | 封装 USART2 发送、DMA 发送优先和阻塞发送兜底;封装 AT 命令启动、回复收集、提示符发送和超时结束。 |
| MQTT 连接服务模块 | 维护 CT02 的长期在线状态,包括探测 AT 口、查询 MQTT 状态、重连、订阅下行主题、处理下行队列、触发周期上报和定期健康检查。 |
| GPS 与控制动作模块 | 执行业务动作,包括开机 GPS 冷启动、周期 GPS 上报、状态查询、GPS 启停、设置上报间隔、单次快照和不支持命令的错误 ACK。 |
| 数据解析与 GPS 缓存模块 | 提供字符串处理、JSON 字段轻量解析、MQTT 主题构造、+MSUB 下行重组、GPS/NMEA 解析、定位质量过滤和经纬度修正。 |
USART2 硬件配置与接收链路:USART2 被配置为 CT02 专用串口,波特率为 115200,8 位数据位、1 位停止位、无校验、无硬件流控。管脚上,PA2 连接 USART2_TX,PA15 连接 USART2_RX;DMA1_Channel5 用于 USART2_RX,模式为循环接收,优先级为 DMA_PRIORITY_VERY_HIGH;DMA1_Channel4 用于 USART2_TX,模式为普通发送,优先级为 DMA_PRIORITY_MEDIUM。USART2 中断和 DMA 通道中断都被打开,因此串口守护可以同时利用 DMA 半满、DMA 满和 USART 空闲中断来及时取走模块输出。
上电初始化完成后,CT02_Guard_AppInit 会优先调用 CT02_Guard_StartDmaRx 启动 USART2 DMA 循环接收。该函数先停止旧 DMA,再以 128 字节缓冲区重新调用 HAL_UART_Receive_DMA,随后打开 USART2 的 IDLE 中断。IDLE 中断在 USART2_IRQHandler 中被识别:当检测到 UART_FLAG_IDLE 时,代码清除 IDLE 标志并调用 CT02_USART2_IdleHandler,最终进入 CT02_Guard_DmaRxDrain。该函数通过 DMA 剩余计数计算当前写入位置,把从上一次读取位置到当前位置之间的所有新字节逐个送入 ct02_guard_on_rx_byte。
这种"DMA 循环缓冲加 IDLE 排空"的方式适合 AT 模块。CT02 返回既可能是短的 OK、ERROR,也可能是较长的 +MSUB 下行 JSON 和 NMEA 定位语句。DMA 避免了每个字节都触发主程序处理,IDLE 中断又能在一段回复结束后尽快把残留字节取走。如果 DMA 启动失败,程序会退回到单字节中断接收 HAL_UART_Receive_IT;如果 USART2 出现错误,HAL_UART_ErrorCallback 会重新启动 DMA 或中断接收,并设置 100 毫秒后的重试时间。这个兜底逻辑保证了串口守护不会因为一次 DMA 启动失败就永久失效。
进入 ct02_guard_on_rx_byte 后,字节不会立即解析业务,而是写入 256 字节的 ISR 环形缓冲。主循环调用 ct02_guard_tick 时,ct02_drain_rx 再把环形缓冲中的字节按 \r 或 \n 切成行,并交给 ct02_process_line。行缓冲区长度为 512 字节,超过长度时计入 serial_rx_overflow 并丢弃当前行。这一层把"中断安全的字节接收"和"可能较复杂的 AT/GPS/MQTT 解析"隔离开,降低了中断中处理字符串和 JSON 的风险。
上电阶段的 MCU 初始化:设备上电后,main 函数先执行 HAL_Init 和 SystemClock_Config,随后按顺序初始化 GPIO、DMA、USART1、USART2 和 ADC。USART2 初始化完成后,CT02_Guard_AppInit 开始建立 CT02 守护上下文。当前工程给出的默认设备 ID 为 ct02-001,默认 GPS 上报间隔为 10 秒;如果 Data EEPROM 中已经保存过合法的间隔值,则优先使用 EEPROM 值。健康检查间隔为 30 秒,GPS 快照最大尝试次数为 30 次,GPS 主动查询间隔为 30 秒,MQTT 连接使用非 clean session 并设置 60 秒 keepalive。
ct02_guard_init 会把这些配置复制到上下文中,并做边界限制:上报间隔被限制在 10 到 65535 秒之间,健康检查间隔被限制在 5 到 3600 秒之间,快照次数和 GPS 查询间隔也会被限制到可控范围。然后状态机进入 CT02_SERVICE_WAIT_AT,等待后续 AT 探测;gps_enabled 初值置为 1,表示守护程序默认认为 GPS 应处于开启状态。
AT 探测、MQTT 连接和主题订阅:CT02 守护的服务状态机由 ct02_service_drive 和 ct02_service_on_done 共同推进。初始状态是 CT02_SERVICE_WAIT_AT,程序每隔约 500 毫秒发送一次 AT。只有收到有效 OK 后,代码才进入守护模式:guard_mode 置 1,boot_ready 置 1,MQTT 连接标志和订阅标志清零,重连退避恢复到 3 秒,并根据设备 ID 生成四个主题:
- 下行控制:
ct02/<deviceId>/down - 控制确认:
ct02/<deviceId>/ack - 状态上报:
ct02/<deviceId>/up - 定位上报:
ct02/<deviceId>/gps
进入守护模式后,状态机先发送 AT+MQTTSTATU 查询 MQTT 连接状态。如果回复表明已经连接,程序进入订阅检查;如果未连接,则发送 AT+MCONNECT=0,60。若连接失败,ct02_service_mconnect_backoff 把重连延迟从 3 秒按倍数增加,最大限制为 30 秒,避免网络异常时持续高速刷 AT 命令。MQTT 连接确认后,程序发送 AT+MSUB? 读取当前订阅列表;如果回复中已经包含本设备的下行主题,subscription_ok 置 1,状态机进入 CT02_SERVICE_READY;否则执行 AT+MSUB="ct02/ct02-001/down",0 订阅下行控制主题。
| 状态阶段 | 作用 |
|---|---|
WAIT_AT | 发送 AT 探测串口和 CT02 AT 固件是否可用,收到 OK 后才进入守护模式。 |
ENSURE_MQTT_QUERY | 发送 AT+MQTTSTATU 检查 MQTT 是否已连接,是服务状态机的健康检查入口。 |
ENSURE_MQTT_CONNECT | 发送 AT+MCONNECT=0,60 发起 MQTT 连接,失败后按 3 到 30 秒退避重试。 |
ENSURE_SUB_QUERY | 发送 AT+MSUB? 检查下行主题是否已订阅。 |
ENSURE_SUB_SET | 发送 AT+MSUB="ct02/ct02-001/down",0 订阅平台下行控制主题。 |
READY | 处理开机 GPS 初始化、下行队列、本地间隔修改、周期 GPS 上报和定期健康检查。 |
GPS 冷启动与定位质量过滤:服务状态机首次进入 READY 后,会检查 startup_cold_start_done。只要尚未执行过开机冷启动,且当前没有 AT 命令、发布任务或业务动作占用通道,就启动 CT02_ACTION_STARTUP_COLD。当前源码中的冷启动命令序列如下:首先发送 AT+PWRM=1 确保 CT02 处于正常功耗/工作模式;随后发送 AT+AGNSSGET=pos.asrmicro.com 获取辅助定位数据,再发送 AT+AGNSSSET 把 A-GNSS 数据写入模块;接着发送 AT+MGPSGET=ALL,1 让模块输出完整 GPS/NMEA 相关信息;之后先用 AT+MGPSC=0 关闭 GPS,再用 AT+GPSMODE=3 设置 GPS 模式,最后用 AT+MGPSC=1 重新开启 GPS。
GPS 冷启动还配合了定位质量门限。公共模块中定义了 90 秒冷启动稳定时间、最少 6 颗卫星、最大 HDOP 为 2.5,并要求 3D 定位。解析到 +GPSST、RMC 或 GGA 后,ct02_cache_push_sample 会先附加最近的 NMEA 质量信息,再执行归一化。若仍处于冷启动稳定期、NMEA 模式不是自主定位、GGA 定位质量无效、GSA 不是 3D 定位、卫星数不足或 HDOP 过大,则样本会被标记为无效定位,不直接作为平台轨迹点。这样做可以避免模块刚上电时把漂移坐标、零点坐标或弱信号坐标上报为真实轨迹。
AT 命令执行与串口发送保护:所有 AT 命令都由 ct02_at_start 创建统一状态。该函数把命令字符串追加 \r\n 后发送,记录命令所有者、标签、超时时间和是否等待提示符。守护程序把 AT 命令分为三个所有者:服务状态机、业务动作和 MQTT 发布。任一时刻只允许一个 AT 状态活动,这样可以避免例如 AT+MQTTSTATU 查询和 AT+MPUB 发布同时抢占同一个 USART2 通道。
USART2 发送优先使用 TX DMA。如果当前 UART 仍处于 BUSY_TX 或 BUSY_TX_RX,发送函数返回"稍后重试",状态机下个 tick 再继续;如果 DMA 发送失败,则退回 HAL_UART_Transmit 阻塞发送,超时时间为 300 毫秒。对于普通 AT+MPUB 能容纳的短 JSON,代码会把 payload 转义后直接放入命令;如果 payload 过长或包含不适合直接嵌入的字符,则切换到 AT+MPUBEX,先发送带长度的命令,等待模块返回 > 提示符后再发送原始 payload。接收路径在 ct02_guard_on_rx_byte 中专门识别这个 > 提示符,避免把它当成普通文本行处理。
GPS 定位解析、缓存与坐标修正:USART2 收到的定位信息有两条解析路径。第一条是 CT02 自带的 +GPSST 文本,格式中包含定位状态、信号值、经度、高度和纬度,代码在 ct02_parse_gpsst_line 中把它转成 ct02_gps_sample_t。第二条是标准 NMEA 语句,ct02_parse_nmea_line 支持 RMC 和 GGA:RMC 用于读取有效标志、经纬度和 UTC 日期时间,GGA 用于读取定位质量、卫星数、经纬度和海拔。NMEA 的度分格式会被 ct02_nmea_to_decimal 转换为十进制度。
除直接定位值外,ct02_note_nmea_quality 还从 GGA、GSA 和 RMC 中提取质量指标,包括 GGA 定位质量、卫星数量、GSA 参与定位卫星数、GSA 定位维度、HDOP 和 RMC 模式字符。由于这些质量语句和定位语句未必在同一行出现,程序把质量信息保留 15 秒,只要新样本的时间和质量记录足够接近,就把质量字段附加到新样本上。
样本进入缓存前会执行坐标归一化。无定位、越界坐标、零点坐标或质量门限未通过的样本会被转成 fixStatus=0,经纬度清零,并保存在 latest_any 中;通过筛选的样本会保存原始 WGS84 坐标到 rawLatWgs84 和 rawLngWgs84,再叠加工程校准偏移:纬度偏移为 -0.0025458608,经度偏移为 0.0045950152。修正后的样本写入 latest_valid,用于周期上报、状态查询、快照回复和屏幕刷新。因此,平台上显示的 lat/lng 是经过本工程校准后的坐标,而原始模块坐标仍随 payload 保留,便于后续追溯。
周期上报和 MQTT 发布负载:READY 状态下,当当前时间超过 next_report_ms 且没有其他动作占用通道时,服务状态机会启动周期动作。周期动作分两步:先向 GPS 主题发布 gps.report,再向 up 主题发布 gps.status。GPS 报告中包含 type、source、ts、fixStatus、lat、lng、alt、cn、coordSystem、rawLatWgs84、rawLngWgs84 和 reason。状态报告则包含动作来源、当前上报间隔、GPS 开关状态和是否有有效定位。
周期上报取样逻辑负责在最新有效定位、最近任意定位和无定位占位样本之间做选择。若 GPS 开启且最新有效样本仍在时间窗内,原因字段为 periodic_fix;若没有有效定位但最近存在任意样本,则上报无定位结果并给出 periodic_no_fix;若 GPS 被关闭,则构造来源为 guard.periodic.disabled 的无定位样本。有效时间窗不是固定值,而是取 60 秒和 3 倍上报间隔中的较大者,避免上报间隔被调长后,上一条有效定位过早被认为过期。
平台下行命令处理:平台下行命令通过本设备的 down 主题进入 CT02 模块,模块再从 USART2 输出 +MSUB 行。由于 +MSUB 可能把主题、payload 长度和 JSON 载荷放在同一行,也可能因串口分段而拆成多行,ct02_msub_consume_line 实现了轻量重组:先解析主题和期望 payload 长度,若 payload 未完整,则继续接收后续普通文本行;如果 2 秒内没有补齐,或遇到新的 +MSUB、OK、ERROR、NMEA 等非续接行,则本次重组失败并计入丢包统计。重组成功后,下行包进入长度为 2 的队列,等待服务状态机在 READY 阶段处理。
当前支持的控制类型围绕 gps.control 展开。若 type 表示 ping 或链路测试,设备回复连通性结果;若 type=gps.control,则根据 action 选择 status、enable、disable、set_interval 或 snapshot。每个动作都会提取 requestId,若 payload 没有提供,则使用接收时刻生成回退编号,保证平台能够把 ACK 和下行请求对应起来。
| 动作 | MCU 侧处理 |
|---|---|
ping | 先向 ACK 主题发布收到确认,再向 up 主题返回 pong 或 payload 中的测试消息,用于平台链路探测。 |
status | 发布 received ACK,等待可配置的探测窗口,查询 AT+MGPSC? 和 AT+GPSMODE?,再上报心跳、GPS 开关、GPS 模式、缓存行数、当前间隔和有效定位状态,最后发布 done ACK。 |
enable | 发布 received 和 running ACK,执行与 GPS 启动相关的 AT 命令序列,探测启动后的输出,随后发布一次 GPS 报告、一次状态报告和 done ACK。 |
disable | 发布 received ACK,执行 AT+MGPSC=0 关闭 GPS,构造无定位报告,发布状态更新,并在 done ACK 中返回 GPS 是否关闭成功。 |
set_interval | 把 intervalSeconds 限制在 10 到 65535 秒之间,更新 report_interval_s 和 next_report_ms,发布 interval_updated 状态和带新间隔的 done ACK。 |
snapshot | 发布 received 和运行阶段 ACK,先观察是否已有 GPS 心跳;若静默且允许冷启动,则执行 GPS 启动命令,然后按配置间隔多次查询 AT+GPSST,最后发布单次 GPS 报告、状态和 done ACK。 |
异常恢复与守护可靠性:USART2 守护有两层恢复机制。第一层是串口链路恢复:主循环中的恢复检查函数会检查接收是否处于 armed 状态;如果 DMA 或中断接收未启动,则每 100 毫秒尝试重新启动 DMA 接收,失败时退回单字节中断接收。第二层是业务状态恢复:READY 状态中如果 mqtt_connected 变为 0,状态机会清除订阅标志并立即回到 MQTT 状态查询;如果发布失败,动作层会认为 MQTT 链路不可用,主动把服务阶段切到重新连接。重连退避限制在 3 到 30 秒之间,既能在网络恢复后自动上线,也能避免弱网环境下 AT 口被持续重试打满。
此外,主函数与外设调度模块还实现了 1.5 秒软件看门狗式的"守护喂狗"统计。每次收到串口字节、发送完成或处理关键状态时,守护计数都会增加;恢复检查函数定期比较该计数是否变化,如果长时间没有变化,则设置超时标志,下一次守护周期调度会把超时计数累加。当前代码没有直接在超时时复位 MCU 或 CT02,而是把它作为运行诊断和后续恢复策略的依据。
串口屏显示与电量检测
USART1 TJC 串口屏显示与本地控制
USART1 用于连接 TJC 串口屏,承担本地界面的显示刷新和参数设置输入。它与 USART2 的 CT02 守护通道相互独立:USART1 面向用户界面,USART2 面向 4G/GPS 模块和 MQTT 平台。这种结构使屏幕刷新失败或屏幕断电不会直接破坏 CT02 模块的 AT 状态机,也便于把屏幕作为可关闭的耗电外设处理。
在显示方向,STM32L0 根据当前运行状态生成串口屏变量命令,例如把平台访问地址、GPS 上报间隔、定位状态、网络状态和电量状态写入对应控件。TJC 屏幕命令通常以文本形式发送,并以连续三个 0xFF 作为结束符。为了降低屏幕解析出错概率,固件发送时应保持"一条控件命令对应一次 UART 发送",不要把多个控件更新拼接成一个过长的串口包。
在输入方向,串口屏把本地设置以固定帧格式发回 STM32L0。定位间隔设置帧可抽象为:
0x05 0x52 L H 0x26
其中 L 和 H 组成 16 位小端整数,表示新的上报间隔秒数。单片机接收该帧后进行范围检查,再更新本地定时变量;同时,该参数也可通过 USART2 路径反馈到 MQTT 平台,使 Web 控制台和本地屏幕保持一致。由此形成"平台下发可改参数、屏幕本地也可改参数、设备端统一保存并回传状态"的闭环。
锂电池电量检测设计
锂电池检测使用 PA3 的 12 位片内 ADC。硬件上,单节 3.7V 锂电池通过两个 100k 电阻串联分压,ADC 采样点取中点,因此进入 PA3 的电压约为电池端电压的一半。设上拉电阻为 $R_1=100\,\mathrm{k\Omega}$,下拉电阻为 $R_2=100\,\mathrm{k\Omega}$,ADC 参考电压为 $V_{\mathrm{REF}}$,12 位采样码值为 $N$,则:
$$V_{\mathrm{ADC}}=\frac{N}{4095}V_{\mathrm{REF}},\qquad V_{\mathrm{BAT}}=2\frac{N}{4095}V_{\mathrm{REF}}$$
当 $V_{\mathrm{REF}}=3.3\,\mathrm{V}$ 时,可近似写成 $V_{\mathrm{BAT}}\approx \frac{6.6N}{4095}$。例如 ADC 读数为 $N=2500$ 时,电池电压约为 $4.03\,\mathrm{V}$。
电池电压到剩余电量的换算不能完全线性,因为锂电池放电曲线中段较平缓、末端下降较快。程序先用线性限幅模型得到基础百分比估算:
$$\mathrm{SOC}_{\mathrm{lin}}=\mathrm{clip}\left(\frac{V_{\mathrm{BAT}}-3.30}{4.20-3.30}\times100\%,\,0,\,100\%\right)$$
其中 $\mathrm{clip}(x,0,100\%)$ 表示把结果限制在 0 到 100\% 之间,$4.20\,\mathrm{V}$ 按满电处理,$3.30\,\mathrm{V}$ 按低电量截止处理。
为了使屏幕显示更接近单节锂电池实际放电特性,电量显示再按分段插值。节点如下:
| 电池电压/V | 3.30 | 3.50 | 3.65 | 3.75 | 3.85 | 4.00 | 4.20 |
|---|---|---|---|---|---|---|---|
| 估算电量/% | 0 | 5 | 20 | 40 | 60 | 80 | 100 |
由于两个 100k 电阻串联后总电阻为 $200\,\mathrm{k\Omega}$,满电 $4.2\,\mathrm{V}$ 时分压支路静态电流约为 $21\,\mathrm{\mu A}$,适合低功耗设备。但该分压网络的等效源阻抗约为 $50\,\mathrm{k\Omega}$,实际采样时应适当增加 ADC 采样时间,必要时在 ADC 输入端并联小电容做低通滤波,以减少高阻源带来的采样误差和瞬态抖动。
项目总结
本报告围绕"面向药品有效性的射频微传感测试系统"省级大学生创新创业训练计划项目,完成了以 GPS 定位数据为示范载荷的 IOT-MQTT 全栈物联网工程 Demo 的设计、实现与验证。该工程并非孤立的 GPS 定位系统,而是项目整体架构中物联网数据上报通路的关键基础设施。其核心目标在于:在射频微传感器尚未完成最终标定的阶段,先行打通从现场设备到远程平台的端到端数据环路,验证通信协议、平台解析逻辑、命令控制闭环以及可视化框架的可行性与稳定性,为后续药液组分分析结果的接入提供可直接迁移的工程底座。
在平台侧,本工程基于 Node.js 构建了一套集成式物联网服务端,涵盖 Aedes MQTT Broker、Express Web 服务、Socket.IO 实时通道、REST API 和 SQLite 本地数据库。平台实现了设备身份认证与主题约束、多格式 GPS 协议解析、设备状态持久化、命令任务全生命周期跟踪、离线命令队列补发以及 Web 控制台实时可视化等功能。通过 FRP 内网穿透与阿里云公网服务器的端口映射,现场 4G 设备可在任意蜂窝网络覆盖区域接入平台,验证了公网部署条件下的服务可用性。Web 控制台以 REST 接口提供全量状态快照、以 Socket.IO 推送增量事件,实现了设备在线状态、GPS 轨迹、消息流和命令结果的多维度实时监控。
在设备侧,STM32L0 微控制器通过 USART2 DMA 循环接收与 IDLE 中断排空机制,构建了高可靠性的 CT02 AT 固件守护程序。该守护程序以非阻塞状态机方式驱动 AT 探测、MQTT 连接维护、下行主题订阅、GPS 冷启动与定位质量过滤、周期性上报以及平台下行命令解析与执行等全链路业务。同时,系统通过 USART1 连接 TJC 串口屏实现本地人机交互,利用 PA3 ADC 完成锂电池电量检测,并通过 PA4 外部中断与 PA5 屏幕供电控制实现"休眠/运行"状态切换,在保证后台通信不间断的前提下有效降低了无人值守时的静态功耗。
从项目整体演进角度看,本工程的最大价值在于"架构先行、数据可替"。当前阶段,设备端通过 MQTT 主题上报 GPS 坐标,平台侧按既定协议解析并入库,Web 端将轨迹点渲染至地图;这一完整流程实质上验证并固化了主题命名规范、payload JSON 结构、命令请求-ACK 闭环机制、离线重发策略以及前端可视化组件。待后续射频微传感器完成药液组分标定后,仅需将设备端的采样载荷由 GPS 数据替换为传感器分析结果(如谐振频率偏移、介电常数变化或组分浓度反演值),并相应扩展平台解析模块中的 payload 识别逻辑,即可直接复用现有的主题体系、数据库表结构、REST 接口和 Web 控制台框架,实现药液组分检测数据的远程实时上报、历史追溯与异常预警。
综上所述,本工程成功构建了一个功能完备、可扩展、低功耗的物联网数据环路,既作为《综合项目实践》课程设计的完整技术成果,也作为省级大创项目"面向药品有效性的射频微传感测试系统"迈向实际应用场景的重要工程基础。后续工作将聚焦于射频微传感器的精度标定、药液样本库建立以及组分分析算法优化,届时本报告所述的物联网全栈架构可直接承载传感分析数据,助力项目最终实现对药液有效性的智能化组网监测。