2. LIN从节点帧头接收与PID校验实现
任务定义
本案例要求从节点根据主节点发送的帧头,在调度表分配的时隙内完成响应。LIN总线采用主从机制:主节点负责发送帧头(同步间隔+同步场+PID),从节点仅在帧头中的PID与自己配置的ID匹配时,才在规定的响应空间内发送数据。核心目标是验证从节点能否在正确的时隙窗口内完成响应,且不占用其他时隙。
硬件连接与拓扑
调度表时序设计
主节点调度表定义如下(帧时隙=10ms,包含帧间间隔):
从节点A配置ID=0x01,从节点B配置ID=0x02。当主节点在时隙1发送PID=0x01的帧头时,从节点A应在该时隙内完成响应;从节点B检测到PID不匹配,必须保持静默。
从节点响应实现
以下代码实现从节点A的调度表响应逻辑,基于标准LIN 2.x协议:
// lin_slave_node.c - 从节点调度表响应实现
#include <stdint.h>
#include <stdbool.h>
// LIN协议常量
#define LIN_BREAK_FIELD 0x00 // 同步间隔场(13位显性)
#define LIN_SYNC_FIELD 0x55 // 同步场
#define LIN_MAX_DATA_FRAME 8 // 最大数据长度
// 从节点配置
#define SLAVE_NODE_ID 0x01 // 本节点PID(含奇偶校验)
#define RESPONSE_DELAY_MAX 4 // 最大响应延迟(字节时间)
// 状态机定义
typedef enum {
ST_IDLE, // 等待帧头
ST_BREAK_DETECT, // 检测到同步间隔
ST_SYNC, // 接收同步场
ST_PID, // 接收PID
ST_DATA, // 发送数据
ST_CHECKSUM, // 发送校验和
ST_COMPLETE // 帧完成
} lin_state_t;
// 帧数据结构
typedef struct {
uint8_t pid; // 保护ID
uint8_t data[LIN_MAX_DATA_FRAME];
uint8_t data_len;
uint8_t checksum;
} lin_frame_t;
// 全局状态
static lin_state_t state = ST_IDLE;
static lin_frame_t tx_frame;
static uint8_t byte_index = 0;
static uint8_t response_timer = 0;
// 模拟的传感器数据(温度值)
static uint8_t get_temperature_data(void) {
// 实际项目中从ADC读取,此处返回模拟值
return 0x25; // 37°C
}
// 计算经典校验和(LIN 1.x)或增强校验和(LIN 2.x)
static uint8_t calculate_checksum(const uint8_t *data, uint8_t len, uint8_t pid) {
uint16_t sum = 0;
// LIN 2.x增强校验和包含PID
sum += pid;
for (uint8_t i = 0; i < len; i++) {
sum += data[i];
if (sum > 0xFF) {
sum -= 0xFF; // 回卷进位
}
}
return (uint8_t)(~sum & 0xFF);
}
// PID奇偶校验检查(标准LIN)
static bool check_pid_parity(uint8_t pid) {
uint8_t p0 = ((pid >> 0) & 0x01) ^ ((pid >> 1) & 0x01) ^
((pid >> 2) & 0x01) ^ ((pid >> 4) & 0x01);
uint8_t p1 = ((pid >> 1) & 0x01) ^ ((pid >> 3) & 0x01) ^
((pid >> 4) & 0x01) ^ ((pid >> 5) & 0x01);
uint8_t expected_p0 = (pid >> 6) & 0x01;
uint8_t expected_p1 = (pid >> 7) & 0x01;
return (p0 == expected_p0) && (p1 == expected_p1);
}
// 主处理函数 - 每个位时隙调用一次
void lin_slave_poll(uint8_t bus_bit) {
static uint8_t bit_buffer = 0;
static uint8_t bit_count = 0;
switch (state) {
case ST_IDLE:
// 检测同步间隔(连续11位显性)
if (bus_bit == 0) {
bit_count++;
if (bit_count >= 11) {
state = ST_BREAK_DETECT;
bit_count = 0;
}
} else {
bit_count = 0;
}
break;
case ST_BREAK_DETECT:
// 等待同步场(0x55)
bit_buffer = (bit_buffer << 1) | bus_bit;
bit_count++;
if (bit_count == 8) {
if (bit_buffer == LIN_SYNC_FIELD) {
state = ST_SYNC;
} else {
state = ST_IDLE; // 同步失败,重新等待
}
bit_count = 0;
}
break;
case ST_SYNC:
// 接收PID
bit_buffer = (bit_buffer << 1) | bus_bit;
bit_count++;
if (bit_count == 8) {
tx_frame.pid = bit_buffer;
bit_count = 0;
// 检查PID奇偶校验
if (!check_pid_parity(tx_frame.pid)) {
state = ST_IDLE; // PID错误
break;
}
// 检查PID是否匹配本节点
if (tx_frame.pid == SLAVE_NODE_ID) {
// 准备响应数据
tx_frame.data[0] = get_temperature_data();
tx_frame.data_len = 1;
tx_frame.checksum = calculate_checksum(tx_frame.data,
tx_frame.data_len,
tx_frame.pid);
byte_index = 0;
response_timer = 0;
state = ST_DATA;
} else {
// PID不匹配,回到空闲状态等待下一帧
state = ST_IDLE;
}
}
break;
case ST_DATA:
// 发送数据字节(从节点驱动总线)
// 实际实现中,此处调用UART发送函数
// send_byte(tx_frame.data[byte_index]);
byte_index++;
if (byte_index >= tx_frame.data_len) {
state = ST_CHECKSUM;
}
break;
case ST_CHECKSUM:
// 发送校验和
// send_byte(tx_frame.checksum);
state = ST_COMPLETE;
break;
case ST_COMPLETE:
// 帧完成,回到空闲
state = ST_IDLE;
break;
}
}
// 响应超时监控 - 每个字节时隙调用
void lin_response_timeout_check(void) {
if (state == ST_DATA || state == ST_CHECKSUM) {
response_timer++;
if (response_timer > RESPONSE_DELAY_MAX) {
// 响应超时,放弃本次传输
state = ST_IDLE;
}
}
}
调度表响应验证
验证从节点是否正确响应调度表,需通过逻辑分析仪或LIN总线分析工具捕获总线流量。验证步骤如下:
- 将主节点与从节点A、B按拓扑图连接,确保共地且终端电阻正确。
- 主节点按调度表周期性发送帧头(PID=0x01, 0x02, 0x01, 0x03...)。
- 使用逻辑分析仪抓取LIN总线信号,观察每个帧头后的响应情况。
判定标准:
- 从节点A在收到PID=0x01的帧头后,应在帧头结束后的4个字节时间内开始发送数据(1字节数据+1字节校验和),且数据内容与校验和正确。
- 从节点B在收到PID=0x01时保持静默,总线在帧头后出现应答超时(主节点可配置超时时间)。
- 从节点A在收到PID=0x02时保持静默,不产生任何总线活动。
- 整个调度周期内,从节点A只在PID=0x01的时隙响应,不占用其他时隙。
时序约束检查
从节点必须在帧头PID字节结束后的4个字节时间内开始响应(LIN规范定义响应空间为帧头后最多4个字节时间的间隔)。若超过此窗口,主节点将判定从节点无响应,并记录总线错误。本实现通过response_timer监控响应超时,确保在时序异常时能自动复位状态机。
编译与运行
# 编译(假设使用GCC在主机上模拟)
gcc -Wall -Wextra -o lin_slave lin_slave_node.c
# 运行模拟测试(需配合测试台架)
./lin_slave
实际嵌入式部署时,将lin_slave_poll()函数挂接到UART接收中断或定时器轮询中,每个位时间调用一次。主节点调度表配置为10ms周期,从节点响应时间远小于该周期,确保不冲突。