基於STM32的CAN匯流排通訊學習筆記

基於STM32的CAN匯流排通訊學習筆記

本文主要簡單介紹CAN匯流排的相關概念,以及通訊協議等知識,和使用STM32自帶的bxCAN外設進行CAN匯流排程式設計實驗,以及程式設計心得。


1. CAN匯流排簡要介紹

概念:CAN是控制器區域網絡(Controller Area Network, CAN)的簡稱,是由以研發和生產汽車電子產品著稱的德國BOSCH公司開發的,並最終成為國際標準(ISO 11898),是國際上應用最廣泛的現場匯流排之一。

—-源於百科
http://baike.baidu.com/link?url=yFY-S4Nsmiiacm3VTFN7P_q59sdPua0fJ8f9lKzyOeJz_1_smgqLJKoPXHtlYqZ0u9Zl2N5-bykZUs5N3EXAcNJvnQGyErZOYU9tOplfSC7

因此,其在分散式控制中有很大用途,尤其在區域網絡。

特點

  1. 多主控制
  2. 柔軟性
  3. 速度快,距離遠(最快1Mbps 可達10KM,當1Mbps時小於40m,速度越高,距離越遠)
  4. 檢測錯誤
  5. 故障封閉
  6. 連線節點多,可分散式控制

匯流排電平

採用差分訊號通訊,由CAN_H和CAN_L組成,電平分顯性電平和隱形電平。

1 顯性電平:CAN_H-CAN_L=2V左右,對應邏輯0。

2隱性電平:CAN_H-CAN_L=0V左右(看不見差別,故認為隱性),對應邏輯1。

通訊協議

1. 常用的幀型別:資料幀,遙控幀,過載幀,間隔幀。(資料幀和遙控幀常用,重點介紹資料幀的通訊協議)

2. 資料幀:

資料幀協議格式需瞭解清楚下面這張圖:

這裡寫圖片描述
資料幀主要分兩種格式:標準格式和擴充套件格式。區別在於,擴充套件格式比標準格式多18位的ID(ID見下講解),但實現的效果一樣。

每個段的解釋見下:(加粗的段為重點了解的段)

1)幀起始段:產生一個位的顯性電平,表示該幀開始。

2)仲裁段(ID段):ID的設定是為了區分資料幀的優先順序,優先順序越高的資料幀,會被優先接收處理。判斷優先順序的高低通過識別:從ID的最高位(MSB)開始判斷,若連續出現顯性電平(邏輯0)個數最多的,優先順序越高。

3)控制段:表資料幀裡資料段的位元組數

4)資料段:使用者需要傳送的資料內容,可一次性傳送0–8個位元組的資料。(每個資料佔用一個位元組)

5)CRC段:檢查幀傳輸錯誤。(檢查範圍:起始端,仲裁段,控制段,資料段)

6)ACK段:確認並響應是否正常接收

7)幀結束:由7個隱形位(邏輯1)組成,因此ID仲裁斷禁止出現1111111****形式的格式。

3. 遙控幀:請求指定ID傳送資料,跟資料幀格式相比少一個資料段。

位時序(波特率的設定)

波特率大和位時間有關,為位時間的倒數關係。

一個位分為4段:同步段,傳播時間段,相位緩衝段1,相位緩衝段2。每個段都是Tq的整數倍,通過設定每個段的Tq數可計算出:波特率=1/(n*Tq)。(可以不用詳細瞭解每個段,但需知道與波特率的關係)


2. STM32的bxCAN外設介紹

STM32提供很好bxCAN外設,專門用於CAN匯流排程式設計。提供的很多的封裝函式,提供了極大的便利,程式設計上大大減少時間,並易於理解。一般的103系列都有帶有一個bxCAN外設,互聯型的有2個bxCAN外設。

特點

由CAN_TX和CAN_RX兩條收發線組成,外電路可通過晶片JT1050的CAN收發晶片,轉換成CAN_H和CAN_L。

bxCAN模式選擇(加粗部分為最常見的)

  1. 工作模式:初始化模式,正常模式,睡眠模式
  2. 測試模式:靜默模式,環回模式,環回靜默模式

    靜默模式:只接不發。

    環回模式:不接收,但發的同時,不僅發給外裝置還自發自接。

    環回靜默模式:不接收,只能自發自接。

  3. 除錯模式。

bxCAN的ID篩選器(關鍵)

使用篩選器,可以篩選出想要接收的指定ID資料,遮蔽不想要的資料,通過設定還篩選器,接收到的資訊ID符合篩選器要求,那麼訊息將會被接收。一般STM32有14個篩選器,互聯型有28個篩選器。

篩選器的兩種工作模式

1. 遮蔽模式:即掩碼模式,通過設定暫存器:CAN_FxR1和CAN_FxR2(x指使用x號篩選器)。CAN_FxR1配置為期望收到的ID,CAN_FxR2為可選擇遮蔽不檢查不關心的ID位,即設定掩碼ID(0表不關心此位,1表關心此位)。

eg,舉例(使用篩選器0):若CAN_F0R1=0xFFFF0000,CAN_F0R2=0xFF00FF00,表示最好期望能收到ID為0xFFFF0000的資料,但是設定了CAN_F0R2=0xFF00FF00,因此只關心[31:24][15:8]位的ID ,其他位不關心,因此只要傳進來的ID為0xFFxx00xx,都可以接收。

2. 列表模式

列表模式沒有設定掩碼ID功能,因此CAN_F0R2充當CAN_F0R1使用,只要接受的ID符合CAN_F0R1或者CAN_F0R2都可以。

bxCAN的傳送和接收

1. 傳送:bxCAN有3個傳送郵箱,進行訊息的傳送。

2. 接收:bxCAN有兩個FIFO,每個FIFO有3個郵箱,通過設定哪個FIFO進行訊息接收,當有訊息時會分別依次存進每個郵箱,若郵箱的訊息沒有及時讀出,會出現溢位。

bxCAN的位時序(波特率設定)

上面的CAN概念簡單的介紹了波特率的設定,bxCAN將傳播時間段和相位緩衝時間段合併成一個段,因此只有3個段的位時間:tsjw,tb1,tb2。

另外波特率還跟bxCAN外設的時鐘匯流排頻率(fAPB1)以及分頻係數(brp)有關。波特率公式:Fpclk1/((tsjw tbs1 tbs2)*brp)

eg,舉例:一般地,bxCAN外設的時鐘匯流排頻率fAPB1=36Mhz(F4系列為42M)。設定tsjw=1,tb1=7,tb2=8,brp=5。
則:波特率=Fpclk1/((tsjw tbs1 tbs2)*brp) = 36M/(1 7 8)*5 = 450Kbps


3. STM32的bxCAN外設實驗(程式設計)

bxCAN初始化流程

  1. 引腳配置以及使能時鐘(APB1),其中CAN_RX引腳為上拉輸入,CAN_TX為複用輸出。
  2. 設定bxCAN模式(見上有講解)。
  3. 設定波特率(tsjw,tb1,tb2,brp)
  4. 設定濾波器。

bxCAN設定濾波器流程

  1. 選擇篩選器組號。
  2. 使用哪個FIFO(FIFO0或FIFO1)關聯到篩選器號(即用哪個FIFO進行接收訊息
  3. 設定篩選器模式以及需要篩選的ID

bxCAN傳送流程

  1. 選用哪種幀型別(一般可選:標準資料幀,擴充套件資料幀,遙控幀)
  2. 設定標準幀(StdId),擴充套件幀(ExtId)的ID,以及需要一次性傳送的資料長度(位元組數)

  3. 將要傳送的資料賦值給結構體成員(最多隻能賦值8個位元組的資料,每個資料1位元組),併傳送。

bxCAN接收流程

  1. 等待有訊息到達。
  2. 將接收的訊息(訊息為結構體型別)存於指定FIFO(有2個FIFO,每個FIFO下有3個郵箱)。
  3. 把訊息的資料提出。
  4. 將FIFO裡的訊息釋放,避免堆積。

程式例程

實驗內容:採用環回模式,過濾器採用掩碼模式,進行擴充套件資料幀的bxCAN自發自接,並將接收的資料傳送的電腦上位機顯示。(程式只貼上主要的內容)

    int main(void)
{
uint8_t  Data[8]="AJU8iK9a";//要傳送的資料,一次不能超過8位元組
CanRxMsg RecieveMess; //注意!不能定義為指標形否則會卡死在CAN接收函式!
char  Recievedata1[8]={0};
char* Recievedata = Recievedata1;
ALL_init();//時鐘,GPIO,串列埠,延時初始化(不貼上)
//can1 環回模式(即傳送資料同時還能給自己發,用於測試) 450Kbps波特率
CANInit(CAN1 , CAN_Mode_LoopBack ,CAN_SJW_1tq , CAN_BS1_7tq , CAN_BS2_8tq ,5);
printf("下面是CAN自測試(環回模式)\r\n");
while(1)
{
if(CAN_TX_data(Data , sizeof(Data)/sizeof(uint8_t)))//注意長度獲取不能在形參內去獲取,否則出錯
{
printf("傳送成功\r\n");
if(CAN_RX_data(RecieveMess , (u8*)Recievedata))
{
printf("接收到資料:%s\r\n",Recievedata);
}
else
{
printf("接收不到\r\n");
}
}
else
{
printf("傳送失敗\r\n");
}
delay_ms(100);
}
}

    /*
函式描述:can初始化配置(包括對時鐘和IO配置)
引數: CANx  CAN模式(環回模式和正常模式) 波特率有關的引數(tsjw,tbs1,tbs2,brp)
返回:初始化成功返回1,否則0
流程:
1:CAN初始化(環回模式和正常模式)
2:過濾器初始化(掩碼模式和列表模式)
CAN波特率計算方法:
CAN1位於APB1線上,時鐘36M
波特率=Fpclk1/((tsjw tbs1 tbs2)*brp) = 36M/(1 7 8)*5 = 450Kbps;
*/
char CANInit(CAN_TypeDef* CANx  ,u8 CAN_Mode_xyz ,u8 tsjw,u8 tbs1,u8 tbs2 ,u8 brp)
{
char StateFlag = 0;
CAN_InitTypeDef CAN_InitStruct;
CAN_FilterInitTypeDef CAN_FilterInitStruct;
//時鐘和複用IO口配置
if(CANx == CAN1)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1  , ENABLE);
GPIOInit(GPIOA , GPIO_Pin_11 , GPIO_Mode_IPU , 1 ); //can_RX   GPIO_Mode_IN_FLOATING
GPIOInit(GPIOA , GPIO_Pin_12 , GPIO_Mode_AF_PP ,2); //can_TX
}
if(CANx == CAN2)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB , ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN2  , ENABLE);
GPIOInit(GPIOB , GPIO_Pin_12 ,GPIO_Mode_IN_FLOATING ,1); //can_RX
GPIOInit(GPIOB , GPIO_Pin_13 , GPIO_Mode_AF_PP , 1 ); //can_TX
}
CAN_DeInit(CANx);
///////////////CAN引數初始化///////////////
CAN_InitStruct.CAN_ABOM = DISABLE;
CAN_InitStruct.CAN_AWUM = DISABLE;
CAN_InitStruct.CAN_Mode = CAN_Mode_xyz;//can模式   CAN_Mode_LoopBack;// CAN_Mode_Normal  
CAN_InitStruct.CAN_NART = DISABLE;
CAN_InitStruct.CAN_RFLM = DISABLE;
CAN_InitStruct.CAN_TTCM = DISABLE;
CAN_InitStruct.CAN_TXFP = DISABLE;  
CAN_InitStruct.CAN_Prescaler = brp; //分頻
CAN_InitStruct.CAN_SJW = tsjw;//CAN_SJW_1tq;//同步時間  Tq
CAN_InitStruct.CAN_BS1 = tbs1;//CAN_BS1_1tq;
CAN_InitStruct.CAN_BS2 = tbs2;//CAN_BS2_4tq;
StateFlag = CAN_Init(CANx, &CAN_InitStruct);//初始化成功返回1
///////////過濾器初始化(掩碼模式)////////////
CAN_FilterInitStruct.CAN_FilterActivation = ENABLE;//使能過濾器
CAN_FilterInitStruct.CAN_FilterNumber = 0;  //過濾器號,可選0--13(F103)
CAN_FilterInitStruct.CAN_FilterFIFOAssignment = CAN_FilterFIFO0; //使用FIFO0,過濾器0關聯到FIFO0(可選FIFO0和FIFO1)
//能通過的標準ID號
CAN_FilterInitStruct.CAN_FilterIdHigh =0xABCDEF98>>16;//0xABC<<4;//;  //標準ID不能為:1111111xxxx型別
CAN_FilterInitStruct.CAN_FilterIdLow =0xABCDEF98&0x0000FFF8;// 0x00 ;
//接收的ID號需要嚴格檢測的位,該位不符合標準ID號相應的位,則不讓通過
CAN_FilterInitStruct.CAN_FilterMaskIdHigh = 0xFFFF ;//0x0000; //0表此位不關心
CAN_FilterInitStruct.CAN_FilterMaskIdLow = 0xFFF8 & 0xFFF8;//擴充套件幀下,掩碼模式只能關心前29位,後3位不能關心
CAN_FilterInitStruct.CAN_FilterMode = CAN_FilterMode_IdMask;  //過濾器為掩碼模式   //CAN_FilterMode_IdList為列表模式
;
CAN_FilterInitStruct.CAN_FilterScale = CAN_FilterScale_32bit;//過濾器為32位
CAN_FilterInit(&CAN_FilterInitStruct); 
return StateFlag;
}

    /*
函式描述:can資料傳送
引數: 傳送的資料
返回:傳送成功返回1
說明: 傳送資料得配置傳送資料的引數,將資料和相關引數寫入結構體再傳送
傳送引數配置流程:
1:選擇幀型別(標準資料幀 ,擴充套件資料幀,遙控幀)
(標準幀ID11位 擴充套件幀ID29位  遙控幀:只有ID無資料,請求指定ID發資料)
2:寫入ID
3:寫入資料(幀資料總長度64位,可最多一次性寫入8個資料,每個資料只佔1位元組)
4:通過結構體把資料及ID引數發出去,並自動返回發出的郵箱號(傳送郵箱一共有3個)
5:等待傳送成功
*/
char CAN_TX_data(u8 *TXdata ,u8 DataLen)
{
int i=0;
u8 mailbox;
CanTxMsg TxMessage;
uint8_t TXdata1[8]={0};
//設定為標準資料幀   (還有擴充套件資料幀  遙控幀)
TxMessage.RTR = CAN_RTR_Data;//資料幀
TxMessage.IDE =CAN_Id_Extended;//CAN_Id_Standard// CAN_Id_Standard;//使用標準幀id
TxMessage.StdId = 0xABC>>1;//0x12;//標準幀ID
TxMessage.ExtId = 0xABCDEF98>>3;//0x12;//擴充套件幀ID
TxMessage.DLC = DataLen;//sizeof(TXdata)/sizeof(uint8_t); //需要一次性傳送的資料個數(不超過8個)
for(i =0 ;i < DataLen ; i  )//
{
TxMessage.Data[i] = TXdata[i];//Data個數小於8個,並且每個資料大小為1位元組
//      printf("%d ",sizeof(TXdata));
}
mailbox = CAN_Transmit(CAN1, &TxMessage);//將訊息傳送出去。返回值為傳送出去的郵箱號
//等待傳送成功
i = 0xfff;
while(CAN_TransmitStatus(CAN1, mailbox) != CAN_TxStatus_Ok &&i) //獲取該郵箱號的傳送成功與否標誌,一定的延遲防止死迴圈
{
i--;
}
if(i==0)  return  0;  //傳送失敗 ,返回0
else  return 1;  //傳送成功,返回1
//
}

    /*
函式描述:can資料接收
引數: 接收的資料和引數的結構體   接收的資料部分
返回:接收成功返回1
說明:接收資料存於結構體中,應對結構體進行解析讀取。
接收流程:
1:等待有訊息到達
2:將接收的訊息(訊息為結構體型別)存於指定FIFO(有2個FIFO,每個FIFO下有3個郵箱)
3:把訊息的資料提出
4:將FIFO裡的訊息釋放,避免堆積。
注意:函式定義形參:CanRxMsg  RecieveData; 應該為非指標形。否則會出現多一個字元的亂碼現象。
*/
char CAN_RX_data(CanRxMsg  RecieveData , uint8_t *RXdata)
{
//  CanRxMsg  RecieveData1;
int i = 0xfff;
if(!CAN_MessagePending(CAN1, CAN_FIFO0)) //注意:CAN_FIFO0,不是CAN_FilterFIFO0
{
return  0;//沒有資料接收 ,返回0
}
CAN_Receive(CAN1, CAN_FIFO0 , &RecieveData);//接收FIOFO_0下的郵箱(CAN1有兩個FIFO,每個FIFO有3級郵箱)
//把這次接收所有資料都提取並存起來
for(i=0; i<RecieveData.DLC ;i  )
{
RXdata[i] =RecieveData.Data[i];
}
//  printf("長度%d ",RecieveData.DLC);
CAN_FIFORelease(CAN1, CAN_FilterFIFO0);  //釋放FIFO_0郵箱的訊息,以便接收新訊息
return 1;  //接收成功,返回1
}

程式設計除錯心得(總結一些知識要點)

1 對CAN_RX_data的函式定義

  1. 如果函式定義成形式:char CAN_RX_data(CanRxMsg* RecieveData , uint8_t *RXdata);會出現如下反應:
    i:如果 CanRxMsg RecieveMess; 放在CAN_RX_data函式外,即主函式裡,將會多列印出一字元:”接收到資料:AED9i8ua”(會多接收到一個的亂碼)
    ii:如果 CanRxMsg RecieveMess; 放在函式內,顯示正常:”接收到資料:AED9i8ua”

  2. 如果函式定義成形式:char CAN_RX_data(CanRxMsg RecieveData , uint8_t *RXdata);,即:RecieveData為非指標。
    無論CanRxMsg RecieveMess; 在函式內還是函式外不影響。

    分析原因:和形參的指標有關(形參應該為非指標形式)。具體詳細原因未解。

2 知識難點(針對過濾器(篩選器)的理解與配置):

  1. 如果是接收的資料是標準幀格式:
    標準幀ID佔用位數為11位, 在傳送函式中設定的標準幀ID(StdId)只需為低11位賦值即可,另外高5位可任意。

    過濾器的ID號與接收的標準幀ID是左對齊形式(即32位與11位的左對齊),因此過濾器的ID號的高11位有過濾的效果,其他位可設任意值。

    舉例,如:傳送函式配置的標誌幀ID:StdId=0xFA8B;則標準幀ID= 010 1000 1011(取最低的11位)
    如果在掩碼模式的所有位都在檢測的情況下,那麼過濾器ID號高11位和標準幀ID應該一樣,
    可以取:CAN_FilterIdHigh=0x516F=010 1000 1011 01111 ( CAN_FilterIdLow 任意)

  2. 同理,如果是接收的資料是擴充套件幀格式:
    標準幀ID佔用位數為29位,只需對ExtId的低29位賦值即可。
    過濾時和過濾ID好也是左對齊,因此過濾器的ID號的高29位有過濾的效果,其他位可設任意值。

3 對過濾器(篩選器)配置方法的改進:

改進:由於以上給幀ID和過濾器ID賦值格式不統一,也不容易計算。為了統一併方便觀察,
對取標準/擴充套件幀ID和過濾器ID的賦值進行如下改進。(最嚴格情況:掩碼模式對所有位都要關心)

  1. 舉例(標準幀),如程式可設定標準幀ID巨集定義為:0xABC(取前11位,最後一位必須取0,不作為標準ID位)。但是,將其寫入StdId時,需右移動一位,取出高11位作為有效位
    StdId = 0xABC>>1; //(取出11位)
    CAN_FilterIdHigh= 0xABC<<4; //11個有效位移動到最左端(使32位過濾ID與11位標準幀的左對齊)

  2. 舉例(擴充套件幀):如程式可設定擴充套件幀ID巨集定義為:0xABCDEF98(取前29位,最後3位必須取0,不作為標準ID位),但是,將其寫入ExtId為時,需右移動3位,取出高29位作為有效位
    ExtId = 0xABCDEF98>>3;(取出29位)
    CAN_FilterIdHigh = 0xABCDEF98>>16;
    CAN_FilterIdLow = 0xABCDEF98&0x0000FFF8 ;//29個有效位移到最高位(使32位過濾ID與29位標準幀的左對齊)

  3. 需要注意:
    在標準幀下,對於32位的過濾器,設定掩碼ID只能關心高11位,後25位不能關心。(掩碼ID:0表不關心此位,1表關心此位
    在擴充套件幀下,設定掩碼ID只能關心高29位,後3位不能關心。


  • 以上如有解釋不周到的,望有識之士給予指教!