1. 项目背景与核心价值
在嵌入式系统开发中,安全通信一直是关键需求。STM32F103作为经典的Cortex-M3内核微控制器,其串口通信功能被广泛应用于工业控制、智能家居等领域。但常规的串口通信往往缺乏基本的安全验证机制,容易受到非法访问。
这个项目实现了一种基于固定密码的串口通信验证方案。当上位机通过串口发送指令时,下位机(STM32F103)会先验证密码的正确性,只有密码匹配才会执行后续操作。这种机制虽然简单,但能有效防止未经授权的设备或人员随意操作系统。
我在实际工业项目中多次遇到这样的需求:产线测试工装需要限制操作权限,智能家居设备需要防止邻居误操作。直接使用这个方案,成本增加几乎为零(只需几行代码),但安全性得到显著提升。下面将完整分享从硬件连接到软件实现的全部细节。
2. 硬件设计与连接要点
2.1 最小系统搭建
STM32F103C8T6最小系统需要以下基本组件:
- 8MHz晶振(HSE时钟源)
- 32.768kHz晶振(RTC时钟可选)
- 复位电路(10kΩ上拉电阻+0.1μF电容)
- 启动模式选择(BOOT0/BOOT1通常接地)
- 3.3V稳压电路(AMS1117-3.3)
特别注意:晶振负载电容通常选择20pF,但实际值需要根据晶振规格调整。我曾因使用了15pF电容导致通信不稳定,更换后问题解决。
2.2 串口硬件连接
使用USART1(PA9-TX, PA10-RX)与上位机通信:
- STM32 TX → USB转串口模块 RX
- STM32 RX → USB转串口模块 TX
- 共地连接(GND对接)
电平转换方案选择:
- 3.3V系统直接连接:适用于目标设备为3.3V电平
- MAX3232芯片:当需要连接PC串口(RS232电平)时使用
- CH340G模块:最常用的USB转TTL方案
实测中发现,某些国产USB转串口模块在长时间工作时会出现数据丢包。推荐使用FT232RL芯片的方案,稳定性更好。
3. 软件实现详解
3.1 开发环境配置
使用Keil MDK-ARM开发环境:
- 安装Keil uVision5(建议V5.38以上版本)
- 安装STM32F1xx_DFP器件支持包
- 配置工程选项:
- Target→选择STM32F103C8
- C/C++→Define→添加USE_STDPERIPH_DRIVER
- Debug→选择ST-Link调试器
串口初始化关键代码:
c复制void USART1_Init(uint32_t baudrate) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// TX配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// RX配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = baudrate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
3.2 密码验证机制实现
采用固定密码+动态响应的验证方案:
- 上位机发送格式:"PASS=yourpassword\r\n"
- 下位机接收后提取密码部分
- 验证通过返回"OK\r\n",失败返回"ERROR\r\n"
核心处理函数:
c复制#define PASSWORD "STM32@2023" // 预设密码
uint8_t CheckPassword(uint8_t *buf) {
if(strncmp((char*)buf, "PASS=", 5) != 0)
return 0;
char *receivedPass = strtok((char*)buf+5, "\r\n");
if(strcmp(receivedPass, PASSWORD) == 0)
return 1;
return 0;
}
void USART1_IRQHandler(void) {
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
static uint8_t rxBuf[64];
static uint8_t index = 0;
uint8_t ch = USART_ReceiveData(USART1);
rxBuf[index++] = ch;
if(ch == '\n' || index >= sizeof(rxBuf)-1) {
rxBuf[index] = '\0';
if(CheckPassword(rxBuf)) {
USART_SendString(USART1, "OK\r\n");
g_authenticated = 1; // 全局认证标志
} else {
USART_SendString(USART1, "ERROR\r\n");
}
index = 0;
}
}
}
安全增强建议:实际项目中可以添加尝试次数限制(如连续3次错误后锁定串口5分钟),防止暴力破解。
4. 通信协议设计
4.1 帧格式规范
完整通信流程示例:
code复制[上位机] PASS=STM32@2023
[下位机] OK
[上位机] LED=ON
[下位机] DONE
协议字段说明:
| 字段 | 说明 | 示例 |
|---|---|---|
| PASS | 密码前缀 | PASS=xxxx |
| OK | 验证成功 | OK\r\n |
| ERROR | 验证失败 | ERROR\r\n |
| CMD | 指令前缀 | LED=ON\r\n |
| DONE | 执行完成 | DONE\r\n |
4.2 超时处理机制
添加通信超时检测:
- 使用SysTick定时器创建1ms时基
- 定义30ms的帧间隔超时
- 超过30ms未收到新字符则认为一帧结束
实现代码片段:
c复制volatile uint32_t g_tickCount = 0;
void SysTick_Handler(void) {
g_tickCount++;
}
uint32_t GetTick(void) {
return g_tickCount;
}
// 在串口中断中添加
void USART1_IRQHandler(void) {
static uint32_t lastTick = 0;
uint32_t now = GetTick();
if(now - lastTick > 30) { // 超时重置缓冲区
index = 0;
}
lastTick = now;
// ...原有处理逻辑
}
5. 上位机测试方案
5.1 使用串口调试助手
推荐测试工具:
- Windows: AccessPort、SSCOM5.13
- Linux: minicom、gtkterm
- macOS: Serial(App Store免费工具)
测试步骤:
- 选择正确的COM端口
- 设置波特率(与下位机一致,常用115200)
- 发送"PASS=STM32@2023"
- 收到OK响应后发送控制指令
5.2 Python自动化测试脚本
python复制import serial
import time
ser = serial.Serial('COM3', 115200, timeout=1)
def send_cmd(cmd):
ser.write((cmd + '\r\n').encode())
return ser.readline().decode().strip()
# 密码验证测试
response = send_cmd("PASS=STM32@2023")
print("Auth response:", response)
if response == "OK":
# 控制指令测试
print("LED control:", send_cmd("LED=ON"))
time.sleep(1)
print("LED control:", send_cmd("LED=OFF"))
ser.close()
6. 常见问题与解决方案
6.1 通信不稳定问题排查
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 数据乱码 | 波特率不匹配 | 检查双方波特率设置 |
| 接收不完整 | 缓冲区溢出 | 增大接收缓冲区大小 |
| 偶尔丢包 | 线路干扰 | 缩短连线,加磁环 |
| 无法通信 | 接线错误 | 检查TX/RX交叉连接 |
6.2 密码验证失败分析
- 大小写问题:确认密码是否区分大小写
- 特殊字符:检查转义字符处理
- 结尾符:确保发送了\r\n
- 空格问题:密码前后可能有隐藏空格
调试技巧:在验证函数中添加调试输出,打印实际接收到的密码字符串。
7. 方案优化与扩展
7.1 动态密码升级
基础方案使用固定密码,安全性有限。可以升级为:
- 时间同步动态密码(TOTP)
- 挑战-响应机制
- AES加密通信
简易动态密码示例:
c复制// 基于分钟数的动态密码
char* GenerateDynamicPass(void) {
static char pass[16];
uint32_t minutes = GetTick() / 60000;
sprintf(pass, "KEY%04d", minutes % 10000);
return pass;
}
7.2 多级权限控制
扩展协议支持不同权限等级:
code复制ADMINPASS=super123 // 管理员密码
USERPASS=user456 // 用户密码
对应不同的指令集权限,在解析命令时检查当前权限级别。
7.3 加密通信实现
使用XXTEA轻量级加密算法:
c复制void xxtea_encrypt(uint32_t *v, int n, uint32_t const key[4]);
// 发送前加密数据
uint32_t data[2] = {0x12345678, 0xABCDEF01};
uint32_t key[4] = {0x1A2B3C4D, 0x5E6F7A8B, 0x9C0D1E2F, 0x3A4B5C6D};
xxtea_encrypt(data, 2, key);
这个方案在保持STM32F103资源占用的同时,显著提升了通信安全性。实际项目中,我曾用类似方案为工业控制器增加基础防护,有效阻止了产线上的误操作问题。对于更高级的安全需求,建议考虑硬件加密芯片或升级到STM32F4系列支持AES硬解密的型号。