第三讲:通信协议基础——串口参数配置与数据帧结构

好,咱们进入正题。这一讲要聊的是二维码模块和POS机主板之间怎么「说话」。说白了,就是串口通信那点事儿。我刚开始做嵌入式开发那会儿,觉得串口协议太简单了,不就是发数据收数据嘛。结果呢?第一次联调二维码模块,死活读不到数据,折腾了一下午才发现是波特率配错了。嗯,从那以后我再也不敢小看这些基础参数了。

3.1 串口参数配置:四个关键数字

串口通信,你得先让两边「约法三章」。哪三章?波特率、数据位、停止位、校验位——其实是四章。我习惯把这四个参数叫做「串口四件套」,少一个都不行。

3.1.1 波特率(Baud Rate)

波特率就是每秒传输多少个符号。对于咱们二维码模块来说,常用的波特率是9600、19200、115200。我个人建议,如果模块和主板距离不超过30厘米,直接用115200。速度快,扫码体验好。

但要注意一点:波特率越高,抗干扰能力越差。我在一个项目里遇到过,线缆长了点,115200下数据全是乱码,降到38400就稳了。所以,别一味追求高速。

常见二维码模块波特率配置:

  • 低速模式:9600 bps(兼容性好,适合长距离)
  • 中速模式:38400 bps(我常用的折中方案)
  • 高速模式:115200 bps(短距离首选)

3.1.2 数据位(Data Bits)

数据位一般就是7位或8位。二维码模块传输的是ASCII码和汉字,8位是标配。7位?那是老式电传机用的,咱们别碰。

3.1.3 停止位(Stop Bits)

停止位通常是1位。偶尔有模块要求2位,但很少见。我做了这么多年,只遇到过一次——某个老款模块,手册上写着必须配2位停止位,否则不干活。嗯,这种特殊情况,按手册来就行。

3.1.4 校验位(Parity Bit)

校验位分三种:无校验(None)、奇校验(Odd)、偶校验(Even)。二维码模块通信,绝大多数场景用无校验。为什么?因为咱们后面有更可靠的校验和机制,没必要在这里浪费一个bit。

我的经验:初始化二维码模块时,先发一条查询指令,看看模块有没有回应。如果没回应,八成是串口参数没对上。我曾经被这个坑过三次,后来学乖了,写了个自动波特率检测函数。

3.2 数据帧结构:协议的核心骨架

参数配好了,接下来就是数据怎么打包。二维码模块的通信协议,说白了就是一套「信封」规则。你得知道信封怎么写,收信人才能看懂。

一个标准的数据帧,通常包含四个部分:

字段 长度 说明
包头 2字节 固定值,比如 0xAA 0x55
数据长度 1-2字节 从指令开始到校验和之前的字节数
指令 1字节 比如 0x01 表示扫码,0x02 表示停止
校验和 1字节 前面所有字节的累加和(取低8位)

3.2.1 包头(Header)

包头的作用就一个:告诉接收方「我要开始发数据了」。常见的包头是0xAA 0x55,或者0x55 0xAA。为什么选这两个值?因为它们二进制是10101010和01010101,交替的0和1,不容易被误判。

我记得有一次,客户说模块偶尔会收到乱码。我抓了波形一看,原来是电源纹波太大,把包头给冲坏了。后来在包头后面加了个超时判断,问题就解决了。

3.2.2 数据长度(Length)

数据长度告诉接收方:后面还有多少字节要收。这个字段很关键,接收方要根据它来决定什么时候停止接收。

举个例子:

// 假设数据帧是这样的
0xAA 0x55 0x05 0x01 0x31 0x32 0x33 0x?? 
// 包头: 0xAA 0x55
// 长度: 0x05(表示后面有5个字节)
// 指令: 0x01
// 数据: 0x31 0x32 0x33("123"的ASCII码)
// 校验和: 待计算

注意:数据长度字段的值,要包含指令字节和数据字节,但不包含校验和。这个细节我见过好几个人搞错,包括我自己——第一次写协议解析时,把校验和也算进去了,结果解析出来的数据总是少一个字节。

3.2.3 指令(Command)

指令就是你要模块干什么。常见的指令有:

  • 0x01:启动扫码(模块开始扫描)
  • 0x02:停止扫码
  • 0x03:查询状态(模块是否空闲)
  • 0x10:设置参数(比如修改波特率)

每个模块厂商的指令定义可能不一样。我建议你拿到模块后,第一件事就是把指令表打印出来贴在工位上。别问我为什么知道——我曾经把0x01和0x10搞反了,调了一整天。

3.2.4 校验和(Checksum)

校验和是保证数据完整性的最后一道防线。最简单的做法是累加和:把包头、长度、指令、数据全部加起来,取低8位。

计算示例:

// 假设数据帧: AA 55 05 01 31 32 33
// 累加: 0xAA + 0x55 + 0x05 + 0x01 + 0x31 + 0x32 + 0x33
//      = 170 + 85 + 5 + 1 + 49 + 50 + 51
//      = 411
// 取低8位: 411 % 256 = 155 = 0x9B
// 所以完整的帧是: AA 55 05 01 31 32 33 9B

避坑指南:我曾经遇到过一个模块,它的校验和算法不是简单的累加,而是异或(XOR)。手册上写的是「Checksum」,我默认用了累加和,结果模块一直返回错误。后来仔细看手册才发现,人家用的是异或。所以,拿到新模块,先看校验算法,别想当然。

3.3 实战:写一个串口初始化函数

理论说完了,咱们来点实际的。下面是我常用的串口初始化代码,以STM32为例:

// 串口初始化函数
// 参数: baud - 波特率
void UART_Init(uint32_t baud)
{
    // 1. 使能时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    
    // 2. 配置GPIO(TX: PA9, RX: PA10)
    GPIO_InitTypeDef gpio;
    gpio.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
    gpio.GPIO_Mode = GPIO_Mode_AF_PP;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &gpio);
    
    // 3. 配置串口参数
    USART_InitTypeDef usart;
    usart.USART_BaudRate = baud;           // 波特率
    usart.USART_WordLength = USART_WordLength_8b;  // 8位数据位
    usart.USART_StopBits = USART_StopBits_1;       // 1位停止位
    usart.USART_Parity = USART_Parity_No;          // 无校验
    usart.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
    USART_Init(USART1, &usart);
    
    // 4. 使能串口
    USART_Cmd(USART1, ENABLE);
    
    printf("串口初始化完成,波特率: %d\n", baud);
}

这段代码我用了好几年,基本没出过问题。唯一要注意的是,有些二维码模块上电后需要几十毫秒的稳定时间。我习惯在初始化后加一个100ms的延时,再发第一条指令。

3.4 数据帧解析:接收端的处理逻辑

发数据容易,收数据才是考验。接收端要做的,就是从一串字节流里把完整的数据帧提取出来。我的做法是状态机:

  1. 状态0:等待包头——收到0xAA,进入状态1;否则继续等
  2. 状态1:等待第二个包头——收到0x55,进入状态2;否则回到状态0
  3. 状态2:接收数据长度——收到长度值,进入状态3
  4. 状态3:接收数据体——根据长度值,收完所有数据,进入状态4
  5. 状态4:接收校验和——收到校验和,验证。通过则处理数据,失败则丢弃

这个状态机看起来简单,但实际调试时容易出问题。我遇到过最坑的情况是:模块在发送数据时突然断电,导致只发了半个帧。接收端卡在状态2,后面的数据全乱了。解决方案是加一个超时机制——如果某个状态停留超过100ms,强制回到状态0。

总结一下这一讲的核心:

  • 串口四件套:波特率、数据位、停止位、校验位,一个都不能错
  • 数据帧结构:包头 + 长度 + 指令 + 数据 + 校验和
  • 校验和算法:先看手册,别默认用累加和
  • 接收端用状态机,别忘了加超时保护

下一讲,咱们会深入二维码模块的指令集,看看怎么让模块真正开始扫码。到时候我会分享一个我踩过的坑——关于模块休眠唤醒的,挺有意思。