03、单线通讯—SIF协议在资源受限MCU中的定时器驱动实现与优化
1. SIF协议在资源受限MCU中的应用场景
在嵌入式开发中,我们经常会遇到一些资源极其有限的单片机,比如STC8G1K08这种只有20个引脚的小型MCU。这类芯片往往没有标准串口(UART)、I2C或SPI等常见的外设接口,但实际项目中又需要与其他设备进行数据通讯。这时候,SIF(Single-wire Interface)单线通讯协议就派上了大用场。
我曾在电动车充电器项目中遇到过这种情况。充电器需要通过一根线与电动车的BMS(电池管理系统)通讯,这根线既要供电又要传输数据。由于硬件限制,我们只能使用一个GPIO口来实现通讯功能。经过多次尝试,最终选择了SIF协议,因为它只需要一个通用定时器和一个GPIO口就能实现完整的数据收发功能。
SIF协议最大的特点就是简单高效。它通过电平持续时间的长短来区分数据位,通常用32Tosc表示短时间,64Tosc表示长时间。一个完整的数据帧由同步信号、数据信号和结束信号三部分组成。同步信号用于告诉接收方数据传输即将开始,通常是一个长时间的低电平(>992Tosc)加上一个短时间的高电平(32Tosc)。
2. 硬件连接与基本通讯规则
2.1 最简单的硬件连接方式
SIF协议最吸引人的地方就是硬件连接极其简单。主从双方只需要一根线连接,再加上共地线就可以了。在实际项目中,我通常这样连接:
- 主机(发送方)的GPIO口设置为推挽输出
- 从机(接收方)的GPIO口设置为高阻输入
- 线上加一个上拉电阻(5V系统用2.2K,3.3V系统用1K)
这种连接方式在电动车充电系统中很常见。电池包作为主机,充电器作为从机,通过单线进行通讯。由于硬件资源有限,从机端通常没有外部中断可用,只能依靠定时器来扫描GPIO的电平变化。
2.2 通讯规则详解
SIF协议的通讯规则其实很好理解。每帧数据由三部分组成:
- 同步信号:>992Tosc的低电平 + 32Tosc的高电平
- 数据信号:8bit × N个数据字节
- 结束信号:特定的电平组合
数据位的表示方式很有特点:
- 逻辑"0":64Tosc低电平 + 32Tosc高电平
- 逻辑"1":32Tosc低电平 + 64Tosc高电平
这里有个小技巧:无论数据是0还是1,一个完整的位周期都是96Tosc(32+64)。这个特性在代码实现时非常有用,我们可以利用它来简化判断逻辑。
3. 定时器驱动的实现方案
3.1 定时器初始化与配置
在没有外部中断的情况下,我们必须依靠定时器来实现数据接收。以STC8G1K08为例,我通常会这样配置定时器0:
void Timer0_Init(void) { AUXR |= 0x80; // 定时器时钟1T模式 TMOD &= 0xF0; // 设置定时器模式:16位自动重载模式 TL0 = 0x5B; // 设置定时初值低8位,5微秒@33.000MHz TH0 = 0xFF; // 设置定时初值高8位 TF0 = 0; // 清除TF0标志 TR0 = 1; // 定时器0开始计时 }这里将定时器设置为5us中断一次。为什么选择5us?因为根据协议,32Tosc对应的时间通常是0.5ms(500us),而500/32≈15us。我们使用5us的定时周期,这样每个Tosc就是3个定时周期(15us/5us=3)。
3.2 状态机设计
接收数据的核心是一个状态机,我通常定义以下几个状态:
typedef enum { INITIAL_STATE=0, // 初始状态,等待同步信号 SYNC_L_STATE=1, // 接收同步低电平 SYNC_H_STATE=2, // 接收同步高电平 DATA_REV_STATE=3, // 接收数据位 RESTART_REV_STATE=4 // 出错重新接收 } REV_STATE_e;状态机的转换逻辑是这样的:
- 初始状态下检测到低电平,进入SYNC_L_STATE
- 低电平持续时间超过992Tosc后检测到高电平,进入SYNC_H_STATE
- 高电平持续时间测量并计算Tosc值,进入DATA_REV_STATE
- 在DATA_REV_STATE中解析每个数据位
- 任何阶段出错都进入RESTART_REV_STATE
4. 数据解析的关键技术
4.1 波特率自适应实现
在实际项目中,主机可能会改变通讯速率,所以从机最好能自适应波特率。SIF协议通过同步信号的高电平时间(32Tosc)来传递时基信息。实现代码如下:
case SYNC_H_STATE: if (DATA_REV_PIN == LOW) { Tosc = H_L_Level_time_cnt / SHORT_TIME_NUM; H_L_Level_time_cnt = 0; receive_state = DATA_REV_STATE; } break;这段代码在检测到同步高电平结束(出现低电平)时,根据实际测量的高电平时间计算出Tosc值。这样无论主机使用什么速率,从机都能自动适应。
4.2 数据位判断的两种方法
判断数据位是0还是1,我总结出两种可靠的方法:
方法一:中点判断法
- 一个数据位周期是96Tosc
- 取中点48Tosc作为判断点
- 在低电平开始后,如果在48Tosc之前出现高电平,则是1;之后出现则是0
方法二:区间判断法
- 将32Tosc分成4份,每份8Tosc
- 取40Tosc到56Tosc作为判断区间
- 在此区间内检测电平状态来判断数据位
实测下来,中点判断法实现更简单,而区间判断法抗干扰能力更强。在电动车充电器这种有轻微干扰的环境中,我最终选择了区间判断法。
5. 代码优化与实践经验
5.1 常见问题与解决方案
在实现SIF协议的过程中,我踩过几个坑,这里分享给大家:
- 数据覆盖问题:如果上一帧数据还没处理完,新数据就可能覆盖缓冲区。解决方法是在INITIAL_STATE增加read_success判断:
if (read_success==0 && DATA_REV_PIN == LOW)- 误判问题:先判断时间再检测电平容易导致误判。优化后的逻辑是先等待电平变化,再根据时间判断数据位:
if (has_read_bit==0) { if (DATA_REV_PIN == HIGH) { if (H_L_Level_time_cnt < (HALF_LOGIC_CYCLE * Tosc)) { // 逻辑"1" } else { // 逻辑"0" } } }5.2 性能优化技巧
经过多次优化,我总结出几个提升SIF通讯可靠性的技巧:
- 施密特触发器:启用GPIO口的施密特触发器可以增强抗干扰能力:
P1NCS |= 0x01; // 使能施密特触发器- 定时器中断优化:将数据处理放在主循环而非定时器中断中,避免中断处理时间过长:
void Timer0_isr() interrupt 1 { if (start_H_L_Level_timming_flag==1) { H_L_Level_time_cnt++; } // 不要在中断中处理复杂逻辑 }- 状态机简化:减少不必要的状态转换,每个状态只做最必要的判断。
在实际项目中,经过这些优化后,SIF协议在STC8G1K08上实现了高达1kbps的稳定通讯速率,完全满足了电动车充电器的通讯需求。这种方案不仅成本低,而且可靠性很高,特别适合资源受限的嵌入式应用场景。
