1.CAN通訊的理解
想學習CAN通訊,那么要對通訊協議有一定的認知。通訊協議是指通信雙方對數據傳送控制的一種約定。約定中包括對數據格式,同步方式,傳輸速度,傳送步驟,檢糾錯方式以及控制字符定義等問題做出統一規定,通信雙方必須共同遵守。
CAN通訊全稱控制器局域網通訊,是用來在局域網中高效傳輸,處理信息的一種通訊方式。它采用數據塊編碼的方式,數據塊根據幀類型的不同有四種格式,可使不同的節點接收到相同的數據,然后再根據各節點內CAN配置選擇處理還是丟棄該信息(這與TCP/IP協議棧的鏈路層的MAC地址過濾很相似,是可以互通理解的),CAN的位流是按照非歸零(NRZ)碼方式編碼,一個完整的位電平有顯性和隱性兩種方式。顯性和隱性是根據CAN總線上的差分電壓VCAN1H-VCAN1L, 若小於閾值則為隱性位,代表邏輯1,大於閾值則為顯性位,代表邏輯0,這種將單電平轉換成兩根差分線的方式提高了電路的可靠性,不過也決定局域網里同時只能有一路數據傳輸,因此CAN通訊是半雙工的。
2.CAN通訊幀格式
CAN報文有四種不同的幀類型:
(1).數據幀:數據幀將數據從發送器傳輸到接收器。
數據幀和可以使用標准幀和擴展幀兩種格式。它們用一個幀間空間與前面的幀分隔。

1).幀起始(SOF) 標志幀的開始,由一個“顯性(0)”位構成。只有在總線空閑時才允許節點發送(信號),其它所有節點必須同步於首先開始發送報文的節點的幀起始前沿。
2).仲裁場 由標識符和傳送幀類型(RTR)組成的仲裁場
標准幀格式:

擴展幀格式:

對於數據幀 RTR恆為0,SRR恆為1,因此可以根據仲裁場起始第12個字符數判斷是標准幀還是擴展幀。
3).控制場 保留位R1,R0(恆為0),以及幀長度選擇位DLC(4位)構成的。
4).數據場 由數據幀里的發送數據組成,長度由DLC控制,但小於等於8字節。
5).CRC場 由CRC序列(CRC Sequence),以及CRC界定符(CRC Delimiter)構成。CRC序列之后是CRC界定符,它包含一個單獨的“隱性(1)”位。
6).應答場(ACK Field) 2位,包含應答間隙(ACK Slot)和應答界定符(ACK Delimiter),當接收器正確地接收到有效的報文,接收器就會在應答間隙(ACK Slot)中寫入顯性位(0),在返回給發送器,完成一次通訊(半雙工).
① 應答間隙
所有接收到匹配CRC序列的節點會在應答間隙期間用“顯性(0)”的位寫入發送器的“隱性(1)”位來組成應答數據。
② 應答界定符
應答界定符是應答場的第二位,並且必須為“隱性(1)”的位。因此,應答間隙(ACK Slot)被兩個“隱性”的位所包圍,也就是CRC界定符和應答界定符。
7).幀結束 由7個隱性位構成,代表幀的結束。
(2).遠程幀:總線節點發出遠程幀,請求發送具有同一識別符的數據幀。

遠程幀除了RTR位默認為1,沒有數據場外,其它與數據幀相同,不在贅述。
(3).錯誤幀:報文發送過程中,檢測到任一節點出錯,即於下一位發送出錯幀,通知發送端停止發送。

錯誤標志:有兩種形式的錯誤標志:激活錯誤標志和認可錯誤標志。
1).激活錯誤”標志由6個連續的“顯性”位組成
2).“認可錯誤”標志由6個連續的“隱性”的位組成,除非被其他節點的“顯性”位重寫。
錯誤界定符:錯誤界定符包括8個“隱性”的位。
錯誤標志傳送了以后,每一個節點就發送一個“隱性”的位,並一直監視總線直到檢測出一個“隱性”的位為止,然后就開始發送其余7個“隱性”位。
(4).過載幀:接收端用於要求發送端延緩發送下一個數據幀或者遠程幀。
1).超載標志: 過載標志由6個“顯性”的位組成。過載標志的所有形式和“激活錯誤”標志的一樣
2).過載界定符包括8個“隱性”的位,具體動作與錯誤界定符一致
CAN通訊是數據塊編碼的半雙工通訊方式,沒有主從設備區別,因此發出報文的節點為該報文的發送器,該節點在總線空閑或丟失仲裁前恆為發送器;如果一個節點不是報文發送器,並且總線不處於空閑狀態,則該節點為接收器。
3.CAN通訊的STM32實現
CAN協議是比較復雜的一種通訊協議,因此需要在學習如何使用stm32實現前了解協議本身的很多內容,下面就可以開始stm32中CAN協議環回測試,用來簡單的理解CAN協議的測試。既然是要STM32實現,那么步驟的設計如下:
(1).工作原理圖
了解了CAN通訊,下面進入正題,CAN通訊連接首先看原理圖如下:

從上面可以看出CAN1_TX: PD1 CAN1_RX PD0
(2).CAN硬件驅動配置
CAN通訊端口配置還是比較簡單的:
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE); GPIO_InitStructure.GPIO_Pin = CAN1_TX_Pin; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //將CAN1輸出端PD1配置為推挽輸出模式
GPIO_Init(CAN1_TX_Port,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = CAN1_RX_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //將CAN1輸入端PD0配置為浮空輸入
GPIO_Init(CAN1_RX_Port, &GPIO_InitStructure);
CAN通訊模式配置(因為是簡單的測試,因此配置為環回模式,過濾器配置為屏蔽位模式)
void CAN1_MODE_Config(void) { CAN_InitTypeDef CAN_InitStructure; CAN_FilterInitTypeDef CAN_FilterInitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); CAN_DeInit(CAN1); CAN_InitStructure.CAN_ABOM = DISABLE; //離線模式由軟件實現
CAN_InitStructure.CAN_AWUM = DISABLE; //軟件喚醒
CAN_InitStructure.CAN_TTCM = DISABLE; //禁止時間觸發通信模式
CAN_InitStructure.CAN_NART = ENABLE; //禁止自動重傳
CAN_InitStructure.CAN_TXFP = DISABLE; //優先級由報文的標識符來決定
CAN_InitStructure.CAN_RFLM = DISABLE; //接受溢出時FIFO不鎖定,下一個收到的報文覆蓋原有報文
CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack; //CAN硬件工作環回模式
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; //重新同步跳躍寬度為2個時間單位
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq; //時間段為8個時間單位
CAN_InitStructure.CAN_BS2 = CAN_BS2_7tq; //時間段為7個時間單位
CAN_InitStructure.CAN_Prescaler = 5; //設定一個時間單位的長度為5,范圍(1~1024)
CAN_Init(CAN1, &CAN_InitStructure); CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; //設定過濾器組為屏蔽位模式
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; //過濾器位寬為32位過濾器一個
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000; //設定過濾器標識符高位(32為高位段,16位為第一個)
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; //設定過濾器標識符低位(32為低位段,16位為第二個)
CAN_FilterInitStructure.CAN_FilterMaskIdHigh= 0x0000; //設定過濾器標識符高位(32為高位段,16位為第一個)
CAN_FilterInitStructure.CAN_FilterMaskIdLow= 0x0000; //設定過濾器標識符低位(32為低位段,16位為第二個)
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; //過濾器FIFO0指向過濾器0
CAN_FilterInitStructure.CAN_FilterNumber = 1; //指定待初始化的過濾器,范圍1~13
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; //使能過濾器
CAN_FilterInit(&CAN_FilterInitStructure); CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); //FIF0消息掛號中斷允許
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn; //CAN1_RX0中斷向量表中開啟
NVIC_Init(&NVIC_InitStructure); }
完成了驅動方面的配置,下面要進行的就是發送CAN數據和接收CAN數據的生成和實現了:
//CAN發送幀構成
void CAN_TxMessageInit(uint32_t std_id, uint32_t ext_id, uint8_t ide, uint8_t rtr, uint8_t dlc, uint8_t *pdata) { uint8_t i; assert_param(dlc>8); CanTxMessage.StdId = std_id&0x7ff; //設定標准標識符0~0x7ff 11位
CanTxMessage.ExtId = ext_id&0x3ffff; //設定額外標識符0~0x3ffff 18位
CanTxMessage.IDE = ide; //輸出標識符類型,STD(標准標識符)或EXT(額外標識符)
CanTxMessage.RTR = rtr; //輸出幀類型,DATA(數據幀)或者REMOTE(遠程幀)
CanTxMessage.DLC = dlc; //幀長度,0~8
for(i=0; i<dlc; i++) { CanTxMessage.Data[i] = *(pdata+i); } } //CAN中斷接收函數
void CAN1_RX0_IRQHandler(void) { ITStatus Status; Status = CAN_GetITStatus(CAN1, CAN_IT_FMP0); //判斷接受到過濾器中斷信號
if(Status == SET) { CAN_Receive(CAN1, CAN_FIFO0, &CanRxMessage); //接收一個CAN報文
memcpy(rdata, &(CanRxMessage.Data[0]), 8); //將接收到的數據轉存到rdata數組中
} CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0); }
因為錯誤幀和超載幀是有stm32自帶的硬件實現的,因此由我們提供的是遠程幀和數據幀,單個幀內數據0~8字節,如果要發送多個數據,需要在其上添加傳輸層實現,具體實現我會在以后在研究(雙CAN多幀通訊實驗)。
主函數的實現如下:
int main(void) { BSP_Init(); //硬件初始化 CAN_TxMessageInit(0x011, 0x0000, CAN_ID_STD,
CAN_RTR_DATA, sizeof(tdata), tdata); //生成CAN報文 CAN_Transmit(CAN1, &CanTxMessage); //發送CAN報文
GPIO_ResetBits(GPIOD, GPIO_LED_1); ARM_DELAY(1000000); GPIO_SetBits(GPIOD, GPIO_LED_1); ARM_DELAY(1000000); while(1) { if(CanRxMessage.StdId == 0x11 && CanRxMessage.DLC == sizeof(tdata)) //判斷接收到的報文
{ if(strncmp((char *)tdata, (char *)rdata, sizeof(rdata)) == 0) { GPIO_ResetBits(GPIOD, GPIO_LED_1); printf("stdID is %x, DIC is %x, receive data is %s \r\n",
CanRxMessage.StdId, CanRxMessage.DLC, rdata); } memset(&CanRxMessage, 0, sizeof(CanRxMessage)); //清除接收到的報文
CAN_Transmit(CAN1, &CanTxMessage); //發送CAN報文
} ARM_DELAY(1000000); GPIO_SetBits(GPIOD, GPIO_LED_1); ARM_DELAY(1000000); } }
如此就完成了簡單的CAN環回測試實驗,具體實驗可通過串口接收端口查看,如下:

具體代碼參考:http://files.cnblogs.com/files/zc110747/8.CAN-Loopback.7z
