Giao thức truyền thông trên STM32 (Phần 4): CAN và FDCAN – khung dữ liệu, phân xử và bit timing
CAN và CAN FD trên STM32 (FDCAN) từ khung dữ liệu, phân xử theo ID, cách tính bit timing, thời gian khung và tải bus đến cấu hình HAL, bộ lọc nhận và phục hồi bus-off. Phần 4 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. 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.
CAN (Controller Area Network) ra đời cho ô tô, giờ có mặt trong máy công nghiệp, robot, thiết bị y tế. Tín hiệu vi sai giúp nó chịu nhiễu tốt, phần cứng tự phát hiện lỗi và tự gửi lại, và nhiều node ngang hàng dùng chung một đôi dây.
Trên STM32 có hai loại ngoại vi CAN. Các dòng mới (G0, G4, H5, H7, U5...) có FDCAN, chạy được cả CAN cổ điển lẫn CAN FD. Các dòng cũ như F1, F4 có bxCAN với bộ hàm HAL khác (HAL_CAN_*). Bài này viết cho FDCAN.
Vài điều nên biết trước khi đọc tiếp. CAN không có địa chỉ node: ID mô tả nội dung thông điệp và quyết định độ ưu tiên. MCU chỉ có chân TX/RX mức logic, nên cần thêm transceiver và hai điện trở 120 Ω ở hai đầu bus. Một node đứng một mình không gửi thành công được vì không có ai trả ACK, nên lúc thử một board hãy dùng chế độ loopback.
1. Nguyên lý
1.1. Tầng vật lý
Hai dây CANH và CANL xoắn với nhau, thông tin nằm ở hiệu điện áp giữa chúng. Bus có hai trạng thái: trội (dominant, logic 0) và lặn (recessive, logic 1). Khi nhiều node phát cùng lúc, trội luôn thắng lặn. Phân xử không mất dữ liệu ở mục 1.3 dựa vào tính chất này.
Hình 1. Bus CAN điển hình. Mỗi node gồm MCU và transceiver (TJA1051, MCP2562FD...). Hai điện trở 120 Ω đặt ở hai đầu bus.
Khi tắt nguồn, đo giữa CANH và CANL phải được khoảng 60 Ω (hai điện trở 120 Ω mắc song song). Ra 120 Ω là thiếu một điện trở, ra rất lớn là thiếu cả hai, ra rất nhỏ là có chập hoặc thừa điện trở. Bus có vấn đề thì làm phép đo này trước, nó chỉ tốn một chiếc đồng hồ vạn năng.
1.2. Khung dữ liệu CAN cổ điển
Hình 2. Khung CAN chuẩn với ID 11 bit và 8 byte dữ liệu: 108 bit (chưa tính bit chèn), cộng 3 bit nghỉ giữa các khung.
ID dài 11 bit với khung chuẩn, 29 bit với khung mở rộng. Trường DLC 4 bit cho biết số byte dữ liệu, từ 0 đến 8. CRC 15 bit bảo vệ khung, và bit ACK do node nhận kéo xuống để xác nhận.
Sau 5 bit giống nhau liên tiếp, phần cứng chèn thêm một bit ngược dấu để các node giữ được đồng bộ (bit stuffing). Vì thế độ dài thực của một khung trên bus phụ thuộc nội dung. CAN cổ điển chạy tối đa 1 Mbit/s.
1.3. Phân xử
Bus rảnh thì node nào cũng có thể bắt đầu phát. Nếu hai node bắt đầu cùng lúc, mỗi node vừa phát vừa đọc lại bus. Node nào phát bit lặn mà đọc thấy trội thì biết có khung ưu tiên hơn, liền dừng phát và chuyển sang nhận. Khung của node thắng đi tiếp nguyên vẹn.
Hình 3. Ví dụ ba node cùng phát: C thua ở bit ID7 (phát 1 nhưng bus là 0), A thua ở bit ID1, B với ID nhỏ nhất (0x120) thắng.
Từ đó suy ra cách đặt ID: thông điệp quan trọng (an toàn, điều khiển thời gian thực) dùng ID nhỏ, thông điệp ít gấp dùng ID lớn. Không để hai node phát cùng một ID với nội dung khác nhau.
1.4. Phát hiện lỗi và trạng thái node
CAN kiểm tra lỗi ở nhiều tầng: lỗi bit (so bit phát với bit đọc lại), lỗi bit chèn, lỗi CRC, lỗi khung và lỗi ACK. Mỗi node có hai bộ đếm lỗi, TEC cho phát và REC cho nhận, và nằm ở một trong ba trạng thái:
| Trạng thái | Điều kiện | Hành vi |
|---|---|---|
| Error Active | TEC và REC đều không quá 127 | Bình thường, báo lỗi bằng cờ lỗi trội |
| Error Passive | TEC hoặc REC lớn hơn 127 | Báo lỗi bằng cờ lặn, phải chờ thêm sau khi phát |
| Bus-Off | TEC lớn hơn 255 | Ngắt khỏi bus, không phát nhận gì |
Node hỏng phần cứng (sai tốc độ, đứt dây) sẽ tăng TEC rất nhanh rồi tự rơi vào bus-off, nên không kéo sập cả bus. Để quay lại, node phải quan sát được 128 lần chuỗi 11 bit lặn liên tiếp trên bus.
1.5. CAN FD
CAN FD (Flexible Data-rate) mang được tới 64 byte dữ liệu và có thể chuyển sang tốc độ cao hơn trong pha dữ liệu (Bit Rate Switch, BRS). Pha phân xử vẫn chạy ở tốc độ danh định (nominal), nên CAN FD có hai bộ thông số bit timing: Nominal và Data. CRC dài hơn (17 hoặc 21 bit), và độ dài dữ liệu chỉ nhận các giá trị 0 đến 8, 12, 16, 20, 24, 32, 48, 64 byte.
2. Bit timing
Mỗi bit CAN chia thành các đoạn đo bằng time quantum (tq). tq là đơn vị thời gian nhỏ nhất, tạo ra bằng cách chia clock ngoại vi cho prescaler:
Bit time = 1 tq (Sync) + TimeSeg1 + TimeSeg2
Bitrate = fdcan_ker_clk / (Prescaler × (1 + TimeSeg1 + TimeSeg2))
Điểm lấy mẫu (sample point) = (1 + TimeSeg1) / (1 + TimeSeg1 + TimeSeg2).
Hình 4. Ví dụ clock 80 MHz, prescaler 5: tq = 62,5 ns, mỗi bit 16 tq cho bitrate 1 Mbit/s với điểm lấy mẫu 87,5%.
Bảng tính với clock ngoại vi 80 MHz, mỗi bit 16 tq, TimeSeg1 = 13, TimeSeg2 = 2:
| Bitrate | Prescaler | tq | Điểm lấy mẫu |
|---|---|---|---|
| 125 kbit/s | 40 | 500 ns | 87,5% |
| 250 kbit/s | 20 | 250 ns | 87,5% |
| 500 kbit/s | 10 | 125 ns | 87,5% |
| 1 Mbit/s | 5 | 62,5 ns | 87,5% |
Tham số còn lại là SJW (Synchronization Jump Width), lượng bù tối đa mỗi lần đồng bộ lại. Thường đặt 1 đến 4 tq, không vượt TimeSeg2. Mọi node trên bus phải cùng bitrate, và điểm lấy mẫu nên nằm trong khoảng 80 đến 87,5%.
Để tốc độ chính xác, clock phải lấy từ thạch anh (HSE) chứ không dùng dao động nội. Nhiều board Nucleo không gắn thạch anh HSE mà lấy clock từ ST-LINK, nên cần kiểm tra cấu hình thực tế. Chọn clock FDCAN là bội số của bitrate để số tq mỗi bit là số nguyên.
Với CAN FD, transceiver phải chịu được tốc độ pha dữ liệu (MCP2562FD lên tới 8 Mbit/s). Dây phải là cặp xoắn, có điện trở 120 Ω ở hai đầu, và đoạn rẽ nhánh (stub) càng ngắn càng tốt.
Tài liệu của ST có một ví dụ tham khảo trên STM32H753 với clock FDCAN 120 MHz, prescaler 1. Pha nominal 1 Mbit/s dùng 120 tq mỗi bit, NominalTimeSeg1 = 101, NominalTimeSeg2 = 18, điểm lấy mẫu khoảng 85%. Pha dữ liệu 8 Mbit/s dùng 15 tq mỗi bit, DataTimeSeg1 = 8, DataTimeSeg2 = 6, điểm lấy mẫu khoảng 60%.
3. Thời gian khung và tải bus
Một khung chuẩn có 47 + 8n bit, với n là số byte dữ liệu, đã tính 3 bit nghỉ nhưng chưa tính bit chèn. Trường hợp xấu nhất, bit chèn thêm tối đa ⌊(34 + 8n − 1) / 4⌋ bit. Bảng tính:
| Số byte dữ liệu | Bit (không chèn) | Bit (xấu nhất) | Thời gian ở 500 kbit/s | Thời gian ở 1 Mbit/s | Khung/giây tối đa ở 500 kbit/s |
|---|---|---|---|---|---|
| 0 | 47 | 55 | 94 đến 110 µs | 47 đến 55 µs | 9 090 đến 10 638 |
| 1 | 55 | 65 | 110 đến 130 µs | 55 đến 65 µs | 7 692 đến 9 090 |
| 2 | 63 | 75 | 126 đến 150 µs | 63 đến 75 µs | 6 666 đến 7 936 |
| 4 | 79 | 95 | 158 đến 190 µs | 79 đến 95 µs | 5 263 đến 6 329 |
| 8 | 111 | 135 | 222 đến 270 µs | 111 đến 135 µs | 3 703 đến 4 504 |
Bảng này dùng để ước lượng tải bus. Ví dụ 10 khung 8 byte mỗi 10 ms, tức 1000 khung/giây, ở 500 kbit/s chiếm 1000 × 111 / 500 000 = 22% đến 27% băng thông. Trong ô tô, tải bus thường được giữ dưới khoảng 30 đến 50% để còn chỗ cho khung ưu tiên thấp và cho việc phục hồi lỗi.
4. Cấu hình trong STM32CubeIDE
| Mục | Giá trị | Ghi chú |
|---|---|---|
| Clock Divider | Theo cấu hình clock | Clock FDCAN lấy từ PLL hoặc HSE, xem Clock Configuration |
| Frame Format | Classic Mode, FD no BRS hoặc FD with BRS | |
| Mode | Normal (hoặc Internal Loopback để thử) | |
| Auto Retransmission | Enable | Tự gửi lại khi mất phân xử hoặc lỗi |
| Nominal Prescaler / Time Seg1 / Time Seg2 / SJW | Theo bảng ở mục 2 | |
| Data Prescaler / Time Seg1 / Time Seg2 | Chỉ khi dùng FD với BRS | |
| Std Filters Nbr | Số bộ lọc ID 11 bit cần dùng | |
| Tx FIFO Queue Elements | Ví dụ 8 | |
| NVIC | FDCAN1 interrupt 0 | Bật để nhận bằng ngắt |
5. Mã nguồn
5.1. Khởi tạo và bộ lọc nhận
CubeMX sinh sẵn MX_FDCAN1_Init. Phần cần thêm là bộ lọc, ngắt và lệnh khởi động:
FDCAN_FilterTypeDef f = {0};
f.IdType = FDCAN_STANDARD_ID;
f.FilterIndex = 0;
f.FilterType = FDCAN_FILTER_MASK;
f.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
f.FilterID1 = 0x120;
f.FilterID2 = 0x7F0; // mask: nhận ID từ 0x120 đến 0x12F
HAL_FDCAN_ConfigFilter(&hfdcan1, &f);
// Khung không khớp bộ lọc nào: từ chối (không nhận)
HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT,
FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE);
HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0);
HAL_FDCAN_Start(&hfdcan1);
Ở chế độ FDCAN_FILTER_MASK, bit nào của mask bằng 1 thì bit tương ứng của ID nhận được phải trùng với FilterID1. Mask 0 (FilterID1 = 0, FilterID2 = 0) nhận mọi ID. Mask 0x7FF chỉ nhận đúng một ID.
5.2. Gửi khung
FDCAN_TxHeaderTypeDef tx = {0};
uint8_t data[8] = {1, 2, 3, 4, 5, 6, 7, 8};
tx.Identifier = 0x123;
tx.IdType = FDCAN_STANDARD_ID;
tx.TxFrameType = FDCAN_DATA_FRAME;
tx.DataLength = FDCAN_DLC_BYTES_8;
tx.ErrorStateIndicator = FDCAN_ESI_ACTIVE;
tx.BitRateSwitch = FDCAN_BRS_OFF;
tx.FDFormat = FDCAN_CLASSIC_CAN;
tx.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
tx.MessageMarker = 0;
if (HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &tx, data) != HAL_OK) {
// FIFO đầy hoặc ngoại vi chưa Start
}
Hàm này chỉ đưa khung vào hàng đợi, việc phân xử và phát do ngoại vi tự làm. Muốn biết khung đã đi chưa, bật FDCAN_IT_TX_COMPLETE.
5.3. Nhận trong callback
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *h, uint32_t it)
{
if (it & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) {
FDCAN_RxHeaderTypeDef rx;
uint8_t d[64];
while (HAL_FDCAN_GetRxFifoFillLevel(h, FDCAN_RX_FIFO0) > 0) {
if (HAL_FDCAN_GetRxMessage(h, FDCAN_RX_FIFO0, &rx, d) != HAL_OK) break;
// rx.Identifier là ID, rx.DataLength là mã DLC (không phải số byte)
// DLC 0..8 ứng với 0..8 byte, DLC 9..15 ứng với 12, 16, 20, 24, 32, 48, 64 byte
}
}
}
DataLength trong HAL là mã của hằng FDCAN_DLC_BYTES_n, không phải số byte. So sánh với các hằng này, hoặc dùng bảng chuyển đổi đúng với phiên bản HAL bạn có.
5.4. CAN FD tốc độ cao
Để dùng CAN FD có BRS, đặt Frame Format là "FD mode with BRS", điền thêm Data Prescaler và Data Time Seg, rồi đổi header khi gửi:
tx.FDFormat = FDCAN_FD_CAN;
tx.BitRateSwitch = FDCAN_BRS_ON;
tx.DataLength = FDCAN_DLC_BYTES_64;
Kích thước phần tử trong bộ nhớ FDCAN (RxBufferSize, TxElmtSize) cũng phải đủ chứa 64 byte, nếu không khung dài sẽ bị cắt hoặc báo lỗi.
5.5. Theo dõi lỗi và phục hồi bus-off
void can_check_bus(void)
{
FDCAN_ProtocolStatusTypeDef ps;
FDCAN_ErrorCountersTypeDef ec;
HAL_FDCAN_GetProtocolStatus(&hfdcan1, &ps);
HAL_FDCAN_GetErrorCounters(&hfdcan1, &ec);
if (ps.BusOff) {
// Ngoại vi tự dừng. Gọi Start để bắt đầu quy trình phục hồi
// (chờ 128 lần chuỗi 11 bit lặn trên bus).
HAL_FDCAN_Start(&hfdcan1);
}
// ec.TxErrorCnt, ec.RxErrorCnt: theo dõi để cảnh báo sớm khi lỗi tăng
// ps.ErrorPassive, ps.Warning: cờ trạng thái lỗi
}
Gọi hàm này theo chu kỳ, ví dụ mỗi 100 ms, hoặc trong HAL_FDCAN_ErrorStatusCallback. Đếm số lần vào bus-off và lưu lại, con số đó rất có ích khi chẩn đoán ngoài hiện trường.
Khi chưa có node thứ hai, đặt Mode = FDCAN_MODE_INTERNAL_LOOPBACK (hoặc EXTERNAL_LOOPBACK) để kiểm tra mã gửi, nhận và bộ lọc ngay trên một board.
6. Lỗi thường gặp
| Triệu chứng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Gửi mãi không xong, bộ đếm lỗi tăng | Bus chỉ có một node (không ai ACK), hoặc sai bitrate giữa các node | Thêm node thứ hai, kiểm tra bitrate, hoặc dùng Loopback |
| Bus-off liên tục | Sai tốc độ, thiếu điện trở 120 Ω, CANH/CANL đảo, transceiver chưa cấp nguồn | Đo điện trở bus (60 Ω), kiểm tra dây và nguồn transceiver |
| Chạy tốt ở 500 kbit/s nhưng lỗi ở 1 Mbit/s hoặc FD | Clock không chính xác, điểm lấy mẫu chưa tối ưu, dây dài, stub dài | Dùng HSE, chỉnh Seg1/Seg2, rút ngắn dây và stub |
| Không nhận được dữ liệu | Bộ lọc nhận chặn mất khung, quên bật ngắt hoặc HAL_FDCAN_Start |
Kiểm tra filter, global filter, NVIC |
| Mất khung khi tải cao | FIFO nhận đầy vì xử lý chậm | Tăng kích thước FIFO, xử lý nhanh trong callback, dùng RTOS queue |
| Lệch độ dài dữ liệu ở CAN FD | Nhầm mã DLC với số byte, hoặc kích thước phần tử bộ nhớ nhỏ | Dùng đúng bảng DLC, cấu hình đủ 64 byte |
| Dùng HAL FDCAN trên chip F4 | F1, F4 dùng bxCAN, không có FDCAN | Dùng HAL_CAN_* cho bxCAN |
7. Thiết kế mạng CAN
Bus nên đi theo một đường thẳng, hai điện trở ở hai đầu, các node rẽ nhánh ngắn. Tránh đi dây hình sao. Dùng cáp xoắn đôi có trở kháng đặc tính khoảng 120 Ω, và nối chung GND giữa các node bằng một dây thứ ba hoặc qua vỏ nếu cần.
Bitrate càng cao thì bus càng phải ngắn. Con số kinh nghiệm hay được trích là khoảng 40 m ở 1 Mbit/s, 100 m ở 500 kbit/s, 500 m ở 125 kbit/s, nhưng vẫn phải kiểm tra lại với dây và môi trường thực tế.
Trong môi trường công nghiệp, có thể thêm diode TVS và cuộn cản chế độ chung để chống nhiễu. Với CAN FD trên bus dài hoặc tốc độ cao, nên chọn transceiver có SIC (signal improvement capability).
8. 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ế |
|---|---|---|---|
| Điện trở bus khi tắt nguồn | Đồng hồ vạn năng giữa CANH và CANL | Khoảng 60 Ω | (điền sau) |
| Bitrate 500 kbit/s | Oscilloscope, đo độ rộng bit | 2 µs mỗi bit | (điền sau) |
| Điểm lấy mẫu thực tế | Oscilloscope hoặc logic analyzer giải mã CAN | 87,5% theo cấu hình | (điền sau) |
| Thời gian một khung 8 byte ở 500 kbit/s | Logic analyzer, đo từ SOF đến hết EOF | 222 đến 270 µs | (điền sau) |
| Tải bus tối đa chịu được | Gửi liên tục, đếm khung nhận ở node kia | 3 703 khung/giây (xấu nhất) | (điền sau) |
| CAN FD: tốc độ pha dữ liệu cao nhất chạy ổn | Tăng dần 2, 4, 5, 8 Mbit/s, ghi nhận lỗi | Phụ thuộc transceiver và dây | (điền sau) |
| Phân xử ưu tiên | Hai node cùng gửi ID khác nhau, quan sát ID nào thắng | ID nhỏ thắng | (điền sau) |
| Thời gian phục hồi bus-off | Gây lỗi có chủ đích, đo thời gian quay lại | Phụ thuộc cấu hình và tải bus | (điền sau) |
9. Hỏi đáp
CAN có cần chung clock không? Không. Mỗi node có clock riêng và đồng bộ lại ở cạnh xuống đầu khung cũng như ở các bit chèn. Vì vậy clock phải đủ chính xác (dùng thạch anh) để sai lệch tích luỹ không vượt quá SJW.
CAN 2.0A và 2.0B khác nhau thế nào? 2.0A dùng ID 11 bit. 2.0B dùng được cả ID 11 bit và 29 bit. Hai loại chạy chung trên một bus được.
CAN FD có chạy chung với CAN cổ điển được không? Node chỉ biết CAN cổ điển sẽ báo lỗi khi gặp khung FD. Nên thống nhất một loại trên toàn bus, hoặc cho các node FD chạy ở chế độ cổ điển.
Bỏ transceiver được không? Không. MCU chỉ phát mức logic. Transceiver tạo mức trội và lặn đúng điện áp trên đôi dây vi sai, chịu nhiễu, và bảo vệ chân MCU.
Kết luận
Khi bus lỗi, đo điện trở giữa CANH và CANL trước khi sửa phần mềm. Tính bit timing để điểm lấy mẫu nằm khoảng 80 đến 87,5%, với clock từ thạch anh. Theo dõi bộ đếm lỗi và xử lý bus-off trong firmware.
Phần 5 chuyển sang LIN: bus một dây giá rẻ dựa trên UART, dùng nhiều cho thiết bị phụ trợ trong ô tô, với khung Break, PID và checksum.
Để giải mã nhanh dữ liệu trong khung CAN, có thể dùng Hex Converter của Capitlab.
Tài liệu tham khảo
- STM32 FDCAN running at 8 Mb/s on Nucleo boards (ST Community)
- Reference manual của dòng STM32 bạn dùng (chương FDCAN) và ST AN5348
- ISO 11898-1, tài liệu transceiver (TJA1051, MCP2562FD)