Arduino 的 CAN 匯流排
諸如 UART(序列埠)、I2C 和 SPI 等通訊協議非常流行,因為可以使用這些協議將多個外設與 Arduino 連線。CAN(控制器區域網絡)是另一種此類協議,在一般情況下並不十分流行,但在汽車領域有許多應用。
本文不會深入探討 CAN 匯流排的細節,您可以在此處找到相關資訊。但是,以下是一些您應該瞭解的事情:
CAN 是一種基於訊息的協議(即,訊息和內容比傳送方更重要)。一個裝置傳送的訊息會被所有裝置(包括髮送裝置本身)接收。
如果多個裝置同時傳送資料,則優先順序最高的裝置將繼續傳送,而其他裝置則會退避。請注意,由於 CAN 是一種基於訊息的協議,因此 ID 分配給訊息而不是裝置。
它使用兩條線進行資料傳輸 CAN_H 和 CAN_L。這兩條線之間的差分電壓決定了訊號。高於閾值的正差表示 1,而負電壓表示 0。
網路中的裝置稱為節點。CAN 非常靈活,可以向網路新增新的節點,也可以移除節點。網路中的所有節點僅共享兩條線。
資料傳輸以幀的形式進行。每個資料幀包含 11 位(基本幀格式)或 29 位(擴充套件幀格式)識別符號位和 0 到 8 個數據位元組。
現在,Arduino Uno 不像支援 UART、SPI 和 I2C 那樣直接支援 CAN。因此,我們將使用外部模組,帶有 TJA1050 收發器的 MCP2515,它透過 SPI 與 Arduino 介面,並使用 CAN 傳送訊息。

電路圖
下面顯示了一個節點(**傳送器**)的電路圖。

如上所示,模組的 Vcc 線連線到 Arduino 的 5V,GND 連線到 GND,CS 連線到引腳 10,SO 連線到引腳 12(MISO),SI 連線到引腳 11(MOSI),SCK 連線到引腳 13(SCK)。
在**接收端**,連線方式類似,只是模組的 INT 引腳連線到 Arduino 的 2 號引腳。

這兩個(傳送器和接收器)透過 CAN_H 和 CAN_L 線連線在一起(CAN_H 連線到 CAN_H,CAN_L 連線到 CAN_L)。
所需庫
我們將使用來自 Seeed Studio 的此庫。Arduino 的庫管理器中找不到此庫。此處提供了下載第三方庫的步驟:https://tutorialspoint.tw/using-a-third-party-library-in-arduino
程式碼演練
安裝庫後,您可以在**檔案 -> 示例 -> CAN_BUS Shield**中找到 send 和**receive_interrupt**示例。

我們將使用這些示例的稍微簡化版本。
下面給出了 SEND 的程式碼:
#include <SPI.h>
#include "mcp2515_can.h"
onst int SPI_CS_PIN = 10;
mcp2515_can CAN(SPI_CS_PIN); // Set CS pin
void setup() {
Serial.begin(115200);
while(!Serial){};
// init can bus : baudrate = 500k
while (CAN_OK != CAN.begin(CAN_500KBPS)) {
Serial.println("CAN init fail, retry...");
delay(100);
}
Serial.println("CAN init ok!");
}
unsigned char stmp[8] = {0, 0, 0, 0, 0, 0, 0, 0};
void loop() {
// send data: id = 0x00, standrad frame, data len = 8, stmp: data buf
stmp[7] = stmp[7] + 1;
if (stmp[7] == 100) {
stmp[7] = 0;
stmp[6] = stmp[6] + 1;
if (stmp[6] == 100) {
stmp[6] = 0;
stmp[5] = stmp[5] + 1;
}
}
CAN.sendMsgBuf(0x00, 0, 8, stmp);
delay(100); // send data per 100ms
Serial.println("CAN BUS sendMsgBuf ok!");
}大部分程式碼是不言自明的。我們將簡要介紹一下實現。
我們使用晶片選擇引腳(在本例中為引腳 10)初始化 CAN。
mcp2515_can CAN(SPI_CS_PIN); // Set CS pin
我們在 Setup 中將波特率設定為 500 kbps,並檢查 CAN 是否已正確啟動。
CAN.begin(CAN_500KBPS))
我們向接收器傳送每幀 8 個位元組。這些位元組最初都為 0。在迴圈中,我們不斷增加最後一個位元組,直到它達到 100,然後將倒數第二個位元組加 1,並再次增加最後一個位元組直到它達到 100,依此類推。如果倒數第二個位元組達到 100,則增加倒數第三個位元組。這使我們可以進行多次迭代。
重要的函式是:
CAN.sendMsgBuf(0x00, 0, 8, stmp);
第一個引數是訊息的 ID(0x00),第二個引數表示我們使用的是基本格式還是擴充套件格式(0 表示基本,1 表示擴充套件),第三個引數是資料的長度(8),第四個引數是資料緩衝區。
這將持續向接收器傳送 8 位元組的資料幀。
下面顯示了 RECEIVE 的程式碼:
#include <SPI.h>
#include "mcp2515_can.h"
const int SPI_CS_PIN = 10;
const int CAN_INT_PIN = 2;
mcp2515_can CAN(SPI_CS_PIN); // Set CS pin
unsigned char flagRecv = 0;
unsigned char len = 0;
unsigned char buf[8];
char str[20];
void setup() {
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// start interrupt
attachInterrupt(digitalPinToInterrupt(CAN_INT_PIN), MCP2515_ISR, FALLING);
// init can bus : baudrate = 500k
while (CAN_OK != CAN.begin(CAN_500KBPS)) {
Serial.println("CAN init fail, retry...");
delay(100);
}
Serial.println("CAN init ok!");
}
void MCP2515_ISR() {
flagRecv = 1;
}
void loop() {
if (flagRecv) {
// check if get data
flagRecv = 0; // clear flag
Serial.println("into loop");
// iterate over all pending messages
// If either the bus is saturated or the MCU is busy,
// both RX buffers may be in use and reading a single
// message does not clear the IRQ conditon.
while (CAN_MSGAVAIL == CAN.checkReceive()) {
// read data, len: data length, buf: data buf
Serial.println("checkReceive");
CAN.readMsgBuf(&len, buf);
// print the data
for (int i = 0; i < len; i++) {
Serial.print(buf[i]); Serial.print("\t");
}
Serial.println();
}
}
}如您所見,初始變數宣告和設定與 SEND 類似。唯一的區別是將中斷附加到數字引腳 2。請記住,每當接收到訊息時,模組的 INT 引腳都會變為低電平。因此,將下降沿中斷附加到連線到模組 INT 引腳的 2 號引腳。
// start interrupt attachInterrupt(digitalPinToInterrupt(CAN_INT_PIN), MCP2515_ISR, FALLING);
在 MCP2515_ISR 函式中,我們只需將**flagRecv**設定為 1,並在迴圈內檢查它。
當**flagRecv**值為 1 時,將檢查 CAN 緩衝區中是否有可用資料(使用**CAN.checkReceive()**)並讀取並列印到序列監視器上。讀取資料的函式是:
CAN.readMsgBuf(&len, buf);
第一個引數指示可用資料的長度,第二個引數是儲存傳入資料的緩衝區。
希望您喜歡這篇文章。建議您瀏覽此庫附帶的其他示例。
資料結構
網路
關係資料庫管理系統
作業系統
Java
iOS
HTML
CSS
Android
Python
C 語言程式設計
C++
C#
MongoDB
MySQL
Javascript
PHP