1. LIN从节点最小工程搭建与调度表运行
任务说明
本案例实现一个LIN从节点的基础收发功能:从节点监听总线,接收主节点发送的帧头(同步间隔场、同步场、PID场),解析PID后若地址匹配,则发送对应的数据帧响应(数据场+校验和),完成一次标准LIN帧通信。通过串口打印收发过程,用于验证从节点与主节点的基本通信流程。
硬件连接与通信拓扑
代码实现
以下代码在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) {
// 本案例中数据接收由中断驱动,主循环可做其他任务
// 如需发送主动数据,可在此处添加
}
编译与烧录
- 使用STM32CubeMX生成工程,配置UART1为115200-8-N-1(调试串口),UART2为19200-8-N-1(LIN总线);
- 将上述代码加入工程,替换main.c中的主循环调用
lin_slave_init()和lin_slave_poll(); - 编译生成hex文件,通过ST-Link烧录至从节点开发板。
验证方法
测试环境:使用另一个开发板作为主节点(或使用USB-LIN调试工具),连接LIN总线(从节点LIN收发器TXD/RXD接MCU UART2,总线端接12V和1kΩ上拉电阻)。
测试步骤:
- 主节点发送帧头:同步间隔场(低电平持续13位时间)+ 同步场0x55 + PID = 0x0A;
- 观察从节点串口调试输出,应打印
Resp: PID=0x0A Data=0x12 0x34 CS=0x??; - 使用示波器或逻辑分析仪抓取LIN总线波形,确认从节点在帧头后自动发送了5字节响应帧;
- 主节点发送不匹配的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,实际以代码计算为准)