- 开发工具
- 测试
- 通信
【免费下载链接】SerialTest
Data transceiver(monitor)/realtime plotter/shortcut/file transceiver over serial port/Bluetooth/network on Windows/Linux/Android/macOS | 跨平台串口/蓝牙/网络调试助手,带数据收发/实时绘图/快捷发送/文件收发面板,可在PC和Android设备上使用
本文基于仓库 demo/README.md 展开,系统介绍 SerialTest(跨平台串口/蓝牙/网络调试助手)配套的demo/示例工程:如何用 MCU 固件按“帧分隔符 + 数据分隔符”的字符串协议喂数据给 Plot 面板实现实时绘图,如何用 JSON 配置文件驱动 Control(快捷发送)面板,以及如何把绘图示例移植到 STC8G、STM32 等非 Arduino 平台。读完后你可以直接照抄示例固件在任意单片机上复现 SerialTest 的实时曲线效果,并掌握控制项 JSON 中每个字段的含义。
一、demo/ 目录结构与命名约定
demo/目录按“硬件平台”划分,完整结构如下:
demo/ ├── Arduino/ │ ├── control_pwm/ # 控制面板(快捷发送)示例 │ ├── plot_cls/ # 绘图 + 清空面板示例 │ ├── plot_performance/ # 高性能绘图示例 │ └── plot_realtime/ # 基础实时绘图示例 ├── STC8G/ │ └── plot_realtime/ # STC8G 绘图示例(Keil 工程) ├── STM32F103C8/ │ └── plot_realtime/ │ ├── CubeMX/ # HAL + CubeMX 工程 │ └── StdPeriph/ # 标准外设库工程 ├── STM32G431KB/ │ └── plot_realtime/ # CubeMX + Keil 工程 └── Control/ # 常见模块的 AT 指令快捷发送配置集命名约定(来自 demo/README.md 的 Notes 部分):
plot_前缀的项目用于演示Plot(实时绘图)面板:MCU 持续向外发送字符串帧,上位机解析成曲线;control_前缀的项目用于演示Control(快捷发送/控制)面板:上位机按 JSON 配置的命令格式把数据发送给 MCU;- demo/Control/ 文件夹下有一些常见命令集合(HC-02/HC-05/JDY-34 等蓝牙模块的 AT 指令),可导入“控制”面板直接使用;
- Arduino 文件夹下包含所有类型的示例,其它平台(STC8G、STM32)仅提供
plot_realtime项目以便移植; - STM32 平台的项目实现了用
printf()函数在 USART 串口上输出数值的功能,可作为“标准 C IO 重定向到串口”的参考; - STC8G 平台的项目实现了
itoa()函数,README 提示更多类型转换函数(如ftoa()、atoi()、atof())可参考作者 CubeMX_Lib 仓库中的Module/UTIL/util.c(外部项目文件,本仓库不包含)。
二、Plot 面板示例:MCU 端要发什么数据
SerialTest 绘图面板的工作原理在 src/plottab.cpp 的PlotTab::processData()中:先把接收到的字节流追加到plotBuf,然后按“帧分隔符”切帧、按“数据分隔符”切字段,每一帧解析为一组数值并添加为一条/多条曲线的点;若某帧第一个字段等于“清空标志”,则调用clearGraph()清空绘图区。因此 MCU 端只要发送形如:
100,3.14,2.71 101,3.14,2.71 ...这样的字符串流即可,对 MCU 来说只是“拼字符串 + 串口发送”,没有任何二进制协议开销。上位机侧三个关键参数的默认值(见 src/plottab.cpp 中loadPreference())为:帧分隔符默认\n(FrameSp_Type 默认值 3),数据分隔符默认,(defaultDataSp = ","),清空标志默认字符串cls(ClearF_Context默认值"cls")。
2.1 plot_realtime:最基础的实时绘图示例
plot_realtime.ino(注释说明已在 ESP32 上测试过)完整代码如下:
// Tested on ESP32 #define ARRAY_LEN 256 uint16_t i; void setup() { Serial.begin(115200); i = 0; } void loop() { Serial.print(i); Serial.print(','); Serial.print(sin((double)i / ARRAY_LEN * 2 * PI) * 20); Serial.print(','); Serial.print(cos((double)i / ARRAY_LEN * 2 * PI) * 20 - 5); Serial.print('\n'); i++; delay(10); } // set Data Num to 3 // set Frame Splitter to \n // set Data Splitter to String, then input "," // X Type can be anything固件每 10 ms 发送一帧 3 字段数据:自增序号 + 正弦值 + 余弦值(余弦整体下移 5 便于观察)。文件末尾注释给出了上位机配套设置:Data Num 设为 3、Frame Splitter 设为\n、Data Splitter 选 String 并输入,、X Type 任选。其中 X Type 对应 src/plottab.cpp 中processData()的三种分支:帧计数器(XType=0,用内部计数做横轴)、第一列数据作 X 轴(XType=1,此时 Data Num 至少为 2,见 src/plottab.cpp 中on_plot_XTypeBox_currentIndexChanged())、时间轴(XType=2,X 轴切换为时间刻度plotTimeTicker)。
2.2 plot_cls:循环波形 + 清空绘图区
plot_cls.ino 在基础示例上做了两点改进:
- 预计算查表:
setup()中预先算好 256 点的sinTable/cosTable,loop()中直接取表发送,降低 MCU 计算负担;注释特别提示Arduino UNO 上请把ARRAY_LEN改为 160 以节省 RAM; - 利用清空标志实现“循环滚动”波形:
i每取模回到 0 时,额外发送一帧cls\n。上位机绘图面板收到首字段为cls的帧就会清空曲线(对应processData()中dataList[0] == plotClearFlag分支)。
void loop() { Serial.print(i); Serial.print(','); Serial.print(sinTable[i]); Serial.print(','); Serial.print(cosTable[i]); Serial.print('\n'); i++; i %= ARRAY_LEN; delay(10); if (i == 0) // clear the plot panel Serial.print("cls\n"); } // set Data Num to 3 // set Frame Splitter to \n // set Data Splitter to String, then input "," // set Clear to String, then input "cls" // X Type can be anything上位机配置在基础示例上多加一条:Clear(清空标志)选 String,输入cls。这样曲线画完一圈后自动清屏重画,非常适合展示周期性信号。
2.3 plot_performance:更高波特率、更多通道
plot_performance.ino 演示了“提高吞吐”的写法:
Serial.begin(921600):把波特率提到 921600;- 使用
sinf()单精度浮点计算查表; - 一帧发送5 个字段:序号、三条相位差 1/3 周期的正弦曲线(
sinTable[(i + ARRAY_LEN / 3) % ARRAY_LEN]与sinTable[(i + ARRAY_LEN / 3 * 2) % ARRAY_LEN]),再加一条三角波(i % (ARRAY_LEN / 4) + 20); loop()中不再delay(),全速发送。
void loop() { Serial.print(i); Serial.print(','); Serial.print(sinTable[i]); Serial.print(','); Serial.print(sinTable[(i + ARRAY_LEN / 3) % ARRAY_LEN]); Serial.print(','); Serial.print(sinTable[(i + ARRAY_LEN / 3 * 2) % ARRAY_LEN]); Serial.print(','); Serial.print(i % (ARRAY_LEN / 4) + 20); Serial.print('\n'); i++; i %= ARRAY_LEN; }配套设置:Data Num 设为 5,其余与基础示例相同。注释同样提示 Arduino UNO 可把ARRAY_LEN降为 320。
2.4 示例小结:字符串协议三要素
| 参数 | 含义 | 示例值 | 上位机解析位置 |
|---|---|---|---|
| Frame Splitter | 一帧数据的结束符 | \n | src/plottab.cpp 中plotFrameSeparator |
| Data Splitter | 帧内字段分隔符 | , | src/plottab.cpp 中plotDataSeparator |
| Clear Flag | 清空绘图区的标志帧首字段 | cls | src/plottab.cpp 中plotClearFlag |
两者均支持“字符串 / Hex /\r\n/\n固定值”四种类型(下拉框索引 0~3 与源码分支一一对应)。分隔符不允许为空:源码中若检测到空串会回退到之前的值或默认值(empty separator is not allowed注释)。
三、control_pwm:Control 面板示例与 JSON 配置
control_pwm.ino 演示反方向的交互:上位机通过“控制”面板按键/滑杆向 MCU 发送命令,MCU 执行后回复状态。它模拟“测量某引脚 DC 电压/占空比”的场景,协议约定(固件上电时自行打印):
axxx # xxx 为 0~255,设置模拟输出值 Start # 开始周期性发送消息 Stop # 停止周期性发送固件核心逻辑:
if (Serial.available()) { cmd = Serial.readStringUntil('\n'); if (cmd[0] == 'a') // set analog val { analogVal = cmd.substring(1).toInt() & 0xFF; analogWrite(analogPin, analogVal); Serial.println("New analog val: " + String(analogVal)); } else if (cmd == "Start") isSending = true; else if (cmd == "Stop") isSending = false; else Serial.println("Invalid command!"); }命令以\n结尾(readStringUntil('\n')),这正是配套 JSON 中 suffix 设为\n的原因。
3.1 控制项 JSON 格式逐字段讲解
control_pwm.json 是一个 JSON 数组,数组中每个对象对应控制面板里的一项。固件注释提示的用法是:在控制面板点击“import”,选择 control_pwm.json,之后即可一键发送Start/Stop或用滑杆调节a0~a255。
以其中两个条目为例:
{ "auto": false, "content": "Start", "hex": false, "max": "100", "min": "0", "name": "Start", "prefix": "", "prefixEnabled": false, "prefixType": 0, "step": "1", "suffix": "", "suffixEnabled": true, "suffixType": 3, "type": 0 } { "auto": false, "content": 0, "hex": false, "max": "255", "min": "0", "name": "SetAnalogVal", "prefix": "a", "prefixEnabled": true, "prefixType": 0, "step": "1", "suffix": "", "suffixEnabled": true, "suffixType": 3, "type": 1 }字段含义可由 src/controlitem.cpp 中的ControlItem::load()/ControlItem::save()(读写一一对应)及 src/controlitem.h 的enum Type确认:
| 字段 | 含义 |
|---|---|
type | 控件类型:0=Command(命令按钮)、1=Slider(滑杆)、2=CheckBox(复选框)、3=SpinBox(数值框),见 src/controlitem.h |
name | 面板上显示的名称 |
content | Command 型为命令文本;Slider/SpinBox 型为初始数值;CheckBox 型为布尔值 |
hex | 命令内容是否按十六进制发送 |
unescape | 是否解析转义序列(如\n、\r) |
prefix/prefixEnabled/prefixType | 发送内容前缀;prefixType:0=字符串、1=Hex、2=\r\n、3=\n,见 src/controlitem.cpp |
suffix/suffixEnabled/suffixType | 发送内容后缀,类型取值与prefixType相同,见 src/controlitem.cpp |
min/max/step | 滑杆/数值框的取值范围与步进 |
auto | 勾选后值变化即自动发送(松开滑杆、改动数值、切换复选框都会触发emit send(data)),见 src/controlitem.cpp |
实际发送时,数据按“前缀 + 内容 + 后缀”顺序拼接:src/controlitem.cpp 的on_sendButton_clicked()先处理前缀(字符串/Hex/\r\n/\n四种类型),再按控件类型附加命令文本、滑杆整数值、1/0或数值框文本,最后附加后缀并emit send(data)。因此上面SetAnalogVal条目(prefix="a"、suffixType=3)发送滑杆值 128 时,实际发出的字节流正是a128\n,与固件cmd[0] == 'a'的解析完全吻合。
该文件还包含一个auto: true的SetAnalogVal(auto)条目,演示“滑杆一动就发一条命令”的实时控制模式。
3.2 demo/Control/:可直接导入的 AT 指令集
demo/Control/ 下有三个常见蓝牙串口模块的 AT 指令集:AT_HC02_zh_CN.json、AT_HC05_zh_CN.json、AT_JDY34_zh_CN.json。以 HC-02 为例,文件头部用#开头的行写明了使用前提:
# HC-02部分AT指令 # AT模式波特率与通信波特率一致,默认波特率为9600 # 上电后若无蓝牙设备连接则一直处于AT模式,此时发送测试通讯命令会返回AT # 在AT模式中改变波特率后后续AT指令也需要使用新的波特率发送条目内容如“测试通讯”(AT)、“恢复出厂设置”(AT+DEFAULT)、“查询模块参数”(AT+RX)、“设波特率为9600”(AT+BAUD4)、“设波特率为115200”(AT+BAUD8)。这类纯命令条目将指令整体写入prefix、content留空——即发送的数据 = 前缀本身,suffixType指定 0(HC-02/HC-05)或 2(JDY-34)分别控制是否追加换行。把整个 JSON 导入控制面板后,调试蓝牙模块只需点按钮,无需手敲 AT 指令。
四、非 Arduino 平台移植:STC8G 与 STM32 的 plot_realtime
README 明确说明:Arduino 之外的平台只提供plot_realtime工程作为移植模板。各平台实现都围绕同一个数据格式(3 字段、\n分帧、,分字段),差别只在“如何把数值变成字符串并从串口发出”。
4.1 STC8G:中断发送 + 手写 itoa()
STC8G/plot_realtime/src/main.c 主循环:
while (1) { myitoa(i, buf, 10); UART_sendStr(buf); UART_sendByte(','); myitoa(sin((double)i / 256 * 2 * PI) * 10000, buf, 10); UART_sendStr(buf); UART_sendByte(','); myitoa(cos((double)i / 256 * 2 * PI) * 10000 - 2000, buf, 10); UART_sendByte('\n'); i++; }注意此处正弦/余弦值都乘以 10000 转为定点整数发送——STC8G 无浮点硬件,用整数流替代浮点文本,上位机侧数值会大 10000 倍,但波形形状不变。
底层串口驱动在 STC8G/plot_realtime/src/uart.c:
UART_init()配置SCON = 0x50(8 位无校验),波特率由定时器 T2 产生,uart.h 中公式为BAUDRATE (65536 - MAIN_Fosc / 115200 / 4),对应11.0592 MHz 主频下的 115200 波特率;UART_sendByte()置SBUF前等待发送完成标志(UART_busy由 TI 中断清零),实现简单可靠的阻塞式发送;myitoa(val, str, radix)支持 2~16 进制:先取模转字符、负数补-,再原地反转字符串;注释指出更多ftoa()/atoi()/atof()转换函数可参考作者 CubeMX_Lib 仓库的util.c(外部仓库文件,本仓库不包含)。
4.2 STM32:重定向 printf() 到 USART
三个 STM32 工程(STM32F103C8 CubeMX、STM32F103C8 StdPeriph、STM32G431KB)均采用同一技巧:实现fputc()重定向,让标准库printf()直接输出到串口。主循环因此可以像 PC 端一样写:
while (1) { printf("%d,%f,%f\n", i, sin((double)i / 256 * 2 * PI) * 20, cos((double)i / 256 * 2 * PI) * 20 - 5); i++; }不同库的fputc实现:
- HAL 库(G431KB 与 F103C8-CubeMX,见 STM32G431KB/plot_realtime/Core/Src/main.c):
// for printf() FILE __stdout; int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; }- 标准外设库(F103C8-StdPeriph,见 STM32F103C8/plot_realtime/StdPeriph/src/main.c):直接写
USART_SendData(USART1, ch)并轮询USART_FLAG_TC发送完成标志;同文件内还有完整的UART_init()(PA9/PA10,115200、8N1、无流控)可供裸机参考。
CubeMX 工程的时钟/引脚配置保存在工程内的.ioc文件(如 G431KB.ioc),Keil 工程位于各工程目录下的MDK-ARM/,已附预编译好的.hex。移植时只需保持printf的%d,%f,%f\n格式不变(3 字段、逗号分隔、换行结帧),上位机按第二节 2.1 的“Data Num 3 / Frame Splitter\n/ Data Splitter,”配置即可出图。
五、快速上手清单
- 实时绘图:以 plot_realtime.ino 为模板烧录(115200 波特率),上位机 Plot 面板勾选启用,Data Num 设为 3、帧分隔
\n、数据分隔,;需要循环波形再加cls清空帧(参考 plot_cls.ino);追求速率参考 plot_performance.ino 的 921600 波特率 + 查表写法。 - 快捷控制:用 control_pwm.json 掌握“前缀 + 内容 + 后缀”的发送拼装模型;命令以
\n结尾时,固件侧用readStringUntil('\n')接收。 - 调蓝牙模块:直接导入 demo/Control/ 中对应模块的 AT 指令集 JSON。
- 移植到新平台:以
demo/<platform>/plot_realtime/为骨架,只需解决“数值 → 字符串 + 串口发送”(Arduino 用Serial.print,STM32 用fputc重定向printf,STC8G 用UART_sendStr+myitoa),数据帧格式保持一致即可复用同一套上位机配置。
- 开发工具
- 测试
- 通信
【免费下载链接】SerialTest
Data transceiver(monitor)/realtime plotter/shortcut/file transceiver over serial port/Bluetooth/network on Windows/Linux/Android/macOS | 跨平台串口/蓝牙/网络调试助手,带数据收发/实时绘图/快捷发送/文件收发面板,可在PC和Android设备上使用
相关推荐
SerialTest项目实时绘图功能详解
SerialTest项目实时绘图功能详解 概述 SerialTest项目提供了一套完整的串口通信解决方案,其中实时绘图功能是其核心特色之一。该功能能够将接收到的
开发工具测试通信Semi Design PR 示例编写指南:用 CodeSandbox 模板为 Pull Request 构建可在线预览的 Demo
Semi Design PR 示例编写指南:用 CodeSandbox 模板为 Pull Request 构建可在线预览的 Demo 本篇技术指南聚焦 Semi
前端UI组件设计系统如何用MiDaS实现惊艳的单目深度估计?2025最新超全指南 🚀
如何用MiDaS实现惊艳的单目深度估计?2025最新超全指南 🚀 MiDaS是一款革命性的开源单目深度估计算法,能让普通图像瞬间生成精准的深度信息。无论是自动
桌面应用嵌入式
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考