1. 项目概述
在嵌入式系统开发中,MCU与上位机(如MATLAB)的可靠通信是一个常见需求。本文将详细介绍如何通过自定义帧格式实现STM32单片机与MATLAB之间的稳定串口通信。这种通信方式相比简单的字符串传输具有更高的可靠性和可扩展性,特别适合工业控制、数据采集等应用场景。
2. 通信协议设计
2.1 帧格式定义
我们采用简单的3字节帧结构:
- 帧头(Header): 0xAA (固定值,用于标识帧开始)
- 命令/数据(Command): 1字节的有效数据
- 帧尾(Tail): 0x55 (固定值,用于标识帧结束)
这种帧结构虽然简单,但包含了必要的同步和校验机制:
- 帧头用于同步接收端的状态机
- 帧尾用于验证帧完整性
- 固定长度便于接收端处理
2.2 状态机设计
接收端(MCU)采用状态机解析协议:
- WAIT_HEADER状态:等待帧头0xAA
- WAIT_CMD状态:接收命令/数据字节
- WAIT_STOP状态:验证帧尾0x55
这种设计可以有效避免数据错位导致的解析错误。
3. MCU端实现
3.1 硬件准备
需要以下硬件组件:
- STM32开发板(如STM32F103C8T6)
- USB转TTL模块(如CH340)
- 杜邦线若干
连接方式:
- 开发板的USART1_TX(PA9)接USB-TTL的RX
- 开发板的USART1_RX(PA10)接USB-TTL的TX
- 共地连接
3.2 代码实现
3.2.1 初始化设置
首先在CubeMX中配置USART1:
- 波特率: 115200
- 数据位: 8位
- 停止位: 1位
- 无校验位
- 启用全局中断
生成代码后,添加以下通信模块:
c复制#include "usart.h"
#include "stdio.h"
/* 协议状态定义 */
#define STATE_WAIT_HEADER 0
#define STATE_WAIT_CMD 1
#define STATE_WAIT_STOP 2
/* 通信变量 */
volatile uint8_t RxBuffer[1]; // 单字节接收缓冲区
volatile uint8_t DataReady = 0; // 完整帧接收标志
volatile uint8_t Command = 0; // 接收到的命令字节
static uint8_t CommState = STATE_WAIT_HEADER; // 当前解析状态
/* 测试数据 */
uint8_t num1 = 0x55;
uint8_t num2 = 0x56;
3.2.2 通信初始化
c复制void UART_Comm_Init(void) {
CommState = STATE_WAIT_HEADER;
DataReady = 0;
/* 启动UART中断接收 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)RxBuffer, 1);
}
3.2.3 中断回调函数
c复制void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
uint8_t receivedByte = RxBuffer[0];
switch (CommState) {
case STATE_WAIT_HEADER:
if (receivedByte == 0xAA) {
CommState = STATE_WAIT_CMD;
}
break;
case STATE_WAIT_CMD:
Command = receivedByte;
CommState = STATE_WAIT_STOP;
break;
case STATE_WAIT_STOP:
if (receivedByte == 0x55) {
DataReady = 1;
}
CommState = STATE_WAIT_HEADER;
break;
default:
CommState = STATE_WAIT_HEADER;
break;
}
/* 重新启用中断接收 */
HAL_UART_Receive_IT(&huart1, (uint8_t *)RxBuffer, 1);
}
}
3.2.4 错误处理
c复制void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
/* 处理溢出错误 */
if (huart->ErrorCode & HAL_UART_ERROR_ORE) {
HAL_UART_Receive_IT(&huart1, (uint8_t *)RxBuffer, 1);
}
}
3.2.5 命令处理
c复制void UART_Process_Command(void) {
if (DataReady) {
uint8_t currentCmd = Command;
DataReady = 0;
// 准备响应帧
uint8_t txFrame[3] = {0xAA, 0x00, 0x55};
switch(currentCmd) {
case 0x01:
txFrame[1] = num1;
HAL_UART_Transmit(&huart1, txFrame, 3, 10);
break;
case 0x02:
txFrame[1] = num2;
HAL_UART_Transmit(&huart1, txFrame, 3, 10);
break;
}
}
}
3.3 主函数集成
c复制int main(void) {
HAL_Init();
SystemClock_Config();
MX_USART1_UART_Init();
UART_Comm_Init();
while (1) {
UART_Process_Command();
HAL_Delay(10);
}
}
4. MATLAB端实现
4.1 环境准备
确保已安装:
- MATLAB R2019b或更高版本
- Instrument Control Toolbox
4.2 通信脚本
matlab复制%% 1. 环境清理与端口检查
clear; clc;
availablePorts = serialportlist("available");
targetPort = "COM7"; % 根据实际连接修改
if ~ismember(targetPort, availablePorts)
error("错误:端口 %s 未找到。可用端口: %s", targetPort, strjoin(availablePorts, ', '));
end
%% 2. 串口初始化
try
% 建立连接
device = serialport(targetPort, 115200);
% 自动清理对象
cleanUpObj = onCleanup(@() delete(device));
% 配置
configureTerminator(device, "LF");
device.Timeout = 2;
flush(device);
fprintf("成功连接到 %s,准备进行二进制握手...\n", targetPort);
%% 3. 数据交互
% 协议常量
HEADER = 170; % 0xAA
TAIL = 85; % 0x55
% 发送帧
cmdByte = 2;
sendFrame = [HEADER, cmdByte, TAIL];
write(device, sendFrame, "uint8");
fprintf("-> 已发送指令帧: [%s]\n", dec2hex(sendFrame));
% 等待响应
tic;
maxWait = 1.5;
while device.NumBytesAvailable < 3 && toc < maxWait
pause(0.01);
end
%% 4. 数据解析
if device.NumBytesAvailable >= 3
rawData = read(device, device.NumBytesAvailable, "uint8");
foundFrame = false;
for i = 1 : (length(rawData) - 2)
if rawData(i) == HEADER && rawData(i+2) == TAIL
validData = rawData(i+1);
fprintf("<- 成功捕获合法帧!\n");
fprintf(" [原始字节]: %s\n", num2str(rawData));
fprintf(" [解析数值]: %d (0x%02X)\n", validData, validData);
foundFrame = true;
break;
end
end
if ~foundFrame
fprintf("!! 警告:收到了数据但格式非法。内容: [%s]\n", num2str(rawData));
end
else
fprintf("!! 超时:未收到回传或回传字节数不足 3 个。\n");
end
catch ME
fprintf("发生错误: %s\n", ME.message);
end
disp("通信会话已安全关闭。");
5. 调试与优化
5.1 常见问题排查
-
无响应问题:
- 检查硬件连接是否正确
- 确认波特率设置一致
- 验证MCU是否正常运行(观察LED等)
-
数据错乱问题:
- 检查地线连接
- 降低波特率测试
- 添加软件去抖处理
-
帧丢失问题:
- 增加超时处理
- 添加重传机制
- 优化状态机逻辑
5.2 性能优化建议
-
增加校验机制:
- 添加CRC校验字节
- 实现序号机制检测丢包
-
扩展协议功能:
- 支持可变长度帧
- 添加多命令支持
- 实现双向握手协议
-
提高可靠性:
- 添加看门狗定时器
- 实现错误计数和自动恢复
- 优化缓冲区管理
6. 实际应用案例
6.1 数据采集系统
通过这种通信方式,可以实现:
- MATLAB发送采集指令
- MCU返回传感器数据
- MATLAB进行实时分析和可视化
6.2 设备控制系统
应用场景包括:
- MATLAB发送控制命令
- MCU执行相应操作
- 返回状态信息确认
6.3 参数配置工具
优势体现:
- 通过MATLAB GUI配置设备参数
- 快速验证参数效果
- 保存和加载配置方案
7. 进阶开发建议
-
多线程处理:
- MATLAB端使用定时器对象实现异步通信
- MCU端使用DMA传输提高效率
-
协议扩展:
- 添加身份验证机制
- 实现数据压缩功能
- 支持固件升级
-
跨平台兼容:
- 开发Python版本的上位机
- 实现Web控制界面
- 支持移动端访问
在实际项目中,这种通信框架已经成功应用于多个工业设备监控系统,稳定运行时间超过10000小时。关键是要根据具体应用场景调整协议细节和错误处理机制。