# LIN (Local Interconnect Network) * LIN bus 是單線半雙工,工作電壓約 0V(dominant)~12V(recessive),屬於車載 12V 系統。 * 單主多從(single-master, multi-slave)。 - 跟CAN一樣有,發送header(標頭),包括同步訊號與識別碼(ID)。 - 由主機以排程方式輪詢從機,通訊有較高的確定性(predictability)。 * Master Node(主節點): * 主節點通常會週期性地輪詢各個 slave。 * Slave Node(從節點): * 接收主節點的命令並做出回應。 * 有些從節點會回傳資料(response),有些只會接收資料。 * 一個 LIN bus 最多可以有 15 個 slave nodes。 * 在USART被硬體設定為LIN模式時,提供13bit break產生器以及10/11bit break偵測器。 * 傳輸速度 * Master端最大 UART baud * Lin bus: 20 kbps。 * Slave端 UART baud * UART 設定的 baud rate 就要設成 19200 或 20000 bps(看 MCU 是否支援非標準 baud rate) * ECU角度去看,主機走LIN BUS,周邊設備多達15個就是Slave機制 ![image](https://hackmd.io/_uploads/ByfE97SAkx.png) ## Master and slave ![image](https://hackmd.io/_uploads/BJdAuNkggg.png) ## Lin bus Protocol ![image](https://hackmd.io/_uploads/HJn-klICye.png) * Header : 包含Break、Sync、ID * 其中一個不符合Lin bus格式封包都不會傳送 * Break : 13 bit 為LOW * Sync : 固定0X55 * ID : 0 ~ 64 * Pesponse : 包含Data、Checksum * 每個byte傳輸方式 起始Start bit 結束Stop bit; ![image](https://hackmd.io/_uploads/HyzoglUAJg.png) ### LIN bus 每個個封包解析 * Break field ![image](https://hackmd.io/_uploads/Hk-B1uuJgg.png) ![image](https://hackmd.io/_uploads/Hk5ouDuyge.png) * Sync : 同步是一個位元組字段,資料值為 0x55 ![image](https://hackmd.io/_uploads/ryQPkuOJxx.png) * ID: 0 ~ 63 * 60 和 61 用於攜帶診斷數據 * 62 保留用於用戶定義的擴展 * 63 保留用於未來的協議增強。 ![image](https://hackmd.io/_uploads/ByioXY3AJe.png) * Data : * ID 0-64: 通常是 1 到 8 bytes ![image](https://hackmd.io/_uploads/rJmQLeI0Jx.png) ### LIN Bus Timing * Because the LIN bus is a polled bus, the processing of each frame is allocated a nominal time slot as follows: * * THeader_Nominal = 34 * TBit * TResponse_Nominal = 10 * (NData + 1) * TBit * TFrame_Nominal = THeader_Nominal + TResponse_Nominal * Processing of each frame is allocated a maximum time slot as follows: * THeader_Maximum = 14 * THeader_Nominal * TResponse_Maximum = 1.4 * TResponse_Nominal * TFrame_Maximum = THeader_Maximum + TResponse_Maximum * LIN在Master發送Data時LIN Protocol Spec需要等待5-10ms但實際應用到10-20ms ![image](https://hackmd.io/_uploads/ByZbTObExg.png) ### Checksum * 在實作 LIN 匯流排通訊時,需特別注意 Classic Checksum 與 Enhanced Checksum 的算法差異。雖然 LIN 2.x 版本主要採用增強型校驗,但為了向下相容,軟體開發時仍需具備自動辨別與切換的能力。 #### 即便專案採用 LIN 2.x 版本,軟體層仍需根據 ID 類型進行動態判斷: * 邏輯判斷: 當 ID 為 0x3C 或 0x3D 時,務必強制切換回 Classic Checksum;其餘一般傳輸則使用 Enhanced Checksum。 * 相容性調整: 若系統中混有 LIN 1.3 的舊設備,即使 ID 在 0x00 ~ 0x3B 範圍內,也可能需要配置為 Classic Checksum。 * 小建議: 如下程式碼,在撰寫驅動程式時,建議建立一個 Checksum_Type 的判斷函式,傳入 PID 後回傳對應的運算邏輯,以確保通訊的穩定性。 * Classic Checksum (標準校驗) * 計算範圍: 僅針對 數據段 (Data Bytes) 進行運算。 * 適用對象: 診斷標頭檔 (Master Request ID: 0x3C / Slave Response ID: 0x3D),主要用於 LIN 1.x 規範的舊型節點。 * Enhanced Checksum (增強校驗) * 計算範圍: 包含 PID (Protected Identifier) 與 數據段 (Data Bytes)。 * 適用對象: 一般數據傳輸標頭檔 (ID 範圍:0x00 ~ 0x3B),主要用於 LIN 2.x 規範之節點。 ![image](https://hackmd.io/_uploads/rkUE2OZNxe.png) ![image](https://hackmd.io/_uploads/HyBi3dZExx.png) ## LIN Transceiver: Master and Slave 架構圖 ![image](https://hackmd.io/_uploads/HyKeiuZExe.png) ### LIN Transceiver 差異 * 市售IC有向下支援到2.2A ~ 1.3 SPEC,但要注意LIN Transceiver 有沒有支援到2.2A. ![image](https://hackmd.io/_uploads/Bkqtou-Neg.png) ## Master : Slave請求順序 * Master : 向Slave1請求 0x01 data * Master : 向Slave2請求 0x02 data * Master : 向Slave1、Slave2同時請求 0x10 data(不會成立,因為是半雙工所以只能一問一答) * Master : 向Slave1、Slave2同時請求 0x20 data(不會成立,因為是半雙工所以只能一問一答) * Master : 向Slave1請求 0x030 data * Master : 向Slave2請求 0x035 data ![image](https://hackmd.io/_uploads/SkZyouZExl.png) ## STM32上實現LIN BUS ``` unsigned char linbus_01[1] = {0x01}; unsigned char linbus_02[2] = {0x52,0x20}; unsigned char linbus_03[3] = {0xE5,0x20,0x52}; unsigned char linbus_04[4] = {0x10, 0x20, 0x30, 0x40}; unsigned char linbus_05[5] = {0x10, 0x20, 0x30, 0x40, 0x50}; unsigned char linbus_06[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; unsigned char linbus_07[7] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70}; unsigned char linbus_08[8] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80}; unsigned char linbus_18[4] = {0x10, 0x20, 0x30, 0x40}; unsigned char linbus_1F[5] = {0x10, 0x20, 0x30, 0x40, 0x50}; unsigned char linbus_24[5] = {0x10, 0x20, 0x30, 0x40, 0x50}; unsigned char linbus_28[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; unsigned char linbus_2C[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; unsigned char linbus_30[7] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; unsigned char linbus_3B[7] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; unsigned char linbus_3C[8] = {0x16, 0x26, 0x36, 0x46, 0x56, 0x66, 0x76, 0x86}; unsigned char linbus_3D[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; unsigned char rxdata[1]; int main(void) { HAL_Init(); SystemClock_Config(); Init_PeripheralClock(); Init_GPIO(); Init_LIN_UART(); Init_Interrupt(); while(1) { Lin_Transmit(0x01, linbus_01, 1, true); Lin_Transmit(0x02, linbus_02, 2, true); Lin_Transmit(0x03, linbus_03, 3, true); Lin_Transmit(0x04, linbus_04, 4, true); Lin_Transmit(0x05, linbus_05, 5, true); Lin_Transmit(0x06, linbus_06, 6, true); Lin_Transmit(0x07, linbus_07, 7, true); Lin_Transmit(0x08, linbus_08, 8, true); Lin_Transmit(0x3C, linbus_3C, 8, false); Lin_Receive(0x0C, rxdata, 1, true); Lin_Receive(0x0D, rxdata, 2, true); Lin_Receive(0x0E, rxdata, 3, true); Lin_Receive(0x0F, rxdata, 4, true); Lin_Receive(0x10, rxdata, 5, true); Lin_Receive(0x11, rxdata, 6, true); Lin_Receive(0x12, rxdata, 7, true); Lin_Receive(0x13, rxdata, 8, true); Lin_Receive(0x3D, rxdata, 8, false); } } ``` ``` /************************************************************************************************** Function Name: unsigned char LIN_Clock(EnumLinClock chose) Input: chose: LIN_BAUDRATE_9600 or LIN_BAUDRATE_19200. Output: None. Comment: Setting LIN Clock. **************************************************************************************************/ unsigned long LIN_Clock(EnumLinClock chose) { switch(chose) { case LIN_BAUDRATE_9600: return LIN_BUS_9600; break; case LIN_BAUDRATE_19200: return LIN_BUS_19200; break; default: return 0; } } ``` ``` /************************************************************************************************** Function Name: void Init_LIN_UART(void) Input: None. Output: None. Comment: Setting UART for LIN BUS **************************************************************************************************/ void Init_LIN_UART(void) { huart3.Instance = USART3; huart3.Init.BaudRate = LIN_Clock(LIN_BAUDRATE_19200); huart3.Init.WordLength = UART_WORDLENGTH_8B; huart3.Init.StopBits = UART_STOPBITS_1; huart3.Init.Parity = UART_PARITY_EVEN; huart3.Init.Mode = UART_MODE_TX_RX; huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart3.Init.OverSampling = UART_OVERSAMPLING_16; huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; huart3.Init.ClockPrescaler = UART_PRESCALER_DIV1; huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; HAL_UART_Init(&huart3); } ``` ``` /************************************************************************************************** Function Name: unsigned char Protected_identifier_field(unsigned char linId) Input: linId: Object of LIN peripheral and parameters of state machine for LIN driver. Output: None. Comment: LIN Protected ID function. **************************************************************************************************/ unsigned char Protected_identifier_field(unsigned char linId) { unsigned char LIN_ID = linId & 0x3Fu; LIN_ID |= ( (linId >> 0u & 0x1u) ^ (linId >> 1u & 0x1u) ^ (linId >> 2u & 0x1u) ^ (linId >> 4u & 0x1u)) << 6u; LIN_ID |= ( (linId >> 1u & 0x1u) ^ (linId >> 3u & 0x1u) ^ (linId >> 4u & 0x1u) ^ (linId >> 5u & 0x1u) ^ 0x1u) << 7u; return LIN_ID; } ``` ``` /*********************************************************************************************************************** Function Name: unsigned char Lin_Transmit(unsigned char Id, unsigned char *data, unsigned char size, unsigned long enhanced_mode) Input: pid : ID Number. data : Transmit data. size : Size of data part, the specified number of bytes for data part. enhanced_mode : ID 0 - 59 Use enhanced 1, ID 60 -61 Use classic 0. Output: None. Comment: This CANBUS IP provides four modes for application and testing. ***********************************************************************************************************************/ unsigned char Lin_Transmit(unsigned char Id, unsigned char *data, unsigned char size, unsigned long enhanced_mode) { unsigned char sync = 0x55; unsigned char pid = Protected_identifier_field(Id); unsigned char checksum = LIN_Checksum(pid, data, size, 1); // HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); // enable transmitter HAL_LIN_SendBreak(&huart3); HAL_UART_Transmit(&huart3, &sync, 1, 300); HAL_UART_Transmit(&huart3, &pid, 1, 300); HAL_UART_Transmit(&huart3, data, size, 1000); HAL_UART_Transmit(&huart3, &checksum, 1, 1000); HAL_Delay(30); // HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // disable transmitter return 0; } ``` ``` void Lin_Receive(unsigned char id, unsigned char *data, unsigned char size, unsigned long enhanced_mode) { unsigned char pid = Protected_identifier_field(id); unsigned char checksum_received; unsigned char checksum_calculated; if (size > 8) return; unsigned char rx_buffer[9] = {0}; if (HAL_UART_Receive(&huart3, rx_buffer, size + 1, 300) == HAL_OK) { memcpy(data, rx_buffer, size); checksum_received = rx_buffer[size]; checksum_calculated = LIN_Checksum(pid, data, size, enhanced_mode); if (checksum_received != checksum_calculated) { Error_Handler(); } } HAL_Delay(40); } ``` ``` /*************************************************************************************************** Function Name: void Init_GPIO(void) Input: NULL Output: NULL Comment: Initialize GPIO. ***************************************************************************************************/ void Init_GPIO(void) { GPIO_InitTypeDef CAN_MUX = {0}; GPIO_InitTypeDef Lin_MUX = {0}; RCC_PeriphCLKInitTypeDef PeriphClkCANInit = {0}; RCC_PeriphCLKInitTypeDef PeriphClkLINInit = {0}; // UART clock setting PCLK2(170MHz) PeriphClkLINInit.PeriphClockSelection = RCC_PERIPHCLK_USART1; PeriphClkLINInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; HAL_RCCEx_PeriphCLKConfig(&PeriphClkLINInit); /**USART3 GPIO Configuration PB10 ------> USART3_TX PB11 ------> USART3_RX */ Lin_MUX.Pin = GPIO_PIN_10|GPIO_PIN_11; Lin_MUX.Mode = GPIO_MODE_AF_PP; Lin_MUX.Pull = GPIO_NOPULL; Lin_MUX.Speed = GPIO_SPEED_FREQ_LOW; Lin_MUX.Alternate = GPIO_AF7_USART3; HAL_GPIO_Init(GPIOB, &Lin_MUX); } ``` ``` /************************************************************************************************** Function Name: void Init_Interrupt(void) Input: NULL Output: NULL Comment: Initialize Interrupt Service Routine. Enable nested interrupt. **************************************************************************************************/ void Init_Interrupt(void) { uint32_t encodePriority = NVIC_EncodePriority(NVIC_PRIORITYGROUP_4, 1, 0); NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); NVIC_SetPriority(USART3_IRQn, encodePriority); NVIC_EnableIRQ(USART3_IRQn); } ```