[MM32软件] MM32F5270 UART实现LIN通信

[复制链接]
2920|9
 楼主| MindMotion 发表于 2024-1-5 16:10 | 显示全部楼层 |阅读模式
本帖最后由 MindMotion 于 2024-1-5 16:10 编辑

1. LIN总线简介

LIN(Local Interconnect Network)总线是基于UART/SCI(通用异步收发器/串行接口)的低成本串行通讯协议,其目标定位于车身网络模块节点间的低端通信,主要用于智能传感器和执行器的串行通信。LIN总线采用单主多从的组网方式,没有CAN总线那样的仲裁机制,辅以简单驱动程序便可实现LIN协议。LIN节点由控制芯片和LIN收发器构成,一般通过芯片搭载的UART模块来实现,主节点控制传输时刻,控制整个网络的通信,从节点按照主节点的调度进行通信。

2. LIN报文结构

LIN总线上有“显性”和“隐性”两种互补的逻辑电平。显性电平是逻辑 0,隐性电平是逻辑1,总线上实行“线与”。
一帧LIN报文由帧头(Header)和应答(Response)两部分组成。主机任务负责发送帧头,从机任务接收帧头并对帧头所包含信息进行解析,然后决定是发送应答,还是接收应答,还是不作任何反应。帧在总线上的传输如下图所示:

图片1.png

帧头包括同步间隔段、同步段以及受保护ID段(PID)。应答包括数据段和校验和段。LIN报文帧整体结构如下图所示。

图片2.png

同步间隔段

同步间隔段标志一帧的开始,由同步间隔(Break)和间隔符(Break Delimiter)构成。同步间隔段至少有13个显性位,间隔符至少有一个隐形位。

同步段

同步段固定一个字节,值固定为0x55。
在LIN帧中,除了同步间隔段,后面各段都是通过字节域的格式传输的。LIN的字节域就是指标准的UART数据传输格式,字节域包括1位起始位(显性)+8位数据位+1位停止位(隐性)。数据传输都是先发送LSB,最后发送 MSB。LIN总线将下降沿作为判断标志,通过字节0x55(01010101b)进行同步,从机节点上可以采用非高精度时钟,如果存在偏差,可以通过同步场来调整,使从机节点数据的波特率与主机节点一致。

受保护ID段

受保护ID段由6位帧ID和2位奇偶校验位组成,帧ID范围为0x00~0x3F共64个。
帧ID标识了帧的类别,从机任务根据帧头ID作出反应(接收/发送/忽略应答),其中P0与P1效验如下:
P0 = ID0⊕ID1⊕ID2⊕ID4
P1 = ¬(ID1⊕ID3⊕ID4⊕ID5)
其中“⊕”代表“异或”运算,“¬”代表“取非”运算。
由公式可以看出,PID 不会出现全 0 或全 1 的情况,如果从机节点收到了“0xFF”或“0x00”,可判断传输错误。LIN总线根据帧ID的不同,将报文分为信号携带帧、诊断帧、保留帧。

图片3.png

应注意从机应答帧是一个完整的帧,与帧结构中的“应答”不同。

数据段

数据段包含1~8个字节,可以分为两种数据类型:信号和诊断消息。信号由信号携带帧传递,诊断消息由诊断帧传递。LIN协议规定可传输的LIN字节数为2、4、8,并不是1~8内任意一个数字。一般应用方面会统一字节数,通常是每帧传输8个字节。

校验和段

校验和段是为了对帧传输内容进行效验。效验分为标准型校验与增强型校验。
将校验对象的各字节作带进位二进制加法(当结果大于等于256 时就减去255),并将所得最终的和逐位取反,以该结果作为要发送的校验和。接收方根据校验和类型,对接收数据作相同的带进位二进制加法,最终的和不取反,并将该和与接收到的校验和作加法,如果结果为0xFF,则校验和无误。这在一定程度上保证了数据传输的正确性。
采用标准型还是增强型是由主机节点管理,发布节点和收听节点根据帧ID来判断采用哪种校验和。

图片5.png

3. LIN通信实验

MM32F5270的UART支持LIN协议下收发断开符号,通过配置UART,根据总线特征编写LIN驱动程序,实现LIN总线通信。相关代码参考灵动官网的LibSamples或在此基础上修改。

3.1 LIN驱动程序

同步间隔段
配置UART支持LIN协议下收发断开符号:

  1. void LIN_MASTER_Break(void)
  2. {
  3.     LIN_MASTER_TXBRK_InterruptFlag = 0;

  4.     UART_LINCmd(UART1, ENABLE);
  5.     UART_SendBreak(UART1);

  6.     while (0 == LIN_MASTER_TXBRK_InterruptFlag)
  7.     {
  8.     }
  9. }

同步段
主机发送0x55:

  1. void LIN_MASTER_SyncByte(void)
  2. {
  3.     LIN_MASTER_SendData(0x55);
  4. }

受保护ID段

  1. uint8_t LIN_FrameIDToPID(uint8_t FrameID)
  2. {
  3.     uint8_t i  = 0;
  4.     uint8_t P0 = 0, P1 = 0, PID = 0xFF;
  5.     uint8_t ID_BIT[6] =
  6.     {
  7.         0, 0, 0, 0, 0, 0
  8.     };

  9.     if (FrameID < 0x40)
  10.     {
  11.         PID = FrameID;

  12.         for (i = 0; i < 6; i++)
  13.         {
  14.             if (FrameID & (0x01 << i))
  15.             {
  16.                 ID_BIT[i] = 1;
  17.             }
  18.             else
  19.             {
  20.                 ID_BIT[i] = 0;
  21.             }
  22.         }

  23.         P0 =  (ID_BIT[0] ^ ID_BIT[1] ^ ID_BIT[2] ^ ID_BIT[4]) & 0x01;
  24.         P1 = ~(ID_BIT[1] ^ ID_BIT[3] ^ ID_BIT[4] ^ ID_BIT[5]) & 0x01;

  25.         if (P0)
  26.         {
  27.             PID |= 0x40;
  28.         }

  29.         if (P1)
  30.         {
  31.             PID |= 0x80;
  32.         }
  33.     }

  34.     return (PID);
  35. }

数据段
主机发送数据:

  1. void LIN_MASTER_SendData(uint8_t Data)
  2. {
  3.     UART_SendData(UART1, Data);

  4.     while (RESET == UART_GetFlagStatus(UART1, UART_FLAG_TXC))
  5.     {
  6.     }
  7. }

从机发送数据:

  1. void LIN_SLAVE_SendData(uint8_t Data)
  2. {
  3.     UART_SendData(UART1, Data);

  4.     while (RESET == UART_GetFlagStatus(UART1, UART_FLAG_TXC))
  5.     {
  6.     }
  7. }

校验和段
标准型校验:

  1. uint8_t LIN_ClassicChecksum(uint8_t *Buffer, uint8_t Length)
  2. {
  3.     uint8_t  i = 0;
  4.     uint16_t Checksum = 0;

  5.     for (i = 0; i < Length; i++)
  6.     {
  7.         Checksum += Buffer[i];

  8.         if (Checksum > 0xFF)
  9.         {
  10.             Checksum %= 0xFF;
  11.         }
  12.     }

  13.     return (~(uint8_t)(Checksum & 0x00FF));
  14. }

增强型校验:

  1. uint8_t LIN_EnhancedChecksum(uint8_t PID, uint8_t *Buffer, uint8_t Length)
  2. {
  3.     uint8_t  i = 0;
  4.     uint16_t Checksum = PID;

  5.     for (i = 0; i < Length; i++)
  6.     {
  7.         Checksum += Buffer[i];

  8.         if (Checksum > 0xFF)
  9.         {
  10.             Checksum %= 0xFF;
  11.         }
  12.     }

  13.     return (~(uint8_t)(Checksum & 0x00FF));
  14. }

主机发送帧头

  1. void LIN_MASTER_SendHeader(uint8_t PID)
  2. {
  3.     LIN_MASTER_Break();

  4.     LIN_MASTER_SyncByte();

  5.     LIN_MASTER_SendData(PID);
  6. }

主机发送报文
诊断帧ID包括主机请求帧0x3C、从机应答帧0x3D,诊断帧用标准型校验和,其他帧使用增强型校验和。

  1. void LIN_Master_SendFrame(uint8_t FrameID, uint8_t *Buffer, uint8_t Length)
  2. {
  3.     uint8_t i = 0;
  4.     uint8_t Checksum = 0;
  5.     uint8_t PID = LIN_FrameIDToPID(FrameID);

  6.     if ((0x3C == FrameID) || (0x3D == FrameID))
  7.     {
  8.         Checksum = LIN_ClassicChecksum(Buffer, Length);
  9.     }
  10.     else
  11.     {
  12.         Checksum = LIN_EnhancedChecksum(PID, Buffer, Length);
  13.     }

  14.     LIN_MASTER_SendHeader(PID);

  15.     for (i = 0; i < Length; i++)
  16.     {
  17.         LIN_MASTER_SendData(Buffer[i]);
  18.     }

  19.     LIN_MASTER_SendData(Checksum);
  20. }

从机发布数据
从机解析帧头信息,将主机发送的PID得到帧ID,根据帧ID选择校验类型,发送数据段和校验和段。

  1. void LIN_SLAVE_Response(uint8_t *Buffer, uint8_t Length)
  2. {
  3.     uint8_t i = 0;
  4.     uint8_t Checksum = 0, FrameID = 0;

  5.     FrameID  = LIN_PIDToFrameID(LIN_SLAVE_RxBuffer[1]);
  6.     Checksum = 0;
  7.    
  8.     if ((0x3C == FrameID) || (0x3D == FrameID))
  9.     {
  10.         Checksum = LIN_ClassicChecksum(Buffer, Length);
  11.     }
  12.     else
  13.     {
  14.         Checksum = LIN_EnhancedChecksum(LIN_SLAVE_RxBuffer[1], Buffer, Length);
  15.     }
  16.    
  17.     for (i = 0; i < Length; i++)
  18.     {
  19.         LIN_SLAVE_SendData(Buffer[i]);
  20.     }
  21.    
  22.     LIN_SLAVE_SendData(Checksum);
  23. }

3.2 主机程序

主机UART配置

  1. void UART_Configure(uint32_t Baudrate)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct;
  4.     NVIC_InitTypeDef NVIC_InitStruct;
  5.     UART_InitTypeDef UART_InitStruct;

  6.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

  7.     UART_StructInit(&UART_InitStruct);
  8.     UART_InitStruct.BaudRate      = Baudrate;
  9.     UART_InitStruct.WordLength    = UART_WordLength_8b;
  10.     UART_InitStruct.StopBits      = UART_StopBits_1;
  11.     UART_InitStruct.Parity        = UART_Parity_No;
  12.     UART_InitStruct.HWFlowControl = UART_HWFlowControl_None;
  13.     UART_InitStruct.Mode          = UART_Mode_Rx | UART_Mode_Tx;
  14.     UART_Init(UART1, &UART_InitStruct);

  15.     UART_IDLRConfig(UART1, 100);    /* LIN Master Only!!! */

  16.     UART_ITConfig(UART1, UART_IT_RX, ENABLE);
  17.     UART_ITConfig(UART1, UART_IT_TXBRK, ENABLE);

  18.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

  19.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_7);
  20.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_7);

  21.     GPIO_StructInit(&GPIO_InitStruct);
  22.     GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9;
  23.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
  24.     GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
  25.     GPIO_Init(GPIOA, &GPIO_InitStruct);

  26.     GPIO_StructInit(&GPIO_InitStruct);
  27.     GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_10;
  28.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
  29.     GPIO_Init(GPIOA, &GPIO_InitStruct);

  30.     NVIC_InitStruct.NVIC_IRQChannel = UART1_IRQn;
  31.     NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
  32.     NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
  33.     NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  34.     NVIC_Init(&NVIC_InitStruct);

  35.     UART_Cmd(UART1, ENABLE);
  36. }

主机中断服务子程序

  1. void UART1_IRQHandler(void)
  2. {
  3.     uint8_t i = 0;

  4.     if(SET == UART_GetITStatus(UART1, UART_IT_TXBRK))
  5.     {
  6.         UART1_RxLength = 0;

  7.         UART_ClearITPendingBit(UART1, UART_IT_TXBRK);

  8.         UART_ITConfig(UART1, UART_IT_RXIDLE, ENABLE);

  9.         LIN_MASTER_TXBRK_InterruptFlag = 1;
  10.     }

  11.     if(SET == UART_GetITStatus(UART1, UART_IT_RX))
  12.     {
  13.         UART1_RxBuffer[UART1_RxLength] = UART1->RDR & 0x00FF;

  14.         UART1_RxLength = (UART1_RxLength + 1) % 100;

  15.         UART_ClearITPendingBit(UART1, UART_IT_RX);
  16.     }

  17.     if(SET == UART_GetITStatus(UART1, UART_IT_RXIDLE))
  18.     {
  19.         for(i= 0; i < UART1_RxLength; i++)
  20.         {
  21.             LIN_MASTER_RxBuffer[i] = UART1_RxBuffer[i];
  22.         }

  23.         LIN_MASTER_RxLength = UART1_RxLength;
  24.         LIN_MASTER_RxFinish = 1;

  25.         UART_ClearITPendingBit(UART1, UART_IT_RXIDLE);
  26.         UART_ITConfig(UART1, UART_IT_RXIDLE, DISABLE);
  27.     }
  28. }

主机例程
主机间隔500ms发布和接收数据,发送帧ID和数据依次累加:

  1. void UART_LIN_Master_Sample(void)
  2. {
  3.     uint8_t i = 0;
  4.     uint8_t FrameID = 0, Mode = 0;
  5.     uint8_t Buffer[2] = { 0, 0 };

  6.     printf("\r\nTest %s", __FUNCTION__);

  7.     LIN_MASTER_RxLength = 0;
  8.     LIN_MASTER_RxFinish = 0;

  9.     for (i = 0; i < 100; i++)
  10.     {
  11.         LIN_MASTER_RxBuffer[i] = 0;
  12.     }

  13.     UART_Configure(19200);

  14.     while (1)
  15.     {
  16.         if (Mode == 0)
  17.         {
  18.             printf("\r\nLIN Master Write...");
  19.             LIN_Master_SendFrame(FrameID, Buffer, sizeof(Buffer));
  20.         }
  21.         else
  22.         {
  23.             printf("\r\nLIN Master Read....");
  24.             LIN_MASTER_SendHeader(LIN_FrameIDToPID(FrameID));

  25.             while (0 == LIN_MASTER_RxFinish)
  26.             {
  27.             }

  28.             LIN_MASTER_RxFinish = 0;

  29.             printf("\r\nLIN Master Rx Length : %d, Rx Buffer : ", LIN_MASTER_RxLength);

  30.             for (i = 0; i < LIN_MASTER_RxLength; i++)
  31.             {
  32.                 printf("0x%02x ", LIN_MASTER_RxBuffer[i]);
  33.             }

  34.             printf("\r\n");

  35.             for (i = 0; i < sizeof(Buffer); i++)
  36.             {
  37.                 Buffer[i]++;
  38.             }

  39.             FrameID = (FrameID + 1) % 0x40;
  40.         }

  41.         Mode = (0 == Mode) ? 1 : 0;

  42.         PLATFORM_DelayMS(500);
  43.     }
  44. }

3.3 从机程序

从机UART配置
使能UART LIN总线模式、使能UART接收断开帧中断、使能接收单字节中断。

  1. void UART_Configure(uint32_t Baudrate)
  2. {
  3.     GPIO_InitTypeDef GPIO_InitStruct;
  4.     NVIC_InitTypeDef NVIC_InitStruct;
  5.     UART_InitTypeDef UART_InitStruct;

  6.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

  7.     UART_StructInit(&UART_InitStruct);
  8.     UART_InitStruct.BaudRate      = Baudrate;
  9.     UART_InitStruct.WordLength    = UART_WordLength_8b;
  10.     UART_InitStruct.StopBits      = UART_StopBits_1;
  11.     UART_InitStruct.Parity        = UART_Parity_No;
  12.     UART_InitStruct.HWFlowControl = UART_HWFlowControl_None;
  13.     UART_InitStruct.Mode          = UART_Mode_Rx | UART_Mode_Tx;
  14.     UART_Init(UART1, &UART_InitStruct);

  15.     UART_LINCmd(UART1, ENABLE);

  16.     UART_ITConfig(UART1, UART_IT_RX, ENABLE);
  17.     UART_ITConfig(UART1, UART_IT_RXBRK, ENABLE);

  18.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

  19.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_7);
  20.     GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_7);

  21.     GPIO_StructInit(&GPIO_InitStruct);
  22.     GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9;
  23.     GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;
  24.     GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
  25.     GPIO_Init(GPIOA, &GPIO_InitStruct);

  26.     GPIO_StructInit(&GPIO_InitStruct);
  27.     GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_10;
  28.     GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
  29.     GPIO_Init(GPIOA, &GPIO_InitStruct);

  30.     NVIC_InitStruct.NVIC_IRQChannel = UART1_IRQn;
  31.     NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
  32.     NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
  33.     NVIC_InitStruct.NVIC_IRQChannelCmd  = ENABLE;
  34.     NVIC_Init(&NVIC_InitStruct);

  35.     UART_Cmd(UART1, ENABLE);
  36. }

从机中断服务子程序

  1. void UART1_IRQHandler(void)
  2. {
  3.     uint8_t i = 0;

  4.     if (SET == UART_GetITStatus(UART1, UART_IT_RXBRK))
  5.     {
  6.         UART1_RxLength = 0;

  7.         UART_ClearITPendingBit(UART1, UART_IT_RXBRK);

  8.         UART_ITConfig(UART1, UART_IT_RXIDLE, ENABLE);
  9.     }

  10.     if (SET == UART_GetITStatus(UART1, UART_IT_RX))
  11.     {
  12.         UART1_RxBuffer[UART1_RxLength] = UART_ReceiveData(UART1);

  13.         UART1_RxLength = (UART1_RxLength + 1) % 100;

  14.         UART_ClearITPendingBit(UART1, UART_IT_RX);
  15.     }

  16.     if (SET == UART_GetITStatus(UART1, UART_IT_RXIDLE))
  17.     {
  18.         for (i = 0; i < UART1_RxLength; i++)
  19.         {
  20.             LIN_SLAVE_RxBuffer[i] = UART1_RxBuffer[i];
  21.         }

  22.         LIN_SLAVE_RxLength = UART1_RxLength;
  23.         LIN_SLAVE_RxFinish = 1;

  24.         UART_ClearITPendingBit(UART1, UART_IT_RXIDLE);
  25.         UART_ITConfig(UART1, UART_IT_RXIDLE, DISABLE);
  26.     }
  27. }

从机例程
从机对帧头包含信息解析,确定是发送应答,还是接收应答。

  1. void UART_LIN_Slave_Sample(void)
  2. {
  3.     uint8_t i = 0;
  4.     uint8_t Checksum = 0, FrameID = 0;
  5.     uint8_t Length   = 0, Buffer[100];

  6.     printf("\r\nTest %s", __FUNCTION__);

  7.     Length             = 0;
  8.     LIN_SLAVE_RxLength = 0;
  9.     LIN_SLAVE_RxFinish = 0;

  10.     for (i = 0; i < 100; i++)
  11.     {
  12.         Buffer[i]             = 0;
  13.         LIN_SLAVE_RxBuffer[i] = 0;
  14.     }

  15.     UART_Configure(19200);

  16.     while (1)
  17.     {
  18.         if (1 == LIN_SLAVE_RxFinish)
  19.         {
  20.             LIN_SLAVE_RxFinish = 0;

  21.             if (0x55 == LIN_SLAVE_RxBuffer[0])
  22.             {
  23.                 if (2 == LIN_SLAVE_RxLength)
  24.                 {
  25.                     LIN_SLAVE_Response(Buffer, Length);
  26.                 }
  27.                 else
  28.                 {
  29.                     for (i = 2; i < LIN_SLAVE_RxLength - 1; i++)
  30.                     {
  31.                         Buffer[i - 2] = LIN_SLAVE_RxBuffer[i];
  32.                     }

  33.                     Length = LIN_SLAVE_RxLength - 3;
  34.                 }
  35.             }
  36.         }
  37.     }
  38. }

3.4 验证
通过UART接口连接两块MM32F5270 MiniBoard,观察串口调试助手:

图片6.png

先由主机发布数据,从机接收数据,接着由从机发布数据,主机接收数据,依次循环进行。根据截图信息,主从机收发数据一致,与程序逻辑相符,两块板LIN通信成功。

tpgf 发表于 2024-2-5 13:23 | 显示全部楼层
lin总线通讯是哪种通讯方式的简化版本?
wowu 发表于 2024-2-5 14:12 | 显示全部楼层
看代码完全像是io在完全模拟lin总线通讯了
木木guainv 发表于 2024-2-5 22:23 | 显示全部楼层
这两种通讯方式的差别还是很大的
晓伍 发表于 2024-2-5 22:59 | 显示全部楼层
看lin总线的介绍 感觉跟can总线差不多呢
paotangsan 发表于 2024-2-5 22:59 | 显示全部楼层
如果总线上出现问题的话 如何及时处理呢
xiaoqizi 发表于 2024-2-5 23:31 | 显示全部楼层
LIN总线是基于SCI(UART)数据格式,采用单主控制器/多从设备的模式,是UART中的一种特殊情况。
wangwu1976@ 发表于 2025-1-22 08:56 | 显示全部楼层
看过的最清晰明了的一篇介绍了。超赞
OKAKAKO 发表于 2025-1-22 17:29 | 显示全部楼层
LIN总线上有“显性”和“隐性”两种互补的逻辑电平。
小小蚂蚁举千斤 发表于 2025-1-22 22:36 | 显示全部楼层
PID 不会出现全 0 或全 1 的情况,如果从机节点收到了“0xFF”或“0x00”,可判断传输错误。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司
简介:上海灵动微电子股份有限公司成立于 2011 年,是中国本土通用 32 位 MCU 产品及解决方案供应商。 灵动股份的 MCU 产品以 MM32 为标识,基于 Arm Cortex-M 系列内核,自主研发软硬件和生态系统。目前已量产近 300 多款型号,累计交付超 4 亿颗,在本土通用 32 位 MCU 公司中位居前列。

93

主题

111

帖子

10

粉丝
快速回复 在线客服 返回列表 返回顶部