1. 程式人生 > >基於CAN 匯流排的資料傳輸與分析討論

基於CAN 匯流排的資料傳輸與分析討論

基於CAN匯流排的資料傳輸與分析討論 首先我們來討論一下為什麼要使用CAN通訊?以及使用CAN通訊能給我們帶來什麼樣的方便? 首先不得不說的是CAN通訊現在被應用於很多的領域,比如說汽車And so on........CAN通訊的好處很簡單,它可以以最少的CPU消耗來處理一大批的檔案,當然這裡所說的檔案是指傳輸的資料之類的,為什麼要傳輸資料呢?因為有些資料你想管理,哈哈哈! 為什麼說CAN通訊可以減少CPU的消耗呢?因為很簡單,CAN把你要處理的特殊資料幫你給處理好了,就好像別人幫你做好了一樣,你只要告訴CAN你想要哪些資料就好了,它就會幫你把你想要的資料給你挑選出來,來供你使用。 而且最重要的一點就是CAN的資料傳輸只是需要兩根線就OK了,哈哈哈  ,你很難想像兩根線來完成那麼多資料的處理吧。通過這兩根線,就可以輕鬆搭建一個最小區域網,簡單來說就是讓這些控制器通過CAN匯流排相互打交道,讓它們自己嗨起來! 大笑 當下我們來分析一下STM32中的CAN通訊原理,我所使用的是一款32的微控制器(ZET6),不過都一樣了,不管使用什麼樣的控制器,原理都是一樣了。 大笑 首先來讓我們看看CAN的工作框圖
哈哈 是不是覺得還是比較簡單的,首先允許我說一下那兩個電阻是什麼東東,這兩個電阻如果你學習過射頻的話,估計都不用我多說啥,這裡照顧一下那些沒有接觸過射頻的童鞋了,這兩個電阻其實它有一個好聽的名字是叫做終端電阻,用途是用來做阻抗匹配的,減少回波反射的。 得意(不懂的話可以自己查查) CAN總線上有兩個電平,一個顯性,一個是隱性電平。也就是CAN_High,CAN_Low,這兩個電平在工作的時候電壓差是2.5V。這裡不難理解了。如果對這個電平不太瞭解的你可以看下TGA1050這款晶片,是CAN的一個收發晶片。(你值得了解) 好了我們接下來開始進入重點學習了 可憐
CAN呢!它的協議主要是通過5種類型餓幀構成的。 幀型別 幀用途 資料幀 用於傳送單元向接收單元傳送資料的幀 遙控幀 用於接收單元向具有相同ID的傳送單元請求資料的幀 錯誤幀 用於當檢查出錯時向其他單元通知錯誤的幀 過載幀 用於接收單元通知其尚未做好接收準備的幀 間隔幀 用於將資料幀及遙控幀與前面的幀分離開來的幀 由於本人比較懶啦,所以以最重要的一個數據幀來講解一下 資料幀有7個段構成,即 1、幀起始。表示資料幀開始的段。 2、仲裁段。表示該幀優先順序的段。 3、控制段。表示資料的位元組數及保留位的段。 4、資料段。資料的內容,一幀可以傳送0~8個位元組的資料。 5、CRC段。檢查幀的傳輸錯誤的段。 6、ACK段。表示確認正常接收的段。 7、幀結束。表示資料幀結束的段。 一下是資料幀的兩種形式,一種是標準幀,一種是擴充套件幀。
這裡提一下,不要將ID視為高7位全部設定為隱性位。RTR表示是否為遠端幀,(0:資料幀。1:遠端幀)IDE位表示表示符選擇位(0:使用標準識別符號。1:表示使用擴充套件識別符號)r0和r1為保留位,必須全部以顯性電平發出。SRR位為代替遠端請求位,為隱性位,它代替了標準幀中RTR,DLC是資料長度表示段,DLC段的有效值為0~8。資料段最多可以有8個位元組,從最高位MSB位開始送出。CRC段,該段用於檢查幀傳輸錯誤,由15個位的CRC順序和1個位的CRC界定符(用於分隔的位)組成。CRC的計算範圍包括幀起始,仲裁段,控制段和資料段。接收方以同樣的方式進行計算,兩者不一致時會通報錯誤。ACK段有ACK曹和ACK界定符兩個位組成,傳送單元的ACK,傳送兩個隱性位,人而接收單元在ACK槽f傳送顯性位,EOF是幀結束,由7個隱性位組成。 至此資料幀的7個段就介紹完成了。 好了下面我們再來介紹一下什麼叫做位速率? 睡覺
由傳送單元在非同步的情況下發送的每秒鐘的位數稱為位速率。一個位可以分為4段。 1、同步段(ss) 2、傳播時間段(PTS) 3、相位緩衝段1(PBS1) 4、相位緩衝段2(PBS2) 這些段又可稱為Time Quantum(簡稱Tq)的最小時間單位構成。 1位分為4個段,每個段又由若干個Tq構成,這稱為位時序。 一個位又由多少個Tq構成、每個段又由多少個Tq構成等,可以任意設定位時序。通過設定位時序,多個單元可同時取樣,也可以任意設定取樣點。各段的作用和Tq可如下表示



這些位的設定可以用來設定CAN的波特率,具體計算如下:
OK到此就把CAN通訊的波特率以及工作模式和幀型別介紹的差不多了。 奮鬥 接下來開始真正的進入CAN最為關鍵而且最為有意思的CAN的過濾器組。 首先來介紹一下CAN的一些資源,CAN支援時間觸發通訊,具有3個傳送郵箱,具有3級深度的2個接收FIFO和可變的過濾器組(最多有28個)。 讓我們來看一下CAN過濾器的工作情況

這裡我用的晶片是STM32ZET6的一款,只有一個CAN控制器, 這個就是CAN的有趣之處了,就是說它可以佔用CPU資源然後自己去篩選合適的來進行過濾。哈哈哈 罵人 每個過濾器組都有2個32位的暫存器,CAN_FxR1和CAN_FxR2組成。 每個過濾器組的位寬都可以獨立配置,以滿足不同應用程式的不同需求。 1個32位的過濾器,包括:STDID[10:0]、EXTID[17:0]、IDE和RTR位 2個16位的過濾器,包括:STDID[10:0]、IDE、RTR和EXTID[17:15]位 過濾器可以配置為遮蔽位模式和表示符列表模式。 遮蔽位模式就是說可以對識別符號的一些位進行遮蔽,可以讓過濾器組不去匹配這些位,然後匹配通過後才讓資料進來。 識別符號列表模式,收到報文的識別符號的每一位都必須和過濾器識別符號相同,才能讓你進來,否則不讓通過。 可以通過相關暫存器配置它的模式和位寬。如下

好了所有的條件都已經準備好了,接下來我們可以進入CAN的接收和傳送流程了 CAN的傳送流程為:總共有三個郵箱,程式選擇1個空的郵箱(TME-1)->設定識別符號的ID,資料長度和傳送的資料->設定CAN_TIxR的TXRQ位為1,請求傳送->郵箱掛號(等待成為最高的優先順序)->預定傳送(等待匯流排空閒)->傳送->郵箱空。流程如下圖:

CAN接收到的有效報文,被存放在3級郵箱深度的FIFO中。FIFO完全由硬體管理,從而節省了CPU的處理負荷。 CAN的接收流程為:FIFO空->收到有效報文->掛號_1(存入FIFO的一個郵箱,這個是由硬體控制,我們不需要理會)->收到有效的報文->掛號_2->收到有效報文->收到有效報文->掛號_3->收到有效報文->溢位。每次讀取一個報文時掛號就減1,直到FIFO空,CAN的接收流程圖如下:

好了至此我們已經將CAN的重要關卡都打通了, 大笑 大笑 大笑 下面來看一下軟體的配置 #include "CAN.H"
/*
CAN 初始化
Fclk初始化時鐘設定為36Mhz
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;
  
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA時鐘
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);//使能CAN時鐘
//判斷是否使能CAN的接收中斷?
#if CAN_RX0_INT_ENABLE
  NVIC_InitTypeDef NVIC_InitStructure;
#endif
  
  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//設定為複用推輓輸出,用來輸出CAN的資料
  GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
  GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  GPIO_Init(GPIOA,&GPIO_InitStructure);
  
  GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//上拉輸入,用來對資料的接收
  GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;
  GPIO_Init(GPIOA,&GPIO_InitStructure);
  
//CAN的單元設計
  CAN_InitStructure.CAN_TTCM=DISABLE;//非時間觸發通訊
  CAN_InitStructure.CAN_ABOM=DISABLE;//軟體自動離線管理
  CAN_InitStructure.CAN_AWUM=DISABLE;//睡眠模式通過軟體喚醒
  CAN_InitStructure.CAN_NART=ENABLE;//禁止報文自動傳輸
  CAN_InitStructure.CAN_RFLM=DISABLE;//報文不鎖定,新的覆蓋舊的
  CAN_InitStructure.CAN_TXFP=DISABLE;//優先順序由報文識別符號決定
  CAN_InitStructure.CAN_Mode=mode;//模式設定 mode:0,普通模式   1,迴環模式
  
//設定波特率
  CAN_InitStructure.CAN_SJW=tsjw;//設定從新跳躍寬度
  CAN_InitStructure.CAN_BS1=tbs1;//時間段1設定
  CAN_InitStructure.CAN_BS2=tbs2;//時間段2設定
  CAN_InitStructure.CAN_Prescaler=brp;//分頻係數
  CAN_Init(CAN1,&CAN_InitStructure);
  
//設定濾波器的相關係數
  CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;//過濾器0     
  CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//關聯到FIFO0這個接收郵箱上
  CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;
  CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
  CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;
  CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
  CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;
  CAN_FilterInitStructure.CAN_FilterNumber=0;
  CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;//32位寬的暫存器
  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_IRQChannelCmd=ENABLE;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//主優先順序為1
  NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;//次優先順序為0
  NVIC_Init(&NVIC_InitStructure);
#endif
  
  return 0; 
}
/*
接收中斷函式管理   目前沒有做其它事情
*/
#if CAN_RX0_INT_ENABLE
void USB_LP_CAN1_RX0_IRQHandler(void)
{
  CanRxMsg  RxMessage;
  CAN_Receive(CAN1,0,&RxMessage);
}
#endif
上面的一段程式是CAN的一個初始化,可以看到我把過濾器設定為遮蔽位模式,而且全部都是不去關心識別符號,也就是說所有的報文都可以通過濾波器組0進來了。 下面配置的是接收和傳送函式,不過這裡用的是32的庫做開發了,有興趣的也可以用暫存器開發了 羨慕 #include "CAN.H"
/*
can 傳送一組資料(固定格式為:ID  0X12,標準幀,資料幀)
len 資料長度
msg 資料指標   最多為八個位元組
返回值 0  表示成功
其它  表示失敗
*/
u8 Can_Send_Msg(u8* msg,u8 len)
{
  u8 mbox;
  u16 i=0;
  CanTxMsg TxMessage;
  TxMessage.StdId=0x12;   //標準識別符號為0
  TxMessage.ExtId=0x12;   //設定擴充套件識別符號(29位)
  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_Ok)&&(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;//返回接收到的資料長度
}
好了在此就講解完了CAN的通訊設計了。這裡我強調一點 CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);//讀取資料,這個函式在進行收據接收的時候,同時也對FIFO中的郵箱進行釋放。好了 。想繼續瞭解的同學們可以具體看看CAN的相關協議,或者讀一讀32的相關章節,還是很有幫助的。 再見 再見 再見 再見 再見