Giao tiếp CAN STM32 – Thực hành cơ bản

Nối tiếp nội dung tổng quan về Giao tiếp CAN, bài viết này sẽ cung cấp các hướng dẫn cách thực hành cơ bản CAN bus với vi điều khiển STM32. Nhiều dòng vi điều khiển STM32 tích hợp CAN Controller, chúng ta cần thêm phần cứng CAN Transceiver – MCP2551 để thử nghiệm. Hai vi điều khiển STM32 được thiết lập để giao tiếp bằng CAN thông qua module MCP2551 và UART được sử dụng để thể hiện dữ liệu lên màn hình máy tính.

1. Phần cứng 

  • Giới thiệu về module MCP2551:
    • Tốc độ truyền nhận dữ liệu tối đa : 1M bit/s
    • Nguồn  cung cấp cho module MCP2551 Vdd = 5V
    • Chân TX hoạt động ở mức logic cao từ 2V đến Vdd (5V) phù hợp với chân RX của STM32 hỗ trợ IO FT (5v tolerant)
    • Chân RX hoạt động ở mức logic cao từ 0.7Vdd (~3.5V) phù hợp ghép nối với chân TX của STM32 hỗ trợ IO FT.

Hình 1.1: Điện áp logic – dòng điện của chân MCP2551 – @MCP datasheet

Hình 1.2: Điện áp logic của chân STM32 được dùng để CAN – @STM32F103 Datasheet

  • Chọn chân PB8 và PB9 kết nối với MCP2551 để giao tiếp CAN. Từ những yếu tố trên chúng ta có thể nối thẳng chân của STM32 qua MCP2551 mà không qua IC chuyển logic hay đệm dòng. 
  • Sơ đồ đấu nối:

Hình 1.3: Sơ đồ kết nối hệ thống

Hình 1.4: Sơ đồ chân của STM32

2. Phần mềm

2.1. Giản đồ quá trình hoạt động của ECU gửi và ECU nhận trong thí nghiệm

  • ECU gửi

Hình 2.1: Giản đồ thực thi của ECU gửi

  • ECU nhận

Hình 2.2: Giản đồ thực thi của ECU nhận

2.2. Cấu hình trên STM32CubeIDE

ECU gửi

  • Sử dụng UART1 của STM32F103C8T6 với các tham số cấu hình như sau:
    • Tốc độ baud: 115200 bit/giây
    • Số bit dữ liệu: 8 bit
    • Số bit stop: 1
    • Không sử dụng bit chẵn lẻ.

Hình 2.3: Cấu hình UART

  • Thiết lập kết nối CAN 500.000 bit/giây (bps) giữa 2 ECU: 
    • Để đạt được tốc độ truyền 500.000 bit/giây của giao thức CAN, trước tiên chúng ta phải cấu hình xung HSE sử dụng bộ thạch anh ngoài (HSE) và cấu hình cho Bus APB1 36MHz
    • Thời gian của 1 bit là 1/500000 giây hoặc 2000ns. Để có được 2000ns cần thiết trong phần ”Time for one Bit section”,  chúng ta phải sửa đổi Prescaler, Time Quanta trong Bit Segment 1 và Time Quanta trong Bit Segment 2 trong các tham số giao thức CAN ở phần cấu hình CAN của Cube IDE:
      • Prescaler = 9 -> Tcan  = 36MHz / 9 = Time Quantum = 250ns
      • Time Quanta in Bit Segment 1 = 3 Times
      • Time Quanta in Bit Segment 2 = 4 Times
      • Time For 1 Bit = 250 + (Tcan * 3) + (Tcan *4) = 250 + (250* 3) + (250*4) = 2000ns.

Hình2.4: Cấu hình xung cho hệ thống
Ảnh 2.5: Cấu hình CAN

ECU Nhận

  • Cấu hình thông số CAN như với ECU gửi
  • Kích hoạt ngắt CAN RX0 khi nhận dữ liệu từ CAN để đảm bảo không bỏ lỡ dữ liệu truyền đến:

Hình 2.6: Cấu hình ngắt trên CAN

2.3. Thực hành viết mã code

  • Sau khi cấu hình tại giao diện Cube, chúng ta tạo source code và lập trình của chương trình tại file main.c
  • Trong phần này sẽ thực hiện gửi gói tin từ 1 ECU có ID = 0x023 với nội dung gói tin là :  “Hello TAPIT\n”, đến ECU khác trên CAN bus
  • Nếu ECU nhận thành công gói tin có nội dung như trên thì sẽ đảo trạng thái của LED

ECU gửi

B1: Khai báo và khởi tạo các biến: 

  • canTX: Buffer chứa data muốn truyền 
  • TxMsg: Struct chứa thông tin gói tin khi truyền trên CAN bus
  • pu32Mailbox: Mailbox của gói tin khi CAN bus bận

Hình 2.7: Khởi tạo biến và struct

B2: Cấu hình các thông tin của gói tin gửi đi

Hình 2.8: Cấu hình gói tin trước khi ECU thực hiện gửi

Trong đó:

  • DLC: Độ dài tối đa của gói tin là 8 bytes
  • ExtId: Trong nội dung bài viết không tìm hiểu về extended ID 
  • IDE: Chọn standard ID
  • RTR: Thiết lập trạng thái gói tin
    • CAN_RTR_DATA: Gói tin được cài đặt là loại Data Frame và truyền dữ liệu
    • CAN_RTR_REMOTE: Gói tin được cài đặt là loại Remote Frame và yêu cầu nhận dữ liệu
  • StdId: ID standard của gói tin truyền trên CAN bus để phân biệt với các gói tin khác của các ECU
  • TransmitGlobalTime:  Khi bật tính năng này lên thì đường truyền sẽ gửi thêm cả khung thời gian, cho phép node nhận biết được gói tin nó nhận được gửi đi vào thời gian nào. Hữu dụng trong một số ứng dụng như đồng bộ hệ thống qua thời gian, sắp xếp tin nhắn hay debug.

B3: Trong vòng lặp của main(), liên tục gửi gói tin canTX và đảo trạng thái của LED trong khoản thời gian 1s

Hình 2.9: Code trong hàm while để gửi gói tin

ECU nhận

  • Đối với ECU nhận, trong bài viết này sử dụng chức năng Filter của chức năng CAN. Chức năng này giúp VĐK lọc các gói tin có ID không đúng yêu cầu trên CAN bus trước khi VXL sinh yêu cầu ngắt

B1: Khai báo và khởi tạo các biến

  • canRX: Buffer chứa data nhận được
  • RxMsg: Chứa thông tin của gói tin nhận được từ CAN bus
  • sFilterConfig: Struct chứa thông tin Filter, sử dụng để kiểm tra gói tin khi nhận được từ CAN bus

Hình 2.10: Khởi tạo biến và Struct

B2: Cho phép yêu cầu ngắt và hàm Callback

  • Bật yêu cầu ngắt khi dữ liệu CAN nhận và được lưu trữ gói tin ở FIFO1 khi đúng ID (nếu có sử dụng filter) hoặc không

Hình 2.11: Bật yêu cầu ngắt khi nhận được gói tin thông qua giao tiếp CAN

  • Tiếp theo, sử dụng hàm Callback của CAN khi nhận được một gói dữ liệu từ CAN, chương trình sẽ bị ngắt và chuyển đến hàm để gửi gói đó qua UART1 và đảo trạng thái của LED

Hình 2.12: Hàm nhận Callback của CAN

  • Nếu muốn chỉ nhận data từ một bảng tin có ID nhất định, chúng ta sử dụng “RxMsg.StdId” (ID của gói tin ECU nhận được) kết hợp với thuật toán if-else như ảnh 3.9

Hình 2.13: Code trong hàm nhận Callback của CAN chỉ chấp nhận tin nhắn từ ECU có ID 0x023

 

  • Lưu ý:  Số lượng ECU trong trong CAN bus trong các hệ thống có thể rất nhiều dẫn đến số lượng gói tin trên bus cũng nhiều theo. Như vậy cần 1 cơ chế lọc gói tin để Vi điều khiển giảm bớt những hạn chế thời gian do việc xử lý tin nhắn gây ra.

B3: Cấu hình CAN Filter

Hình 2.14: Cấu hình CAN Filter

  • Cấu hình Struct trong CAN (filter): 
    • sFilterConfig được khai báo bằng kiểu dữ liệu lần lượt là CAN_FilterTypeDef
    • Lọc tất cả các dữ liệu được gửi đến ESC, ví dụ không đúng ID được cấu hình vào bộ Filter (Bank) thì không nhận dữ liệu đó
    • FilterActivation: Có sử dụng bộ lọc này không?
    • SlaveStartFilterBank: Chỉ định số lượng Filters Bank được sử dụng, STM32F103 được tính hợp bộ CAN kép (CAN0, CAN1) bao gồm 28 Banks, có thể chia mỗi CAN tùy theo mục đích người dùng
    • FilterBank: Mỗi Bank được sử dụng để lưu trữ thông tin filter của ECU khi nhận được gói tin từ CAN bus bao gồm ID ,..
    • FilterFIFOAssignment: Chỉ định FIFO nào sẽ nhận dữ liệu, ở đây có 2 cái là FIFO0 và FIFO1 (Giải thích chi tiết FIFO có thể tham khảo trang  663/1136 [12]) 
    • FilterMode: Có 2 chế độ là IDMask mode và IDList mode
      • IDMask mode: so sánh ID Mask được chỉ định với ID trong gói tin nhận được, các bit được cài đặt trước có thể trùng hoặc không trùng 
      • IDList mode: so sánh ID Mask có trùng tất cả các bit so với ID của gói tin nhận được
    • FilterIdHigh: Là 16 Bits cao của thanh ghi ID, ID này được so sánh với ID của dữ liệu nhận được
    • FilterIdLow: Ngược lại với FilterIdHigh thì đây là 16 Bits thấp của thanh ghi ID
    • FilterMaskIdHigh và FilterMaskIdLow: Được sử dụng cho ID Mask mode được giới thiệu sau
    • FilterScale: 
  • Mask Mode, One 32-Bit Filter, Standard ID (11-bit) (reference manual STM32F103x [12])
    • Mask mode: các thanh ghi ID được liên kết với các thanh ghi Mask để chỉ rõ bit nào của ID được chấp nhận là “must match” hay “don’t care”
    • Filter: có 2 phần bao gồm Filter ID (ID của gói tin làm mốc để thực hiện chức năng Mask nhằm chọn ra gói tin có ID muốn nhận) và Filter Mask là những vị trí bit ID trong gói tin mà ECU này muốn nhận.
  • VD: Nếu 1 ECU muốn nhận 2 gói tin có ID 0x023 và 0x033 thì chúng ta chỉ cần set cả Filter ID và Filter Mask đều là 0x03, quá trình được minh họa ở ảnh 3.9.1:

Hình 2.15: Quá trình thực hiện so sánh để lấy ID 0x03

  • Tùy vào ứng dụng của bạn, có thể sử dụng IDMask mode để kiểm tra vị trí bit của ID, hoặc kiểm tra tất cả các bit trong ID thì dùng IDList mode. Với STM32, giá trị Filter ID và Filter mask sẽ được ghi vào các thanh ghi 32-bit và mỗi thanh ghi đó được chia thành 2 phần: high register bao gồm 16 bit cao (MSB) và low register là các bit thấp. Trong bài thực hành vì chúng ta đang sử dụng 11 bits ở mode standard ID và nó sẽ lưu ở 11 bits đầu của thanh ghi FxR1( x = filter bank number) còn mask bit sẽ được lưu ở thanh ghi FxR2 minh họa trong hình:

Hình 2.16: Ghi các giá trị ID vào vị trí các thanh ghi (STM32F103x Reference Manual [12])

  • Lưu ý: Phần high register, 5 bit đầu là dành cho extended ID và chúng ta chỉ sử dụng standard ID vậy nên trong chương trình phải dịch qua trái 5 bits trong phần cấu hình CAN filter (chi tiết như trong ảnh 2.16)

B4: Cuối cùng, kết nối và nạp code 2 ECU và kiểm tra chương trình trong thực tế bằng cách quan sát kết quả thông qua UART, ở đây bài thực hành sử dụng phần mềm Hercules:


Hình 2.17. Thiết lập phần cứng trong thí nghiệm

Hình 2.18 Dữ liệu nhận được ở ECU nhận gửi lên phần mềm hercules trên máy tính thông qua UART

Như vậy, bài viết đã cung cấp các hướng dẫn để có thể thử nghiệm giao tiếp CANbus giữa hai vi điều khiển STM32. Chúc các bạn thành công! 


Chúc các bạn thành công!
Nhóm tác giả : Lê Hữu Bảo Thuận, Trần Đình Hoàng Long
Hướng dẫn : Nguyễn Huỳnh Nhật Thương