CAN bus (phần 1): tín hiệu, khung tin, phân xử ưu tiên và gửi nhận bằng ESP32

Giải thích CAN bus: hai dây CAN_H, CAN_L, mức trội và mức lặn, khung tin có ID 11 bit, phân xử ưu tiên, CRC và ACK. Kèm hai chương trình ESP32 dùng bộ điều khiển TWAI: tự kiểm tra và hai nút gửi nhận.

CAN là mạng truyền dữ liệu sinh ra cho ô tô và nay có mặt trong máy công nghiệp, robot, thang máy, xe điện. Điểm khác biệt lớn nhất so với RS-485 là phần "thông minh" nằm ngay trong phần cứng: bộ điều khiển CAN tự phân xử khi hai nút gửi cùng lúc, tự tính CRC, tự xác nhận và tự gửi lại khi lỗi. ESP32 có sẵn một bộ điều khiển CAN, tên trong tài liệu là TWAI. Bài viết giải thích tín hiệu, khung tin và cơ chế phân xử ưu tiên của CAN, rồi dùng ESP32 để tự kiểm tra và để hai nút gửi nhận với nhau.

Đây là phần 1. Phần 2 nói về bộ lọc, tốc độ bit và xử lý lỗi.

1. Đường truyền CAN

Sơ đồ đường truyền CAN: ba nút nối song song vào cặp dây xoắn CAN_H, CAN_L qua bộ thu phát, hai điện trở đầu cuối 120 ôm ở hai đầu đường dây
Các nút ngang hàng mắc song song trên cặp dây CAN_H, CAN_L có điện trở 120 Ω ở hai đầu.
  • Hai dây CAN_H và CAN_L xoắn vào nhau, mang tín hiệu vi sai. Hai đầu đường dây có hai điện trở 120 Ω.
  • Mỗi nút gồm một bộ điều khiển CAN (nằm trong vi điều khiển) và một bộ thu phát (IC riêng) nối ra đường dây.
  • Không có nút chủ. Nút nào có dữ liệu thì gửi, và mọi nút khác đều nhận được khung đó.
  • Tốc độ càng cao, đường dây càng phải ngắn: khoảng 40 m ở 1 Mbit/s, 100 m ở 500 kbit/s, 250 m ở 250 kbit/s, 500 m ở 125 kbit/s. Mọi nút trên một đường dây phải dùng cùng tốc độ.

2. Mức trội và mức lặn

Giản đồ tín hiệu CAN: ở mức trội ứng với bit 0, CAN_H lên khoảng 3,5 V và CAN_L xuống khoảng 1,5 V; ở mức lặn ứng với bit 1, cả hai dây ở khoảng 2,5 V
Mức trội (bit 0) và mức lặn (bit 1) trên hai dây (hình vẽ minh họa nguyên lý).
  • Lặn (recessive, bit 1): không nút nào tác động, hai dây cùng ở khoảng 2,5 V.
  • Trội (dominant, bit 0): bộ thu phát kéo CAN_H lên khoảng 3,5 V và CAN_L xuống khoảng 1,5 V.
  • Khi một nút phát bit trội và nút khác phát bit lặn, đường dây ở mức trội. Mức trội luôn thắng. Mọi cơ chế của CAN đều dựa trên tính chất này.

3. Khung tin CAN

Cấu trúc khung dữ liệu CAN chuẩn: bit bắt đầu SOF, mã nhận dạng 11 bit, RTR, IDE, DLC 4 bit, dữ liệu 0 đến 8 byte, CRC 15 bit, ACK và EOF 7 bit
Khung dữ liệu CAN chuẩn: mã nhận dạng 11 bit, tối đa 8 byte dữ liệu, CRC và ACK.
  • Mã nhận dạng (ID) 11 bit cho biết khung chứa nội dung gì, ví dụ "tốc độ động cơ" hay "nhiệt độ ắc quy". ID không phải địa chỉ nút nhận: nút nào quan tâm tới nội dung đó thì dùng.
  • DLC là số byte dữ liệu, từ 0 đến 8.
  • CRC 15 bit do bộ điều khiển tự tính và tự kiểm tra.
  • ACK: nút gửi để trống một bit ở mức lặn; bất kỳ nút nào nhận đúng khung sẽ phát bit trội vào chỗ đó. Nếu không ai xác nhận, nút gửi coi như gửi lỗi và tự gửi lại.
  • Ngoài khung chuẩn còn có khung mở rộng với ID 29 bit, và chuẩn CAN FD mang được nhiều dữ liệu hơn. Bài này chỉ dùng khung chuẩn.

4. Hai nút gửi cùng lúc thì sao?

Minh họa phân xử ưu tiên trên CAN: nút A gửi ID 0x101 và nút B gửi ID 0x102 cùng lúc, đến bit 1 nút B phát mức lặn nhưng đọc lại thấy mức trội nên ngừng gửi, khung của nút A đi tiếp nguyên vẹn
Ở bit đầu tiên khác nhau, nút phát bit trội thắng; nút kia tự dừng.
  • Nút nào cũng đọc lại đường dây trong lúc gửi. Trong phần ID, nếu một nút phát bit lặn mà đọc lại thấy mức trội, nó biết có nút khác đang gửi ID nhỏ hơn và lập tức ngừng.
  • Khung của nút thắng không bị hỏng, không mất thời gian. Nút thua tự gửi lại ngay khi đường dây rảnh.
  • Vì vậy ID càng nhỏ thì mức ưu tiên càng cao. Khi thiết kế, dữ liệu khẩn cấp được gán ID nhỏ.
  • Hai nút không được gửi cùng một ID: khi đó không có gì để phân xử và dữ liệu sẽ đè lên nhau.

5. Chuẩn bị

  • Hai mạch ESP32 DevKit V1 30 chân (hoặc bản dùng chip CH340). Riêng chương trình 1 chỉ cần một mạch.
  • Hai module thu phát CAN, nên dùng loại chạy 3,3 V (IC SN65HVD230 hoặc tương đương).
  • Một đoạn dây đôi xoắn nối CANH với CANH, CANL với CANL.
  • Breadboard và dây cắm.
  • Arduino IDE 2.x với gói esp32 by Espressif Systems (bài dùng phiên bản 3.3.10). Trình điều khiển CAN có sẵn trong gói này, không cần cài thêm thư viện.

6. Chương trình 1: tự kiểm tra với một mạch

Bình thường một nút CAN không gửi được nếu không có nút khác xác nhận. Bộ điều khiển của ESP32 có chế độ không cần xác nhận kèm tính năng tự nhận lại khung vừa gửi, đủ để kiểm tra phần mềm với một mạch. Nối một dây từ D5 sang D4 để tín hiệu phát quay thẳng về chân thu.

/*
  PYWORLD.VN - CAN trên ESP32: tự kiểm tra bộ điều khiển CAN (TWAI) bằng cách tự gửi tự nhận một khung tin
  Chỉ cần một mạch: nối một dây từ D5 (TX của CAN) sang D4 (RX của CAN), hoặc gắn một module thu phát CAN
  Chế độ không cần xác nhận (NO_ACK) cho phép gửi khi trên đường truyền chưa có nút nào khác
*/

#include <driver/twai.h>

#define CAN_TX_PIN 5
#define CAN_RX_PIN 4

void setup() {
  Serial.begin(115200);

  twai_general_config_t cauHinh = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)CAN_TX_PIN, (gpio_num_t)CAN_RX_PIN, TWAI_MODE_NO_ACK);
  twai_timing_config_t tocDo = TWAI_TIMING_CONFIG_500KBITS();
  twai_filter_config_t boLoc = TWAI_FILTER_CONFIG_ACCEPT_ALL();

  if (twai_driver_install(&cauHinh, &tocDo, &boLoc) != ESP_OK || twai_start() != ESP_OK) {
    Serial.println("\nKhong khoi dong duoc bo dieu khien CAN");
    while (true) {
      delay(1000);
    }
  }
  Serial.println("\nCAN 500 kbit/s, che do tu kiem tra");
}

void loop() {
  static uint8_t soDem = 0;
  soDem++;

  // Khung chuẩn: mã nhận dạng 11 bit, tối đa 8 byte dữ liệu
  twai_message_t gui = {};
  gui.identifier = 0x123;
  gui.data_length_code = 4;
  gui.data[0] = soDem;
  gui.data[1] = 0xAA;
  gui.data[2] = 0x55;
  gui.data[3] = (uint8_t)(millis() / 1000);
  gui.self = 1;   // Yêu cầu tự nhận lại chính khung này

  if (twai_transmit(&gui, pdMS_TO_TICKS(100)) != ESP_OK) {
    Serial.println("Khong dua duoc khung vao hang doi gui");
    delay(1000);
    return;
  }

  twai_message_t nhan;
  if (twai_receive(&nhan, pdMS_TO_TICKS(200)) == ESP_OK) {
    Serial.printf("Nhan lai ID 0x%03X, %d byte:", (unsigned)nhan.identifier, nhan.data_length_code);
    for (int i = 0; i < nhan.data_length_code; i++) {
      Serial.printf(" %02X", nhan.data[i]);
    }
    Serial.println(nhan.data[0] == soDem ? "  -> dung" : "  -> SAI");
  } else {
    Serial.println("Khong nhan lai duoc khung: kiem tra day noi D5 voi D4 hoac module thu phat");
  }
  delay(1000);
}

7. Sơ đồ nối module thu phát

Sơ đồ nối ESP32 DevKit V1 với module thu phát CAN 3,3 V: chân CTX nối D5, CRX nối D4, GND nối GND, 3V3 nối 3V3, hai cọc CANH và CANL đi tới module thứ hai
D5 nối chân phát, D4 nối chân thu của module; nút thứ hai nối y hệt.
  • Tháo dây nối D5 với D4 của chương trình 1 trước khi nối module.
  • Module thu phát CAN thường có sẵn một điện trở 120 Ω. Với hai module ở hai đầu dây là vừa đủ. Khi mắc nhiều nút, chỉ giữ điện trở ở hai nút đầu và cuối.
  • Module dùng IC chạy 5 V (ví dụ TJA1050) phát mức 5 V ở chân thu: phải qua mạch chia áp trước khi vào D4.
  • Nối chung GND giữa các nút khi chúng dùng nguồn khác nhau.

8. Chương trình 2: hai nút gửi và nhận

Nạp cùng chương trình cho hai mạch, chỉ đổi ID_CUA_TOI: một mạch 0x101, mạch kia 0x102. Mỗi nút gửi một khung 4 byte mỗi giây và in mọi khung nhận được.

/*
  PYWORLD.VN - CAN trên ESP32: hai nút gửi và nhận khung tin qua module thu phát CAN
  Nạp cho hai mạch, mỗi mạch một mã nhận dạng riêng (ID_CUA_TOI = 0x101 và 0x102); nối CANH với CANH, CANL với CANL
  Mỗi nút gửi một khung mỗi giây và in mọi khung nhận được; module thu phát nối TX = D5, RX = D4
*/

#include <driver/twai.h>

#define CAN_TX_PIN 5
#define CAN_RX_PIN 4

const uint32_t ID_CUA_TOI = 0x101;   // Đổi thành 0x102 khi nạp cho mạch thứ hai
const unsigned long CHU_KY_GUI_MS = 1000;

uint16_t soDem = 0;
unsigned long lucGuiCuoi = 0;

void setup() {
  Serial.begin(115200);

  twai_general_config_t cauHinh = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)CAN_TX_PIN, (gpio_num_t)CAN_RX_PIN, TWAI_MODE_NORMAL);
  twai_timing_config_t tocDo = TWAI_TIMING_CONFIG_500KBITS();
  twai_filter_config_t boLoc = TWAI_FILTER_CONFIG_ACCEPT_ALL();

  if (twai_driver_install(&cauHinh, &tocDo, &boLoc) != ESP_OK || twai_start() != ESP_OK) {
    Serial.println("\nKhong khoi dong duoc bo dieu khien CAN");
    while (true) {
      delay(1000);
    }
  }
  Serial.printf("\nNut CAN ID 0x%03X, 500 kbit/s\n", (unsigned)ID_CUA_TOI);
}

void loop() {
  // Gửi định kỳ: bộ đếm 16 bit và số giây đã chạy, byte cao trước
  if (millis() - lucGuiCuoi >= CHU_KY_GUI_MS) {
    lucGuiCuoi = millis();
    soDem++;
    uint16_t soGiay = (uint16_t)(millis() / 1000);

    twai_message_t gui = {};
    gui.identifier = ID_CUA_TOI;
    gui.data_length_code = 4;
    gui.data[0] = soDem >> 8;
    gui.data[1] = soDem & 0xFF;
    gui.data[2] = soGiay >> 8;
    gui.data[3] = soGiay & 0xFF;

    esp_err_t ketQua = twai_transmit(&gui, pdMS_TO_TICKS(50));
    if (ketQua == ESP_OK) {
      Serial.printf("Gui  ID 0x%03X: dem %u, %u giay\n", (unsigned)ID_CUA_TOI, soDem, soGiay);
    } else {
      // Hàng đợi đầy hoặc bộ điều khiển đã ngừng: thường do không có nút nào khác xác nhận
      Serial.printf("Khong gui duoc (loi 0x%X): kiem tra nut con lai va day CANH, CANL\n", (unsigned)ketQua);
    }
  }

  // Nhận mọi khung đang chờ; nút nào trên đường truyền cũng thấy mọi khung
  twai_message_t nhan;
  while (twai_receive(&nhan, 0) == ESP_OK) {
    Serial.printf("Nhan ID 0x%03X, %d byte:", (unsigned)nhan.identifier, nhan.data_length_code);
    for (int i = 0; i < nhan.data_length_code; i++) {
      Serial.printf(" %02X", nhan.data[i]);
    }
    if (nhan.data_length_code == 4) {
      Serial.printf("  (dem %u, %u giay)", (nhan.data[0] << 8) | nhan.data[1], (nhan.data[2] << 8) | nhan.data[3]);
    }
    Serial.println();
  }
  delay(5);
}
  • Chương trình chỉ điền ID, số byte và dữ liệu vào cấu trúc twai_message_t. Mọi việc còn lại do bộ điều khiển lo.
  • Ý nghĩa của từng byte do người thiết kế hệ thống quy ước. Ở đây hai byte đầu là bộ đếm, hai byte sau là số giây, byte cao trước.
  • Bộ lọc đang để nhận mọi ID. Phần 2 sẽ cho nút chỉ nhận các ID mình cần.

9. Kết quả mong đợi

Hình minh họa Serial Monitor của hai chương trình CAN trên ESP32: khung ID 0x123 được nhận lại đúng trong chế độ tự kiểm tra, và nút ID 0x101 gửi khung của mình xen kẽ với nhận khung ID 0x102 của nút kia
Kết quả mong đợi trên Serial Monitor của hai chương trình (hình minh họa).
  1. Chương trình 1: mở Tools > Serial Monitor ở 115200 baud. Mỗi giây hiện một khung nhận lại đúng. Rút dây D5 với D4: báo không nhận lại được.
  2. Chương trình 2: mỗi mạch in khung mình gửi và khung của mạch kia.
  3. Tắt một mạch: mạch còn lại bắt đầu báo không gửi được sau vài giây, vì không còn ai xác nhận khung của nó.

10. Xử lý lỗi thường gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
Chương trình 1 không nhận lại được khungChưa nối D5 với D4, hoặc sai chânNối đúng một dây D5 sang D4; nếu dùng module thì kiểm tra nguồn và hai dây TX, RX
Hai nút không thấy nhauNối ngược CANH với CANL, hai nút khác tốc độ, hoặc nối nhầm TX với RX của moduleCANH nối CANH; cùng 500 kbit/s; D5 vào chân phát, D4 vào chân thu
Báo không gửi được liên tụcTrên đường dây chỉ có một nút đang chạy nên không ai xác nhậnBật nút còn lại; kiểm tra dây CANH, CANL
Chạy được với dây ngắn, lỗi với dây dàiThiếu điện trở đầu cuối, hoặc tốc độ quá cao so với chiều dàiĐặt 120 Ω ở hai đầu (đo được 60 Ω giữa CANH và CANL khi tắt nguồn); giảm tốc độ
ESP32 khởi động chập chờn khi gắn moduleGPIO5 là chân cấu hình lúc khởi độngĐổi chân TX của CAN sang chân khác, ví dụ GPIO21, và sửa CAN_TX_PIN
Hai nút cùng ID làm dữ liệu saiTrùng mã nhận dạngMỗi loại khung chỉ do một nút gửi

11. Xem thêm


Tin tức liên quan

Bluetooth Low Energy (BLE) là gì: quảng bá, GATT, dịch vụ, đặc tính và ESP32
Bluetooth Low Energy (BLE) là gì: quảng bá, GATT, dịch vụ, đặc tính và ESP32

Giải thích Bluetooth Low Energy: quảng bá và kết nối, cấu trúc GATT gồm dịch vụ, đặc tính, mô tả, UUID. Hướng dẫn cho ESP32 làm thiết bị BLE có đặc tính đọc, ghi, thông báo và thử bằng điện thoại.

Điều khiển hồng ngoại hoạt động thế nào: sóng mang 38 kHz, giao thức NEC và ESP32
Điều khiển hồng ngoại hoạt động thế nào: sóng mang 38 kHz, giao thức NEC và ESP32

Giải thích điều khiển hồng ngoại: sóng mang 38 kHz, mắt thu và giao thức NEC với khung 32 bit, mã lặp. Hướng dẫn tự giải mã phím điều khiển và tự phát lệnh bằng ESP32, không dùng thư viện ngoài.

RF 433 MHz là gì: điều chế ASK/OOK và tự viết giao thức phát, thu bằng ESP32
RF 433 MHz là gì: điều chế ASK/OOK và tự viết giao thức phát, thu bằng ESP32

Giải thích sóng RF 433 MHz và điều chế ASK/OOK, cách mã hóa bit bằng độ rộng xung và khung tin 24 bit. Hướng dẫn tự viết chương trình phát và thu trên hai mạch ESP32, không dùng thư viện ngoài.

Vòng dòng 4-20 mA là gì và cách đọc cảm biến 4-20 mA bằng ESP32
Vòng dòng 4-20 mA là gì và cách đọc cảm biến 4-20 mA bằng ESP32

Giải thích vòng dòng 4-20 mA: vì sao dùng dòng điện, vì sao bắt đầu từ 4 mA, cách đổi ra giá trị đo và ngưỡng báo lỗi. Hướng dẫn đọc bằng ESP32 qua điện trở shunt 120 Ω, với ADC có sẵn và với ADS1115.

CAN bus (phần 2): bộ lọc nhận, tốc độ bit và xử lý lỗi bus-off trên ESP32
CAN bus (phần 2): bộ lọc nhận, tốc độ bit và xử lý lỗi bus-off trên ESP32

Phần 2 về CAN bus với ESP32: đặt bộ lọc nhận theo mã lọc và mặt nạ, chọn tốc độ bit theo chiều dài dây, hiểu ba trạng thái lỗi của nút và viết chương trình theo dõi bộ đếm lỗi, tự hồi phục sau bus-off.

Modbus TCP/IP (phần 2): ESP32 làm máy khách và cổng chuyển đổi Modbus TCP sang RTU
Modbus TCP/IP (phần 2): ESP32 làm máy khách và cổng chuyển đổi Modbus TCP sang RTU

Hướng dẫn dùng ESP32 làm máy khách Modbus TCP đọc thanh ghi của một thiết bị trong mạng, và làm cổng chuyển đổi Modbus TCP sang Modbus RTU qua RS-485: cách đổi khung, mã thiết bị, thời gian chờ, mã lỗi 0x0B.

Modbus TCP/IP (phần 1): phần đầu MBAP và ESP32 làm máy chủ qua Wi-Fi
Modbus TCP/IP (phần 1): phần đầu MBAP và ESP32 làm máy chủ qua Wi-Fi

Giải thích Modbus TCP/IP: phần đầu MBAP, cổng 502, điểm khác với Modbus RTU. Kèm chương trình ESP32 làm máy chủ Modbus TCP qua Wi-Fi với 10 thanh ghi giữ và 8 cuộn, tự phân tích khung, không dùng thư viện.

TCP và UDP là gì: địa chỉ IP, cổng, kết nối và cách dùng trên ESP32
TCP và UDP là gì: địa chỉ IP, cổng, kết nối và cách dùng trên ESP32

Giải thích TCP và UDP cho người làm vi điều khiển: địa chỉ IP, cổng, kết nối, xác nhận, vì sao TCP là dòng byte. Kèm hai chương trình ESP32: máy chủ TCP nhận lệnh và phát, nhận gói UDP trong mạng Wi-Fi.


Bình luận
Đã thêm vào giỏ hàng