2. LIN从节点帧头接收与PID校验实现

任务定义

本案例要求从节点根据主节点发送的帧头,在调度表分配的时隙内完成响应。LIN总线采用主从机制:主节点负责发送帧头(同步间隔+同步场+PID),从节点仅在帧头中的PID与自己配置的ID匹配时,才在规定的响应空间内发送数据。核心目标是验证从节点能否在正确的时隙窗口内完成响应,且不占用其他时隙。

硬件连接与拓扑

主节点 MCU + LIN收发器 调度表执行器 从节点A ID=0x01 温度传感器 从节点B ID=0x02 车窗开关 LIN总线 R=1kΩ 帧头 帧头 响应 响应

调度表时序设计

主节点调度表定义如下(帧时隙=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总线分析工具捕获总线流量。验证步骤如下:

  1. 将主节点与从节点A、B按拓扑图连接,确保共地且终端电阻正确。
  2. 主节点按调度表周期性发送帧头(PID=0x01, 0x02, 0x01, 0x03...)。
  3. 使用逻辑分析仪抓取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周期,从节点响应时间远小于该周期,确保不冲突。