stm32 can總線學習筆記 can總線協議基礎 stm32 位時序配置 標準幀與拓展幀 stm32 CAN id過濾器 接收數據 NORMAL和LOOPBACK模式 NORMAL模式下收不到數據

這段時間折騰stm32與樹莓派之間的can總線通訊遇到了不少問題,樹莓派那端的已經寫在樹莓派外掛MCP2515模塊爬坑記錄裏面了。這次來總結下CAN總線協議和講講stm32如何使用CAN總線。

can總線協議基礎

首先我們來大概看看CAN總線協議是怎樣的。

完整的CAN電路是由CAN控制器和CAN收發器組成的。協議相關的內容由CAN控制器完成。CAN 控制器和CAN收發器用CAN TX和CAN RX兩根線傳輸TTL電平信號。低電平代表二進制的0,高電平代表二進制的1。

CAN H 和CAN L就是CAN總線,所有設備的CAN 收發器都會掛在這兩根線上。數據通過差分信號在這兩個線間傳輸:

  • CAN H - CAN L < 0.5V 表示二進制的1
  • CAN H - CAN L > 0.9V 表示二進制的0

爲了避免信號的反射和干擾,還需要在CAN H和CAN L之間接上120歐姆的終端電阻。

CAN收發器將CAN控制器通過CAN TX線傳來的二進制碼流轉換爲差分信號用CAN H、CAN L兩根線發送出去。同時接收端設備的CAN接收器會監聽CAN H、CAN L兩根線的差分信號,轉換成二進制碼流通過CAN RX線傳給CAN控制器

位時序

can總線並沒有主從之分,當can總線上的一個設備發送數據時,它以廣播的形式在can總線上發送報文給所有的設備。其他設備通過過濾報文的id,處理自己感興趣的報文。

由於CAN通訊協議並沒有時鐘信號線,所以要求發送端與接收端的波特率是一致的,而can總線的數據發送效率需要我們去自定義。

要設置設置速率,我先要了解CAN的位時序概念。CAN協議把每個bit分成了四個時間段。我們可以用示波器在CAN TX或者CAN RX上量到下面的10101的波形,把每個bit的波形放大其實會有四段時間:

  • ss : 同步段(Synchronization Segment)固定爲1Tq
  • pts : 傳播段(Propagation Time Segment)1~8Tq
  • pbs1 : 相位緩衝段1(Phase Buffer Segment 1)1~8Tq
  • pbs2 : 相位緩衝段2(Phase Buffer Segment 2)2~8Tq

每個段的時長單位是Tq(Time Quantum),這個Tq可以由我們去設置,例如設置爲1000ns。

stm32 位時序配置

上面的是標準的CAN總線位時序,具體每一段的意義我沒有深入去了解,但是對於使用來講並不重要。而stm32裏面將pts和pbs1合併了,所以它剩下了三段:

  • ss : Synchronization Segment固定爲1Tq
  • ts1 : Time segment 1, 即 pts + pbs1
  • ts2 : Time segment 2, 即pbs2

由於ss固定爲1Tq,所以我們在STM32CubeMX裏面可以設置的是Tq、ts1和ts2:

Tq並不能直接設置,要通過Prescaler設置分頻去設置。

例如我們將can的時鐘頻率設置爲36MHz:

所以Prescaler設置成36的時候Tq可以這樣計算:
1Tq = 36 MHz / 36 = 1 MHz = 1000 ns

這個時候我們就能去計算一個bit的時間了,如上圖我們把ts1設置爲4,ts2設置爲5,再加上ss固定的1Tq:

1Tq + 4Tq + 5Tq = 10 Tq = 10000 ns = 10 us

波特率爲 1 s / 10 us = 100k

於是我們可以在樹莓派中設置CAN的波特率爲100k:

sudo ip link set can0 up type can bitrate 100000

當然也可以設置每一段的時間:

sudo ip link set can0 up type can tq 1000 prop-seg 3 phase-seg1 1 phase-seg2 5 sjw 4

prop-seg和phase-seg1加起來等於ts1即可。當然有同學會看到還有另外一個sjw(ReSynchronization Jump Width)的參數,這個時間是用於同步的不影響波特率,範圍是1~4Tq,我這裏設置成4Tq。

標準幀與拓展幀

如此配置之後樹莓派就能接收到stm32通過CAN總線發送的數據了,發送的代碼如下:

HAL_StatusTypeDef Can_TxMessage(uint8_t ide,uint32_t id,uint8_t len,uint8_t *data)
{
    uint32_t   TxMailbox;
    CAN_TxHeaderTypeDef CAN_TxHeader;
    HAL_StatusTypeDef   HAL_RetVal;
    uint16_t i=0;
    if(ide == 0)
    {
        CAN_TxHeader.IDE = CAN_ID_STD;
        CAN_TxHeader.StdId = id;
    }
    else
    {
        CAN_TxHeader.IDE = CAN_ID_EXT;
        CAN_TxHeader.ExtId = id;
    }
    CAN_TxHeader.DLC = len;
    CAN_TxHeader.RTR = CAN_RTR_DATA;
    CAN_TxHeader.TransmitGlobalTime = DISABLE;
    while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0)
    {
        i++;
        if(i>0xfffe)
        {
            return HAL_ERROR;
        }
    }
    HAL_Delay(500);
    HAL_RetVal = HAL_CAN_AddTxMessage(&hcan,&CAN_TxHeader,data,&TxMailbox);
    if(HAL_RetVal != HAL_OK)
    {
        return HAL_ERROR;
    }
    return HAL_OK;
}

// 發送數據
uint8_t data[8]={170,170,170,170,170,170,170,170};
Can_TxMessage(0,0x222,8,data);

Can_TxMessage的第一個參數可以配置CAN報文是標準幀還是拓展幀。它們其實基本只有id的長度不一樣而已。這個id就是上面我們提到的用於過濾CAN廣播的標識符。
標準幀的id有11位,這11位被命名爲STDID。拓展幀在標準幀的基礎上增加了18位所以有29位,這個拓展的18位被命名爲EXID。

stm32 CAN id過濾器

stm32 提供了一組過濾器,可以用於過濾CAN報文,只要符合某一個過濾器的規則,該報文即被接收。

過濾器過濾報文有兩種模式: 列表模式與掩碼模式

掩碼模式

掩碼模式下我們需要配置屏蔽寄存器和標識符寄存器,屏蔽寄存器用於配置需要匹配的CAN id的比特位。屏蔽碼寄存器某位爲1表示接收到的CAN ID對應的位必須和標識符寄存器對應的位相同。

例如我們將屏蔽碼寄存器配置爲0xF,意味着我們只關心CAN ID二進制的後4位,此時再將標識符寄存器配置爲0xa,意味着所有二進制後四位爲1010的CAN ID都能能被接收(例如0xa/0xaa/0xffa等)。

0000 0000 ffff  # 掩碼寄存器
0000 0000 1010  # 標識符寄存器
--------------
0000 0000 1010  # 0xa
0000 1010 1010  # 0xaa
1111 1111 1010  # 0xffa

原理是這個原理,但是是stm32的配置還是需要了解一下的。雖然CAN報文的id長度只有標準幀的11位或者拓展幀的29位,但是stm32中卻是用了16位寬或者32位寬的寄存器去保存掩碼和標識符。所以會有除了id和mask之外還會有其他的位需要配置。

32位寬的掩碼模式

我們先來看下面這附圖,它說明了32位寬的掩碼模式的寄存器每一位的作用:

id和mask皆由4個字節組成,第一個字節存放了STDID的10~3bit,第二個字節放了STDID的2~0bit還有EXID的17~13bit,第三個字節放了EXID的12~5bit,第四個字節放了EXID的4~0bit、IDE(擴展幀標識)、RTR(遠程幀標誌)和一個預留的0。

我們在代碼中通過FilterMaskIdHigh、FilterIdLow、FilterMaskIdHigh、FilterMaskIdLow去設置掩碼和標識符:

uint32_t ext_id =0xa;
uint32_t mask =0xf;

CAN_FilterTypeDef CAN_FilterType;

// 過濾器的id,STM32F072RBTx提供了14個過濾器所以id可以配置成0~13
CAN_FilterType.FilterBank=0;

// 設置位寬爲32位
CAN_FilterType.FilterScale=CAN_FILTERSCALE_32BIT;

// 設置爲掩碼模式
CAN_FilterType.FilterMode=CAN_FILTERMODE_IDMASK;

// 設置前兩個字節的STDID[10:3]、STDID[2:0]、EXID[17:13]
CAN_FilterType.FilterIdHigh=((ext_id<<3) >>16) &0xffff;

// 設置後兩個字節的EXID[12:5]、EXID[4:0]、IDE、RTR、預留的一個0
CAN_FilterType.FilterIdLow=(ext_id<<3) | CAN_ID_EXT;

// 設置掩碼前兩個字節,左移3位再或CAN_ID_EXT是因爲最後的三位並不是ID,而是IDE、RTR和預留的0
CAN_FilterType.FilterMaskIdHigh=((mask<<3|CAN_ID_EXT)>>16)&0xffff;

// 設置掩碼後兩個字節,左移3位再或CAN_ID_EXT是因爲最後的三位並不是ID,而是IDE、RTR和預留的0
CAN_FilterType.FilterMaskIdLow=(mask<<3|CAN_ID_EXT)&0xffff;

// 將消息放到FIFO0這個隊列裏
CAN_FilterType.FilterFIFOAssignment=CAN_RX_FIFO0;

// 激活過濾器
CAN_FilterType.FilterActivation=ENABLE;

// 設置過濾器
if(HAL_CAN_ConfigFilter(&hcan,&CAN_FilterType)!=HAL_OK)
{
  Error_Handler();
}

16位寬的掩碼模式

16位寬的寄存器示意圖如下:

id和mask都是兩個字節,但是真正使得標準id起作用的只有第一個字節和第二個自己的前3位。這裏各只用了兩個字節,也就是說一個過濾器可以設置兩組id和mask,FilterMaskIdHigh和FilterMaskIdHigh一組FilterIdLow和FilterMaskIdLow一組:

uint32_t std_id1 =0xa;
uint32_t mask1 = 0xf;
uint32_t std_id2 =0xbb;
uint32_t mask2 = 0xff;

CAN_FilterTypeDef CAN_FilterType;

// 過濾器的id,STM32F072RBTx提供了14個過濾器所以id可以配置成0~13
CAN_FilterType.FilterBank=0;

// 設置位寬爲16位
CAN_FilterType.FilterScale=CAN_FILTERSCALE_16BIT;

// 設置爲掩碼模式
CAN_FilterType.FilterMode=CAN_FILTERMODE_IDMASK;

// 設置第一組的id,左移5位是因爲最後的5bit是RTR、IDE和EXID[17:15]
CAN_FilterType.FilterIdHigh=(std_id1<<5) | CAN_ID_STD;

// 設置第一組的mask,左移5位是因爲最後的5bit是RTR、IDE和EXID[17:15]
CAN_FilterType.FilterMaskIdHigh= ((mask1<<5)|CAN_ID_STD)

// 設置第二組的id,左移5位是因爲最後的5bit是RTR、IDE和EXID[17:15]
CAN_FilterType.FilterIdLow=(std_id2<<5)|CAN_ID_STD;

// 設置第二組的mask,左移5位是因爲最後的5bit是RTR、IDE和EXID[17:15]
CAN_FilterType.FilterMaskIdLow=(mask2<<5|CAN_ID_STD);

// 將消息放到FIFO0這個隊列裏
CAN_FilterType.FilterFIFOAssignment=CAN_RX_FIFO0;

// 激活過濾器
CAN_FilterType.FilterActivation=ENABLE;

// 設置過濾器
if(HAL_CAN_ConfigFilter(&hcan,&CAN_FilterType)!=HAL_OK)
{
  Error_Handler();
}

列表模式

列表模式意味着我們將想要接收的CAN id直接配置到過濾器。

32位寬的列表模式

32位寬的列表模式下,可以設置兩個id,FilterMaskIdHigh和FilterMaskIdHigh一個,FilterIdLow和FilterMaskIdLow一個:

uint32_t ext_id1 =0xa;
uint32_t ext_id2 =0xbb;

CAN_FilterTypeDef CAN_FilterType;

// 過濾器的id,STM32F072RBTx提供了14個過濾器所以id可以配置成0~13
CAN_FilterType.FilterBank=0;

// 設置位寬爲32位
CAN_FilterType.FilterScale=CAN_FILTERSCALE_32BIT;

// 設置爲列表模式
CAN_FilterType.FilterMode=CAN_FILTERMODE_IDLIST;

// 設置第一個id的高字節,左移三位是因爲最後的三位是IDE、RTR和預留的0
CAN_FilterType.FilterIdHigh=((ext_id1<<3)>>16)&0xffff;

// 設置第一個id的低字節,左移三位是因爲最後的三位是IDE、RTR和預留的0
CAN_FilterType.FilterIdLow=((ext_id1<<3)&0xffff)|CAN_ID_EXT;

// 設置第二個id的高字節,左移三位是因爲最後的三位是IDE、RTR和預留的0
CAN_FilterType.FilterMaskIdHigh=((ext_id2<<3)>>16)&0xffff;

// 設置第二個id的低字節,左移三位是因爲最後的三位是IDE、RTR和預留的0
CAN_FilterType.FilterMaskIdLow=((ext_id2<<3)&0xffff)|CAN_ID_EXT;

// 將消息放到FIFO0這個隊列裏
CAN_FilterType.FilterFIFOAssignment=CAN_RX_FIFO0;

// 激活過濾器
CAN_FilterType.FilterActivation=ENABLE;

// 設置過濾器
if(HAL_CAN_ConfigFilter(&hcan,&CAN_FilterType)!=HAL_OK)
{
  Error_Handler();
}

16位寬的列表模式

16位寬的列表模式下,可以設置四個id,FilterMaskIdHigh、FilterMaskIdHigh、FilterIdLow和FilterMaskIdLow各一個:

uint16_t ext_id1 =0xa;
uint16_t ext_id2 =0xb;
uint16_t ext_id3 =0xc;
uint16_t ext_id4 =0xd;

CAN_FilterTypeDef CAN_FilterType;

// 過濾器的id,STM32F072RBTx提供了14個過濾器所以id可以配置成0~13
CAN_FilterType.FilterBank=0;

// 設置位寬爲16位
CAN_FilterType.FilterScale=CAN_FILTERSCALE_16BIT;

// 設置爲列表模式
CAN_FilterType.FilterMode=CAN_FILTERMODE_IDLIST;

// 設置第一個id,左移五位是因爲最後的五位是RTR、IDE和EXID[17:15]
CAN_FilterType.FilterIdHigh=(ext_id1<<5)|CAN_ID_STD;

// 設置第二個id,左移五位是因爲最後的五位是RTR、IDE和EXID[17:15]
CAN_FilterType.FilterIdLow=(ext_id2<<5)|CAN_ID_STD;

// 設置第三個id,左移五位是因爲最後的五位是RTR、IDE和EXID[17:15]
CAN_FilterType.FilterMaskIdHigh=(ext_id3<<5)|CAN_ID_STD;

// 設置第四個id,左移五位是因爲最後的五位是RTR、IDE和EXID[17:15]
CAN_FilterType.FilterMaskIdLow=(ext_id4<<5)|CAN_ID_STD;

// 將消息放到FIFO0這個隊列裏
CAN_FilterType.FilterFIFOAssignment=CAN_RX_FIFO0;

// 激活過濾器
CAN_FilterType.FilterActivation=ENABLE;

// 設置過濾器
if(HAL_CAN_ConfigFilter(&hcan,&CAN_FilterType)!=HAL_OK)
{
  Error_Handler();
}

接收數據

我們可以看到設置過濾器的時候,會配置將過濾出來的數據放到FIFO0這個隊裏裏面:

// 將消息放到FIFO0這個隊列裏
CAN_FilterType.FilterFIFOAssignment=CAN_RX_FIFO0;

然後我們還有兩步需要操作:

  1. 激活這個隊列的通知
if(HAL_CAN_ActivateNotification(&hcan,CAN_IT_RX_FIFO0_MSG_PENDING)!=HAL_OK)
{
    Error_Handler();
}
  1. 在STM32CubeMX中使能CAN的接收中斷:

然後就能重寫HAL_CAN_RxFifo0MsgPendingCallback函數去處理接收的數據了:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    printf("HAL_CAN_RxFifo0MsgPendingCallback\r\n");
    CAN_RxHeaderTypeDef CAN_RxHeader;
    HAL_StatusTypeDef HAL_Retval;
    uint8_t Rx_Data[8];
    uint8_t Data_Len = 0;
    uint32_t ID = 0;
    uint8_t i;
    HAL_Retval = HAL_CAN_GetRxMessage(hcan,CAN_RX_FIFO0,&CAN_RxHeader,Rx_Data);
    if(HAL_Retval == HAL_OK)
    {
        Data_Len = CAN_RxHeader.DLC;
        if(CAN_RxHeader.IDE)
        {
            ID = CAN_RxHeader.ExtId;
        }
        else
        {
            ID = CAN_RxHeader.StdId;
        }
        printf("id:%x\r\n",ID);
        printf("Data_Len:%d\r\n",Data_Len);
        for(i=0;i<8;i++)
        {
            printf("Rx_Data[%d]=%x\r\n",i,Rx_Data[i]);  
        }
    }
}

NORMAL和LOOPBACK模式

正常模式下設備是收不到自己發送的報文的,我們可以設置LOOPBACK模式實現自發自收,但是注意該模式只用於調試,此時報文其實不會在總線上傳播,所以其他設備是收不到發送的報文的:

//hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.Mode = CAN_MODE_LOOPBACK;

NORMAL模式下收不到數據

我在調CAN總線的最後遇到了這樣一個問題:LOOPBACK模式下能收到自己發的數據,但NORMAL模式下收不到樹莓派發送的數據。

通過示波器測量: stm32的CAN RX可以量到樹莓派發送的數據波形了,波特率是100k,甚至CAN TX都量到有stm32響應的波形。

而且有個奇怪的現象,stm32在loopback下發數據,CAN TX可以量到完整的數據波形,但是如果改成NORMAL模式,就只能量到一個bit的數據。

於是我懷疑是收發器哪裏出問題了,導致發送失敗,在接收到數據的時候由於發送響應失敗,導致接收的流程也斷掉了,沒有去到回調函數那裏。

最終定位到收發器TJA1042T的STB腳沒有拉低,導致收發器處於Standby模式:

除了這個原因之外在網上也有看到這種情況的其他可能原因:

  1. 迴環下應該與GPIO無關

  2. GPIO是否初始化正確,時鐘啓用

  3. 是否複用,AFIO時鐘是否啓用

  4. 迴環下是否有CAN_Tx應該有輸出

  5. 終端電阻是否有

  6. CAN收發器電路電壓是否正常

  7. 波特率是否標準

  8. 換塊板試一下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章