☰
SerialTest demo 示例指南:为实时绘图与控制面板编写 Arduino / STC8G / STM32 端固件
2026/10/14 17:03:46 网站建设 项目流程
  • 开发工具
  • 测试
  • 通信

【免费下载链接】SerialTest

Data transceiver(monitor)/realtime plotter/shortcut/file transceiver over serial port/Bluetooth/network on Windows/Linux/Android/macOS | 跨平台串口/蓝牙/网络调试助手,带数据收发/实时绘图/快捷发送/文件收发面板,可在PC和Android设备上使用

项目地址:https://gitcode.com/gh_mirrors/se/SerialTest
点击查看免费下载

本文基于仓库 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 在基础示例上做了两点改进:

  1. 预计算查表:setup()中预先算好 256 点的sinTable/cosTable,loop()中直接取表发送,降低 MCU 计算负担;注释特别提示Arduino UNO 上请把ARRAY_LEN改为 160 以节省 RAM;
  2. 利用清空标志实现“循环滚动”波形: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一帧数据的结束符\nsrc/plottab.cpp 中plotFrameSeparator
Data Splitter帧内字段分隔符,src/plottab.cpp 中plotDataSeparator
Clear Flag清空绘图区的标志帧首字段clssrc/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面板上显示的名称
contentCommand 型为命令文本;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,”配置即可出图。

五、快速上手清单

  1. 实时绘图:以 plot_realtime.ino 为模板烧录(115200 波特率),上位机 Plot 面板勾选启用,Data Num 设为 3、帧分隔\n、数据分隔,;需要循环波形再加cls清空帧(参考 plot_cls.ino);追求速率参考 plot_performance.ino 的 921600 波特率 + 查表写法。
  2. 快捷控制:用 control_pwm.json 掌握“前缀 + 内容 + 后缀”的发送拼装模型;命令以\n结尾时,固件侧用readStringUntil('\n')接收。
  3. 调蓝牙模块:直接导入 demo/Control/ 中对应模块的 AT 指令集 JSON。
  4. 移植到新平台:以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设备上使用

项目地址:https://gitcode.com/gh_mirrors/se/SerialTest
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询