一、CAN通信簡介
1.1 CAN簡介
CAN全稱是Controller Area Network,控制器局域網絡,是ISO國際標準化的串行通信協議。CAN是國際上應用最廣泛的現場總線之一。
CAN通信只有兩根信號線,分別是CAN_H和CAN_L,CAN 控制器根據這兩根線上的電位差來判斷總線電平??偩€申平分為顯性電平和隱性申平,二者必居其一。發送方通過使總線電平發生變化,將消息發送給接收方。
- ? 2.0V---------邏輯0------顯性電平
- ? 0 V-----------邏輯1------隱性電平
CAN總線遵從“線與”機制, 顯性電平可以覆蓋隱性電平 。這就導致 只有所有節點都發送隱形電平時總線才處于隱性狀態 。
CAN通信示意圖
1.2 CAN協議特點
- ? 多主控 在總線空閑時所有單元都可以發送消息。當兩個以上單元發送消息時,會根據標識符(ID)決定發送的優先級。
- ? 通信速度較快 ,最高可達1Mbps。 通信距離較遠 。當速度為1Mbps時,傳輸距離小于40m。當速度小于500Kbps時,傳輸距離最遠可達10Km。
- ? 具有錯誤檢測,錯誤通知和錯誤恢復功能 。CAN總線上的任意一個單元都可以檢測錯誤,當任意一個單元檢測出錯誤時,會立刻通知其他單元。正在發送消息的單元一旦檢測出錯誤,會強制結束當前發送。強制結束的單元會不斷重新發送消息,直到發送成功。
- ? 故障封閉功能 CAN可以判斷出錯誤的類型是總線上暫時的數據錯誤還是持續的數據錯誤。當總線上持續出現數據錯誤時,可以將引起故障的單元從總線上隔離出去。
- ? 連接節點多 理論上連接單元沒有數量限制,但是連接單元越多,速度就會越低。
11.3 CAN通信的幀類型
CAN通信有5種幀類型
在上述的幾種幀里,數據很和遙控幀有標準幀和擴展幀兩種。標準幀有11位ID,擴展幀有29位ID。
1.4 數據幀結構
CAN通信數據幀的構成如下
CAN通信數據幀結構
- ? 幀起始 表示數據幀開始的段,標準幀和擴展幀的幀起始都是由1個位的顯性電平組成。
- ? 仲裁段 表示數據幀優先級的段。
仲裁段
RTR是用來表示是否是遠程幀(遙控幀)。RTR為0是數據幀,RTR為1是遠程幀。擴展幀中的IDE是標識符的選擇位,如果為0,使用標準標識符,如果為1,使用擴展標識符。擴展幀的SRR相當于標準幀中的RTR位。 標準幀的ID禁止高七位是隱性電平 。
? 控制段 控制段由6位構成,表示數據段的字節數。
控制段
擴展幀的r0和r1是保留位,保留位必須全部以顯性電平發送。DLC是數據的長度碼,數據的字節數范圍是0~8。IDE是標識符的選擇位,如果為0,使用標準標識符,如果為1,使用擴展標識符。? 數據段 數據段可以包含0~8個字節的數據。從MSB(最高位)開始傳輸。標準幀和擴展幀的數據段相同。
? CRC段 CRC段用于校驗,檢查幀傳輸是否存在錯誤。CRC段包含15位CRC序列和1位CRC界定符。標準幀和擴展幀的CRC段相同。
CRC段? ACK段 ACK段用來確認是否正常接收。由ACK槽和ACK界定符2位組成。標準幀和擴展幀的ACK段相同。
ACK段? 幀結束 由7位隱形電平組成,表示幀的結束。標準幀和擴展幀的幀結束相同。
11.5 CAN的位時序
由發送單元在非同步狀態下每秒鐘發送的位數稱為 位速率 。一個位可以分成4段。
? 同步段 SS
? 傳播時間段 PTS
? 相位緩沖段1 PBS1
? 相位緩沖段2 PBS2
上面的這些段由稱為Time Quantum( Tq )的最小時間單位組成。1個位分成4個段,一個段又分成若干個Tq,這成為 位時序 。
位構成
采樣點是讀取總線電平,并將讀到的電平作為位值的點。
1.6 CAN的仲裁功能
在總線空閑態,最先開始發送消息的單元獲得發送權。當多個單元同時開始發送時,各發送單元從仲裁段的第一位開始進行仲裁。連 續輸出顯性電平最多的單元可繼續發送 。
仲裁過程
二、STM32F1的CAN
2.1 bxCAN簡介
STM32F1芯片自帶bxCAN 控制器 (Basic Extended CAN),即基本擴展CAN,可與 CAN 網絡進行交互,它支持 2.0A 和 B 版本的CAN 協議。STM32F1的bxCAN有以下特點
- ? 支持 CAN 協議 2.0A 和 20B 主動模式
- ? 波特率最高達 1Mbps
- ? 支持時間觸發通信
- ? 具有 3 個發送郵箱
- ? 具有 3 級深度的 2 個接收 FIFO
- ? 可變的過濾器組(STM32F103ZET6有14個)
bxCAN模塊可以完全自動地接收和發送CAN報文,且完全支持標準標識符(11位)和擴展標識符(29位)。
2.2 bxCAN工作模式
bXCAN有3個主要的工作模式: 初始化模式 、正常模式和 睡眠模式 。除此之外,還有測試模式,靜默模式,環回模式。
2.2.1 初始化模式
首先看一下CAN主控制寄存器 (CAN_MCR)的INRQ位。
寄存器介紹CAN_MCR
寄存器CAN_MSR介紹
通過介紹可以直到,想要進入初始化模式,軟件先將CAN_MCR的INRQ位置1。然后等待硬件將CAN主狀態寄存器(CAN_MSR)的INAK位置1。此時進入初始化模式。
當bxCAN處于初始化模式時,禁止報文的接收和發送,并且CANTX引腳輸出隱性位(高電平)。
2.2.2 正常模式
在初始化完成后,軟件應該讓硬件進入正常模式,以便正常接收和發送報文。繼續看上面對于CAN主控制寄存器INRQ位的介紹。軟件將INRQ位清0,可以使CAN從初始化模式進入正常模式。此時等待硬件將CAN主狀態寄存器的INAK位清0即可。
2.2.3 睡眠模式
bxCAN可工作在低功耗的睡眠模式。在該模式下,bxCAN的時鐘停止了,但軟件仍然可以訪問郵箱寄存器。
寄存器CAN_MSR介紹
可以看出,軟件將CAN主控制寄存器的SLEEP置1,即可請求進入睡眠模式。清零該位,退出睡眠模式。另外,如果CAN_MCR寄存器的AWUM位為’1’,一旦檢測到CAN總線的活動,硬件就自動對SLEEP位清’0’來喚醒bxCAN。
2.2.4 靜默模式
將CAN_BTR寄存器的SILM位置’1’,來選擇靜默模式。
寄存器CAN_BTR介紹
在靜默模式下,bxCAN可以正常地接收數據幀和遠程幀,但只能發出隱性位,而不能真正發送報文。如果bxCAN需要發出顯性位(確認位、過載標志、主動錯誤標志),那么這樣的顯性位在內部被接回來從而可以被CAN內核檢測到,同時CAN總線不會受到影響而仍然維持在隱性位狀態。因此,靜默模式通常用于分析CAN總線的活動,而不會對總線造成影響-顯性位(確認位、錯誤幀)不會真正發送到總線上。
靜默模式
2.2.5 環回模式
將CAN_BTR寄存器的LBKM位置’1’,來選擇環回模式。在環回模式下,bxCAN把發送的報文當作接收的報文并保存(如果可以通過接收過濾)在接收郵箱里。
寄存器CAN_BTR寄存器介紹
環回模式可用于自測試。為了避免外部的影響,在環回模式下CAN內核忽略確認錯誤(在數據/遠程幀的確認位時刻,不檢測是否有顯性位)。在環回模式下,bxCAN在內部把Tx輸出回饋到Rx輸入上,而完全忽略CANRX引腳的實際狀態。發送的報文可以在CANTX引腳上檢測到。
環回模式
2.3 位時序和波特率
STM32將每一位分成三段
- ? 同步段 SS
- ? 時間段1 BS1
- ? 時間段2 BS2
位時序
其中tpclk是APB1總線的時鐘頻率,默認為36MHz。
三、CAN配置步驟
- ? 使能CAN時鐘,將對應引腳復用映射為CAN功能 。STM32F103ZET6只有一個CAN,對應引腳如下
CAN_RX配置為上拉輸入模式,CAN_TX配置為復用推挽輸出。
- ? 設置CAN工作模式,波特率等 。庫函數提供了一個結構體和一個函數來配置。初始化函數為
uint8_t CAN_Init(CAN_TypeDef* CANx, CAN_InitTypeDef* CAN_InitStruct)
結構體成員如下
波特率 = Fpclk1 / ((CAN_BS1 + CAN_BS2 + 1)* CAN_Prescaler)
- ? 設置CAN篩選器(過濾器) 庫函數也提供了篩選器的配置函數
void CAN_FilterInit(CAN_FilterInitTypeDef* CAN_FilterInitStruct)
結構體內容如下
- 選擇CAN中斷類型,開啟中斷 庫函數提供了一個中斷的配置函數
void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState)
CAN的中斷類型有很多,這里就不再一一介紹了。
#define IS_CAN_IT(IT) (((IT) == CAN_IT_TME) || ((IT) == CAN_IT_FMP0) ||
((IT) == CAN_IT_FF0) || ((IT) == CAN_IT_FOV0) ||
((IT) == CAN_IT_FMP1) || ((IT) == CAN_IT_FF1) ||
((IT) == CAN_IT_FOV1) || ((IT) == CAN_IT_EWG) ||
((IT) == CAN_IT_EPV) || ((IT) == CAN_IT_BOF) ||
((IT) == CAN_IT_LEC) || ((IT) == CAN_IT_ERR) ||
((IT) == CAN_IT_WKU) || ((IT) == CAN_IT_SLK))
- ? CAN發送和接收消息 CAN發送消息的函數是
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage)
發送之前需要配置好消息的結構體,消息結構體成員如下
- 選擇CAN中斷類型,開啟中斷 庫函數提供了一個中斷的配置函數
void CAN_ITConfig(CAN_TypeDef* CANx, uint32_t CAN_IT, FunctionalState NewState)
CAN的中斷類型有很多,這里就不再一一介紹了。
#define IS_CAN_IT(IT) (((IT) == CAN_IT_TME) || ((IT) == CAN_IT_FMP0) ||
((IT) == CAN_IT_FF0) || ((IT) == CAN_IT_FOV0) ||
((IT) == CAN_IT_FMP1) || ((IT) == CAN_IT_FF1) ||
((IT) == CAN_IT_FOV1) || ((IT) == CAN_IT_EWG) ||
((IT) == CAN_IT_EPV) || ((IT) == CAN_IT_BOF) ||
((IT) == CAN_IT_LEC) || ((IT) == CAN_IT_ERR) ||
((IT) == CAN_IT_WKU) || ((IT) == CAN_IT_SLK))
- ? CAN發送和接收消息 CAN發送消息的函數是
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage)
發送之前需要配置好消息的結構體,消息結構體成員如下
- ? CAN狀態獲取 庫函數提供了很多可以獲取CAN狀態標志的函數,比如
uint8_t CAN_TransmitStatus(CAN_TypeDef* CANx, uint8_t TransmitMailbox)
FlagStatus CAN_GetFlagStatus(CAN_TypeDef* CANx, uint32_t CAN_FLAG);
四、實戰項目
4.1 CAN初始化
/*
*==============================================================================
*函數名稱:Drv_Can_Init
*函數功能:初始化CAN
*輸入參數:tsjw:重新同步跳躍寬度(Tsjw);tbs1:BS1長度;tbs2:BS2長度;
brp:Tq大小;mode:CAN工作模式
*返回值:無
*備 注:無
*==============================================================================
*/
void Drv_Can_Init (u8 tsjw,u8 tbs1,u8 tbs2,u16 brp,u8 mode)
{
// 結構體定義
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
// 開啟時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); // 打開CAN1時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA端口時鐘打開
// 初始化GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // PA11 CAN_RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉輸入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // PA12 CAN_TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 復用推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // IO口速度為50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 初始化CAN
CAN_InitStructure.CAN_TTCM=DISABLE; // 非時間觸發通信模式
CAN_InitStructure.CAN_ABOM=DISABLE; // 軟件自動離線管理
CAN_InitStructure.CAN_AWUM=DISABLE; // 睡眠模式通過軟件喚醒(清除CAN- >MCR的SLEEP位)
CAN_InitStructure.CAN_NART=ENABLE; // 使用報文自動傳送
CAN_InitStructure.CAN_RFLM=DISABLE; // 報文不鎖定,新的覆蓋舊的
CAN_InitStructure.CAN_TXFP=DISABLE; // 優先級由報文標識符決定
CAN_InitStructure.CAN_Mode= mode; //CAN工作模式設置
CAN_InitStructure.CAN_SJW=tsjw; // 重新同步跳躍寬度(Tsjw)為tsjw+1個時間單位 CAN_SJW_1tq~CAN_SJW_4tq
CAN_InitStructure.CAN_BS1=tbs1; // Tbs1范圍CAN_BS1_1tq ~CAN_BS1_16tq
CAN_InitStructure.CAN_BS2=tbs2; // Tbs2范圍CAN_BS2_1tq ~ CAN_BS2_8tq
CAN_InitStructure.CAN_Prescaler=brp; //分頻系數(Fdiv)為brp+1
CAN_Init(CAN1, &CAN_InitStructure); // 初始化CAN1
// 初始化過濾器
CAN_FilterInitStructure.CAN_FilterNumber=0; // 過濾器0
CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; // 掩碼模式
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); // 過濾器初始化
}
4.2 CAN發送
/*
*==============================================================================
*函數名稱:Med_Can_Send_Msg
*函數功能:發送報文
*輸入參數:msg:數據段指針;len:數據長度
*返回值:0:發送成功;1:發送失敗
*備 注:固定ID為0x12
*==============================================================================
*/
u8 Med_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 = 0; // 消息類型為數據幀,一幀8位
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;
}
4.3 CAN接收
/*
*==============================================================================
*函數名稱:Med_Can_Receive_Msg
*函數功能:接收報文
*輸入參數:buf:數據緩存區指針
*返回值:0:沒有接收到數據;其他:接收數據長度
*備 注:無
*==============================================================================
*/
u8 Med_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 RxMessage.DLC;i ++)
{
buf[i] = RxMessage.Data[i];
}
return RxMessage.DLC;
}
4.4 CAN收發測試
利用按鍵WK UP控制報文的發送,按下一次發送一次報文。配置CAN波特率為500Kbps,環回模式。利用串口打印接收數據。需要注意的是,STM32只有CAN控制器,想要實現報文的收發,需要自己連接CAN收發器。
首先初始化CAN
// 初始化CAN,500Kbps波特率
Drv_Can_Init(CAN_SJW_1tq,CAN_BS1_9tq,CAN_BS2_8tq,4,CAN_Mode_LoopBack);
然后編寫主程序
u8 gKeyValue = 0; // 獲取按鍵值
u8 gSendData[8] = {'1','2','3','4','5','6','7','8'}; // 發送內容數組
u8 gReceData[8]; // 接收內容數組
u8 gFlag = 0; // 接收發送標志
int main(void)
{
Med_Mcu_Iint(); // 系統初始化
while(1)
{
gKeyValue = Med_KeyScan();
// WK UP 按下發送消息
if (gKeyValue == 1)
{
gFlag = Med_Can_Send_Msg(gSendData,8);
// 發送失敗
if (gFlag)
{
printf ("Send Defeat!rn");
}
else
{
printf ("Send Success!rn");
}
}
// 接收報文
gFlag = Med_Can_Receive_Msg(gReceData);
// 接收成功
if (gFlag)
{
printf ("Receive Data:%srn",gReceData);
}
}
}
測試結果如下
測試結果
4.5 補充說明
上面的CAN收發測試程序,發送的內容是字符串“12345678”。如果發送的是數字12345678。在串口打印接收數據時需要先將接收到的數據轉換成字符,然后再打印。轉換方法很簡單,只需要對接收數組的每一位加48即可。
-
控制器
+關注
關注
112文章
16214瀏覽量
177479 -
CAN通信
+關注
關注
5文章
93瀏覽量
17814 -
緩沖器
+關注
關注
6文章
1920瀏覽量
45450 -
STM32
+關注
關注
2266文章
10876瀏覽量
354926 -
CRC效驗
+關注
關注
0文章
30瀏覽量
1093
發布評論請先 登錄
相關推薦
評論