2. LIN报文帧结构解析与字节序验证

任务定义

本案例要求校准LIN主节点的串口波特率,使其精确工作在20kbps。校准方法为:主节点发送一个包含已知字节序列的LIN帧,通过示波器或逻辑分析仪测量帧起始位(Break)之后的显性位时间,根据实测位时间与理论值50µs的偏差,调整主节点UART的波特率寄存器值,直至误差在允许范围内。

实现方案

采用STM32F103系列MCU作为LIN主节点,使用其USART外设的波特率寄存器BRR进行校准。核心思路:

  1. 发送一个固定格式的LIN帧(包含0x55同步字节),该字节的位序列为01010101b,便于测量单个位时间;
  2. 通过逻辑分析仪捕获同步字节的下降沿间隔,计算实际位时间;
  3. 根据实测位时间与目标50µs的偏差,按比例修正BRR值;
  4. 重复发送与测量,直至位时间误差≤2%(即49µs~51µs)。

代码实现

// lin_baud_calib.c - LIN主节点波特率校准(STM32F103, 8MHz HSE)
#include "stm32f10x.h"
#include <stdio.h>

// 目标:20kbps,位时间50µs
#define TARGET_BIT_TIME_US   50
#define TOLERANCE_US         1   // ±1µs(±2%)

// 系统时钟72MHz,APB2=72MHz,USART1挂载在APB2
// BRR = fck / (16 * baud)
// 初始波特率设为19200,后续根据测量修正
volatile uint32_t current_brr = 0x4B0;  // 19200bps对应BRR值(72MHz/16/19200=234.375→234)

// 发送一字节(阻塞)
void uart_send_byte(USART_TypeDef* USARTx, uint8_t data) {
    while (!(USARTx->SR & USART_FLAG_TXE));
    USARTx->DR = data;
}

// 发送LIN帧头:Break(13位显性) + 同步间隔 + 0x55同步字节
void lin_send_frame_header(void) {
    // 发送Break:拉低TX引脚13位时间(650µs @20kbps)
    // 此处用GPIO模拟Break,然后切换回USART
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 配置TX引脚(PB6)为推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 拉低13位时间(650µs @20kbps)
    GPIO_ResetBits(GPIOB, GPIO_Pin_6);
    delay_us(650);
    GPIO_SetBits(GPIOB, GPIO_Pin_6);
    delay_us(50);  // 间隔
    
    // 切换回USART复用功能
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 发送同步字节0x55
    uart_send_byte(USART1, 0x55);
}

// 根据实测位时间更新BRR
// measured_us: 实测的单个位时间(微秒)
void update_baud_from_measurement(float measured_us) {
    float error_ratio = measured_us / TARGET_BIT_TIME_US;
    uint32_t new_brr = (uint32_t)(current_brr * error_ratio);
    
    // 限制范围:15kbps~25kbps
    if (new_brr < 180) new_brr = 180;   // 25kbps
    if (new_brr > 300) new_brr = 300;   // 15kbps
    
    current_brr = new_brr;
    USART1->BRR = current_brr;
}

// 简单延时(非精确,仅用于Break)
void delay_us(uint32_t us) {
    for (volatile uint32_t i = 0; i < us * 8; i++);
}

// 主校准流程
void baud_calibration_loop(void) {
    // 假设外部测量工具(逻辑分析仪)通过串口/GPIO反馈实测位时间
    // 实际应用中,可通过ADC读取电位器或通过上位机命令输入
    float measured_bit_time;
    
    printf("LIN波特率校准开始,目标20kbps\r\n");
    
    for (int iter = 0; iter < 5; iter++) {
        // 发送测试帧
        lin_send_frame_header();
        
        // 此处模拟从测量工具获取位时间(实际项目中通过通信接口读取)
        // 真实场景:逻辑分析仪测量0x55字节的位宽,通过UART/USB反馈
        printf("迭代%d:请测量同步字节位时间并输入(µs): ", iter+1);
        scanf("%f", &measured_bit_time);
        
        if (measured_bit_time >= (TARGET_BIT_TIME_US - TOLERANCE_US) &&
            measured_bit_time <= (TARGET_BIT_TIME_US + TOLERANCE_US)) {
            printf("校准完成!位时间=%.1fµs,在±1µs容差内\r\n", measured_bit_time);
            return;
        }
        
        update_baud_from_measurement(measured_bit_time);
        printf("已更新BRR=0x%X,当前理论波特率=%.1f kbps\r\n",
               current_brr, 72000000.0f / (16 * current_brr) / 1000);
    }
    printf("达到最大迭代次数,校准结束\r\n");
}

// USART1初始化(PB6=TX, PB7=RX)
void usart1_init(uint32_t baud) {
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOB, ENABLE);
    
    // TX=PB6, RX=PB7
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    USART_InitStructure.USART_BaudRate = baud;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_TX | USART_Mode_RX;
    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
}

int main(void) {
    // 系统时钟已配置为72MHz(外部8MHz晶振)
    usart1_init(19200);
    
    // 重定向printf到USART1(需实现fputc)
    // ...
    
    baud_calibration_loop();
    
    while (1);
}

位时间测量与校准原理

测量点说明:逻辑分析仪捕获TX引脚(PB6)波形,测量0x55同步字节中连续两个下降沿之间的时间间隔。由于0x55的位模式为01010101b,每个数据位均发生跳变,因此相邻下降沿间隔恰好等于1个位时间。

LIN同步字节0x55位时间测量波形 位时间(50µs) START 0 1 0 1 0 1 0 1 STOP 显性(0) 隐性(1) 下降沿测量点

校准流程与验证

校准步骤:

  1. 将逻辑分析仪探头连接至主节点TX引脚(PB6),采样率建议≥1MHz;
  2. 运行程序,发送包含0x55的LIN帧头;
  3. 在逻辑分析仪波形上测量同步字节中相邻下降沿间隔(应测量第2~3个下降沿之间的时间,避开起始位边缘效应);
  4. 将实测值(µs)输入程序,程序自动计算误差并更新BRR寄存器;
  5. 重复步骤2~4,直至实测位时间落在49µs~51µs区间。

判定标准:当连续两次测量的位时间均在50µs±1µs范围内,且BRR值不再变化,即认为校准完成。此时主节点波特率误差≤2%,满足LIN协议对主节点±0.5%的精度要求(实际校准后通常可达±0.2%以内)。

验证方法:校准完成后,发送完整的LIN帧(包含同步、标识符和数据),使用从节点或总线分析仪验证通信正常。若从节点能正确响应,则证明波特率校准成功。

关键参数说明

参数值说明
目标波特率20kbpsLIN标准速率
位时间理论值50µs1/20000
容差范围±1µs±2%误差
BRR初值0x4B0 (1200)对应19200bps
BRR计算公式BRR = 72MHz / (16 × baud)USART1挂载APB2

注意事项:Break信号的发送使用GPIO模拟,其时长精度不影响位时间测量;测量时应以同步字节的位间隔为准,而非Break长度。若系统时钟本身存在偏差,可通过调整BRR值一并修正。