前言
很多計(jì)算機(jī)從業(yè)者對(duì)CAN總線這種通信協(xié)議一知半解甚至一頭霧水典尾,可能是由于其主要應(yīng)用于汽車工業(yè)領(lǐng)域盼砍,讓人覺(jué)得幾分神秘压储。其實(shí),作為計(jì)算機(jī)從業(yè)者徘禁,面對(duì)各種網(wǎng)絡(luò)協(xié)議,其復(fù)雜程度可以吊打CAN總線協(xié)議髓堪。所以本文作為系列課程的第一課送朱,就是要揭開(kāi)CAN總線的神秘面紗,讓讀者在動(dòng)手寫(xiě)代碼并調(diào)試的過(guò)程中體會(huì)CAN總線協(xié)議的工作原理干旁。
愿景是讓您車上實(shí)操驶沼,看看汽車到底在傳輸什么CAN總線消息。對(duì)了争群,首先要有輛車回怜。當(dāng)然,這是玩笑换薄。在沒(méi)有車的情況下玉雾,可以模擬CAN總線消息的傳輸,同樣可以理解CAN總線的運(yùn)行機(jī)制轻要。
接下來(lái)复旬,我們從開(kāi)發(fā)板、CAN模塊冲泥,軟件開(kāi)發(fā)環(huán)境等各方面分別介紹驹碍,目標(biāo)是第一個(gè)CAN總線消息收發(fā)程序能夠運(yùn)行起來(lái)失都!
使用Arduino DUE開(kāi)發(fā)板
Arduino DUE是一個(gè)基于Atmel SAM3X8E ARM Cortex-M3 CPU的微控制器開(kāi)發(fā)板。同時(shí)幸冻,它也是第一款基于32位的ARM核心微控制器粹庞。有54個(gè)數(shù)字輸入輸出針腳(其中12個(gè)可以被用作PWM輸出),12個(gè)模擬信號(hào)輸入洽损,4個(gè)UART(硬件串口)庞溜。
詳細(xì)配置如下表所示,
微處理器 | AT91SAM3X8E |
---|---|
運(yùn)行電壓 | 3.3V |
輸入電壓 (推薦) | 7-12V |
輸入電壓 (極限) | 6-16V |
數(shù)字I/O針腳 | 54 (其中12 個(gè)PWM) |
模擬信號(hào)輸入 | 12 |
模擬信號(hào)輸出 | 2 (DAC) |
全部I/O線輸出直流電流 | 130 mA |
3.3V針腳輸出直流電流 | 800 mA |
5V針腳輸出直流電流 | 800 mA |
閃存 | 所有可用于用戶程序共512 KB |
SRAM | 96 KB (兩個(gè)bank: 64KB 和 32KB) |
時(shí)鐘速度 | 84 MHz |
注意,不同于其他Arduino家族開(kāi)發(fā)板運(yùn)行電壓是5V碑定,Arduino DUE運(yùn)行電壓是3.3V流码。超過(guò)這個(gè)電壓可能會(huì)損壞開(kāi)發(fā)板。
Arduino DUE開(kāi)發(fā)板實(shí)物圖
Arduino DUE針腳定義
下圖是Arduino DUE針腳定義延刘。注意左下角CANRX0漫试、CANTX0,這引出了本文的重點(diǎn)碘赖,也就是說(shuō)Arduino DUE原生支持CAN總線(關(guān)于CAN總線驾荣,后面會(huì)介紹)控制器模塊。但是貌似只支持一個(gè)(RX和TX分別是CAN模塊的收發(fā)信號(hào)線)CAN控制器模塊普泡?至于為什么需要多于一個(gè)CAN控制器模塊播掷,后面會(huì)揭曉。
Arduino DUE對(duì)CAN控制器模塊的支持
打開(kāi)頂部Atmel SAM3X8E ARM Cortex-M3 CPU鏈接撼班,找到如下Block Diagram歧匈,注意CAN0、CAN1砰嘁。
這說(shuō)明件炉,在微控制器級(jí)別,該MCU支持兩個(gè)CAN控制器模塊矮湘,而且暴露出CANRX0和CANTX0斟冕、CANRX1和CANTX1共4個(gè)針腳。當(dāng)然板祝,MCU支持不等于開(kāi)發(fā)板支持宫静,因?yàn)镸CU針腳不一定被開(kāi)發(fā)板暴露。
很慶幸券时,經(jīng)過(guò)查閱相關(guān)資料孤里,確認(rèn)MCU里4個(gè)CAN針腳全部被Arduino DUE暴露,只是Arduino DUE官方文檔沒(méi)有明說(shuō)CANRX1和CANTX1橘洞。實(shí)際CAN0和CAN1如下圖所示捌袜,
好了,現(xiàn)在從硬件角度來(lái)說(shuō)炸枣,Arduino DUE內(nèi)置了兩個(gè)CAN控制器模塊虏等。注意弄唧,這里說(shuō)的時(shí)控制器模塊,但就像計(jì)算機(jī)理有USB控制器模塊霍衫,不等于有USb外設(shè)候引。要想實(shí)現(xiàn)諸如鍵盤(pán)、鼠標(biāo)等USB外設(shè)的通信敦跌,還需要有USB設(shè)備澄干。類似地,如果想實(shí)現(xiàn)CAN通信還需要有個(gè)CAN總線收發(fā)器模塊柠傍。不過(guò)麸俘,為了不至于制造困惑,接下來(lái)我們來(lái)討論什么是CAN總線惧笛,然后再回來(lái)討論CAN總線收發(fā)器模塊从媚。
CAN總線簡(jiǎn)介
CAN是Controller Area Network的簡(jiǎn)稱,中文翻譯為控制器局域網(wǎng)總線患整。CAN總線是一種用于實(shí)時(shí)串行通信協(xié)議總線拜效,它可以使用兩根交纏在一起的雙絞線來(lái)傳輸信號(hào)。CAN協(xié)議用于汽車中各種不同元件之間通信并级,以取代昂貴笨重的配電線束拂檩。該協(xié)議的健壯性使其用途延伸到其他自動(dòng)化和工業(yè)領(lǐng)域。
簡(jiǎn)單來(lái)說(shuō)嘲碧,
假設(shè)(注意用詞)有兩根線,均長(zhǎng)約40米(和大刀無(wú)關(guān)父阻,為什么40米愈涩,后面課程會(huì)說(shuō)),一根命名為CAN High加矛,一個(gè)命名為CAN Low履婉,對(duì)應(yīng)隱形電平和顯性電平(這個(gè)后面課程會(huì)詳細(xì)介紹)。
這兩根線為了抵消彼此因?yàn)楦哳l電流(電平同時(shí)變化)產(chǎn)生的電磁干擾斟览,即共模消除干擾毁腿,采用雙絞線的形式兩者絞纏(類似于網(wǎng)線里面?zhèn)z倆絞纏)在一起。
Arduino DUE的CAN0總線控制器連接到CAN0總線收發(fā)器苛茂,收發(fā)器有兩根線已烤,一根是CAN High,一根是CAN Low妓羊。這兩根線的CAN High和CAN Low分別連接到40米雙絞線的CAN High和CAN Low胯究。
CAN Hig電壓介于2.5V ~ 3.5V;CAN Low電壓介于1.5V ~ 2.5V躁绸,從而High和Low形成差分信號(hào)裕循。注意臣嚣,兩根線不可錯(cuò)接。
同樣的剥哑,Arduino DUE的CAN1總線控制器連接到CAN1總線收發(fā)器硅则,收發(fā)器有兩根線,一根是CAN High株婴,一根是CAN Low抢埋。這兩根線的CAN High和CAN Low分別連接到40米雙絞線的CAN High和CAN Low。注意督暂,不可錯(cuò)接揪垄。
這樣,CAN0和CAN1都連接到40米的雙絞線逻翁,兩者通過(guò)這種方式建立了連接饥努。
這就像居民樓里入戶的220V交流電電纜,每戶都是2根線八回,零線和火線酷愧,各種用電設(shè)備都接在這連根線上。理論上缠诅,所有變電站下方的220V用電設(shè)備溶浴,其兩根火線或兩根零線其電位一致。
把居民入戶220V交流電纜和CAN總線相比不同之處在于管引,CAN總線任意兩個(gè)節(jié)點(diǎn)直接都可以發(fā)送消息士败。
另外,有些人可能疑惑褥伴,為什么一直在糾結(jié)是不是兩個(gè)CAN模塊呢谅将?難道一個(gè)模塊不行嗎?CAN總線協(xié)議規(guī)定重慢,一個(gè)CAN幀必須得到至少一個(gè)除了本節(jié)點(diǎn)的其他節(jié)點(diǎn)的確認(rèn)(Acknowledgement)饥臂,否則視為出錯(cuò)。所以似踱,如果有兩個(gè)或者兩個(gè)以上的CAN控制器即收發(fā)器模塊便于測(cè)試隅熙,尤其繼集成在個(gè)開(kāi)發(fā)板上。這也是為什么我們?nèi)腴T(mén)的第一課選擇了Arduino DUE核芽。
關(guān)于CAN總線線介紹到這里囚戚,詳細(xì)介紹請(qǐng)關(guān)注后續(xù)課程。
CAN收發(fā)器和控制器
CAN收發(fā)器和控制器分別對(duì)應(yīng)CAN總線的物理層和數(shù)據(jù)鏈路層狞洋。
CAN收發(fā)器
CAN收發(fā)器是將差分信號(hào)轉(zhuǎn)換成TTL (Transistor Transistor Logic)電平信號(hào)弯淘,或者將TTL電平信號(hào)轉(zhuǎn)成差分信號(hào)。
- CAH High - CAN Low = 2.5V - 2.5V = 0.0V吉懊,顯性電平庐橙。
- CAH High - CAN Low = 3.5V - 1.5V = 2.0V假勿,隱形電平。
CAN控制器
CAN控制器實(shí)現(xiàn)CAN總線的協(xié)議棧以及數(shù)據(jù)鏈路層态鳖,
- 用于生成CAN數(shù)據(jù)幀并以二進(jìn)制的方式發(fā)送转培。并在此過(guò)程中進(jìn)行位填充、添加CRC校驗(yàn)浆竭、應(yīng)答檢測(cè)等浸须。
-
用于將接收到的二進(jìn)制進(jìn)行解析,并在此過(guò)程中進(jìn)行收發(fā)比對(duì)邦泄、去位填充删窒、執(zhí)行CRC校驗(yàn)等操作。
圖10 CAN控制器工作原理
連接CAN收發(fā)器到Arduino DUE開(kāi)發(fā)板
準(zhǔn)備好了知識(shí)顺囊,現(xiàn)在可以開(kāi)始上手了肌索。首先硬件準(zhǔn)備。
設(shè)備準(zhǔn)備
按照下表來(lái)準(zhǔn)備設(shè)備特碳。
設(shè)備 | 型號(hào) | 數(shù)量 | 是否必選 |
---|---|---|---|
開(kāi)發(fā)板 | Arduino DUE | 1 | 是 |
CAN收發(fā)器 | 任何集成SN65HVD230芯片收發(fā)器诚亚。比如微雪、丟石頭等品牌午乓。 | 2 | 是 |
面包板 | 任何5排以上的面包板 | 1 | 否 |
跳線 | 任何公-公(使用面包板)站宗、或者公-母(不使用面包板)針跳線。 | 若干 | 是 |
設(shè)備連線
按照下表來(lái)連接設(shè)備益愈。
開(kāi)發(fā)板 | CAN收發(fā)器0 | CAN收發(fā)器1 |
---|---|---|
CAN High | CAN High | |
CAN Low | CAN Low | |
3.3V | 3.3V | 3.3V |
GND | GND | GND |
CANRX | RX | |
CANTX | TX | |
DAC0 | RX | |
53 | TX |
參考圖片如下梢灭,
檢查無(wú)誤后,插上MicroUSB腕唧,建議使用Native USB Port(離電源插口遠(yuǎn)的插口)上電或辖。
留一個(gè)疑問(wèn)?雙絞線在哪枣接?
使用Arduino IDE開(kāi)發(fā)
請(qǐng)自行安裝Arduino IDE。安裝完成后缺谴,運(yùn)行Arduino IDE但惶,打開(kāi)BOARDS MANAGER,搜關(guān)鍵字“Due”湿蛔,然后點(diǎn)“INSTALL”安裝膀曾。
安裝完開(kāi)發(fā)板,在安裝Library阳啥。打開(kāi)LIBRARY MANAGER添谊,搜索“can_due”,滾動(dòng)到最下方察迟,點(diǎn)“INSTALL”安裝斩狱,
打開(kāi)”File“耳高,然后選擇‘Save“,可以命名為“CANTransceiver”所踊,此時(shí)泌枪,項(xiàng)目如下所示,
接下來(lái)我們通過(guò)代碼在兩個(gè)收發(fā)器之間傳送消息秕岛。
#include <due_can.h>
// Leave the macro defined in the case you use the Native USB Port.
// Comment out if you use then Programming Port.
#define Serial SerialUSB
#define MAXIMUM_CAN_FRAME_DATA_LENGTH 8
#define MAXIMUM_FRAMES_NUMBER 5000
void setup()
{
Serial.begin(115200);
while (!Serial);
if (Can0.begin(CAN_BPS_1000K) && Can1.begin(CAN_BPS_1000K))
Serial.println("CAN0 and CAN1 initialized.");
else
Serial.println("Failed to initialize the CAN devices.");
echo();
}
void loop()
{
// put your main code here, to run repeatedly:
}
static void echo(void)
{
CAN_FRAME frame1, frame2, incoming;
uint32_t id = random(0x00000001, 0x00FFFDFF);
Serial.println(id, HEX);
frame1.id = id;
frame1.length = MAXIMUM_CAN_FRAME_DATA_LENGTH;
frame1.data.low = 0x0001;
frame1.data.high = 0x1000;
frame1.extended = 1;
uint16_t addition = 0x0200;
frame2.id = id + addition;
frame2.length = MAXIMUM_CAN_FRAME_DATA_LENGTH;
frame2.data.low = 0x00000002;
frame2.data.high = 0x20000000;
frame2.extended = 1;
Can0.watchFor(id);
Can1.watchFor(id + addition);
uint32_t sentFrames, receivedFrames;
uint16_t counter = 0;
Can0.sendFrame(frame2);
sentFrames ++;
while (1)
{
if (Can0.available() > 0)
{
Can0.read(incoming);
Can0.sendFrame(frame2);
delayMicroseconds(100);
receivedFrames ++;
sentFrames ++;
counter++;
}
if (Can1.available() > 0)
{
Can1.read(incoming);
Can1.sendFrame(frame1);
delayMicroseconds(100);
receivedFrames ++;
sentFrames ++;
counter++;
}
if (counter > MAXIMUM_FRAMES_NUMBER)
{
counter = 0;
Serial.print("Sent: ");
Serial.print(sentFrames);
Serial.print(", Received: ");
Serial.print(receivedFrames);
Serial.println(".");
}
}
}
代碼簡(jiǎn)單解釋如下碌燕,
- 首先在void setup()方法里初始化Can0和Can1。
- 在echo()方法里继薛,首先定義了三個(gè)數(shù)據(jù)幀變量修壕,frame1、frame2以及傳入的incoming幀遏考。查看CAN_FRAME慈鸠,發(fā)現(xiàn)這是一個(gè)結(jié)構(gòu)體,容易轉(zhuǎn)換成字節(jié)流诈皿。
- 接著對(duì)frame1和frame2進(jìn)行賦值林束,分別是id、數(shù)據(jù)字段稽亏、長(zhǎng)度以及是否是擴(kuò)展幀壶冒,這些都是必選字段。
- watchFor是通過(guò)id過(guò)濾截歉,器用過(guò)濾會(huì)忽然那些非符合條件的幀胖腾,畢竟像汽車這種適合CAN總線使用的場(chǎng)景,可能有多達(dá)數(shù)百個(gè)CAN設(shè)備瘪松,而且一個(gè)設(shè)備一秒鐘發(fā)送很多幀都有可能咸作。
所以,一個(gè)過(guò)濾功能非常有必要宵睦。
上傳燒錄程序
上傳燒錄记罚,運(yùn)行程序,打開(kāi)Serial Monitor可以看到數(shù)據(jù)幀在兩個(gè)CAN收發(fā)器之間傳送壳嚎。
小結(jié)
本文作為CAN總線第一課桐智,弱化了概念,簡(jiǎn)化了程序代碼烟馅,目的就是為讓您上手说庭。