天天看點

基于STM32的CAN總線通信學習筆記基于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

. 資料幀:

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

基于STM32的CAN總線通信學習筆記基于STM32的CAN總線通信學習筆記

資料幀主要分兩種格式:标準格式和擴充格式。差別在于,擴充格式比标準格式多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[]="AJU8iK9a";//要發送的資料,一次不能超過8位元組

        CanRxMsg RecieveMess; //注意!不能定義為指針形否則會卡死在CAN接收函數!
        char  Recievedata1[]={};
        char* Recievedata = Recievedata1;

        ALL_init();//時鐘,GPIO,序列槽,延時初始化(不粘貼)

        //can1 環回模式(即發送資料同時還能給自己發,用于測試) 450Kbps波特率
        CANInit(CAN1 , CAN_Mode_LoopBack ,CAN_SJW_1tq , CAN_BS1_7tq , CAN_BS2_8tq ,);

        printf("下面是CAN自測試(環回模式)\r\n");

        while()
        {

            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();

        }
    }
           
/*
    函數描述: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 = ;
        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 ,  ); //can_RX   GPIO_Mode_IN_FLOATING
            GPIOInit(GPIOA , GPIO_Pin_12 , GPIO_Mode_AF_PP ,); //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 ,); //can_RX
            GPIOInit(GPIOB , GPIO_Pin_13 , GPIO_Mode_AF_PP ,  ); //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--13(F103)
        CAN_FilterInitStruct.CAN_FilterFIFOAssignment = CAN_FilterFIFO0; //使用FIFO0,過濾器0關聯到FIFO0(可選FIFO0和FIFO1)

        //能通過的标準ID号
        CAN_FilterInitStruct.CAN_FilterIdHigh =>>;//0xABC<<4;//;  //标準ID不能為:1111111xxxx類型
        CAN_FilterInitStruct.CAN_FilterIdLow =&;// 0x00 ;

    //接收的ID号需要嚴格檢測的位,該位不符合标準ID号相應的位,則不讓通過
        CAN_FilterInitStruct.CAN_FilterMaskIdHigh =  ;//0x0000; //0表此位不關心
        CAN_FilterInitStruct.CAN_FilterMaskIdLow =  & ;//擴充幀下,掩碼模式隻能關心前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=;
        u8 mailbox;
        CanTxMsg TxMessage;
        uint8_t TXdata1[]={};

        //設定為标準資料幀   (還有擴充資料幀  遙控幀)
        TxMessage.RTR = CAN_RTR_Data;//資料幀
        TxMessage.IDE =CAN_Id_Extended;//CAN_Id_Standard// CAN_Id_Standard;//使用标準幀id

        TxMessage.StdId = >>;//0x12;//标準幀ID
        TxMessage.ExtId = >>;//0x12;//擴充幀ID
        TxMessage.DLC = DataLen;//sizeof(TXdata)/sizeof(uint8_t); //需要一次性發送的資料個數(不超過8個)


        for(i = ;i < DataLen ; i++)//
        {
            TxMessage.Data[i] = TXdata[i];//Data個數小于8個,并且每個資料大小為1位元組
    //      printf("%d ",sizeof(TXdata));
        }

        mailbox = CAN_Transmit(CAN1, &TxMessage);//将消息發送出去。傳回值為發送出去的郵箱号


        //等待發送成功
        i = ;
        while(CAN_TransmitStatus(CAN1, mailbox) != CAN_TxStatus_Ok &&i) //擷取該郵箱号的發送成功與否标志,一定的延遲防止死循環
        {
        i--;
        }

        if(i==)  return  ;  //發送失敗 ,傳回0
        else  return ;  //發送成功,傳回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 = ;

        if(!CAN_MessagePending(CAN1, CAN_FIFO0)) //注意:CAN_FIFO0,不是CAN_FilterFIFO0
        {
            return  ;//沒有資料接收 ,傳回0
        }

        CAN_Receive(CAN1, CAN_FIFO0 , &RecieveData);//接收FIOFO_0下的郵箱(CAN1有兩個FIFO,每個FIFO有3級郵箱)

        //把這次接收所有資料都提取并存起來
        for(i=; i<RecieveData.DLC ;i++)
        {
            RXdata[i] =RecieveData.Data[i];
        }

    //  printf("長度%d ",RecieveData.DLC);

        CAN_FIFORelease(CAN1, CAN_FilterFIFO0);  //釋放FIFO_0郵箱的消息,以便接收新消息

        return ;  //接收成功,傳回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位不能關心。

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

繼續閱讀