STM32串口通信实战从标准库配置到数据包解析引言在嵌入式开发领域串口通信就像工程师的瑞士军刀——简单却功能强大。无论是调试信息输出、设备间数据交换还是固件升级USART模块都扮演着关键角色。但看似简单的串口配置却暗藏不少陷阱波特率误差导致乱码、数据帧格式不匹配引发通信失败、缓冲区溢出造成数据丢失...这些问题往往让初学者在调试过程中焦头烂额。本文将基于STM32标准库带你深入理解串口通信的底层机制避开那些教科书上不会告诉你的坑。不同于简单的API调用教程我们会从电气特性、时序要求到协议设计全方位剖析串口通信的实战要点。无论你是在做毕业设计还是开发工业级产品这些经验都将帮助你构建更可靠的通信系统。1. USART硬件配置从原理到实践1.1 时钟树与波特率精度许多开发者在使用串口时遇到的第一个坑就是波特率不匹配。表面上看代码配置正确但实际通信却出现乱码。这往往与时钟树配置和波特率计算有关。STM32的USART时钟源来自APB总线。以常见的STM32F103系列为例USART1挂载在APB2总线最高72MHzUSART2/3挂载在APB1总线最高36MHz波特率计算公式为波特率 f_PCLK / (16 × USARTDIV)其中USARTDIV是一个包含整数和小数部分的16位值存储在USART_BRR寄存器中。常见错误示例// 目标波特率115200PCLK272MHz时 USART_InitStructure.USART_BaudRate 115200; // 实际计算USARTDIV39.0625 // 但BRR寄存器只能配置为39.0625≈39.0625 // 实际波特率72000000/(16×39.0625)115200 ✔️ // 目标波特率115200PCLK136MHz时 USART_InitStructure.USART_BaudRate 115200; // 计算USARTDIV19.53125 // 实际配置19.53125≈19.5312 // 实际波特率36000000/(16×19.5312)115207 ❌(误差0.06%)虽然0.06%的误差在大多数场合可以接受但在高速或长距离通信时可能累积成问题。建议使用ST提供的波特率计算工具或以下方法验证// 波特率验证代码 void CheckBaudRate(USART_TypeDef* USARTx) { uint32_t computed (RCC_Clocks.PCLK2_Frequency / (16 * USARTx-BRR)); printf(实际波特率%lu\n, computed); }1.2 GPIO配置的隐藏细节GPIO配置看似简单但有几个关键点常被忽视配置项发送引脚(TX)接收引脚(RX)模式GPIO_Mode_AF_PPGPIO_Mode_IN_FLOATING速度GPIO_Speed_50MHz无要求上拉/下拉通常不需要建议启用上拉特别提醒避免将RX配置为GPIO_Mode_IPU(内部上拉输入)这可能导致信号边沿变缓长距离通信时建议在TX端串联33Ω电阻抑制振铃跨设备通信时务必确认共地连接// 推荐的GPIO初始化代码 GPIO_InitTypeDef GPIO_InitStructure; // 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);2. 数据帧格式容易被忽略的关键参数2.1 停止位与空闲状态停止位配置不当是导致帧错误的常见原因。STM32支持多种停止位配置0.5位智能卡模式1位最常用1.5位2位选择原则大多数设备使用1位停止位在噪声环境中可考虑使用2位停止位增强鲁棒性确保通信双方配置一致USART_InitStructure.USART_StopBits USART_StopBits_1; // 推荐默认值2.2 校验位的实战应用校验位配置选项无校验(USART_Parity_No)奇校验(USART_Parity_Odd)偶校验(USART_Parity_Even)校验位选择建议场景推荐配置优点调试输出无校验节省带宽工业环境偶校验检测单bit错误已有CRC校验无校验避免冗余// 启用偶校验的配置示例 USART_InitStructure.USART_WordLength USART_WordLength_9b; USART_InitStructure.USART_Parity USART_Parity_Even;3. 中断与DMA高效数据处理的秘密3.1 接收中断的优化配置新手常犯的错误是过度依赖轮询方式检查接收状态这不仅浪费CPU资源还可能导致数据丢失。正确的中断配置流程使能USART接收中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);配置NVICNVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);编写中断服务例程void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t ch USART_ReceiveData(USART1); // 处理接收到的字节 USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }关键点及时清除中断标志位避免在中断服务例程中进行耗时操作对于高速数据流考虑使用环形缓冲区3.2 DMA传输的高级技巧当波特率高于115200时建议使用DMA减轻CPU负担。典型配置步骤初始化DMA通道DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel5); // USART1_TX DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)USART1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)TxBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize BufferSize; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel5, DMA_InitStructure);使能USART的DMA请求USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);启动DMA传输DMA_Cmd(DMA1_Channel5, ENABLE);性能优化技巧使用双缓冲技术避免传输间隙合理设置DMA优先级监控DMA传输完成中断4. 数据包协议设计从基础到实战4.1 帧格式设计原则原始字节流通信不可靠需要设计数据包协议。常见帧结构[包头][长度][数据][校验][包尾]示例Hex数据包格式0xFF 0xFF [长度] [数据...] [CRC16] 0xFE实现代码片段#pragma pack(push, 1) typedef struct { uint8_t header[2]; // 0xFF 0xFF uint8_t length; // 数据长度 uint8_t data[256]; // 有效载荷 uint16_t crc; // CRC16校验 uint8_t footer; // 0xFE } SerialPacket; #pragma pack(pop)4.2 状态机实现解析状态机是处理数据包的强大工具。典型状态包括等待包头接收数据校验处理等待包尾状态机实现示例typedef enum { STATE_WAIT_HEADER, STATE_RECEIVE_LENGTH, STATE_RECEIVE_DATA, STATE_CHECK_CRC, STATE_WAIT_FOOTER } PacketState; void ProcessUART(uint8_t ch) { static PacketState state STATE_WAIT_HEADER; static uint8_t dataIndex 0; static uint8_t expectedLength 0; static uint8_t packetBuffer[256]; switch(state) { case STATE_WAIT_HEADER: if(ch 0xFF) { state STATE_RECEIVE_LENGTH; dataIndex 0; } break; case STATE_RECEIVE_LENGTH: expectedLength ch; state (expectedLength 0) ? STATE_RECEIVE_DATA : STATE_CHECK_CRC; break; case STATE_RECEIVE_DATA: packetBuffer[dataIndex] ch; if(dataIndex expectedLength) { state STATE_CHECK_CRC; } break; case STATE_CHECK_CRC: // CRC校验逻辑 state STATE_WAIT_FOOTER; break; case STATE_WAIT_FOOTER: if(ch 0xFE) { // 完整包处理 } state STATE_WAIT_HEADER; break; } }4.3 错误处理机制健壮的通信协议需要包含错误处理超时检测每字节间隔不应超过3个字节时间CRC校验失败重传数据长度异常处理缓冲区溢出保护// 超时检测示例 #define BYTE_TIMEOUT_MS (1000 * 10 / 波特率) // 10个字节时间 uint32_t lastReceiveTime 0; void USART1_IRQHandler(void) { lastReceiveTime HAL_GetTick(); // ...处理数据 } void CheckTimeout(void) { if(HAL_GetTick() - lastReceiveTime BYTE_TIMEOUT_MS) { // 重置状态机 currentState STATE_WAIT_HEADER; } }5. 调试技巧与性能优化5.1 常见问题排查表现象可能原因排查方法完全无通信线序错误/波特率差太大检查TX/RX交叉连接随机乱码地线未接/波特率误差示波器测量波特率前几个字节丢失初始化顺序错误确保GPIO先于USART初始化偶发数据错误中断优先级冲突调整NVIC优先级DMA传输不完整缓冲区未对齐检查内存地址是否4字节对齐5.2 性能优化 checklist[ ] 使用DMA替代中断驱动传输[ ] 合理设置GPIO速度TX引脚建议50MHz[ ] 优化中断优先级USART中断应高于耗时外设[ ] 启用硬件流控CTS/RTS防止溢出[ ] 使用内存屏障确保数据一致性// 内存屏障使用示例 __DMB(); // 数据内存屏障 USART_SendData(USART1, data);6. 进阶应用多串口管理与协议栈6.1 多串口统一接口设计当项目需要使用多个串口时建议采用面向对象的设计思想typedef struct { USART_TypeDef* USARTx; DMA_Channel_TypeDef* DMA_Tx; DMA_Channel_TypeDef* DMA_Rx; uint8_t (*Send)(uint8_t* data, uint16_t len); uint8_t (*Receive)(uint8_t* buffer, uint16_t* len); } UART_Device; UART_Device UART1_Dev { .USARTx USART1, .DMA_Tx DMA1_Channel4, .Send UART1_Send, .Receive UART1_Receive }; // 统一发送接口 uint8_t SendToAll(UART_Device* dev, uint8_t* data, uint16_t len) { return dev-Send(data, len); }6.2 自定义协议栈实现基于串口的轻量级协议栈可包含以下层物理层处理原始字节收发链路层帧组装/解析、差错控制传输层数据分片、重传机制应用层业务逻辑处理协议栈框架示例void ProtocolStack_Init(void) { // 硬件层初始化 UART_Init(115200); DMA_Init(); // 协议层初始化 FrameParser_Init(); Retransmission_Init(); } void ProtocolStack_Run(void) { while(1) { // 接收处理 if(UART_ReceiveReady()) { uint8_t data UART_ReadByte(); FrameParser_PutByte(data); } // 发送处理 if(Application_HasData()) { uint8_t packet[MAX_PACKET]; uint16_t len Application_GetData(packet); Transmitter_SendPacket(packet, len); } } }7. 硬件设计注意事项7.1 PCB布局布线建议保持串口信号线远离高频噪声源如时钟线、PWM输出长距离传输时添加终端匹配电阻通常33-100Ω在RX/TX线上串联小电阻22-100Ω抑制振铃确保良好共地必要时使用磁珠隔离数字地和模拟地7.2 电平转换电路选型当连接不同电平标准的设备时转换类型推荐方案特点5V↔3.3V电阻分压成本低单向双向电平转换TXB0104自动方向检测隔离通信ADM3251E带2500V隔离RS-232转换MAX32323V供电2通道典型应用电路STM32_TX ──┬── 100Ω ──┐ │ ├─ TXB0104 ── 外部设备 STM32_RX ──┴── 100Ω ──┘8. 软件架构设计模式8.1 生产者-消费者模型适用于接收数据处理// 环形缓冲区实现 typedef struct { uint8_t buffer[256]; uint16_t head; uint16_t tail; uint16_t count; } RingBuffer; void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE)) { uint8_t data USART_ReceiveData(USART1); RingBuffer_Put(rxBuffer, data); // 生产者 } } void Application_Task(void) { uint8_t data; if(RingBuffer_Get(rxBuffer, data)) { // 消费者 // 处理数据 } }8.2 事件驱动架构使用回调机制解耦通信处理typedef void (*UART_Callback)(uint8_t* data, uint16_t len); typedef struct { UART_Callback onReceived; UART_Callback onSent; UART_Callback onError; } UART_Events; UART_Events uart1Events; void UART_SetCallbacks(UART_Events events) { uart1Events events; } void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE)) { uint8_t data USART_ReceiveData(USART1); if(uart1Events.onReceived) { uart1Events.onReceived(data, 1); } } }9. 测试与验证方法论9.1 自动化测试框架构建串口通信测试系统测试用例设计边界测试最大/最小波特率压力测试连续大数据量传输错误注入人为插入错误数据测试工具链PC端Python脚本pySerial逻辑分析仪捕获波形自定义测试固件回环测试# Python测试脚本示例 import serial import time def test_baudrate(port, baudrate): try: ser serial.Serial(port, baudrate, timeout1) test_data bABCDEFG ser.write(test_data) received ser.read(len(test_data)) return test_data received except: return False baudrates [9600, 115200, 230400, 460800, 921600] for rate in baudrates: result test_baudrate(COM3, rate) print(f波特率 {rate}: {通过 if result else 失败})9.2 性能指标测量关键性能指标及测量方法吞吐量测试发送1MB数据记录总时间计算实际传输速率考虑协议开销延迟测量使用GPIO引脚标记发送/接收时刻示波器测量信号间隔可靠性验证连续传输24小时统计误码率不同电压条件下的通信稳定性10. 从标准库到HAL迁移指南10.1 主要差异对比特性标准库HAL库初始化方式结构体直接赋值句柄初始化函数中断处理手动清除标志位自动清除回调机制DMA集成需要单独配置内置DMA支持代码体积较小较大可移植性仅限于F1系列全系列兼容10.2 迁移步骤示例标准库代码USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_Init(USART1, USART_InitStructure);等效HAL代码UART_HandleTypeDef huart1; huart1.Instance USART1; huart1.Init.BaudRate 115200; huart1.Init.WordLength UART_WORDLENGTH_8B; HAL_UART_Init(huart1);中断处理对比// 标准库 void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE)) { uint8_t data USART_ReceiveData(USART1); USART_ClearITPendingBit(USART1, USART_IT_RXNE); } } // HAL库 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart-Instance USART1) { // 处理接收完成事件 } }11. 固件升级方案设计11.1 Bootloader实现要点通过串口实现IAP升级的关键组件通信协议自定义简单协议如YMODEM简化版包含文件信息、校验和、重传机制内存布局Bootloader占用前16KB Flash应用程序从0x08004000开始共享4字节标志位0x08003FFC跳转机制typedef void (*pFunction)(void); pFunction JumpToApplication; void JumpToApp(uint32_t appAddress) { __disable_irq(); uint32_t stackPointer *(volatile uint32_t*)appAddress; uint32_t resetHandler *(volatile uint32_t*)(appAddress 4); __set_MSP(stackPointer); JumpToApplication (pFunction)resetHandler; JumpToApplication(); }11.2 安全考虑固件签名验证ECDSA/Ed25519加密传输AES-128/256防回滚机制版本号检查完整性校验SHA-256// 简化的校验流程 uint8_t VerifyFirmware(void* data, uint32_t len) { // 1. 检查魔数 if(*(uint32_t*)data ! 0xDEADBEEF) return 0; // 2. 校验CRC32 uint32_t crc Compute_CRC32((uint8_t*)data 8, len - 8); if(crc ! *(uint32_t*)(data 4)) return 0; // 3. 版本检查 if(*(uint16_t*)(data 8) currentVersion) return 0; return 1; }12. 低功耗设计技巧12.1 睡眠模式下的串口唤醒配置步骤启用串口唤醒功能USART_WakeUpConfig(USART1, USART_WakeUp_IdleLine);配置NVIC唤醒中断NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);进入低功耗模式前配置USART_ReceiverWakeUpCmd(USART1, ENABLE); __WFI(); // 进入睡眠12.2 动态波特率调整根据通信需求动态调整功耗void SetLowPowerMode(uint8_t enable) { if(enable) { // 切换到低速波特率 USART_InitStructure.USART_BaudRate 9600; USART_Init(USART1, USART_InitStructure); // 关闭接收器 USART_Cmd(USART1, DISABLE); USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); } else { // 恢复高速模式 USART_InitStructure.USART_BaudRate 115200; USART_Init(USART1, USART_InitStructure); // 重新使能接收 USART_Cmd(USART1, ENABLE); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); } }13. 多机通信与网络拓扑13.1 RS-485组网技术硬件配置要点选用SN65HVD72等半双工收发器终端电阻匹配120Ω总线偏置电阻保证空闲状态软件协议设计主从轮询机制地址分配方案1-255冲突检测与重试策略// RS-485发送使能控制 #define DE_PIN GPIO_Pin_1 #define DE_PORT GPIOB void RS485_SendMode(uint8_t enable) { GPIO_WriteBit(DE_PORT, DE_PIN, enable ? Bit_SET : Bit_RESET); Delay_us(10); // 等待稳定 } void SendToAddress(uint8_t addr, uint8_t* data, uint16_t len) { RS485_SendMode(1); USART_SendData(USART1, addr); USART_SendData(USART1, len); for(uint16_t i0; ilen; i) { USART_SendData(USART1, data[i]); } RS485_SendMode(0); }13.2 无线透传模块集成常见方案对比模块类型典型型号接口方式传输距离功耗WiFiESP-01SAT指令室内50m中BLECC2541串口透传10m低LoRaSX1278SPI3km极低4GSIM7600USB/串口全网覆盖高集成示例ESP8266 AT指令void WiFi_SendATCommand(const char* cmd, uint32_t timeout) { UART_ClearBuffer(); USART_SendString(USART2, cmd); USART_SendString(USART2, \r\n); uint32_t start HAL_GetTick(); while(!UART_CheckResponse(OK, 2) (HAL_GetTick() - start timeout)) { // 等待响应 } } void WiFi_Init(void) { WiFi_SendATCommand(AT, 1000); WiFi_SendATCommand(ATCWMODE1, 1000); WiFi_SendATCommand(ATCWJAP\SSID\,\PASSWORD\, 5000); }14. 实时性与优先级管理14.1 中断优先级配置原则USART中断优先级设置建议接收中断高于发送中断高波特率通信使用更高优先级避免与定时器关键中断冲突// 合理的优先级配置示例 NVIC_InitTypeDef NVIC_InitStructure; // USART1接收中断 - 最高优先级 NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_Init(NVIC_InitStructure); // USART1 DMA中断 - 次高优先级 NVIC_InitStructure.NVIC_IRQChannel DMA1_Channel4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_Init(NVIC_InitStructure);14.2 实时性能优化技巧双缓冲技术减少内存拷贝开销uint8_t rxBuffer[2][256]; uint8_t activeBuffer 0; void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE)) { rxBuffer[activeBuffer][index] USART_ReceiveData(USART1); if(index 256) { activeBuffer ^ 1; // 切换缓冲区 index 0; } } }零拷贝设计直接处理接收缓冲区优先级继承确保关键任务及时处理时间戳标记精确测量处理延迟15. 调试接口与日志系统15.1 分级日志系统实现typedef enum { LOG_LEVEL_DEBUG, LOG_LEVEL_INFO, LOG_LEVEL_WARNING, LOG_LEVEL_ERROR } LogLevel; void Log_Print(LogLevel level, const char* format, ...) { static const char* levelStr[] {DBG, INF, WRN, ERR}; if(level currentLogLevel) return; char buffer[256]; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); printf([%lu][%s] %s\r\n, HAL_GetTick(), levelStr[level], buffer); } // 使用示例 Log_Print(LOG_LEVEL_DEBUG, Sensor value: %d, sensorRead());15.2 调试协议设计二进制调试协议示例偏移长度描述01协议版本11命令类型22数据长度4N有效载荷4N2CRC16校验命令类型定义0x01: 读取内存0x02: 写入内存0x03: 调用函数0x04: 获取系统信息#pragma pack(push, 1) typedef struct { uint8_t version; uint8_t command; uint16_t length; uint8_t data[256]; uint16_t crc; } DebugPacket; #pragma pack(pop) void HandleDebugCommand(DebugPacket* pkt) { uint16_t computedCrc ComputeCRC16(pkt, sizeof(DebugPacket)-2); if(computedCrc ! pkt-crc) return; switch(pkt-command) { case 0x01: // 读取内存 MemoryReadHandler(pkt); break; case 0x02: // 写入内存 MemoryWriteHandler(pkt); break; // 其他命令处理... } }16. 跨平台兼容性设计16.1 字节序处理// 网络字节序转换 uint32_t htonl(uint32_t hostlong) { return ((hostlong 0xFF) 24) | ((hostlong 0xFF00) 8) | ((hostlong 8) 0xFF00) | ((hostlong 24) 0xFF); } // 浮点数传输方案 typedef union { float f; uint8_t b[4]; } FloatUnion; void SendFloat(float value) { FloatUnion u; u.f value; for(int i0; i4; i) { USART_SendData(USART1, u.b[i]); } }16.2 协议版本控制版本协商流程设备上电发送版本信息主机回复支持的版本列表选择双方都支持的最高版本// 版本协商数据结构 typedef struct { uint8_t major; uint8_t minor; uint16_t supportedFeatures; } ProtocolVersion; ProtocolVersion deviceVersion { .major 1, .minor 2, .supportedFeatures 0x0003 // 位掩码 }; void VersionNegotiation(void) { USART_SendPacket(deviceVersion, sizeof(deviceVersion)); ProtocolVersion hostVersion; if(USART_ReceivePacket(hostVersion, sizeof(hostVersion))) { uint8_t useVersion MIN(deviceVersion.major, hostVersion.major); currentProtocol useVersion; } }17. 安全通信实践17.1 基础加密技术AES-128加密示例#include tiny_aes.h void EncryptPacket(uint8_t* data, uint16_t len, const uint8_t* key) { struct AES_ctx ctx; AES_init_ctx(ctx, key); for(uint16_t i0; ilen; i16) { AES_ECB_encrypt(ctx, data i); } } // 使用示例 uint8_t key[16] {0x2b, 0x7e, 0x15, 0x16, ...}; uint8_t packet[32] {...}; EncryptPacket(packet, sizeof(packet), key);17.2 防重放攻击序列号时间戳方案typedef struct { uint32_t sequence; uint32_t timestamp; uint8_t data[248]; uint8_t mac[32]; // HMAC-SHA256 } SecurePacket; uint32_t lastSequence 0; uint32_t lastTimestamp 0; uint8_t ValidatePacket(SecurePacket* pkt) { // 检查序列号 if(pkt-sequence lastSequence) return 0; // 检查时间戳允许±10秒误差 uint32