DSP28377S_CAN通訊

Captain cook發表於2020-10-22

CAN通訊的由來

為適應“減少線束的數量”、“通過多個LAN,進行大量資料的高速通訊”的需要,1986 年德國電氣商博世公司開發出面向汽車的CAN 通訊協議。CAN屬於現場匯流排的範疇,它是一種有效支援分散式控制或實時控制的序列通訊網路。

CAN通訊格式

CAN通訊共有5種,分別為資料幀、遙控幀、錯誤幀、過載幀、幀間隔。資料幀格式由下圖所示,分為標準格式和擴充格式,筆者目前僅使用到標準資料幀,使用其中的64bits資料段進行CAN節點間的資料互動,以ID號區分資料型別。注意ACK!實際波形中彼己電平高度不一
在這裡插入圖片描述

CAN通訊配置

①初始化CAN的對映GPIO,使用TI封裝好的函式初始化CAN,選擇CAN時鐘源,設定波特率,使能CAN中斷觸發源,開啟CAN;

void InitCana(void)
{
    InitCanaGpio();

    //
    // Initialize the CAN controller
    //
    CANInit(CANA_BASE);

    //
    // Setup CAN to be clocked off the M3/Master subsystem clock
    //
    CANClkSourceSelect(CANA_BASE, 0);

    //
    // Set up the bit rate for the CAN bus.  This function sets up the CAN
    // bus timing for a nominal configuration.  You can achieve more control
    // over the CAN bus timing by using the function CANBitTimingSet() instead
    // of this one, if needed.
    // In this example, the CAN bus is set to 500 kHz.  In the function below,
    // the call to SysCtlClockGet() is used to determine the clock rate that
    // is used for clocking the CAN peripheral.  This can be replaced with a
    // fixed value if you know the value of the system clock, saving the extra
    // function call.  For some parts, the CAN peripheral is clocked by a fixed
    // 8 MHz regardless of the system clock in which case the call to
    // SysCtlClockGet() should be replaced with 8000000.  Consult the data
    // sheet for more information about CAN peripheral clocking.
    //
    CANBitRateSet(CANA_BASE, 200000000, 200000);    // 波特率200kbps

    //
    // Enable interrupts on the CAN peripheral.  This example uses static
    // allocation of interrupt handlers which means the name of the handler
    // is in the vector table of startup code.  If you want to use dynamic
    // allocation of the vector table, then you must also call CANIntRegister()
    // here.
    //
    CANIntEnable(CANA_BASE, CAN_INT_MASTER | CAN_INT_ERROR | CAN_INT_STATUS);

    //
    // Enable the CAN for operation.
    //
    CANEnable(CANA_BASE);

    //
    // Enable CAN Global Interrupt line0
    //
    CANGlobalIntEnable(CANA_BASE, CAN_GLB_INT_CANINT0);
}

②確定收發快取陣列、ID號;

//  CAN通訊
unsigned char ucRXMsgData1[8];   // CAN接受資料
unsigned char ucTXMsgData2[8];   // CAN傳送資料
unsigned char ucTXMsgData3[8];   // CAN傳送資料
unsigned char ucTXMsgData4[8];   // CAN傳送資料
unsigned char ucTXMsgData5[8];   // CAN傳送資料
unsigned char ucTXMsgData6[8];   // CAN傳送資料

tCANMsgObject sRXCANMessage1 = {RX_MSG_OBJ_ID1, 0, 2, 8, ucRXMsgData1};    // CAN接收結構體  MSG_OBJ_RX_INT_ENABLE = 2
tCANMsgObject sTXCANMessage2 = {TX_MSG_OBJ_ID2, 0, 1, 8, ucTXMsgData2};    // CAN傳送結構體  MSG_OBJ_TX_INT_ENABLE = 1
tCANMsgObject sTXCANMessage3 = {TX_MSG_OBJ_ID3, 0, 1, 8, ucTXMsgData3};    // CAN傳送結構體  MSG_OBJ_TX_INT_ENABLE = 1
tCANMsgObject sTXCANMessage4 = {TX_MSG_OBJ_ID4, 0, 1, 8, ucTXMsgData4};    // CAN傳送結構體  MSG_OBJ_TX_INT_ENABLE = 1
tCANMsgObject sTXCANMessage5 = {TX_MSG_OBJ_ID5, 0, 1, 8, ucTXMsgData5};    // CAN傳送結構體  MSG_OBJ_TX_INT_ENABLE = 1
tCANMsgObject sTXCANMessage6 = {TX_MSG_OBJ_ID6, 0, 1, 8, ucTXMsgData6};    // CAN傳送結構體  MSG_OBJ_TX_INT_ENABLE = 1

③根據自己的通訊機制,封裝好CAN通訊收發函式;

void CANA_TX(void)
{

    unsigned int T_switch = 0;  // 選擇資料傳送

    CANA_TX_FRAME_CNT = CANA_TX_FRAME_CNT + 1;      // 幀計數自增
//    CANA_TX_FRAME_CNT = CANA_TX_FRAME_CNT & 0xFF;   // 幀計數達到255後清零,迴圈計數

    T_switch = CANA_TX_FRAME_CNT % 5;

    switch(T_switch)
    {
        case 0:
            ucTXMsgData2[0] = CAN_CNT_delta & 0xFF;    // 幀計數
            ucTXMsgData2[1] = 0; // ...此處略去
            ucTXMsgData2[2] = 0; // ...此處略去
            ucTXMsgData2[3] = 0; // ...此處略去
            ucTXMsgData2[4] = 0; // ...此處略去
            ucTXMsgData2[5] = 0; // ...此處略去
            ucTXMsgData2[6] = 0; // ...此處略去
            ucTXMsgData2[7] = 0; // ...此處略去
            CanaMessageSet(TX_MSG_OBJ_ID2, &sTXCANMessage2, MSG_OBJ_TYPE_TX);
            break;

        case 1:
            ucTXMsgData3[0] = CAN_CNT_delta & 0xFF;    // 幀計數
            ucTXMsgData3[1] = 0; // ...此處略去
            ucTXMsgData3[2] = 0; // ...此處略去
            ucTXMsgData3[3] = 0; // ...此處略去
            ucTXMsgData3[4] = 0; // ...此處略去
            ucTXMsgData3[5] = 0; // ...此處略去
            ucTXMsgData3[6] = // ...此處略去
            ucTXMsgData3[7] = // ...此處略去
            CanaMessageSet(TX_MSG_OBJ_ID3, &sTXCANMessage3, MSG_OBJ_TYPE_TX);
            break;

        case 2:
            ucTXMsgData4[0] = CAN_CNT_delta & 0xFF;    // 幀計數
            ucTXMsgData4[1] = // ...此處略去
            ucTXMsgData4[2] = // ...此處略去
            ucTXMsgData4[3] = // ...此處略去
            ucTXMsgData4[4] = // ...此處略去
            ucTXMsgData4[5] = // ...此處略去
            ucTXMsgData4[6] = // ...此處略去
            ucTXMsgData4[7] = // ...此處略去
            CanaMessageSet(TX_MSG_OBJ_ID4, &sTXCANMessage4, MSG_OBJ_TYPE_TX);
            break;

        case 3:
            ucTXMsgData5[0] = CAN_CNT_delta & 0xFF;    // 幀計數
            ucTXMsgData5[1] = // ...此處略去
            ucTXMsgData5[2] = // ...此處略去
            ucTXMsgData5[3] = // ...此處略去
            ucTXMsgData5[4] = // ...此處略去
            ucTXMsgData5[5] = // ...此處略去
            ucTXMsgData5[6] = // ...此處略去
            ucTXMsgData5[7] = // ...此處略去
            CanaMessageSet(TX_MSG_OBJ_ID5, &sTXCANMessage5, MSG_OBJ_TYPE_TX);
            break;

        case 4:
            ucTXMsgData6[0] = CAN_CNT_delta & 0xFF;    // 幀計數
            ucTXMsgData6[1] = // ...此處略去
            ucTXMsgData6[2] = // ...此處略去
            ucTXMsgData6[3] = // ...此處略去
            ucTXMsgData6[4] = // ...此處略去
            ucTXMsgData6[5] = // ...此處略去
            ucTXMsgData6[6] = // ...此處略去
            ucTXMsgData6[7] = // ...此處略去
            CanaMessageSet(TX_MSG_OBJ_ID6, &sTXCANMessage6, MSG_OBJ_TYPE_TX);
            CANA_TX_Active_Flag = 0;
            break;

        default:
            break;
    }

    if(CANA_TX_FRAME_CNT >= 254)
    {
        CANA_TX_FRAME_CNT = 0;
    }
}
void CANA_RX(void)
{
    if((CANA_errorFlag == 0) && (CANA_RX_Flag == 1))
    {
        RX_FRAME_CANA.CNT = (Uint16)ucRXMsgData1[0]; // 位元組1
        RX_FRAME_CANA.x= (Uint16)(((ucRXMsgData1[1] & 0xF0) >> 4) & 0x0F);   // 位元組2H
        RX_FRAME_CANA.xx = (Uint16)ucRXMsgData1[1] & 0x0F;          // 位元組2L
        RX_FRAME_CANA.xxx= (Uint16)(((ucRXMsgData1[2] & 0xF0) >> 4) & 0x0F);    // 位元組3H
        RX_FRAME_CANA.xxxx= (Uint16)ucRXMsgData1[2] & 0x0F;               // 位元組3L
        RX_FRAME_CANA.xxxxx= ((Uint16)(ucRXMsgData1[3] & 0xFF)) * 0.2;      // 位元組4
        RX_FRAME_CANA.xxxxxx= ((Uint16)(ucRXMsgData1[4] & 0xFF)) * 196.08;  // 位元組5
        RX_FRAME_CANA.xxxxxxx= ((int)(((ucRXMsgData1[5] & 0xFF) << 8 ) | (ucRXMsgData1[6] & 0xFF))) * 0.02;    // 位元組6 位元組7

    }
}

④使用中斷服務函式,傳輸接收傳送結構體;

interrupt void CANA0_ISR(void)
{
    /************************************************************
    Description:CANA0中斷服務程式
       用於檢測中斷產生原因,傳送接收上位機資料
    ************************************************************/
    uint32_t status;

    //
    // Read the CAN-A interrupt status to find the cause of the interrupt
    //
    status = CANIntStatus(CANA_BASE, CAN_INT_STS_CAUSE);

    //
    // If the cause is a controller status interrupt, then get the status
    //
    if(status == CAN_INT_INT0ID_STATUS)
    {
        //
        // Read the controller status.  This will return a field of status
        // error bits that can indicate various errors.  Error processing
        // is not done in this example for simplicity.  Refer to the
        // API documentation for details about the error status bits.
        // The act of reading this status will clear the interrupt.
        //
        status = CANStatusGet(CANA_BASE, CAN_STS_CONTROL);

        //
        // Check to see if an error occurred.
        //
        if(((status  & ~(CAN_ES_TXOK | CAN_ES_RXOK)) != 7) &&
                   ((status  & ~(CAN_ES_TXOK | CAN_ES_RXOK)) != 0))
        {
            //
            // Set a flag to indicate some errors may have occurred.
            //
            CANA_errorFlag = 1;
        }
    }
    //
    // Check if the cause is the CAN-A receive message object 1
    //
    else if(status == RX_MSG_OBJ_ID1)
    {
        //
        // Get the received message
        //
        CANMessageGet(CANA_BASE, RX_MSG_OBJ_ID1, &sRXCANMessage1, true);

        //
        // Getting to this point means that the RX interrupt occurred on
        // message object 1, and the message RX is complete.  Clear the
        // message object interrupt.
        //
        CANIntClear(CANA_BASE, RX_MSG_OBJ_ID1);

        //
        // Since the message was received, clear any error flags.
        //
        CANA_errorFlag = 0;

        CANA_RX_Flag = 1;
        CANA_TX_Active_Flag = 1;
        Timer_CANA_TX_1ms = 0;
    }
    //
    // Check if the cause is the CAN-A send message object 1
    //
    else if(status == TX_MSG_OBJ_ID2)
    {
        //
        // Getting to this point means that the TX interrupt occurred on
        // message object 1, and the message TX is complete.  Clear the
        // message object interrupt.
        //
        CANIntClear(CANA_BASE, TX_MSG_OBJ_ID2);

        //
        // Since the message was sent, clear any error flags.
        //
        CANA_errorFlag = 0;
    }
    //
    // Check if the cause is the CAN-A send message object 1
    //
    else if(status == TX_MSG_OBJ_ID3)
    {
        //
        // Getting to this point means that the TX interrupt occurred on
        // message object 1, and the message TX is complete.  Clear the
        // message object interrupt.
        //
        CANIntClear(CANA_BASE, TX_MSG_OBJ_ID3);

        //
        // Since the message was sent, clear any error flags.
        //
        CANA_errorFlag = 0;
    }
    //
    // Check if the cause is the CAN-A send message object 1
    //
    else if(status == TX_MSG_OBJ_ID4)
    {
        //
        // Getting to this point means that the TX interrupt occurred on
        // message object 1, and the message TX is complete.  Clear the
        // message object interrupt.
        //
        CANIntClear(CANA_BASE, TX_MSG_OBJ_ID4);

        //
        // Since the message was sent, clear any error flags.
        //
        CANA_errorFlag = 0;
    }
    //
    // Check if the cause is the CAN-A send message object 1
    //
    else if(status == TX_MSG_OBJ_ID5)
    {
        //
        // Getting to this point means that the TX interrupt occurred on
        // message object 1, and the message TX is complete.  Clear the
        // message object interrupt.
        //
        CANIntClear(CANA_BASE, TX_MSG_OBJ_ID5);

        //
        // Since the message was sent, clear any error flags.
        //
        CANA_errorFlag = 0;
    }
    //
    // Check if the cause is the CAN-A send message object 1
    //
    else if(status == TX_MSG_OBJ_ID6)
    {
        //
        // Getting to this point means that the TX interrupt occurred on
        // message object 1, and the message TX is complete.  Clear the
        // message object interrupt.
        //
        CANIntClear(CANA_BASE, TX_MSG_OBJ_ID6);

        //
        // Since the message was sent, clear any error flags.
        //
        CANA_errorFlag = 0;
    }
    //
    // If something unexpected caused the interrupt, this would handle it.
    //
    else
    {
        //
        // Spurious interrupt handling can go here.
        //
    }

    //
    // Clear the global interrupt flag for the CAN interrupt line
    //
    CANGlobalIntClear(CANA_BASE, CAN_GLB_INT_CANINT0);

    //
    // Acknowledge this interrupt located in group 9
    //
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP9;

}

實驗驗證

採用上位機(PC)發1幀,下位機(DSP)應答5幀的方式,實現遙控與遙測,要想實現更為精準的定時,則可以採取其他的方式,比如TT-CAN等。上位機傳送間隔6ms,下位機應答間隔1ms。上位機資料幀ID(00000000001),下位機資料幀ID(00000000010、00000000011、00000000100、00000000101、00000000110)。使用ZLG的示波器,因為他自帶CAN通訊解碼功能,使用起來非常方便。
上位機遙控幀:
在這裡插入圖片描述
下位機遙測幀①
在這裡插入圖片描述
下位機遙測幀⑤
在這裡插入圖片描述

結束語

筆者對於CAN的使用,僅停留在資料幀這一簡單的幀種類上,以後若是有專案需要,則再補充學習其他的幀種類。TI對於CAN的支援比較到位,我們可以直接呼叫相應的函式,即可實現功能。當然規則越明細,開發人員對其標準化程度會越高,但使用靈活度、自由度變差。

參考資料目錄

《TMS320F2837xS Delfino Microcontrollers Datasheet》Memory章節
《TMS320F2837xS Delfino Microcontrollers Technical Reference Manual》CAN章節
RENESAS應用手冊《CAN入門書》
C2000Ware有關CAN的所有例程