1. LIN从节点最小工程搭建与调度表运行

LIN从节点最小工程搭建与调度表运行 学习卡片

任务说明

本案例实现一个LIN从节点的基础收发功能:从节点监听总线,接收主节点发送的帧头(同步间隔场、同步场、PID场),解析PID后若地址匹配,则发送对应的数据帧响应(数据场+校验和),完成一次标准LIN帧通信。通过串口打印收发过程,用于验证从节点与主节点的基本通信流程。

硬件连接与通信拓扑

LIN Bus (12V) 主节点 MCU + LIN收发器 发送帧头 从节点(本案例) MCU (UART) + LIN收发器 接收帧头 → 解析PID 地址匹配 → 发送数据帧 UART调试输出 (115200) 帧结构: [同步间隔场] [同步场 0x55] [PID] [数据场(1-8)] [校验和] 主节点发送 从节点响应

代码实现

以下代码在STM32平台上实现从节点收发逻辑,核心依赖UART外设与一个GPIO引脚(用于检测LIN总线隐性/显性电平,以识别同步间隔场)。代码采用标准LIN协议:波特率19200bps,从节点地址(NAD)设为0x01,对应PID = 0x0A(经典LIN颜色/亮度从节点地址)。

// lin_slave.c - LIN从节点基础收发实现
// 硬件依赖:STM32 UART1 (TX=PA9, RX=PA10), GPIOA PIN0 用于总线电平检测
// 定时器TIM2用于超时与间隔场测量

#include "stm32f1xx_hal.h"
#include <string.h>

// 从节点配置
#define LIN_NAD              0x01      // 从节点地址
#define LIN_PID_BASE         0x0A      // 从节点PID(地址位已包含)
#define LIN_DATA_LEN         2         // 本案例响应2字节数据

// 帧状态机
typedef enum {
    ST_IDLE,
    ST_BREAK,
    ST_SYNC,
    ST_PID,
    ST_DATA,
    ST_CHECKSUM
} LIN_FRAME_STATE;

// 全局变量
static LIN_FRAME_STATE lin_state = ST_IDLE;
static uint8_t rx_buffer[8];
static uint8_t rx_index = 0;
static uint8_t rx_pid = 0;
static uint8_t rx_checksum = 0;
static uint8_t data_ready = 0;

// UART句柄(由CubeMX生成)
extern UART_HandleTypeDef huart1;
extern TIM_HandleTypeDef htim2;

// 函数声明
static uint8_t lin_calc_checksum(uint8_t pid, uint8_t *data, uint8_t len);
static void lin_send_response(uint8_t pid);
static void lin_process_byte(uint8_t byte);

// 从节点初始化
void lin_slave_init(void) {
    lin_state = ST_IDLE;
    rx_index = 0;
    data_ready = 0;
    // 使能UART接收中断
    HAL_UART_Receive_IT(&huart1, &rx_buffer[0], 1);
}

// UART接收完成回调(每收到1字节触发)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart->Instance == USART1) {
        uint8_t byte = rx_buffer[0];
        lin_process_byte(byte);
        // 继续接收下一字节
        HAL_UART_Receive_IT(&huart1, &rx_buffer[0], 1);
    }
}

// 字节级状态机处理
static void lin_process_byte(uint8_t byte) {
    switch (lin_state) {
        case ST_IDLE:
            // 检测同步间隔场:总线低电平持续 > 13位时间
            // 由GPIO外部中断或定时器捕获实现,此处简化为收到0x00且前导间隔已由硬件检测
            if (byte == 0x00) {  // 同步间隔场后的首个字节通常是0x00(间隔场本身不通过UART接收)
                lin_state = ST_SYNC;
            }
            break;
            
        case ST_SYNC:
            if (byte == 0x55) {  // 同步场
                lin_state = ST_PID;
            } else {
                lin_state = ST_IDLE;  // 同步错误,回到空闲
            }
            break;
            
        case ST_PID:
            rx_pid = byte;
            // 检查PID是否匹配本从节点(含奇偶校验位,此处简化处理)
            if ((rx_pid & 0x3F) == LIN_PID_BASE) {
                lin_state = ST_DATA;
                rx_index = 0;
            } else {
                // PID不匹配,不响应,回到空闲
                lin_state = ST_IDLE;
                // 可选:打印不匹配信息
                char msg[32];
                sprintf(msg, "PID mismatch: 0x%02X\r\n", rx_pid);
                HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100);
            }
            break;
            
        case ST_DATA:
            // 从节点接收数据(主节点可能发送数据到从节点)
            // 本案例从节点只响应,不接收长数据,因此收到数据直接忽略
            // 实际应用中根据PID方向位判断是接收还是发送
            lin_state = ST_CHECKSUM;
            break;
            
        case ST_CHECKSUM:
            // 校验和验证(本案例从节点不接收数据,因此校验和直接忽略)
            lin_state = ST_IDLE;
            break;
            
        default:
            lin_state = ST_IDLE;
            break;
    }
}

// 发送数据帧响应(从节点→主节点)
static void lin_send_response(uint8_t pid) {
    uint8_t frame[5];
    uint8_t data[2] = {0x12, 0x34};  // 示例数据:电压值0x12, 温度值0x34
    
    frame[0] = 0x55;      // 同步场(从节点响应时也需发送)
    frame[1] = pid;       // PID
    frame[2] = data[0];   // 数据1
    frame[3] = data[1];   // 数据2
    frame[4] = lin_calc_checksum(pid, data, 2);  // 校验和
    
    // 通过UART发送(LIN收发器会自动转换电平)
    HAL_UART_Transmit(&huart1, frame, 5, 100);
    
    // 调试打印
    char msg[64];
    sprintf(msg, "Resp: PID=0x%02X Data=0x%02X 0x%02X CS=0x%02X\r\n",
            pid, data[0], data[1], frame[4]);
    HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100);
}

// LIN经典校验和计算(仅数据场参与)
static uint8_t lin_calc_checksum(uint8_t pid, uint8_t *data, uint8_t len) {
    uint16_t sum = 0;
    // 经典校验和:不包含PID
    for (uint8_t i = 0; i < len; i++) {
        sum += data[i];
        if (sum > 0xFF) {
            sum -= 0xFF;
        }
    }
    return (uint8_t)(~sum & 0xFF);
}

// 主循环中检查是否有需要处理的标志
void lin_slave_poll(void) {
    // 本案例中数据接收由中断驱动,主循环可做其他任务
    // 如需发送主动数据,可在此处添加
}

编译与烧录

  1. 使用STM32CubeMX生成工程,配置UART1为115200-8-N-1(调试串口),UART2为19200-8-N-1(LIN总线);
  2. 将上述代码加入工程,替换main.c中的主循环调用lin_slave_init()和lin_slave_poll();
  3. 编译生成hex文件,通过ST-Link烧录至从节点开发板。

验证方法

测试环境:使用另一个开发板作为主节点(或使用USB-LIN调试工具),连接LIN总线(从节点LIN收发器TXD/RXD接MCU UART2,总线端接12V和1kΩ上拉电阻)。

测试步骤:

  1. 主节点发送帧头:同步间隔场(低电平持续13位时间)+ 同步场0x55 + PID = 0x0A;
  2. 观察从节点串口调试输出,应打印Resp: PID=0x0A Data=0x12 0x34 CS=0x??;
  3. 使用示波器或逻辑分析仪抓取LIN总线波形,确认从节点在帧头后自动发送了5字节响应帧;
  4. 主节点发送不匹配的PID(如0x0B),从节点应打印PID mismatch且不发送响应。

判定标准:

  • 从节点能正确识别同步间隔场和同步场,进入PID解析状态;
  • PID匹配时,从节点在帧头后自动发送数据帧,且波形时序符合LIN协议(响应帧在帧头后4位时间内开始);
  • PID不匹配时,从节点保持静默,不干扰总线。

预期输出示例:

Resp: PID=0x0A Data=0x12 0x34 CS=0xBA
PID mismatch: 0x0B

(校验和值0xBA由数据0x12、0x34计算得出:0x12+0x34=0x46,取反得0xB9,此处按LIN规范进位修正后为0xBA,实际以代码计算为准)