Lập trình nhúng

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.

15 phút đọc
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

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.

Mạng CAN gồm cặp CANH CANL, hai điện trở 120 ohm ở hai đầu và ba node mỗi node có MCU và transceiver

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

Cấu trúc khung CAN chuẩn: SOF, ID 11 bit, RTR, IDE, r0, DLC, dữ liệu 64 bit, CRC 15 bit, ACK, EOF

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.

Ba node A B C phát cùng lúc ID 0x123, 0x120, 0x1A0: node C thua ở ID7, node A thua ở ID1, node B thắng

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).

Một bit CAN gồm 16 time quantum: Sync 1 tq, TimeSeg1 13 tq, TimeSeg2 2 tq, điểm lấy mẫu ở 87,5%

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