<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于FlexCAN適配CANopenNode

CHANBAEK ? 來源:安德魯的設計筆記本 ? 作者:安德魯蘇 ? 2023-06-23 15:51 ? 次閱讀
  • 準備微控制器基本工程
  • 在微控制器上適配CANopenNode
    • 配置電路板的時鐘和引腳 board_init.c
    • 準備硬件定時器 main.c
    • 對接CAN驅動 CO_driver.c & main.c

本文是《CAN總線開發一本全(6) - CANopenNode組件》的補充其中一個小節。

總結在微控制器平臺上移植CANopenNode,需要根據具體硬件條件,適配2個源文件:

  • CANopenNode-1.3/stack/drvTemplate/CO_driver.c 文件。
    • 補充CO_CANmodule_init() 函數:初始化CAN外設硬件,配置CAN協議引擎、收發報文消息的參數,以及啟用中斷。
    • 補充CO_CANsend() 函數:復制CANopenNode組件中緩沖區的消息幀到硬件引擎,交由CAN硬件外設發送到總線上。
    • 補充CO_CANinterrupt() 函數:由硬件的CAN中斷服務調用,實現硬件外設層面上的發送和接收CAN通信幀。
    • 補充CO_CANverifyErrors() 函數:上報硬件CAN外設模塊的檢測到的錯誤狀態。
  • CANopenNode-1.3/example/main.c 文件。
  • 創建并配置硬件定時器周期中斷服務,以1ms為周期,調用CANopenNode的定時器周期執行線程的函數。

接下來,將以集成了FlexCAN外設模塊的MM32F0140微控制器為例,實現對CANopenNode v1.3的適配過程。

目前靈動官方的軟件開發平臺MindSDK已經適配了CANopenNode協議棧,并創建了一系列樣例工程:

  • co_basic
  • co_pdo_master
  • co_pdo_slave
  • co_sdo_master
  • co_sdo_slave
  • co_with_eeprom
  • co_with_flash

為了描述適配CANopenNode的過程,這里仍然從零開始,展現完整的移植開發過程。

準備微控制器基本工程

首先從靈動MindSDK的網站上(https://mindsdk.mindmotion.com.cn/)獲取到POKT-F0140(使用MM32F0140主控)開發板的flexcan驅動樣例工程,flexcan_loopback,作為模板工程。這個模板工程里包含了MM32F0140微控制器正常工作的所有必要源碼,包括芯片頭文件、啟動程序、中斷向量表、以及一系列初始化硬件電路板到進入main()函數的源碼,以及flexcan外設模塊的驅動程序。

  • 將模板工程的工程名改為fthr-f1040_canopen_demo_mdk
  • 將CANopenNode組件的源碼包CANopenNode-v1.3復制到canopen_demo工程的根目錄下
  • 將其中stack/drvTemplate目錄下的CO_driver.cCO_driver_target.h文件復制到canopen_demo工程的board目錄下
  • 將其中example目錄下的的CO_OD.c、CO_OD.hmain.c文件復制到canopen_demo工程的application目錄下

Keil MDK環境中打開canopen_demo工程。添加源文件和包含路徑到工程中,如圖x所示。

  • 添加CANopenNode-v1.3目錄下,CANopenNode-v1.3/stack目錄下所有的C源文件到工程
  • 添加CANopenNode-v1.3目錄和CANopenNode-v1.3/stack目錄到工程包含路徑
  • 添加application目錄下新增文件CO_OD.c、CO_OD.hmain.c,和board目錄下新增文件CO_driver.cCO_driver_target.h,到canopen_demo工程中。

圖片

figure-canopen-demo-proj-settings

圖x canopen_demo工程中包含CANopenNode源碼整理好文件之后,試著編譯一下工程,沒有警告和錯誤,可以正常使用。如圖x所示。

圖片

figure-canopen-demo-proj-build-log

圖x canopen_demo工程編譯正確此時的canopen_demo工程中,包含了CANopenNode的所有源碼、FlexCAN外設模塊的驅動,以及使用MM32F0140微控制器的所有必要的源文件,并且可以通過編譯器驗證編寫程序代碼的正確性。后續進行適配工作過程中,將通過開發者自行編碼,將CANopenNode和FlexCAN外設模塊綁定起來,并可實時編譯工程驗證編碼內容。

在微控制器上適配CANopenNode

CANopeoNode組件中自帶main.c文件,約定了整個CANopen協議棧的運行框架。在CANopenNode中的main.c文件中,定義了應用程序入口main()函數,以及定時器中斷服務程序入口和CAN外設模塊中斷服務程序入口。在本例的移植工程中,定時器相關的程序被置于main.c文件中,而具體微控制器平臺上的CAN外設模塊相關的配置程序代碼則位于CO_driver.c文件中。

配置電路板的時鐘和引腳 board_init.c

  1. 配置時鐘

這里需要至少啟用硬件定時器TIM2模塊(產生1ms周期中斷)和FlexCAN模塊,另外,POKT-F0140開發板使用PB8和PB9作為CAN接口引腳,也需要啟用對應IO端口的時鐘。

在clock_init.c文件中更新BOARD_InitBootClocks()源碼:

void BOARD_InitBootClocks(void)
{
    CLOCK_ResetToDefault();
    CLOCK_BootToHSE72MHz();

    /* TIM2.*/
    RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_TIM2, true);
    RCC_ResetAPB1Periphs(RCC_APB1_PERIPH_TIM2);
    /* FLEXCAN. */
    RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_FLEXCAN, true);
    RCC_ResetAPB1Periphs(RCC_APB1_PERIPH_FLEXCAN);
    ...
    /* GPIOB. */
    RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOB, true); /* PB8 - CAN1_RX, PB9 - CAN1_TX. */
    RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOB);
}
  1. 配置引腳

FTHR-F0140開發板使用PB8和PB9作為CAN接口引腳,需要配置引腳的復用功能為CAN服務。

pin_init.c文件中更新BOARD_InitPins()源碼:

void BOARD_InitPins(void)
{
    ...
    /* fthr-f0140. */
    /* PA9 - FLEXCAN_RX. */
    gpio_init.Pins  = GPIO_PIN_9;
    gpio_init.PinMode  = GPIO_PinMode_In_Floating;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &gpio_init);
    GPIO_PinAFConf(GPIOA, gpio_init.Pins, GPIO_AF_8);

    /* PA10 - FLEXCAN_TX. */
    gpio_init.Pins  = GPIO_PIN_10;
    gpio_init.PinMode  = GPIO_PinMode_AF_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;                                                                                                       GPIO_Init(GPIOA, &gpio_init);
    GPIO_PinAFConf(GPIOA, gpio_init.Pins, GPIO_AF_8);
}

配置板子的BOARD_InitBootClocks()函數和BOARD_InitPins()函數,將在board_init.c文件中被BOARD_Init()函數調用,

void BOARD_Init(void)
{
    BOARD_InitBootClocks();
    BOARD_InitPins();

    BOARD_InitDebugConsole();
}

BOARD_Init()函數最終將在main.c文件中被調用,實現對電路板的初始化工作。

/* main ***********************************************************************/
int main (void){
    CO_NMT_reset_cmd_t reset = CO_RESET_NOT;

    /* Configure microcontroller. */
    BOARD_Init();
    ...
}

準備硬件定時器 main.c

CANopenNode的三個線程之一,定時器周期執行線程,以1ms為間隔周期執行。例如,可以配置硬件定時器TIM2產生周期為1ms的中斷服務,并在定時器的中斷服務程序中嵌入CANopenNode提供main.c文件中的tmrTask_thread()函數。

main.c文件中編寫brd_tim_init()函數,配置TIM2硬件定時器,并在main()函數中調用:

#include "board_init.h"

void BOARD_TIM_Init(void);

/* main ***********************************************************************/
int main (void)
{
        ...
        /* Configure Timer interrupt function for execution every 1 millisecond */
        BOARD_TIM_Init();
        ...
}

/* Setup the hardware timer. */
void BOARD_TIM_Init(void)
{
    /* Set the counter counting step. */
    TIM_Init_Type tim_init;
    tim_init.ClockFreqHz = BOARD_TIM_FREQ;
    tim_init.StepFreqHz = BOARD_TIM_UPDATE_STEP; /* 1ms. */
    tim_init.Period = BOARD_TIM_UPDATE_PERIOD - 1u;
    tim_init.EnablePreloadPeriod = false;
    tim_init.PeriodMode = TIM_PeriodMode_Continuous;
    tim_init.CountMode = TIM_CountMode_Increasing;
    TIM_Init(BOARD_TIM_PORT, &tim_init);

    /* Enable interrupt. */
    TIM_EnableInterrupts(BOARD_TIM_PORT, TIM_INT_UPDATE_PERIOD, true);
    NVIC_EnableIRQ(BOARD_TIM_IRQn);
    
    /* Start the timer. */
    TIM_Start(BOARD_TIM_PORT);
}

其中,關于硬件定時器的配置參數的定義統一放置于board_init.h文件。

/* TIM1. */
#define BOARD_TIM_PORT               (TIM_Type *)TIM2
#define BOARD_TIM_IRQn               TIM2_IRQn
#define BOARD_TIM_IRQHandler         TIM2_IRQHandler
#define BOARD_TIM_FREQ               CLOCK_SYS_FREQ
#define BOARD_TIM_UPDATE_STEP        1000000u
#define BOARD_TIM_UPDATE_PERIOD      1000u

main()函數中調用了BOARD_TIM_Init()函數,配置硬件定時器TIM2產生1ms為周期的中斷,并啟動定時器。此時,對應在硬件定時器的中斷服務程序中調用CANopenNode的定時器線程函數tmrTask_thread(),并在其中清硬件定時器中斷的標志位。

/* timer thread executes in constant intervals ********************************/
void tmrTask_thread(void)
{
    INCREMENT_1MS(CO_timer1ms);

    if (CO- >CANmodule[0]- >CANnormal)
    {
        bool_t syncWas;

        /* Process Sync */
        syncWas = CO_process_SYNC(CO, TMR_TASK_INTERVAL);

        /* Read inputs */
        CO_process_RPDO(CO, syncWas);

        /* Further I/O or nonblocking application code may go here. */

        /* Write outputs */
        CO_process_TPDO(CO, syncWas, TMR_TASK_INTERVAL);

        /* verify timer overflow */
        if (TIM_STATUS_UPDATE_PERIOD == (TIM_GetInterruptStatus(BOARD_TIM_PORT) & TIM_STATUS_UPDATE_PERIOD) )
        {
            CO_errorReport(CO- >em, CO_EM_ISR_TIMER_OVERFLOW, CO_EMC_SOFTWARE_INTERNAL, 0u);
            TIM_ClearInterruptStatus(BOARD_TIM_PORT, TIM_STATUS_UPDATE_PERIOD);
        }
    }
}

/* Timer interrupt function ***************************************************/
void BOARD_TIM_IRQHandler(void)
{
    TIM_ClearInterruptStatus(BOARD_TIM_PORT, TIM_STATUS_UPDATE_PERIOD);

    tmrTask_thread();
}

這里要注意,CANopenNode原生的tmrTask_thread()函數的實現模板中,停用了“/* verify timer overflow */”之后的代碼。這些被停用的代碼,原本可以用來驗證tmrTask_thread()函數內部操作,例如處理同步過程、讀接收PDO和寫發送PDO,在清了上一次1ms中斷的硬件標志位后的1ms中是否能夠執行完畢。如果tmrTask_thread()函數的處理時間過長,超出了一個周期任務的執行時間,此時檢測到1ms定時器中斷標志位再次置位,即出現超時。在1ms周期任務超時之后,CANopen協議棧會認為這是一個可能產生風險的任務,因此可調用CO_errorReport()函數將錯誤情況上報給CANopen協議棧。

對接CAN驅動 CO_driver.c & main.c

CO_driver.c文件中定義了大量的具體微控制器平臺的CAN外設硬件模塊相關的驅動函數,但在最基礎的移植過程中,僅需重點關注4個函數即可:

  • CO_CANmode_init() - 初始化CAN外設模塊,并配置好比特率、消息幀過濾器,以及收發中斷等。
  • CO_CANsend() - 將消息緩沖區中的數據搬運至CAN外設模塊的硬件發送緩沖區中,即將發送CAN消息幀到CAN總線上。
  • CO_CANinterrupt() - 綁定到CAN外設模塊的硬件中斷的服務程序,主要實現CAN硬件的接收過程,即將CAN外設模塊從CAN總線上捕獲下來的CAN消息幀數據轉存到CANopenNode組件的接收緩沖區中,供協議棧進一步處理。當使用中斷方式發送CAN消息幀時,也需要在CO_CANinterrupt()函數中調用CO_CANsend()函數發送CAN消息幀。
  • CO_CANverifiyErrors() - 查看CAN外設模塊的硬件錯誤。因為CAN總線上的消息幀需要經過仲裁才能上線,所以這里查錯函數主要檢查的是發送消息幀超時的情況。

原生CANopenNode組件包中的CO_driver.c文件中的函數已經實現了絕大部分同協議棧交互的業務邏輯,在具體微控制器平臺上是適配時,僅需要將少量同硬件相關的步驟綁定到微控制器硬件的操作上即可。

CO_CANmodule_init()

CO_driver.c文件中向CO_CANmodule_init()函數嵌入初始化FlexCAN外設模塊的代碼,包括初始化FlexCAN的通信引擎,配置好過濾器等(本移植工程未啟硬件過濾器功能,由CANopenNode的軟件過濾器處理)。

#include "board_init.h"

/******************************************************************************/
CO_ReturnError_t CO_CANmodule_init(
        CO_CANmodule_t         *CANmodule,
        void                   *CANdriverState,
        CO_CANrx_t              rxArray[],
        uint16_t                rxSize,
        CO_CANtx_t              txArray[],
        uint16_t                txSize,
        uint16_t                CANbitRate)
{
    uint16_t i;

    /* verify arguments */
    if(CANmodule==NULL || rxArray==NULL || txArray==NULL){
        return CO_ERROR_ILLEGAL_ARGUMENT;
    }

    /* Configure object variables */
    CANmodule- >CANdriverState = CANdriverState;
    CANmodule- >rxArray = rxArray;
    CANmodule- >rxSize = rxSize;
    CANmodule- >txArray = txArray;
    CANmodule- >txSize = txSize;
    CANmodule- >CANnormal = false;
    CANmodule- >useCANrxFilters = false;/* microcontroller dependent */
    CANmodule- >bufferInhibitFlag = false;
    CANmodule- >firstCANtxMessage = true;
    CANmodule- >CANtxCount = 0U;
    CANmodule- >errOld = 0U;
    CANmodule- >em = NULL;

    for(i=0U; i< rxSize; i++){
        rxArray[i].ident = 0U;
        rxArray[i].mask = 0xFFFFU;
        rxArray[i].object = NULL;
        rxArray[i].pFunct = NULL;
    }
    for(i=0U; i< txSize; i++){
        txArray[i].bufferFull = false;
    }

    /* Configure CAN timing */
    FLEXCAN_TimConf_Type flexcan_tim_conf;
    flexcan_tim_conf.EnableExtendedTime = false;
    flexcan_tim_conf.PhaSegLen1 = 5u;
    flexcan_tim_conf.PhaSegLen2 = 1u;
    flexcan_tim_conf.PropSegLen = 2u;
    flexcan_tim_conf.JumpWidth = 1u;

    /* Configure CAN module registers */
    FLEXCAN_Init_Type flexcan_init;
    flexcan_init.MaxXferNum = BOARD_FLEXCAN_XFER_MaxNum; /* The max mb number to be used. */
    flexcan_init.ClockSource = FLEXCAN_ClockSource_Periph; /* Use peripheral clock. */
    flexcan_init.BitRate = CANbitRate * 1000u; /* Set bitrate. */
    flexcan_init.ClockFreqHz = BOARD_FLEXCAN_CLOCK_FREQ; /* Set clock frequency. */
    flexcan_init.SelfWakeUp = FLEXCAN_SelfWakeUp_BypassFilter; /* Use unfiltered signal to wake up flexcan. */
    flexcan_init.WorkMode = FLEXCAN_WorkMode_Normal; /* Normal workmode, can receive and transport. */
    flexcan_init.Mask = FLEXCAN_Mask_Global; /* Use global mask for filtering. */
    flexcan_init.EnableSelfReception = false; /* Not receiving mb frame sent by self. */
    flexcan_init.EnableTimerSync = true; /* Every tx or rx done, refresh the timer to start from zero. */
    flexcan_init.TimConf = &flexcan_tim_conf; /* Set timing sychronization. */
    FLEXCAN_Init(BOARD_FLEXCAN_PORT, &flexcan_init);

    /* Set tx mb. */
    FLEXCAN_ResetMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH);
    FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, FLEXCAN_MbCode_TxInactive);

    /* Set rx mb. */
    FLEXCAN_RxMbConf_Type flexcan_mb_conf;
    flexcan_mb_conf.Id = 0x000u; /* Id for filtering with mask and receiving. */
    flexcan_mb_conf.MbType = FLEXCAN_MbType_Data; /* Only receive standard data frame. */
    flexcan_mb_conf.MbFormat = FLEXCAN_MbFormat_Standard;
    FLEXCAN_SetRxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, &flexcan_mb_conf);
    FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, FLEXCAN_MbCode_RxEmpty);/* Set for receiving. */

    /* Configure CAN module hardware filters */

    /* CAN module filters are not used, all messages with standard 11-bit */
    /* identifier will be received */
    /* Configure mask 0 so, that all messages with standard identifier are accepted */
    FLEXCAN_RxMbMaskConf_Type mb_mask_conf;
    mb_mask_conf.MbType = FLEXCAN_MbType_Data;
    mb_mask_conf.MbFormat = FLEXCAN_MbFormat_Standard;
    mb_mask_conf.IdMask = 0x000u;
    FLEXCAN_EnableFreezeMode(BOARD_FLEXCAN_PORT, true);
    FLEXCAN_SetGlobalMbMaskConf(BOARD_FLEXCAN_PORT, &mb_mask_conf);
    FLEXCAN_EnableFreezeMode(BOARD_FLEXCAN_PORT, false);


    /* configure CAN interrupt registers */
    FLEXCAN_EnableMbInterrupts(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_INT | BOARD_FLEXCAN_TX_MB_INT, true);
    NVIC_EnableIRQ(BOARD_FLEXCAN_IRQn);

    return CO_ERROR_NO;
}

此處有兩個要點:

  • 開啟了FlexCAN的CAN ID過濾器的掩碼為0x000(掩碼為1的位會對接收幀ID進行約束,要求強制匹配),這意味著可以捕獲CAN總線上的所有幀,此時僅能使用了一個MB作為CAN通信幀的數據緩沖(它的CAN ID是任何值已經不重要了)。后續將由CANopen協議棧的軟件對不同的CAN ID進行匹配,進而再分別處理。
  • 模板源碼中的CO_CANmodule_init()函數還包含了是否使用硬件過濾機制的配置字段useCANrxFilters,在初始化CANopen時,若指定的接收幀緩沖數量小于32時,可啟用硬件過濾器(對應硬件多通道)機制。但具體的CAN外設模塊在硬件層面上實現的緩沖區可能是不同的,不一定是32個緩沖單元,并且不同CAN外設對緩沖區的操作行為是不同的,例如,MM32F0140集成的FlexCAN在基本模式下是按照CAN ID進行分別存放對應的MB,但在啟用FlexCAN的FIFO模式后,就可以以FIFO的方式,用一個隊列管理不同CAN ID的消息幀。為了減少對硬件特性的依賴,這里的移植代碼固定使用無硬件過濾的機制,對CAN ID的分析由CANopenNode軟件完成,設定useCANrxFilters的值為false,對應也不再需要考慮CO_CANrxBufferInit()函數的實現。

CO_CANsend()

在CO_driver.c文件中編寫BOARD_FlexCAN_TxFrame()函數,實現通過CAN外設模塊發送消息幀,然后嵌入到CANopenNode的CO_CANsend()函數中,并在CO_CANinterrupt()函數中調用。其中,CO_CANinterrupt()是CANopenNode定義的CAN中斷服務程序,還需要綁定到目標微控制器平臺上的硬件CAN中斷服務程序上。

/******************************************************************************/
/* Send a message frame to CAN bus. */
bool BOARD_FlexCAN_TxFrame(CO_CANtx_t *buffer)
{
    FLEXCAN_Mb_Type mb;

    if (!buffer- >rtr)
    {
        mb.TYPE = FLEXCAN_MbType_Data; /* Data frame type. */
    }
    else
    {
        mb.TYPE = FLEXCAN_MbType_Remote; /* Remote frame type. */
    }
    mb.ID = buffer- >ident; /* Indicated ID number. */
    mb.FORMAT = FLEXCAN_MbFormat_Standard; /* STD frame format. */
    mb.PRIORITY = BOARD_FLEXCAN_XFER_PRIORITY; /* The priority of the frame mb. */

    /* Set the information. */
    mb.BYTE0 = buffer- >data[0];
    mb.BYTE1 = buffer- >data[1];
    mb.BYTE2 = buffer- >data[2];
    mb.BYTE3 = buffer- >data[3];
    mb.BYTE4 = buffer- >data[4];
    mb.BYTE5 = buffer- >data[5];
    mb.BYTE6 = buffer- >data[6];
    mb.BYTE7 = buffer- >data[7];

    /* Set the workload size. */
    mb.LENGTH = buffer- >DLC;

    /* Send. */
    bool status = FLEXCAN_WriteTxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, &mb);
    FLEXCAN_SetMbCode(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_CH, FLEXCAN_MbCode_TxDataOrRemote); /* Write code to send. */

    return status;
}

CO_ReturnError_t CO_CANsend(CO_CANmodule_t *CANmodule, CO_CANtx_t *buffer)
{
    CO_ReturnError_t err = CO_ERROR_NO;

    /* Verify overflow */
    if (buffer- >bufferFull)
    {
        if (!CANmodule- >firstCANtxMessage)
        {
            /* don't set error, if bootup message is still on buffers */
            CO_errorReport((CO_EM_t*)CANmodule- >em, CO_EM_CAN_TX_OVERFLOW, CO_EMC_CAN_OVERRUN, buffer- >ident);
        }
        err = CO_ERROR_TX_OVERFLOW;
    }

    CO_LOCK_CAN_SEND();

    if ( BOARD_FlexCAN_TxFrame(buffer) ) /* copy the frame to can hardware. */
    {
        CANmodule- >bufferInhibitFlag = buffer- >syncFlag;
    }
    /* if no buffer is free, message will be sent by interrupt */
    else
    {
        buffer- >bufferFull = true;
        CANmodule- >CANtxCount++;
    }
    CO_UNLOCK_CAN_SEND();

    return err;
}

在嵌入CO_CANsend()函數時,有個要點:

  • 這里在發送CAN消息幀實際實現了一個中斷發送的機制。當在CO_CANsend()函數中調用brd_can_tx()函數時,程序將CANopenNode將要發送的消息幀數據搬運到CAN外設硬件的發送緩沖區中,并觸發發送機制,等待在合適的時機將數據送上總線(需要等待獲得仲裁才能將消息幀送上總線)。如果當前積壓的發送消息數量為0,CANmodule->CANtxCount == 0,則可以向CAN外設的硬件發送緩沖區寫數,否則,意味著當前CAN外設的硬件發送緩沖區中還有消息等待上線,此時只能記錄一下計數器,CANmodule->CANtxCount++。后續的發送過程就需要在中斷中的發送過程中完成了,在當前發送消息幀上線之后,發送完成,會觸發CAN外設的中斷服務程序,屆時將檢查CANmodule->CANtxCount計數器的值:如果已經是0,表示后續不需要再發幀了,那就清標志位,結束;如果不是0,那么在前一幀發送完成后,繼續載入新的消息幀到硬件發送緩沖區,直到發送完消息隊列中的最后一個消息,最后一次進中斷,同上。
  • CANmodule->CANtxCount計數器相當于是CAN硬件發送緩沖區的信號量,可以作為緩沖區是否為空的標志:若值為0,則對應硬件發送緩沖區為空;若值不為0,則用buffer->bufferFull標記CAN硬件發送緩沖區正在使用,同時使用CANmodule->CANtxCount計數器的值表示正在排隊的數量。

CO_CANinterrupt()

CO_driver.c文件中編寫brd_can_rx()函數,從CAN外設模塊的硬件接收緩沖區中讀收到的消息幀,然后嵌入在CO_CANinterrupt()函數中處理接收過程。

/******************************************************************************/
void CO_CANinterrupt(CO_CANmodule_t *CANmodule)
{
    uint32_t status = FLEXCAN_GetMbStatus(BOARD_FLEXCAN_PORT);

    if (BOARD_FLEXCAN_RX_MB_STATUS == (status & BOARD_FLEXCAN_RX_MB_STATUS))
    {
        /* receive interrupt */
        CO_CANrxMsg_t *rcvMsg;      /* pointer to received message in CAN module */
        CO_CANrxMsg_t rcvMsgBuff;

        uint16_t index;             /* index of received message */
        uint32_t rcvMsgIdent;       /* identifier of the received message */
        CO_CANrx_t *buffer = NULL;  /* receive message buffer from CO_CANmodule_t object. */
        bool_t msgMatched = false;

        /* get message from module here */
        rcvMsg = &rcvMsgBuff;
        FLEXCAN_Mb_Type flexcan_rx_mb;
        FLEXCAN_ReadRxMb(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_CH, &flexcan_rx_mb);
        rcvMsg- >ident = flexcan_rx_mb.ID;
        rcvMsg- >DLC = flexcan_rx_mb.LENGTH;
        rcvMsg- >data[0] = flexcan_rx_mb.BYTE0;
        rcvMsg- >data[1] = flexcan_rx_mb.BYTE1;
        rcvMsg- >data[2] = flexcan_rx_mb.BYTE2;
        rcvMsg- >data[3] = flexcan_rx_mb.BYTE3;
        rcvMsg- >data[4] = flexcan_rx_mb.BYTE4;
        rcvMsg- >data[5] = flexcan_rx_mb.BYTE5;
        rcvMsg- >data[6] = flexcan_rx_mb.BYTE6;
        rcvMsg- >data[7] = flexcan_rx_mb.BYTE7;

        rcvMsgIdent = rcvMsg- >ident;

        /* CAN module filters are not used, message with any standard 11-bit identifier */
        /* has been received. Search rxArray form CANmodule for the same CAN-ID. */
        buffer = &CANmodule- >rxArray[0];
        for (index = CANmodule- >rxSize; index > 0U; index--)
        {
            if(((rcvMsgIdent ^ buffer- >ident) & buffer- >mask) == 0U)
            {
                msgMatched = true;
                break;
            }
            buffer++;
        }

        /* Call specific function, which will process the message */
        if (msgMatched && (buffer != NULL) && (buffer- >pFunct != NULL))
        {
            buffer- >pFunct(buffer- >object, rcvMsg);
        }

        /* Clear interrupt flag */
        FLEXCAN_ClearMbStatus(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_RX_MB_STATUS);
    }
    else if (BOARD_FLEXCAN_TX_MB_STATUS == (status & BOARD_FLEXCAN_TX_MB_STATUS))
    {
        /* Clear interrupt flag */
        FLEXCAN_ClearMbStatus(BOARD_FLEXCAN_PORT, BOARD_FLEXCAN_TX_MB_STATUS);

        /* First CAN message (bootup) was sent successfully */
        CANmodule- >firstCANtxMessage = false;
        /* clear flag from previous message */
        CANmodule- >bufferInhibitFlag = false;
        /* Are there any new messages waiting to be send */
        if (CANmodule- >CANtxCount > 0U)
        {
            uint16_t i;             /* index of transmitting message */

            /* first buffer */
            CO_CANtx_t *buffer = &CANmodule- >txArray[0];
            /* search through whole array of pointers to transmit message buffers. */
            for(i = CANmodule- >txSize; i > 0U; i--)
            {
                /* if message buffer is full, send it. */
                if (buffer- >bufferFull)
                {
                    buffer- >bufferFull = false;
                    CANmodule- >CANtxCount--;

                    /* Copy message to CAN buffer */
                    CANmodule- >bufferInhibitFlag = buffer- >syncFlag;
                    CO_CANsend(CANmodule, buffer);
                    break;                      /* exit for loop */
                }
                buffer++;
            }/* end of for loop */

            /* Clear counter if no more messages */
            if (i == 0U)
            {
                CANmodule- >CANtxCount = 0U;
            }
        }
    }
    else
    {
        /* some other interrupt reason */
    }
}

CO_CANinterrupt()函數中,實現了接收CAN消息幀和發送CAN消息幀的過程:

  • 在接收過程中,CAN外設硬件收到總線上的消息幀后觸發中斷服務程序,程序從硬件接收緩沖區將消息幀讀出來,填充到CANmodule結構體的接收幀隊列成員中,之后由成員對應的處理函數消化掉接收到的消息幀。
  • 在發送過程中,程序需要逐個處理掉之前的已經壓入軟件緩沖區中待發送的消息幀。當軟件發送緩沖區為空時,由CO_CANsend()函數觸發的發送過程會先把當前的消息幀寫入硬件發送緩沖區中并啟動發送,之后由發送完成事件觸發中斷。每次進入發送完成中斷服務程序時,程序會先檢查軟件發送緩沖區中的消息幀的數量是不是0:如果是,說明后面沒有需要繼續發送的消息幀了,直接清標志位,收工;如果不是,說明還需要接著發送已經緩存的消息幀,那就再次調用CO_CANsend()函數搬運幀數據到硬件發送緩沖區中并觸發的發送過程,之后由發送完成事件觸發中斷,直至最后清空發送緩沖區再清標志位。

CO_CANinterrupt()函數將在main.c文件中被硬件的CAN中斷服務函數調用。

/* CAN interrupt function *****************************************************/
//void /* interrupt */ CO_CAN1InterruptHandler(void){
void BRD_CAN_IRQHandler(void)
{
    CO_CANinterrupt(CO- >CANmodule[0]);

    /* clear interrupt flag */
    /* the interrupt flags are cleared when processing each mb in flexcan. */    
}

CO_CANverifyErrors()

CO_driver.c文件中CO_CANinterrupt()函數中,嵌入從CAN外設讀錯誤計數值和狀態標志位的代碼,將硬件的錯誤狀態反饋給CANopenNode協議棧。

void CO_CANverifyErrors(CO_CANmodule_t *CANmodule){
    uint16_t rxErrors, txErrors, overflow;
    CO_EM_t* em = (CO_EM_t*)CANmodule- >em;
    uint32_t err;

    /* get error counters from module. Id possible, function may use different way to
     * determine errors. */
    //rxErrors = CANmodule- >txSize;
    //txErrors = CANmodule- >txSize;
    //overflow = CANmodule- >txSize;
    rxErrors = (uint16_t) ((BOARD_FLEXCAN_PORT- >ECR & FLEXCAN_ECR_RXERRCNT_MASK) > > FLEXCAN_ECR_RXERRCNT_SHIFT);
    txErrors = (uint16_t) ((BOARD_FLEXCAN_PORT- >ECR & FLEXCAN_ECR_TXERRCNT_MASK) > > FLEXCAN_ECR_TXERRCNT_SHIFT);
    overflow = (uint16_t) ((BOARD_FLEXCAN_PORT- >ESR1 & FLEXCAN_ESR1_ERROVR_MASK) > > FLEXCAN_ESR1_ERROVR_SHIFT);
    ...
}

這里的rxErrorstxErrors是CAN外設接收幀和發送幀的錯誤計數器,一般的CAN外設模塊(例如FlexCAN),會對從CAN總線上捕獲消息幀和發送消息幀進行超時管理,因為CAN總線的發送過程存在仲裁,確實可能在通信繁忙的時間段有一些優先級比較低(CAN ID值比較大)的消息幀無法順利發出。此時,如果有通信幀久久沒有成功發出,則會上報給CANopen協議棧,進一步可能會通過NMT協議調整網絡通信的節奏,盡量讓關鍵數據(由于延遲提升的優先級)得以通暢傳輸。

CO_CANclearPendingSyncPDOs()

另外,還需要在CO_CANclearPendingSyncPDOs()函數中增加對FlexCAN硬件發送緩沖區的檢查,當需要發送同步消息時,如果有未上線的消息占用發送消息緩沖區(當新的同步消息準備發出時,之前未發出的同步消息已經失效,不再具有同步的意義),則CANopen可以強制騰空發送緩沖區,為最新的同步消息騰出空間準備發送。

/******************************************************************************/
void CO_CANclearPendingSyncPDOs(CO_CANmodule_t *CANmodule)
{
    uint32_t tpdoDeleted = 0U;

    CO_LOCK_CAN_SEND();

    /* Abort message from CAN module, if there is synchronous TPDO.
     * Take special care with this functionality. */
    if (   (BOARD_FLEXCAN_RX_MB_STATUS == (BOARD_FLEXCAN_RX_MB_STATUS & FLEXCAN_GetMbStatus(BOARD_FLEXCAN_PORT)) )
        && CANmodule- >bufferInhibitFlag)
    {
        /* clear TXREQ */
        CANmodule- >bufferInhibitFlag = false;
        tpdoDeleted = 1U;
    }
    /* delete also pending synchronous TPDOs in TX buffers */
    if (CANmodule- >CANtxCount != 0U)
    {
        CO_CANtx_t *buffer = &CANmodule- >txArray[0];
        for (uint16_t i = CANmodule- >txSize; i > 0U; i--)
        {
            if (buffer- >bufferFull)
            {
                if (buffer- >syncFlag)
                {
                    buffer- >bufferFull = false;
                    CANmodule- >CANtxCount--;
                    tpdoDeleted = 2U;
                }
            }
            buffer++;
        }
    }
    CO_UNLOCK_CAN_SEND();


    if (tpdoDeleted != 0U)
    {
        CO_errorReport((CO_EM_t*)CANmodule- >em, CO_EM_TPDO_OUTSIDE_WINDOW, CO_EMC_COMMUNICATION, tpdoDeleted);
    }
}

至此,一個基本的使用CANopenNode組件實現的CANopen的框架即移植完成。編譯項目,清理可能的錯誤,即可下載工程的開發板運行程序。

Build started: Project: project
*** Using Compiler 'V6.18', folder: 'C:\\Keil_v5\\ARM\\ARMCLANG\\Bin'
Rebuild target 'Target 1'
compiling application.c...
...
compiling CO_trace.c...
compiling crc16-ccitt.c...
compiling eeprom.c...
linking...
Program Size: Code=32600 RO-data=2468 RW-data=996 ZI-data=6244  
".\\Objects\\project.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed:  00:00:02
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 微控制器
    +關注

    關注

    48

    文章

    6855

    瀏覽量

    148055
  • CAN總線
    +關注

    關注

    145

    文章

    1817

    瀏覽量

    129798
  • 定時器
    +關注

    關注

    23

    文章

    3153

    瀏覽量

    112343
  • 引腳
    +關注

    關注

    16

    文章

    1053

    瀏覽量

    49123
  • CANopen
    +關注

    關注

    7

    文章

    204

    瀏覽量

    43001
收藏 人收藏

    評論

    相關推薦

    基于MM32G5330的FlexCAN實現CANopenNode協議棧移植

    本文將介紹如何基于靈動MM32G5330的FlexCAN實現CANopenNode協議棧的移植,并使用靈動官方提供的開發板Mini-G5333進行驗證。
    發表于 04-12 09:15 ?450次閱讀
    基于MM32G5330的<b class='flag-5'>FlexCAN</b>實現<b class='flag-5'>CANopenNode</b>協議棧移植

    微控制器集成的FlexCAN外設模塊

    FlexCAN(由Silvaco International公司設計),這些CAN總線通信引擎在汽車電子應用中已經被廣泛使用,用以實現內存中的數據與CAN總線上串行信號的的相互轉換。靈動微電子
    的頭像 發表于 06-16 11:06 ?1444次閱讀
    微控制器集成的<b class='flag-5'>FlexCAN</b>外設模塊

    SPC560Dxx FlexCAN傳輸錯誤

    我正在使用SPC560D-DIS Discovery板進行開發和SPC5工作室。 我已經為FlexCAN測試導入了測試應用程序'SPC560Dxx OS-Less CAN Test
    發表于 11-22 10:36

    FlexCAN不起作用

    嗨, 我正在使用spc5_studio測試SPC560P50L5,FlexCAN的內置示例是環回測試。當我禁用環回并啟用管理程序模式時,CAN_TXD引腳上沒有任何內容。我不知道這個問題,我需要
    發表于 04-02 11:51

    MQX FlexCAN FIFO消息接收延遲怎么解決?

    我正在使用 MQX FlexCAN FIFO 接收機制。除了消息接收延遲外,它工作正常。當收到第一個 CAN 報文時,FIFO 的輸出是一條僅包含零的報文。消息 2-5 也是??如此。當接收到第 6
    發表于 03-16 08:07

    如何使用FlexCan中斷接收S32R45上的數據?

    flexCan 演示使用輪詢來接收數據,我想使用 flexCan 中斷。我已啟用 flexCan0 IRQ,并定義了回調函數。定義回調函數格式(見附件)。但是,它無法正常工作。第一次可以進入這個函數
    發表于 03-20 08:08

    FlexCAN傳輸不工作的原因?

    大家好,我正在嘗試使用 FlexCAN 并在發送時遇到問題。使用項目示例中的示例 - sk32146 FlexCAN,并使用帶 12 V 電源的硬件 S32K146_EVB 板。從能夠獲取數據
    發表于 03-29 06:54

    CAN為什么叫FlexCAN?有什么不同?

    CAN為什么叫FlexCAN?有什么不同?
    發表于 04-06 07:19

    FlexCAN配置為Legacy RxFIFO模式的步驟是什么?

    FlexCAN 配置為 Legacy RxFIFO 模式的步驟是什么? 到目前為止我已經做了: 1. 從pin工具配置PIN 2. 從外圍工具配置FlexCAN3 + IntCtrl 在代碼
    發表于 05-17 08:37

    MindSDK中FlexCAN驅動程序及樣例工程

    和MM32F0140微控制器,其中就有FlexCAN外設模塊的驅動程序以及樣例工程,以及對CAN總線通信協議CANopen的適配工程。本文將介紹MindSDK中FlexCAN驅動程序及樣例工程,展現一種典型的CAN總線驅動程序的
    的頭像 發表于 06-23 15:41 ?767次閱讀
    MindSDK中<b class='flag-5'>FlexCAN</b>驅動程序及樣例工程

    CANopenNode的移植接口詳解

    CANopen是實現CAN設備組網的典型協議棧和規范,對應于軟件系統中,有一些開源的軟件組件,實現了CANopen協議棧,例如CANopenNode和CAN Festival。CANFestival
    的頭像 發表于 06-23 15:49 ?2386次閱讀
    <b class='flag-5'>CANopenNode</b>的移植接口詳解

    一文淺談FlexCAN OTA

    FlexCAN OTA
    的頭像 發表于 09-27 16:17 ?588次閱讀
    一文淺談<b class='flag-5'>FlexCAN</b> OTA

    MM32F0140 FlexCAN一致性測試 (2)

    MM32F0140 FlexCAN一致性測試 (2)
    的頭像 發表于 11-10 18:23 ?408次閱讀
    MM32F0140 <b class='flag-5'>FlexCAN</b>一致性測試 (2)

    MM32F0140 FlexCAN一致性測試(1)

    MM32F0140 FlexCAN一致性測試 (1)
    的頭像 發表于 11-10 17:50 ?304次閱讀
    MM32F0140 <b class='flag-5'>FlexCAN</b>一致性測試(1)

    MM32F0140學習筆記——FlexCAN 控制器局域網

    MM32F0140學習筆記——FlexCAN 控制器局域網
    的頭像 發表于 10-27 09:25 ?1153次閱讀
    MM32F0140學習筆記——<b class='flag-5'>FlexCAN</b> 控制器局域網
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>