本文是《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)建了一系列樣例工程:
為了描述適配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.c
和CO_driver_target.h
文件復(fù)制到canopen_demo
工程的board
目錄下 - 將其中
example
目錄下的的CO_OD.c
、CO_OD.h
和main.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.c
、CO_OD.h
和main.c
,和board
目錄下新增文件CO_driver.c
和CO_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
- 配置時(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);
}
- 配置引腳
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;
}