基於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位不能關心。


  • 以上如有解釋不周到的,望有識之士給予指教!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章