- STM32內置bxCAN外設(CAN控制器、拓展CAN),支持CAN2.0A和2.0B(全部的CAN),可以自動發送CAN報文和按照過濾器自動接收指定CAN報文,程序只需處理報文數據而無需關注總線的電平細節
- 波特率最高可達1兆位/秒,高速CAN波特率為125K~1MBps,可支持高速CAN
- 意思就是發送的時候有3個緩存區,可以存入3個待發報文,
- 2個3級深度的接收FIFO,這是接收的緩存區,總共可以緩存2*3=6個報文
14個過濾器組(互聯型28個),看芯片是不是互聯型,這個過濾器,就是用來過濾接收報文ID的,所有掛載在CAN總線的報文,都是廣播發出來的,每個設備都能收到,各種報文都混雜在 起,都在總線上傳輸,但是對于某個特定的設備而言,它往往只需要接收部分ID的報文,這樣我們就可以配置這個過濾器,讓CAN外設,只接收我想要的報文,無關報文直接就過濾掉,這樣就非常方便
STM32 CAN外設額外的一些特色功能:時間觸發通信、自動離線恢復、自動喚醒、禁止自動重傳、接收FIFO溢出處理方式可配置、發送優先級可配置、雙CAN模式
一、CAN網拓撲結構
這里每個CAN節點都掛載在CAN總線上,對于其中一個CAN節點,都由CAN控制器和CAN收發器組成,CAN控制器一般集成在MCU,也就是單片機里面,然后STM32引出CAN_RX和CAN_TX引腳,與CAN收發器連接,之后CAN收發器引出CAN_High和CAN_LOW,與CAN總線相連
stm32的can就兩個引腳CAN_RX、CAN_TX
二、CAN收發器電路
VCC供電必須接5V,不能接3.3V,因為協議規定,CAN_H引腳在顯性電平時電壓為3.5V,要是供電只有3.3V,那么CAN總線的電壓,就沒法符合規范了。
TXD和RXD,和左邊的CAN控制器相連,T接T,R接R,不用交叉,這一點別和串口搞混了
SJA1000這個芯片,是一個獨立的CAN控制器,比如如果你用一些低端的單片機,,它沒有內置CAN控制器,那么就可以通過外置這個SJA1000,實現CAN通信
模塊中間的R3是120Ω電阻,這個電阻就是終端電阻了,可以看到,這里每個收發器模塊,自帶的就有一個終端電阻,所以當兩個模塊相連時,正好是兩個終端電阻,但是如果三個或更多模塊相連,那么終端電阻就也會有3個或更多,這一點,不符合協議的要求,所以,當3個或更多收發器模塊相連時,我們得把中間模塊的終端電阻去掉,這才是符合協議的做法
三、STM32的CAN基本結構
發送郵箱有3個,每個郵箱可以存入一個CAN報文,如果我們想發出一個報文,那我們就把這個報文寫入到其中一個空置郵箱,之后設置寄存器請求發送,就完事了,然后剩下的所有步驟,比如等待總線空閑,操作引腳輸出波形,進行位同步和仲裁等等,這些步驟,全都由硬件電路自動執行,所以使用起來,其實非常簡單。
接收部分的接收過濾器和2個FIFO,當CAN總線上出現一個數據幀或者遙控幀時,CAN硬件電路都會把這個報文緩存下來,至于是不是要保留這個報文,那得看它能不能通過過濾器,過濾器內,我們可以設置過濾規則,告訴硬件,我們想要什么ID的報文,如果硬件收到了這些舊的報文,就可以把它存入FIFO,如果收到的報文,無法通過任何一個過濾器,說明這個D的報文,我們并不需要,那硬件就直接就把它扔掉,別來煩我們,這樣可以減輕軟件的負擔。然后,通過過濾器的報文會自動存入主接收FIFO 0或者主接收FIFO 1,FIFO的意思是先進先出寄存器,你也可以把它稱為隊列,通過過濾器的報文,要進隊伍0或者隊伍1里面排隊,等待CPU讀取,這里設計了兩個隊伍,每個隊伍有3個郵箱,也就是最大存入3個報文,如果接收報文很快,CPU無法及時讀走,那報文就可以在FIFO 0或者FIFO1里面排隊,這樣在一定程度上,可以避免報文丟失。
波特率計算
STM32中的位時序
計算出來的波特率一定要在高速或低速CAN總線的范圍內,如果不在其范圍內,則要調整。
這里的波特率就是表中的400000 bit/s
下面設置的幾種模式:
- 時間觸發模式
- 自動總線關閉管理
- 使能喚醒
- 自動重發模式
- 接收FIFO鎖定模式:接收的FIFO滿了,新獲取的數據是覆蓋呢還是丟棄呢,不使能就是鎖定模式:新獲取數據直接丟棄
- 發送FIFO優先級:不使能就是依次使用發送郵箱,使能就根據標識符ID進行優先發送
Test MODE? 測試模式:
- Normal:正常模式,多主機之間通信
- Loopback:回環模式,單主機通信測試
- Silent
- Loopback combined with Silent
標識符ID過濾器:
某個CAN設備節點向總線發出的數據,到底是否是另一個CAN設備節點需要的數據呢?CAN外設節點使用過濾器判定是否是自己使用的數據。
stm32f103的CAN外設(互聯型)有28個過濾器,非互聯型有14個過濾器,所以接下來介紹一下過濾器的原理。
標識符過濾器框圖:
- 每個過濾器的核心由兩個32位寄存器組成:R1[31:0]和R2[31:0]
- FSCx:位寬設置,置0為16位, 置1為32位
- FBMx:模式設置,置0為屏蔽模式,置1為列表模式。
- FFAx:關聯設置,置0為FIFO0, 置1為FIFO1
- FACTx:激活設置,置0為禁用,置1為啟用。
屏蔽模式就是設置某些位為1,然后傳輸過程中進行匹配,如果發現為1的位置但是傳輸的ID的相應位不是1,而是0,則直接舍棄。也就是對某幾個bit位進行匹配
列表模式是要求必須完全匹配,每個bit位都要一樣
屏蔽模式主要用于連續的ID的多個報文,列表模式主要用于精確某個幾個報文。
擴展格式的有29位ID,所以必須使用32位模式過濾
#include "CAN_Driver.h"
#include "stm32f1xx_hal_can.h"
#include "driver_oled.h"
#include <stdio.h>
#include <string.h>
//1.配置CAN模塊的接收過濾器:奇數可以通過,偶數不能能過。
int setCAN_Filter(void)
{//設置過濾器規則:CAN_FilterTypeDef canFilter;//使用哪個過濾器,stm32f103的CAN外設(互聯型)有28個過濾器,非互聯型有14個過濾器canFilter.FilterBank = 0;//過濾器的模式,置0為屏蔽模式,置1為列表模式。CAN_FILTERMODE_IDMASK就是0屏蔽模式canFilter.FilterMode = CAN_FILTERMODE_IDMASK;//can過濾器的長度:32位或者16位的canFilter.FilterScale = CAN_FILTERSCALE_32BIT;//設置規則://這里設置過濾規則是只要奇數,STID[0]位是高16位的第5位(從0開始算),所以這里設置0x20,看下面的設置標識符屏蔽的圖片canFilter.FilterIdHigh = 0x0020;canFilter.FilterIdLow = 0x0000;//設置掩碼屏蔽,高16位肯定和上面的標識符屏蔽一樣都是0x20,到那時掩碼要看IDE(ID擴展標志位)和RTR位(遠程請求標志位),我們這個程序只是作為環回模式,自收自發,所以這兩位設為1,不需要canFilter.FilterMaskIdHigh = 0x0020;canFilter.FilterMaskIdLow = 0x0006;//通過過濾器的報文放置在哪一個FIFO中:canFilter.FilterFIFOAssignment = CAN_FILTER_FIFO0;//使能FIFOcanFilter.FilterActivation = CAN_FILTER_ENABLE;//把配置好的過濾器參數設置到can模塊中:HAL_CAN_ConfigFilter(&hcan,&canFilter);//啟動CAN模塊:HAL_CAN_Start(&hcan);vTaskDelay(10);return 0;
}//2.發送CAN報文
int CAN_sendMsg(uint32_t msgID, uint8_t* pdata, uint8_t datalen)
{//配置發送報文的格式:stdid + 數據 + RTR + IDE + DLCCAN_TxHeaderTypeDef TxHeader;TxHeader.StdId = msgID;TxHeader.RTR = 0;//數據幀TxHeader.IDE = 0; //標準幀TxHeader.DLC = datalen;//查看有沒有空閑郵箱:while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);//如果有空閑郵箱:uint32_t TxMailbox;HAL_CAN_AddTxMessage(&hcan, &TxHeader, pdata, &TxMailbox);//在OLED屏上顯示一下結果:char buf[32] = {0};sprintf(buf, "sendok msgid=%d",TxHeader.StdId);OLED_PrintString(0,0, buf);vTaskDelay(100);return 0;
}//3.接收CAN報文
int CAN_recvMsg(uint8_t* pdata)
{CAN_RxHeaderTypeDef RxHeader;//判斷一下接收的FIFO0中有沒有數據:if(HAL_CAN_GetRxFifoFillLevel(&hcan,CAN_RX_FIFO0) == 0){//沒有接收到OLED_PrintString(0,2, "msg not recv");}else{//接收到了:OLED_PrintString(0,2, "msg is recved");HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &RxHeader, pdata);char buf[32] = {0};sprintf(buf, "ID=%d,data=%d",RxHeader.StdId, *pdata);OLED_PrintString(0,4,buf);}vTaskDelay(100);return 0;
}
設置標識符屏蔽
STID[0]位是高16位的第6位
設置掩碼