STM32學習心得三十二:CAN通信基礎知識、原理、配置及實驗

記錄一下,方便以後翻閱~
主要內容:
1) CAN通信基礎知識;
2) STM32 CAN控制器簡介;
3) 相關實驗代碼解讀。
參考資料:《STM32中文參考手冊_V10》第22章——控制器局域網(bxCAN)
實驗功能:CAN實驗需要兩個開發板,系統啓動後,主開發板可以讓STM32F1的CAN工作在環回模式/普通模式下,通過KEY_UP按鍵切換模式。默認是環回模式,在環回模式下,按下KEY0,則可以在串口調試助手上面看到自發自收的消息。如果是普通模式,按下次開發板的SW4,可以在主開發板對應的串口調試助手上看到收到的信息。
硬件連接:
實驗需要兩個開發版,將它們的CAN_H相連,CAN_L相連。具體的,主開發板選擇PA11和PA12作爲CAN_RX和CAN_TX,次開發板選擇PB8和PB9作爲CAN_RX和CAN_TX。
1. CAN基礎知識
1.1 什麼是CAN(Controller Area Network)
CAN是ISO國際標準化的串行通信協議。由德國電氣商博世公司在1986 年率先提出。此後,CAN 通過ISO11898 及ISO11519 進行了標準化。現在在歐洲已是汽車網絡的標準協議。
CAN協議經過ISO標準化後有兩個標準:ISO11898標準和ISO11519-2標準。其中ISO11898是針對通信速率爲125Kbps~1Mbps的高速通信標準,而ISO11519-2是針對通信速率爲125Kbps以下的低速通信標準。
CAN具有很高的可靠性,廣泛應用於:汽車電子、工業自動化、船舶、醫療設備、工業設備等方面。
1.2 CAN的特點
1) 多主控制。總線空閒時,所有單元都可發送消息,而兩個以上的單元同時開始發送消息時,根據標識符(ID,非目的地址)決定優先級。兩個以上的單元同時開始發送消息時,對各消息ID 的每個位進行逐個仲裁比較。仲裁獲勝(優先級最高)的單元可繼續發送消息,仲裁失利的單元則立刻停止發送而進行接收工作;
2) 系統柔軟性。連接總線的單元,沒有類似“地址”的信息,因此,在總線上添加單元時,已連接的其他單元的軟硬件和應用層都不需要做改變;
3) 速度快,距離遠。最高1Mbps(距離<40M),最遠可達10KM(速率<5Kbps);
4) 具有錯誤檢測、錯誤通知和錯誤恢復功能。所有單元都可以檢測錯誤(錯誤檢測功能),檢測出錯誤的單元會立即同時通知其他所有單元(錯誤通知功能),正在發送消息的單元一旦檢測出錯誤,會強制結束當前的發送。強制結束髮送的單元會不斷反覆地重新發送此消息直到成功發送爲止(錯誤恢復功能);
5) 故障封閉功能。CAN 可以判斷出錯誤的類型是總線上暫時的數據錯誤(如外部噪聲等)還是持續的數據錯誤(如單元內部故障、驅動器故障、斷線等)。由此功能,當總線上發生持續數據錯誤時,可將引起此故障的單元從總線上隔離出去;
6) 連接節點多。CAN 總線是可同時連接多個單元的總線。可連接的單元總數理論上是沒有限制的。但實際上可連接的單元數受總線上的時間延遲及電氣負載的限制。降低通信速度,可連接的單元數增加;提高通信速度,則可連接的單元數減少。
正是因爲CAN協議的這些特點,使得CAN特別適合工業過程監控設備的互連,因此,越來越受到工業界的重視,並已公認爲最有前途的現場總線之一。
1.3 ISO11898標準物理層特徵
物理層特徵如圖所示:
在這裏插入圖片描述
上圖中,CAN 控制器根據CAN_L和CAN_H上的電位差來判斷總線電平。總線電平分爲顯性電平(CAN_H約3.5V,CAN_L約1.5V)和隱性電平(CAN_H和CAN_L都爲2.5V時)。發送方通過使總線電平發生變化,將消息發送給接收方:
1) 顯性電平對應邏輯:0,CAN_H和CAN_L之差爲2V左右;
2) 隱性電平對應邏輯:1,CAN_H和CAN_L之差爲0V。
邏輯電平值0和1對應STM32芯片上的I/O口的電平值。
邏輯電平值對應CAN_H和CAN_L,中間會經過CAN收發芯片TJA1050,連接如下圖所示:
在這裏插入圖片描述
顯性電平具有優先權,只要有一個單元輸出顯性電平,總線上即爲顯性電平。而隱形電平則具有包容的意味,只有所有的單元都輸出隱性電平,總線上才爲隱性電平(顯性電平比隱性電平更強)。另外,在CAN總線的起止端都有一個120Ω的終端電阻,來做阻抗匹配,以減少回波反射。
1.4 幀種類型
1.4.1 5種類型
1) 數據幀(最重要):用於發送單元向接收單元傳輸數據的幀;
2) 遙控幀:用於接收單元向具有相同ID的發送單元請求數據的幀;
3) 錯誤幀:用於當檢測出錯誤時向其他單元通知錯誤的幀;
4) 過載幀:用於接收單元通知其尚未做好接收準備的幀;
5) 間隔幀:用於將數據幀及遙控幀與前面的幀分離開來的幀。
其中,數據幀和遙控幀有標準格式和擴展格式兩種格式:
標準格式有11個位的標識符(ID),擴展格式有29 個位的ID 。
1.4.2 數據幀介紹
1.4.2.1 數據幀由7段組成:
1) 幀起始(SOF):表示數據幀開始的段;
2) 仲裁段:表示該幀優先級的段;
3) 控制段:表示數據的字節數及保留位的段;
4) 數據段:數據的內容,一幀可發送0~8個字節的數據;
5) CRC段:檢查幀的傳輸錯誤的段;
6) ACK段:表示確認正常接收的段;
7) 幀結束(EOF):表示數據幀結束的段。
1.4.4.2 數據幀的構成:
在這裏插入圖片描述
上圖中,D表示顯性電平,R表示隱形電平(下同)。
1.4.2.3 數據幀解析
1) 幀起始:標準幀和擴展幀都是由1個位的顯性電平,值爲0表示幀起始;
2) 仲裁段:表示數據優先級的段,標準幀和擴展幀格式在本段有所區別,如圖所示:
在這裏插入圖片描述
ID:高位在前,低位在後。基本ID,禁止高7位都爲隱性,即不能:ID=1111111XXXX
RTR:遠程請求位。0,數據幀;1, 遠程幀;
SRR:替代遠程請求位(RTR)。設置爲1(隱性電平);
IDE:標識符選擇位。0,標準標識符;1,擴展標識符。
標準格式裏也有IDE,只是放在了控制段裏。
3) 控制段:由6個位構成,表示數據段的字節數。標準幀和擴展幀的控制段稍有不同,如圖所示:
在這裏插入圖片描述
r0,r1:保留位,必須以顯現電平發送,但是接收可以是隱性電平;
DLC:數據長度碼,0~8,表示發送/接收的數據長度(字節);
IDE:標識符選擇位。0,標準標識符;1,擴展標識符。
4) 數據段:該段可包含0~8個字節的數據,從最高位(MSB)開始輸出。標準幀和擴展幀在這個段的格式完全一樣:
在這裏插入圖片描述
5) CRC段:該段用於檢查幀傳輸錯誤。由15個位的CRC順序和1個位的CRC界定符(用於分隔的位)組成,標準幀和擴展幀在這個段的格式也是相同的:
在這裏插入圖片描述
CRC的值計算範圍包括:幀起始、仲裁段、控制段、數據段。
接收方以同樣的算法計算 CRC 值並進行比較,不一致時會通報錯誤。
6) ACK段:此段用來確認是否正常接收。由ACK槽(ACK Slot)和ACK界定符2個位組成。標準幀和擴展幀在這個段的格式也是相同的:
在這裏插入圖片描述
發送單元ACK段:發送2個隱性位。
接收單元ACK段:接收到正確消息的單元,在ACK槽發送顯性位,通知發送單元,正常接收結束。稱之爲發送ACK/返回ACK。
注意:發送 ACK 的是既不處於總線關閉態也不處於休眠態的所有接收單元中,接收到正常消息的單元(發送單元不發送ACK)。
正常消息是指:不含填充錯誤、格式錯誤、CRC 錯誤的消息。
7)幀結束:由7個位的隱性位組成(即7個1)。標準幀和擴展幀在這個段格式完全一樣。
1.5 總線仲裁
同時多個單元發送數據時,總線仲裁過程:
在這裏插入圖片描述
規律:1,總線空閒時,最先發送的單元獲得發送優先權,一但發送,其他單元無法搶佔。2,如果有多個單元同時發送,則連續輸出顯性電平多的單元,具有較高優先級。
從ID開始比較,如果ID相同,還可能會比較RTR和SRR等位。
1.6 位時序
1.6.1 位速率:由發送單元在非同步的情況下發送的每秒鐘的位數稱爲位速率。一個位一般可以分爲如下四段:
1) 同步段(SS);
2) 傳播時間段(PTS);
3) 相位緩衝段1(PBS1);
4) 相位緩衝段2(PBS2)。
這些段又由可稱爲 Time Quantum(以下稱爲Tq)的最小時間單位構成。1 位分爲4 個段,每個段又由若干個Tq 構成,這稱爲位時序。
位時間=1/波特率,因此,知道位時間,我們就可以知道波特率。
1 位由多少個Tq 構成、每個段又由多少個Tq 構成等,可以任意設定位時序。通過設定位時序,多個單元可同時採樣,也可任意設定採樣點。
1.6.2 位時序各段的作用和 Tq 數如下表:
在這裏插入圖片描述
備註:在STM32上,傳播時間段和相位緩衝段的值是加在一起的,則對應Tq數爲2~16。
1.6.2 一個位的構成:
在這裏插入圖片描述
圖中採樣時間加大或減少量的最大值就是SJW。
舉例:假設設置1M的波特率,則Tq設爲0.1μs。
2. STM32 CAN控制器簡介
2.1 STM32自帶了基本擴展CAN外設,又稱bxCAN。bxCAN的特點如下:
2.1.1 支持CAN協議2.0A和2.0B主動模式;
2.1.2 波特率最高達1Mbps;
2.1.3 支持時間觸發通信;
2.1.4 具有3個發送郵箱
2.1.5 具有3級深度的2個接收FIFO
2.1.6 可變的篩選器組(也稱過濾器組,普通的F1有14個,F1互聯型和F4有28個)。
2.2 模式
2.2.1 工作模式(通過CAN_MCR寄存器控制INRQ和SLEEP)
2.2.1.1 初始化模式(INRQ=1,SLEEP=0);
2.2.1.2 正常模式(INRQ=0,SLEEP=0);
2.2.1.3 睡眠模式(SLEEP=1)。
2.2.2 測試模式(通過CAN_BTR寄存器控制LBKM和SILM)
2.2.2.1 靜默模式( LBKM=0,SILM=1 ):
接收總線上CANRX的數據,不發送數據至CANTX上(一直髮1,即隱形電平);
在這裏插入圖片描述
2.2.2.2 環回模式( LBKM=1,SILM=0 ):
發送數據到總線的CANTX上,且通過環路傳至接收端,但不接受CANRX的數據;
在這裏插入圖片描述
2.2.2.3 環回靜默模式(LBKM=1,SILM=1):自己給自己發數據,測試用。
在這裏插入圖片描述
2.2.3 調試模式
2.3 bxCAN框圖
在這裏插入圖片描述
2.3.1 普通F1系列只有1個主CAN,互聯型F1和F4還有一個從CAN,即兩個CAN;
2.3.2 兩個CAN分別擁有自己的發送郵箱和接收FIFO,但是他們共用28個篩選器。
2.4 標識符刪選器
2.4.1 CAN的標識符不表示目的地址而是表示發送優先級。接收節點根據標識符的值,來決定是否接收對應消息;
2.4.2 STM32 CAN控制器,提供了28個可配置的篩選器組(F1僅互聯型纔有28個,其他的只有14個),可降低CPU處理CAN通信的開銷;
2.4.3 STM32 CAN控制器每個篩選器組由2個32位寄存器組成(CAN_FxR1和CAN_FxR2,x=0~27)。根據位寬不同,每個篩選器組可提供:
1個32位篩選器,包括:STDID[10:0]、EXTID[17:0]、IDE和RTR位;
或者2個16位篩選器,包括:STDID[10:0]、IDE、RTR和EXTID[17:15]位。
2.4.4 篩選器可配置爲:屏蔽位模式和標識符列表模式。在屏蔽位模式下,標識符寄存器和屏蔽寄存器一起,指定報文標識符的任何一位,應該按照“必須匹配”或“不用關心”處理。而在標識符列表模式下,屏蔽寄存器也被當作標識符寄存器用。因此,不是採用一個標識符加一個屏蔽位的方式,而是使用2個標識符寄存器。接收報文標識符的每一位都必須跟篩選器標識符相同。
通過CAN_FM1R和CAN_FS1R寄存器可配置篩選器的位寬和模式:
在這裏插入圖片描述
篩選器的用途:
1) 爲了過濾出一組標識符,應該設置篩選器組工作在屏蔽位模式(標識符掩碼);
2) 爲了過濾出一個標識符,應該設置過濾器組工作在標識符列表模式;
3) 應用程序不用的篩選器組,應該保持在禁用狀態(通過CAN_FA1R設置);
4) 篩選器組中的每個篩選器,都被編號爲(即:篩選器編號)從0開始,到某個最大數值-取決於篩選器組的模式和位寬的設置;
5) 通過CAN_FFA1R寄存器的設置,可以將篩選器組關聯到FIFO0/FIFO1。
舉例:
設置篩選器組0工作在:1個32位篩選器-標識符屏蔽模式,然後設置CAN_F0R1=0XFFFF0000,CAN_F0R2=0XFF00FF00。
則CAN_F0R1的值是期望收到的ID,即(STID+EXTID+IDE+RTR)最好是:0XFFFF0000;
而CAN_F0R2=0XFF00FF00是我們需要關心的ID,表示收到的映像,其位[31:24]和位[15:8]這16個位的必須和CAN_F0R1中對應的位一模一樣,而另外的16個位則不關心,即收到的映像必須是0XFFxx00xx,纔算是正確的(x表示不關心)。
2.5 STM32 CAN的發送流程
CAN的發送流程爲:
程序選擇1個空置的郵箱(TME=1)->設置標識符(ID),數據長度和發送數據à設置CAN_TIxR的TXRQ位爲1,請求發送->郵箱掛號(等待成爲最高優先級)->預定發送(等待總線空閒)->發送->郵箱空置。
在這裏插入圖片描述
APRQ=1意思是強制退出;若設置NART重複發送狀態,則發送失敗會回到預定狀態,嘗試再次發送,待達到所設的最大次數仍爲成功,則也會發送失敗。
2.6 接收流程
CAN接收流程爲:
FIFO空->收到有效報文->掛號_1(存入FIFO的一個郵箱,這個由硬件控制,我們不需要理會)->收到有效報文->掛號_2->收到有效報文->掛號_3->收到有效報文->溢出。
CAN收到的有效報文,存儲在3級郵箱深度的FIFO中。FIFO接收到的報文數,我們可以通過查詢CAN_RFxR的FMP寄存器來得到,只要FMP不爲0,我們就可以從FIFO讀出收到的報文。
在這裏插入圖片描述
報文FIFO具有鎖定功能(由CAN_MCR,RFLM位控制),鎖定後,新數據將丟棄,不鎖定則新數據將替代老數據。
2.7 位時序
STM32的CAN位時序,如下圖所示:
在這裏插入圖片描述
提示:STM32的CAN將傳播時間段(PTS)和相位緩衝時間段1(PBS1)合併成時間段1(BS1);
STM32F103,設TS1=8、TS2=7、BRP=3,波特率=36000/[(9+8+1)*4]=500Kbps;
STM32F407,設TS1=6、TS2=5、BRP=5,波特率=42000/[(7+6+1)*6]=500Kbps。
2.8 寄存器簡介
2.8.1 CAN主控制寄存器(CAN_MCR)
在這裏插入圖片描述
INRQ位用來控制初始化請求:
設置INRQ=0,可使CAN從初始化模式進入正常工作模式;
設置INRQ=1,可使CAN從正常工作模式進入初始化模式 。
CAN初始化時,先設置INRQ=1,進入初始化模式,進行初始化(尤其是CAN_BTR的設置,該寄存器,必須在CAN正常工作之前設置),之後再設置INRQ=0,進入正常工作模式。
2.8.2 CAN位時序寄存器(CAN_BTR)
在這裏插入圖片描述
備註:tCAN就是tq。
2.8.3 CAN接收FIFO寄存器(CAN_RF0R/CAN_RF1R)
在這裏插入圖片描述
CAN_RF0R用於FIFO0控制(寄存器描述如上圖);
CAN_RF1R用於FIFO1控制。
2.8.4 CAN發送郵箱標識符寄存器(CAN_TIxR)(x=0~2)

在這裏插入圖片描述
2.8.5 CAN發送郵箱數據長度和時間戳寄存器 (CAN_TDTxR) (x=0~2)
在這裏插入圖片描述
TIME[15:0]和TGT位,用於時間戳相關設置,可參考《STM32中文參考手冊》相關章節。
2.8.6 CAN發送郵箱數據寄存器(CAN_TDLxR/CAN_TDHxR)(x=0~2)
在這裏插入圖片描述
圖爲CAN_TDLxR寄存器的描述,用於存儲低4個字節的數據。CAN_TDHxR寄存器與之類似,用於存儲高4個字節的數據。要發送的數據就是存儲在這兩個寄存器。
2.8.7 CAN接收FIFO郵箱標識符寄存器(CAN_RIxR)(x=0/1)
在這裏插入圖片描述
2.8.8 CAN接收FIFO郵箱數據長度和時間戳寄存器(CAN_RDTxR)(x=0/1)
在這裏插入圖片描述
TIME[15:0],用於時間戳相關設置,FMI用於存儲篩選器匹配索引,可參考《STM32中文參考手冊》相關章節。
2.8.9 CAN接收FIFO郵箱郵箱數據寄存器(CAN_RDLxR/CAN_RDHxR)(x=0/1)
在這裏插入圖片描述
圖爲CAN_RDLxR寄存器的描述,用於存儲低4個字節的數據。CAN_RDHxR寄存器與之類似,用於存儲高4個字節的數據。接收到的數據就存儲在這兩個寄存器。
2.8.10 CAN篩選器模式寄存器(CAN_FM1R)
在這裏插入圖片描述
該寄存器設置篩選器的工作模式,必須在CAN_FMR寄存器FINIT=1時配置,對於STM32F103ZET6,只有[13:0]位有效,對於互聯性STM32F1或者STM32F407,則全部有效。
2.8.11 CAN篩選器尺度寄存器(CAN_FS1R)
在這裏插入圖片描述
該寄存器用於設置篩選器的位寬,必須在CAN_FMR寄存器FINIT=1時配置,對於STM32F103ZET6,只有[13:0]位有效,對於互聯性STM32F1或者STM32F407,則全部有效。
2.8.12 CAN篩選器FIFO關聯寄存器(CAN_FFA1R)
在這裏插入圖片描述
該寄存器設置報文通過篩選器組之後,被存入的FIFO,如果對應位爲0,則存放到FIFO0;如果爲1,則存放到FIFO1。該寄存器也只能在過濾器處於初始化模式( CAN_FMR 寄存器的FINIT=1 )下配置。
2.8.13 CAN篩選器激活寄存器(CAN_FA1R)
在這裏插入圖片描述
該寄存器用於設置篩選器組的開啓和關閉。對對應位置1,即開啓對應的篩選器組;置0則關閉該篩選器組。
2.8.14 CAN篩選器組i寄存器x(CAN_FiRx)(i=0~27,x=1/2)
在這裏插入圖片描述
提示:STM32F103ZET6只有0~13個i。
每個篩選器組的CAN_FiRx都由2個32位寄存器構成,即:CAN_FiR1和CAN_FiR2。根據過濾器位寬和模式的不同設置,這兩個寄存器的功能也不盡相同。
2.9 初始化流程
2.9.1 配置相關引腳的複用功能,使能CAN時鐘:
要用CAN,先要使能CAN的時鐘,CAN的時鐘通過APB1ENR的第25位來設置;
其次要設置CAN的相關引腳爲複用輸出:設置PA11爲上拉輸入(CAN_RX引腳),PA12爲複用輸出(CAN_TX引腳);
並使能PA口的時鐘。
2.9.2 設置CAN工作模式及波特率等:
通過先設置CAN_MCR寄存器的INRQ位,讓CAN進入初始化模式,然後設置CAN_MCR的其他相關控制位。再通過CAN_BTR設置波特率和工作模式(正常模式/環回模式)等信息。 最後設置INRQ爲0,退出初始化模式。
2.9.3 設置濾波器:
本例程將使用篩選器組0,並工作在32位標識符屏蔽位模式下。先設置CAN_FMR的FINIT位,進入初始化模式,然後設置篩選器組0的工作模式以及標識符ID和屏蔽位。最後激活篩選器,並退出初始化模式。
3. 相關實驗代碼解讀
這裏僅給出主開發板的實驗代碼,次開發板的實驗代碼跟主開發板的代碼基本差不多。
3.1 can.h頭文件代碼解讀

#ifndef __CAN_H
#define __CAN_H  
#include "sys.h"       
//CAN接收RX0中斷使能//
#define CAN_RX0_INT_ENABLE 0                               //0,不使能;1,使能,默認不使能//                                      
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode); //CAN初始化函數// 
u8 Can_Send_Msg(u8* msg,u8 len);                           //發送數據函數//
u8 Can_Receive_Msg(u8 *buf);                               //接收數據函數//
#endif

3.2 can.c文件代碼解讀

#include "can.h"
#include "led.h"
#include "delay.h"
#include "usart.h"
//CAN初始化函數//
//tsjw:重新同步跳躍時間單元.範圍:CAN_SJW_1tq~ CAN_SJW_4tq//
//tbs2:時間段2的時間單元.   範圍:CAN_BS2_1tq~CAN_BS2_8tq//
//tbs1:時間段1的時間單元.   範圍:CAN_BS1_1tq ~CAN_BS1_16tq//
//brp :波特率分頻器.範圍:1~1024;  tq=(brp)*tpclk1//
//波特率=Fpclk1/((tbs1+1+tbs2+1+1)*brp)//
//mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,迴環模式//
//Fpclk1的時鐘在初始化的時候設置爲36M,如果設置CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack)//
//則波特率爲:36M/((8+9+1)*4)=500Kbps//
//返回值:0,初始化OK,其他,初始化失敗//
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{ 
 GPIO_InitTypeDef       GPIO_InitStructure; 
 CAN_InitTypeDef         CAN_InitStructure;
 CAN_FilterInitTypeDef   CAN_FilterInitStructure;
 #if CAN_RX0_INT_ENABLE 
  NVIC_InitTypeDef      NVIC_InitStructure;
 #endif
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   //使能PORTA時鐘//                                
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);    //使能CAN1時鐘// 
 //配置GPIOA的引腳12,對應CAN_TX//
 GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;       //複用推輓輸出//
 GPIO_Init(GPIOA, &GPIO_InitStructure);   
 //配置GPIOA的引腳11,對應CAN_RX//
 GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;
 GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;         //上拉輸入//
 GPIO_Init(GPIOA, &GPIO_InitStructure);   
 //CAN單元設置,主要針對CAN_MCR和CAN_BTR寄存器//
 CAN_InitStructure.CAN_TTCM=DISABLE;  
 //針對CAN_MCR主控制寄存器,位[7],TTCM時間觸發通信模式,設0,禁止時間觸發通信模式// 
 CAN_InitStructure.CAN_ABOM=DISABLE;  //軟件自動離線管理//  
 //針對CAN_MCR主控制寄存器,位[6],ABOM自動離線管理,設0,軟件對INRQ位置1隨後清0//
 //一旦硬件檢測到128次11位連續的隱性位,則退出離線狀態// 
 CAN_InitStructure.CAN_AWUM=DISABLE;  
 //針對CAN_MCR主控制寄存器,位[5],AWUM自動喚醒模式,設0,軟件喚醒(清除SLEEP位)//
 CAN_InitStructure.CAN_NART=ENABLE; 
 //針對CAN_MCR主控制寄存器,位[4],NART禁止報文自動重傳,設1,CAN報文只被發送1次//
 CAN_InitStructure.CAN_RFLM=DISABLE;  
 //針對CAN_MCR主控制寄存器,位[3],RFLM接收FIFO鎖定模式,設0//
 //在接收溢出時FIFO未被鎖定,當接收FIFO的報文未被讀出,下一個收到的報文會覆蓋原有的報文// 
 CAN_InitStructure.CAN_TXFP=DISABLE;  
 //針對CAN_MCR主控制寄存器,位[2],TXFP發送FIFO優先級,設0,優先級由報文的標識符來決定//
 CAN_InitStructure.CAN_Mode= mode;    
 //針對CAN_BTR位時序寄存器,位[30],LBKM迴環模式,設0時禁止迴環模式,設1時允許迴環模式//
 //設置波特率,以下4行代碼都是針對CAN_BTR位時序寄存器//
 CAN_InitStructure.CAN_SJW=tsjw;     
 //位[24~25],SJW重新同步跳躍寬度//
 CAN_InitStructure.CAN_BS1=tbs1;    
 //位[16~19],TS1時間段1//
 CAN_InitStructure.CAN_BS2=tbs2;    
 //位[20~22],TS2時間段2//
 CAN_InitStructure.CAN_Prescaler=brp; //位[0~9],BRP波特率分頻器,分頻係數(Fdiv)爲brp+1// 
 CAN_Init(CAN1, &CAN_InitStructure);        
 //CAN過濾器配置//
 CAN_FilterInitStructure.CAN_FilterNumber=0;                       //過濾器0//
 CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;  
 //針對CAN_FM1R過濾器模式寄存器,設0,過濾器組x的2個32位寄存器工作在標識符屏蔽位模式//
 CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;    //32位寬// 
 CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;                  //32位ID//
 CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
 CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;              //32位MASK//
 CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
 CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//過濾器0關聯到FIFO0//
 CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;              //激活過濾器0//
 CAN_FilterInit(&CAN_FilterInitStructure);  
 #if CAN_RX0_INT_ENABLE 
 CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);                         //FIFO0消息掛號中斷允許//      
 NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;           
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVIC_InitStructure);
 #endif
 return 0;
}   
#if CAN_RX0_INT_ENABLE 
//編寫中斷服務函數//       
void USB_LP_CAN1_RX0_IRQHandler(void)
{
 CanRxMsg RxMessage;
 //CanRxMsg是一個結構體,包含7個入口參數//
 //uint32_t StdId,uint32_t ExtId,uint8_t IDE,uint8_t RTR,uint8_t DLC, uint8_t Data[8],uint8_t FMI//
 int i=0;
    CAN_Receive(CAN1, 0, &RxMessage);
 for(i=0;i<8;i++)
 printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data[i]);                              //把RxMessage裏的Data讀取//
}
#endif
//can發送一組數據(固定格式:ID爲0X12,標準幀,數據幀) 
//len:數據長度(最大爲8),msg:數據指針,最大爲8個字節,返回值:0,成功;其他,失敗//
u8 Can_Send_Msg(u8* msg,u8 len)
{ 
 u8 mbox;
 u16 i=0;
 CanTxMsg TxMessage;
 TxMessage.StdId=0x12;         //標準標識符// 
 TxMessage.ExtId=0x12;         //擴展標示符// 
 TxMessage.IDE=CAN_Id_Standard;  //標準幀//
 TxMessage.RTR=CAN_RTR_Data;    //數據幀//
 TxMessage.DLC=len;          //數據長度//
 for(i=0;i<len;i++)
 TxMessage.Data[i]=msg[i];             
 mbox= CAN_Transmit(CAN1, &TxMessage);   
 i=0; 
 while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++; //等待發送結束//
 if(i>=0XFFF)return 1;
 return 0;  
}
//can口接收數據查詢
//buf:數據緩存區,返回值:0,無數據被收到;其他,接收的數據長度//
u8 Can_Receive_Msg(u8 *buf)
{          
  u32 i;
  CanRxMsg RxMessage;
  if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;   //沒有接收到數據,直接退出// 
  CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);             //讀取數據// 
  for(i=0;i<8;i++)
    buf[i]=RxMessage.Data[i];  
  return RxMessage.DLC; 
}

3.3 main.c文件代碼解讀

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"  
#include "can.h" 
 int main(void)
 {  
 u8 key;
 u8 i=0,t=0;
 u8 cnt=0;
 u8 canbuf[8];
 u8 res;
 u8 mode=CAN_Mode_LoopBack;                     //CAN工作模式;CAN_Mode_Normal(0):普通模式,CAN_Mode_LoopBack(1):環回模式//
 delay_init();                                 //延時函數初始化//   
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//設置中斷優先級分組爲組2:2位搶佔優先級,2位響應優先級//
 uart_init(115200);                            //串口初始化爲115200//
 LED_Init();                                 //初始化與LED連接的硬件接口//
 KEY_Init();                                 //按鍵初始化//    
 CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);  //CAN初始化環回模式,波特率500Kbps//     
 while(1)
 {
  key=KEY_Scan(0);
  if(key==KEY0_PRES)                           //KEY0按下,發送一次數據//
  {
   for(i=0;i<8;i++)
   {
    canbuf[i]=cnt+i;                         //填充發送緩衝區//
    printf("\n發送數據爲:%d\r\n",canbuf[i]);
    }
   res=Can_Send_Msg(canbuf,8);                                  //發送8個字節// 
   if(res)
    printf("\n發送失敗!\r\n");
   else   
    printf("\n發送成功!\r\n");   
  }else if(key==WKUP_PRES)                                       //WK_UP按下,改變CAN的工作模式
  {    
   mode=!mode;
   CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode); //CAN普通模式初始化, 波特率500Kbps 
   if(mode==0)                                                  
   {
    printf("\n切換至普通模式。\r\n");                          //普通模式,需要2個開發板// 
   }else                                                        
   {
    printf("\n切換至迴環模式。\r\n");                          //迴環模式,一個開發板就可以測試了//
   } 
  }   
  key=Can_Receive_Msg(canbuf);
  if(key)                                                        //接收到有數據//
  {   
    for(i=0;i<key;i++)
   {  
     printf("\n接收數據爲:%d\r\n",canbuf[i]);    
    }
  }
  t++; 
  delay_ms(10);
  if(t==20)
  {
   LED0=!LED0;                                                  //提示系統正在運行 
   t=0;
   cnt++;
  }     
 }
}

4. 實驗結果
在這裏插入圖片描述
舊知識點
1)複習如何新建工程模板,可參考STM32學習心得二:新建工程模板
2)複習基於庫函數的初始化函數的一般格式,可參考STM32學習心得三:GPIO實驗-基於庫函數
3)複習寄存器地址,可參考STM32學習心得四:GPIO實驗-基於寄存器
4)複習位操作,可參考STM32學習心得五:GPIO實驗-基於位操作
5)複習寄存器地址名稱映射,可參考STM32學習心得六:相關C語言學習及寄存器地址名稱映射解讀
6)複習時鐘系統框圖,可參考STM32學習心得七:STM32時鐘系統框圖解讀及相關函數
7)複習延遲函數,可參考STM32學習心得九:Systick滴答定時器和延時函數解讀
8)複習ST-LINK仿真器的參數配置,可參考STM32學習心得十:在Keil MDK軟件中配置ST-LINK仿真器
9)複習ST-LINK調試方法,可參考STM32學習心得十一:ST-LINK調試原理+軟硬件仿真調試方法
10)複習串口通信相關知識,可參考STM32學習心得十四:串口通信相關知識及配置方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章