Giao thức truyền thông trên STM32 (Phần 5): LIN – khung Break, PID và checksum
LIN trên STM32 dùng USART, từ cấu trúc khung (Break, Sync, PID, dữ liệu, checksum), cách tính PID và checksum có ví dụ từng bước, thời gian khung đến mã master và slave bằng HAL. Phần 5 của series giao thức truyền thông trên STM32.

Ghi chú: sơ đồ trong bài do Capitlab vẽ để minh hoạ. Số liệu trong các bảng là giá trị tính theo công thức, cột "Đo thực tế" sẽ được cập nhật khi đo trên phần cứng. Ví dụ PID và checksum đã được kiểm tra bằng chương trình. Mã nguồn soạn theo tài liệu HAL và datasheet, chưa chạy thử trên phần cứng, nên hãy kiểm tra lại trước khi dùng cho sản phẩm.
LIN (Local Interconnect Network) là bus một dây, rẻ và chậm, dùng trong ô tô cho các thiết bị phụ như gương, ghế, cửa sổ, cảm biến mưa, tức là những chỗ dùng CAN thì thừa. Về bản chất, LIN là UART cộng thêm một khuôn khổ khung và quy tắc master-slave, nên STM32 chạy LIN bằng USART khá tự nhiên. Phần lớn kiến thức nền đã có ở phần 1 về UART.
Một mạng LIN có một master và nhiều slave (thường tới 16 node), một dây dữ liệu cộng GND, tốc độ 1 đến 20 kbit/s, hay gặp nhất là 19,2 kbit/s. Slave chỉ lên tiếng khi master gọi.
1. Nguyên lý
1.1. Mạng và tầng vật lý
Master điều phối toàn mạng bằng một schedule table (bảng lịch): danh sách các khung, theo thứ tự, mỗi khung một ô thời gian. Slave không tự phát được mà chỉ trả lời khi header mang ID nó phụ trách. Vì không ai tranh nhau phát nên LIN không cần phân xử như CAN. Cái giá là tốc độ thấp, chỉ hợp cho thiết bị phụ.
Mức điện áp trên bus theo ắc quy, khoảng 12 V, nên giữa MCU và dây LIN bắt buộc có transceiver.
Hình 1. MCU nối với transceiver LIN bằng TX và RX của USART. Đường LIN một dây có điện trở kéo lên VBAT ở node master.
1.2. Khung LIN
Mỗi khung gồm header do master gửi, và response do master hoặc một slave gửi tuỳ theo ID:
Hình 2. Khung LIN. Break đủ dài để slave nhận ra đây là đầu khung, Sync (0x55) cho slave đo lại tốc độ, PID cho biết khung này là gì.
| Thành phần | Nội dung |
|---|---|
| Break | Đường ở mức thấp ít nhất 13 bit time, rồi nhả lên cao ít nhất 1 bit time (delimiter). Báo hiệu bắt đầu khung |
| Sync | Byte 0x55. Slave đo thời gian của chuỗi bit đảo liên tục này để tự đồng bộ tốc độ baud |
| PID | 6 bit ID cộng 2 bit parity |
| Data | 1 đến 8 byte |
| Checksum | 1 byte |
Thời gian danh định: THeader = 34 bit và TResponse = 10 × (N + 1) bit, với N là số byte dữ liệu. Đặc tả cho phép khung kéo dài tới 1,4 lần giá trị danh định. Bảng tính, chưa gồm khoảng nghỉ giữa các khung:
| Số byte dữ liệu | Tổng bit | 9,6 kbit/s | 19,2 kbit/s | Tối đa cho phép ở 19,2 kbit/s (× 1,4) |
|---|---|---|---|---|
| 1 | 54 | 5,63 ms | 2,81 ms | 3,94 ms |
| 2 | 64 | 6,67 ms | 3,33 ms | 4,67 ms |
| 4 | 84 | 8,75 ms | 4,38 ms | 6,13 ms |
| 8 | 124 | 12,92 ms | 6,46 ms | 9,04 ms |
Ô thời gian của mỗi khung trong bảng lịch phải đủ cho thời gian tối đa (nhân 1,4), không phải thời gian danh định. Nếu không, khung sau sẽ đè lên khung trước.
1.3. PID và parity
ID dài 6 bit (0 đến 63). Hai bit parity tính từ các bit của ID:
P0 = ID0 ⊕ ID1 ⊕ ID2 ⊕ ID4P1 = NOT (ID1 ⊕ ID3 ⊕ ID4 ⊕ ID5)
PID = ID (bit 0 đến 5) | P0 << 6 | P1 << 7. Slave kiểm tra parity, sai thì bỏ qua cả header.
Hình 3. Hai ví dụ tính PID. ID 0x01 có cả hai bit parity bằng 1 nên PID là 0xC1. ID 0x0D có cả hai bằng 0 nên PID vẫn là 0x0D.
Vài PID hay gặp, đã kiểm tra lại bằng chương trình: ID 0x00 ra 0x80, 0x10 ra 0x50, 0x3C (khung chẩn đoán từ master) ra 0x3C, 0x3D (khung chẩn đoán từ slave) ra 0x7D.
1.4. Checksum
Checksum LIN là tổng có nhớ vòng rồi đảo bit: cộng dồn các byte, tổng vượt 255 thì trừ 255 (tức cộng phần tràn trở lại), cuối cùng lấy NOT. Có hai kiểu:
- Classic: chỉ cộng các byte dữ liệu. Đây là kiểu của LIN 1.x.
- Enhanced: cộng cả PID và các byte dữ liệu. LIN 2.x thêm kiểu này, nhưng khung chẩn đoán (ID 0x3C và 0x3D) vẫn dùng classic.
Một số thiết bị chỉ biết classic, nên khi nối thiết bị lạ phải đọc tài liệu xem nó dùng kiểu nào.
Hình 4. Tính checksum enhanced từng bước. Kết quả 0xED. Cùng dữ liệu nhưng tính classic (không có PID) cho 0xFA.
Có thể tự kiểm tra bằng máy tính CRC / checksum của Capitlab, chọn LIN enhanced hoặc classic.
2. Cấu hình trên STM32
Nhiều dòng STM32 có chế độ LIN ngay trong USART: phần cứng tự phát break khi gửi và tự phát hiện break khi nhận. HAL có sẵn HAL_LIN_Init và HAL_LIN_SendBreak.
| Mục | Giá trị | Ghi chú |
|---|---|---|
| Mode | LIN (Asynchronous với LIN) | Chọn trong CubeMX nếu chip hỗ trợ |
| Baud Rate | 19200 | Tốc độ phổ biến của LIN |
| Word Length | 8 Bits | |
| Parity | None | Parity LIN nằm trong PID, không phải của UART |
| Stop Bits | 1 | |
| Break detect length | 10 hoặc 11 bit | Ngưỡng nhận biết break phía thu |
| Clock | Dùng HSE | LIN dung sai tốc độ nhỏ |
Nếu chip không có chế độ LIN, vẫn làm được bằng UART thường. Hạ baudrate xuống khoảng 9/13 tốc độ chuẩn, gửi byte 0x00, rồi trả baudrate về cũ. Bit start cộng 8 bit 0 là 9 bit mức thấp ở tốc độ chậm, tương đương hơn 13 bit ở tốc độ chuẩn.
3. Mã nguồn
3.1. Tính PID và checksum
static uint8_t lin_pid(uint8_t id)
{
uint8_t b[6];
for (int i = 0; i < 6; i++) b[i] = (id >> i) & 1;
uint8_t p0 = b[0] ^ b[1] ^ b[2] ^ b[4];
uint8_t p1 = !(b[1] ^ b[3] ^ b[4] ^ b[5]);
return (id & 0x3F) | (p0 << 6) | (p1 << 7);
}
static uint8_t lin_checksum(uint8_t pid, const uint8_t *d, uint8_t n, int enhanced)
{
uint16_t sum = enhanced ? pid : 0;
for (uint8_t i = 0; i < n; i++) {
sum += d[i];
if (sum > 0xFF) sum -= 0xFF;
}
return (uint8_t)(~sum);
}
Kiểm tra nhanh: lin_pid(0x01) phải bằng 0xC1, và lin_checksum(0x0D, {AB,BC,CD,DE,EF}, 5, 1) phải bằng 0xED (classic thì 0xFA).
3.2. Master gửi một khung
void lin_master_send(uint8_t id, const uint8_t *data, uint8_t n)
{
uint8_t pid = lin_pid(id);
uint8_t frame[2 + 8 + 1];
uint8_t k = 0;
frame[k++] = 0x55; // Sync
frame[k++] = pid; // PID
for (uint8_t i = 0; i < n; i++) frame[k++] = data[i];
frame[k++] = lin_checksum(pid, data, n, 1);
HAL_LIN_SendBreak(&huart2); // Break do phần cứng phát
HAL_UART_Transmit(&huart2, frame, k, 50);
}
Với khung mà slave phải trả lời, master chỉ gửi Break, Sync, PID, rồi chuyển sang nhận và chờ response trong thời gian cho phép, tối đa 1,4 lần giá trị danh định ở mục 1.2.
3.3. Slave nhận header
Slave cần nhận ra Break, đọc Sync, đọc PID và kiểm tra parity, rồi tuỳ ID mà nhận hoặc gửi response. Cách gọn nhất là một máy trạng thái chạy trong ngắt nhận:
typedef enum { WAIT_BREAK, WAIT_SYNC, WAIT_PID, RX_DATA } lin_state_t;
static volatile lin_state_t st = WAIT_BREAK;
static uint8_t rx_byte, rx_buf[10], rx_n, rx_pid, want;
void USART2_IRQHandler(void)
{
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_LBD)) { // phát hiện break
__HAL_UART_CLEAR_FLAG(&huart2, UART_CLEAR_LBDF); // tên cờ xóa tùy dòng chip
st = WAIT_SYNC;
}
HAL_UART_IRQHandler(&huart2);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *h)
{
switch (st) {
case WAIT_SYNC: if (rx_byte == 0x55) st = WAIT_PID; else st = WAIT_BREAK; break;
case WAIT_PID:
if (lin_pid(rx_byte & 0x3F) == rx_byte) { // parity đúng
rx_pid = rx_byte; rx_n = 0; want = response_length(rx_byte & 0x3F);
st = RX_DATA;
} else st = WAIT_BREAK;
break;
case RX_DATA:
rx_buf[rx_n++] = rx_byte;
if (rx_n == want + 1) { // dữ liệu + checksum
if (lin_checksum(rx_pid, rx_buf, want, 1) == rx_buf[want]) { /* khung hợp lệ */ }
st = WAIT_BREAK;
}
break;
default: break;
}
HAL_UART_Receive_IT(&huart2, &rx_byte, 1);
}
response_length là bảng bạn tự định nghĩa, ID nào bao nhiêu byte. Đoạn mã chỉ để minh hoạ luồng xử lý. Tên cờ break (LBD hoặc LBDF) và cách xoá cờ khác nhau giữa các dòng STM32, cần đối chiếu reference manual. Slave thật còn phải đo lại tốc độ từ byte Sync, phần này khá rườm rà, nên nhiều dự án dùng một LIN stack có sẵn thay vì tự viết.
4. Lưu ý khi thiết kế
LIN cho phép sai lệch tốc độ giữa các node rất nhỏ, nên master phải chạy bằng thạch anh, không dùng dao động RC.
Điện trở kéo lên thường là 1 kΩ ở master và khoảng 30 kΩ ở slave. Giá trị 30 kΩ thường đã có sẵn trong transceiver hoặc module.
Checksum của LIN chỉ là một phép cộng đơn giản. Nếu dữ liệu quan trọng đến mức cần phát hiện lỗi chặt hơn, nên dùng CAN.
5. Lỗi thường gặp
| Triệu chứng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Slave không nhận ra header | Break quá ngắn (dưới 13 bit time ở phía phát), hoặc sai độ dài break phát hiện | Đo Break bằng logic analyzer, chỉnh 10 hay 11 bit phía thu |
| Sai checksum | Nhầm giữa classic và enhanced, quên cộng PID | Khung chẩn đoán dùng classic, khung thường dùng enhanced (LIN 2.x) |
| PID bị từ chối | Tính sai parity | Kiểm tra lại lin_pid: ID 0x01 phải ra 0xC1 |
| Không có tín hiệu trên bus | Thiếu nguồn 12 V cho transceiver, thiếu điện trở kéo lên | Kiểm tra nguồn, đo mức trên dây LIN |
| Lúc được lúc mất khung | Sai tốc độ giữa master và slave, clock không ổn | Dùng thạch anh cho master |
| Response không về kịp | Master đóng cửa sổ quá sớm | Cho phép đến 1,4 lần thời gian danh định |
6. Hỏi đáp
LIN khác UART chỗ nào? Ký tự trên dây giống hệt UART 8N1. LIN thêm Break, Sync, PID, checksum và quy tắc master-slave. STM32 chạy LIN bằng USART, với chế độ LIN phần cứng hoặc tự phát break.
Sao byte Sync lại là 0x55? 0x55 là 01010101b, có nhiều cạnh đảo liên tiếp. Slave đo khoảng cách giữa các cạnh để tính chính xác độ dài bit và tự chỉnh tốc độ, nhờ vậy slave dùng dao động nội vẫn nhận đúng.
Dùng LIN thay RS-485 được không? LIN dành cho khoảng cách ngắn (trong xe, tối đa khoảng 40 m theo đặc tả) và tốc độ thấp. Cần đi xa hơn hoặc nhanh hơn thì xem RS-485 ở phần 6.
7. Kế hoạch đo thực tế
Cột "Đo thực tế" để trống cho đến khi chạy trên phần cứng.
| Thử nghiệm | Cách đo | Giá trị tính toán | Đo thực tế |
|---|---|---|---|
| Độ dài Break thực tế | Logic analyzer, đo thời gian đường ở mức thấp | Tối thiểu 13 bit time = 677 µs ở 19,2 kbit/s | (điền sau) |
| Kiểm tra PID và checksum | So giải mã LIN của logic analyzer với giá trị tính | ID 0x01 → PID 0xC1, checksum 0xED (ví dụ ở trên) | (điền sau) |
| Thời gian một khung 8 byte | Logic analyzer, từ Break đến hết checksum | 6,46 ms ở 19,2 kbit/s | (điền sau) |
| Chu kỳ schedule table ổn định | Đo thời gian giữa các header liên tiếp | Theo cấu hình bảng lịch | (điền sau) |
| Dạng sóng trên bus 12 V | Oscilloscope, quan sát mức và độ dốc | Phụ thuộc transceiver | (điền sau) |
| Độ lệch tốc độ master/slave chịu được | Chỉnh tốc độ master lệch dần, ghi nhận lúc slave lỗi | Phụ thuộc slave | (điền sau) |
Kết luận
LIN là UART cộng khung master-slave. Break phải đủ 13 bit mức thấp, Sync 0x55 để slave bắt tốc độ, và hai phía phải dùng cùng kiểu checksum.
Phần 6 chuyển sang RS-485: lớp vật lý vi sai bán song công, nền của nhiều giao thức công nghiệp, nhất là Modbus.
Tài liệu tham khảo
- Introduction to the LIN Bus Protocol (NI)
- How to Implement LIN Protocol on STM32 Using UART (ControllersTech)
- LIN checksum (TI, SDAA220A)
- Đặc tả LIN 2.x và tài liệu transceiver (TJA1021)