色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于FlexCAN適配CANopenNode

CHANBAEK ? 來源:安德魯?shù)脑O(shè)計(jì)筆記本 ? 作者:安德魯蘇 ? 2023-06-23 15:51 ? 次閱讀
  • 準(zhǔn)備微控制器基本工程
  • 在微控制器上適配CANopenNode
    • 配置電路板的時(shí)鐘和引腳 board_init.c
    • 準(zhǔn)備硬件定時(shí)器 main.c
    • 對(duì)接CAN驅(qū)動(dòng) CO_driver.c & main.c

本文是《CAN總線開發(fā)一本全(6) - CANopenNode組件》的補(bǔ)充其中一個(gè)小節(jié)。

總結(jié)在微控制器平臺(tái)上移植CANopenNode,需要根據(jù)具體硬件條件,適配2個(gè)源文件:

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

接下來,將以集成了FlexCAN外設(shè)模塊的MM32F0140微控制器為例,實(shí)現(xiàn)對(duì)CANopenNode v1.3的適配過程。

目前靈動(dòng)官方的軟件開發(fā)平臺(tái)MindSDK已經(jīng)適配了CANopenNode協(xié)議棧,并創(chuàng)建了一系列樣例工程:

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

為了描述適配CANopenNode的過程,這里仍然從零開始,展現(xiàn)完整的移植開發(fā)過程。

準(zhǔn)備微控制器基本工程

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

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

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

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

圖片

figure-canopen-demo-proj-settings

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

圖片

figure-canopen-demo-proj-build-log

圖x canopen_demo工程編譯正確此時(shí)的canopen_demo工程中,包含了CANopenNode的所有源碼、FlexCAN外設(shè)模塊的驅(qū)動(dòng),以及使用MM32F0140微控制器的所有必要的源文件,并且可以通過編譯器驗(yàn)證編寫程序代碼的正確性。后續(xù)進(jìn)行適配工作過程中,將通過開發(fā)者自行編碼,將CANopenNode和FlexCAN外設(shè)模塊綁定起來,并可實(shí)時(shí)編譯工程驗(yàn)證編碼內(nèi)容。

在微控制器上適配CANopenNode

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

配置電路板的時(shí)鐘和引腳 board_init.c

  1. 配置時(shí)鐘

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

在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開發(fā)板使用PB8和PB9作為CAN接口引腳,需要配置引腳的復(fù)用功能為CAN服務(wù)。

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()函數(shù)和BOARD_InitPins()函數(shù),將在board_init.c文件中被BOARD_Init()函數(shù)調(diào)用,

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

    BOARD_InitDebugConsole();
}

BOARD_Init()函數(shù)最終將在main.c文件中被調(diào)用,實(shí)現(xiàn)對(duì)電路板的初始化工作。

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

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

準(zhǔn)備硬件定時(shí)器 main.c

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

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

#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);
}

其中,關(guān)于硬件定時(shí)器的配置參數(shù)的定義統(tǒng)一放置于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()函數(shù)中調(diào)用了BOARD_TIM_Init()函數(shù),配置硬件定時(shí)器TIM2產(chǎn)生1ms為周期的中斷,并啟動(dòng)定時(shí)器。此時(shí),對(duì)應(yīng)在硬件定時(shí)器的中斷服務(wù)程序中調(diào)用CANopenNode的定時(shí)器線程函數(shù)tmrTask_thread(),并在其中清硬件定時(shí)器中斷的標(biāo)志位。

/* 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()函數(shù)的實(shí)現(xiàn)模板中,停用了“/* verify timer overflow */”之后的代碼。這些被停用的代碼,原本可以用來驗(yàn)證tmrTask_thread()函數(shù)內(nèi)部操作,例如處理同步過程、讀接收PDO和寫發(fā)送PDO,在清了上一次1ms中斷的硬件標(biāo)志位后的1ms中是否能夠執(zhí)行完畢。如果tmrTask_thread()函數(shù)的處理時(shí)間過長(zhǎng),超出了一個(gè)周期任務(wù)的執(zhí)行時(shí)間,此時(shí)檢測(cè)到1ms定時(shí)器中斷標(biāo)志位再次置位,即出現(xiàn)超時(shí)。在1ms周期任務(wù)超時(shí)之后,CANopen協(xié)議棧會(huì)認(rèn)為這是一個(gè)可能產(chǎn)生風(fēng)險(xiǎn)的任務(wù),因此可調(diào)用CO_errorReport()函數(shù)將錯(cuò)誤情況上報(bào)給CANopen協(xié)議棧。

對(duì)接CAN驅(qū)動(dòng) CO_driver.c & main.c

CO_driver.c文件中定義了大量的具體微控制器平臺(tái)的CAN外設(shè)硬件模塊相關(guān)的驅(qū)動(dòng)函數(shù),但在最基礎(chǔ)的移植過程中,僅需重點(diǎn)關(guān)注4個(gè)函數(shù)即可:

  • CO_CANmode_init() - 初始化CAN外設(shè)模塊,并配置好比特率、消息幀過濾器,以及收發(fā)中斷等。
  • CO_CANsend() - 將消息緩沖區(qū)中的數(shù)據(jù)搬運(yùn)至CAN外設(shè)模塊的硬件發(fā)送緩沖區(qū)中,即將發(fā)送CAN消息幀到CAN總線上。
  • CO_CANinterrupt() - 綁定到CAN外設(shè)模塊的硬件中斷的服務(wù)程序,主要實(shí)現(xiàn)CAN硬件的接收過程,即將CAN外設(shè)模塊從CAN總線上捕獲下來的CAN消息幀數(shù)據(jù)轉(zhuǎn)存到CANopenNode組件的接收緩沖區(qū)中,供協(xié)議棧進(jìn)一步處理。當(dāng)使用中斷方式發(fā)送CAN消息幀時(shí),也需要在CO_CANinterrupt()函數(shù)中調(diào)用CO_CANsend()函數(shù)發(fā)送CAN消息幀。
  • CO_CANverifiyErrors() - 查看CAN外設(shè)模塊的硬件錯(cuò)誤。因?yàn)镃AN總線上的消息幀需要經(jīng)過仲裁才能上線,所以這里查錯(cuò)函數(shù)主要檢查的是發(fā)送消息幀超時(shí)的情況。

原生CANopenNode組件包中的CO_driver.c文件中的函數(shù)已經(jīng)實(shí)現(xiàn)了絕大部分同協(xié)議棧交互的業(yè)務(wù)邏輯,在具體微控制器平臺(tái)上是適配時(shí),僅需要將少量同硬件相關(guān)的步驟綁定到微控制器硬件的操作上即可。

CO_CANmodule_init()

CO_driver.c文件中向CO_CANmodule_init()函數(shù)嵌入初始化FlexCAN外設(shè)模塊的代碼,包括初始化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;
}

此處有兩個(gè)要點(diǎn):

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

CO_CANsend()

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

/******************************************************************************/
/* 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()函數(shù)時(shí),有個(gè)要點(diǎn):

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

CO_CANinterrupt()

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

/******************************************************************************/
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()函數(shù)中,實(shí)現(xiàn)了接收CAN消息幀和發(fā)送CAN消息幀的過程:

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

CO_CANinterrupt()函數(shù)將在main.c文件中被硬件的CAN中斷服務(wù)函數(shù)調(diào)用。

/* 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()函數(shù)中,嵌入從CAN外設(shè)讀錯(cuò)誤計(jì)數(shù)值和狀態(tài)標(biāo)志位的代碼,將硬件的錯(cuò)誤狀態(tài)反饋給CANopenNode協(xié)議棧。

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外設(shè)接收幀和發(fā)送幀的錯(cuò)誤計(jì)數(shù)器,一般的CAN外設(shè)模塊(例如FlexCAN),會(huì)對(duì)從CAN總線上捕獲消息幀和發(fā)送消息幀進(jìn)行超時(shí)管理,因?yàn)镃AN總線的發(fā)送過程存在仲裁,確實(shí)可能在通信繁忙的時(shí)間段有一些優(yōu)先級(jí)比較低(CAN ID值比較大)的消息幀無法順利發(fā)出。此時(shí),如果有通信幀久久沒有成功發(fā)出,則會(huì)上報(bào)給CANopen協(xié)議棧,進(jìn)一步可能會(huì)通過NMT協(xié)議調(diào)整網(wǎng)絡(luò)通信的節(jié)奏,盡量讓關(guān)鍵數(shù)據(jù)(由于延遲提升的優(yōu)先級(jí))得以通暢傳輸。

CO_CANclearPendingSyncPDOs()

另外,還需要在CO_CANclearPendingSyncPDOs()函數(shù)中增加對(duì)FlexCAN硬件發(fā)送緩沖區(qū)的檢查,當(dāng)需要發(fā)送同步消息時(shí),如果有未上線的消息占用發(fā)送消息緩沖區(qū)(當(dāng)新的同步消息準(zhǔn)備發(fā)出時(shí),之前未發(fā)出的同步消息已經(jīng)失效,不再具有同步的意義),則CANopen可以強(qiáng)制騰空發(fā)送緩沖區(qū),為最新的同步消息騰出空間準(zhǔn)備發(fā)送。

/******************************************************************************/
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);
    }
}

至此,一個(gè)基本的使用CANopenNode組件實(shí)現(xiàn)的CANopen的框架即移植完成。編譯項(xiàng)目,清理可能的錯(cuò)誤,即可下載工程的開發(fā)板運(yùn)行程序。

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
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 微控制器
    +關(guān)注

    關(guān)注

    48

    文章

    7573

    瀏覽量

    151702
  • CAN總線
    +關(guān)注

    關(guān)注

    145

    文章

    1953

    瀏覽量

    130908
  • 定時(shí)器
    +關(guān)注

    關(guān)注

    23

    文章

    3253

    瀏覽量

    115057
  • 引腳
    +關(guān)注

    關(guān)注

    16

    文章

    1204

    瀏覽量

    50705
  • CANopen
    +關(guān)注

    關(guān)注

    8

    文章

    267

    瀏覽量

    43624
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    基于MM32G5330的FlexCAN實(shí)現(xiàn)CANopenNode協(xié)議棧移植

    本文將介紹如何基于靈動(dòng)MM32G5330的FlexCAN實(shí)現(xiàn)CANopenNode協(xié)議棧的移植,并使用靈動(dòng)官方提供的開發(fā)板Mini-G5333進(jìn)行驗(yàn)證。
    發(fā)表于 04-12 09:15 ?1572次閱讀
    基于MM32G5330的<b class='flag-5'>FlexCAN</b>實(shí)現(xiàn)<b class='flag-5'>CANopenNode</b>協(xié)議棧移植

    SPC560Dxx FlexCAN傳輸錯(cuò)誤

    我正在使用SPC560D-DIS Discovery板進(jìn)行開發(fā)和SPC5工作室。 我已經(jīng)為FlexCAN測(cè)試導(dǎo)入了測(cè)試應(yīng)用程序'SPC560Dxx OS-Less CAN Test
    發(fā)表于 11-22 10:36

    FlexCAN不起作用

    嗨, 我正在使用spc5_studio測(cè)試SPC560P50L5,FlexCAN的內(nèi)置示例是環(huán)回測(cè)試。當(dāng)我禁用環(huán)回并啟用管理程序模式時(shí),CAN_TXD引腳上沒有任何內(nèi)容。我不知道這個(gè)問題,我需要
    發(fā)表于 04-02 11:51

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

    我正在使用 MQX FlexCAN FIFO 接收機(jī)制。除了消息接收延遲外,它工作正常。當(dāng)收到第一個(gè) CAN 報(bào)文時(shí),F(xiàn)IFO 的輸出是一條僅包含零的報(bào)文。消息 2-5 也是??如此。當(dāng)接收到第 6
    發(fā)表于 03-16 08:07

    如何使用FlexCan中斷接收S32R45上的數(shù)據(jù)?

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

    FlexCAN傳輸不工作的原因?

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

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

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

    FlexCAN配置為L(zhǎng)egacy RxFIFO模式的步驟是什么?

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

    靈動(dòng)MM32F0140:FlexCAN控制器介紹

    1 FlexCAN 簡(jiǎn)介 FlexCAN 控制器局域網(wǎng)模塊是符合 ISO 11898-1 標(biāo)準(zhǔn)和 CAN 2.0B 規(guī)范的通信控制器,支持 CAN 總線協(xié)議。FlexCAN 模塊框圖如 圖1 所示
    的頭像 發(fā)表于 05-13 16:42 ?4432次閱讀
    靈動(dòng)MM32F0140:<b class='flag-5'>FlexCAN</b>控制器介紹

    FlexCAN 的基本使用方法及特性

    一直關(guān)注我們靈動(dòng)微課堂的朋友們,想必通過前面的介紹已經(jīng)掌握了 FlexCAN 的基本使用方法,也能夠在自己的方案中運(yùn)用自如了。今天小編想和大家借助 ZLG 的CAN Scope工具了解我們這顆 MCU 的 CAN 的特性,看看在汽車 ECU 應(yīng)用上它是如何保證通訊的魯棒性。
    的頭像 發(fā)表于 07-22 11:28 ?4699次閱讀

    MindSDK中FlexCAN驅(qū)動(dòng)程序及樣例工程

    和MM32F0140微控制器,其中就有FlexCAN外設(shè)模塊的驅(qū)動(dòng)程序以及樣例工程,以及對(duì)CAN總線通信協(xié)議CANopen的適配工程。本文將介紹MindSDK中FlexCAN驅(qū)動(dòng)程序及樣例工程,展現(xiàn)一種典型的CAN總線驅(qū)動(dòng)程序的
    的頭像 發(fā)表于 06-23 15:41 ?1283次閱讀
    MindSDK中<b class='flag-5'>FlexCAN</b>驅(qū)動(dòng)程序及樣例工程

    CANopenNode的移植接口詳解

    CANopen是實(shí)現(xiàn)CAN設(shè)備組網(wǎng)的典型協(xié)議棧和規(guī)范,對(duì)應(yīng)于軟件系統(tǒng)中,有一些開源的軟件組件,實(shí)現(xiàn)了CANopen協(xié)議棧,例如CANopenNode和CAN Festival。CANFestival
    的頭像 發(fā)表于 06-23 15:49 ?4569次閱讀
    <b class='flag-5'>CANopenNode</b>的移植接口詳解

    一文淺談FlexCAN OTA

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

    MM32F0140 FlexCAN一致性測(cè)試 (2)

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

    FlexCAN中文手冊(cè)

    電子發(fā)燒友網(wǎng)站提供《FlexCAN中文手冊(cè).pdf》資料免費(fèi)下載
    發(fā)表于 06-20 11:14 ?4次下載
    主站蜘蛛池模板: 国产色综合色产在线视频| 夜夜穞狠狠穞| 午夜视频在线瓜伦| 阴茎插入阴道| 国产AV一区二区三区传媒| 伦理片97影视网| 亚洲精品AV无码永久无码| 成人毛片免费播放| 凌馨baby| 亚洲精品天堂自在久久77| yy8090理论三级在线看| 接吻吃胸摸下面啪啪教程| 日韩人妻少妇一区二区三区| 中文无码第3页不卡av| 国精产品一区二区三区| 沈芯语麻豆0076 视频| 99re6久久在热线视频| 久久热在线视频精品| 亚洲国产精品无码中文字幕| 国产AV电影区二区三区曰曰骚网| 免费视频网站嗯啊轻点| 伊人久久综合成人亚洲| 国内精品久久| 亚洲国产高清在线观看视频| 国产精品18久久久久网站| 日本人添下面的全过程| 99热只有这里有精品| 男人网站在线| 99国产在线精品视频| 美女18黄| 99国产在线视频| 牛和人交videos欧美| 纵欲(高H)| 麻豆一区二区三区蜜桃免费| 一本到道免费线观看| 国产一级特黄a大片99| 午夜男人免费福利视频| 国产成人精品久久一区二区三区 | WWW亚洲精品久久久乳| 牛牛在线(正)精品视频| 99久久久国产精品免费调教|