引言
CANopen是實現(xiàn)CAN設(shè)備組網(wǎng)的典型協(xié)議棧和規(guī)范,對應(yīng)于軟件系統(tǒng)中,有一些開源的軟件組件,實現(xiàn)了CANopen協(xié)議棧,例如CANopenNode和CAN Festival。CANFestival和CANopenNode都是用于在嵌入式系統(tǒng)上實現(xiàn)CANopen協(xié)議通信的開源軟件協(xié)議棧,但需要注意的是,它們使用了不同的開源協(xié)議:
- CANFestival使用LGPLv2開源協(xié)議,這意味著CANFestival的源代碼是免費提供的,任何人都可以使用、修改和分發(fā),但衍生作品使用相同的GPL許可證。如果一個公司在產(chǎn)品中使用CANFestival組件,他們也必須按照同樣的LGPLv2開源協(xié)議提供其產(chǎn)品的源代碼。
- CANopenNode使用 Apache v2.0開源協(xié)議。這是一個自由度比LGPLv2更為開發(fā)的一個開源協(xié)議,允許在使用軟件方面有更大的靈活性。任何人都可以使用、修改和發(fā)布CANopenNode,甚至用于商業(yè)目的,而不需要發(fā)布其衍生作品的源代碼。
表x CANopenNode vs CAN Festival
本文將以CANopenNode為例,講解CANopen協(xié)議的一種實現(xiàn),并在具體的微控制器平臺上適配運行。
CANopenNode項目簡介
CANopenNode是一個免費的開源CANopen協(xié)議棧的實現(xiàn)。CANopen協(xié)議棧是一個在嵌入式控制器上的基于CAN總線高層應(yīng)用協(xié)議,遵循國際標準CiA 301(EN 50325-4)。CANopenNode實現(xiàn)了CANopen協(xié)議棧的絕大多數(shù)功能:
- 網(wǎng)絡(luò)管理協(xié)議中,NMT從站狀態(tài)機(啟動、停止、復(fù)位設(shè)備)和簡單的NMT主站。
- 錯誤控制協(xié)議中,心跳消息的生產(chǎn)者(發(fā)送方)和消費者(接收方)
- PDO連接和動態(tài)映射到過程變量的快速交互
- SDO快速傳輸、常規(guī)(分段)傳輸和塊傳輸
- SDO主站
- 緊急消息
- 同步協(xié)議中的生產(chǎn)者(發(fā)送方)和消費者(接收方)
- 授時協(xié)議中的生產(chǎn)者(發(fā)送方)和消費者(接收方)
- 非易失性存儲
- LSS服務(wù)的主站和從機,LSS快速掃描
CANopenNode的源碼在開源軟件網(wǎng)站GitHub上發(fā)布,https://github.com/CANopenNode/CANopenNode,開發(fā)者可以直接下載完整的CANopenNode源碼。此處需要注意,v1.3之后,持續(xù)開發(fā)的v2.0/4.0,在實現(xiàn)內(nèi)容上有較大變化。本文選用已經(jīng)標注“Verified”的v1.3版本,這也是目前最新的相對穩(wěn)定的發(fā)布版本。
figure-conode-github-pack
圖x 在GitHub上下載CANopenNode源碼包CANopenNode組件是以ANSI C語言編寫,可以作為一個標準的應(yīng)用組件,方便地移植到不同的微控制器平臺,或者是實時操作系統(tǒng)上。通過使用CANopenNode組件,可以在CANopen設(shè)備節(jié)點上創(chuàng)建一個對象字典(Object Dictionary),其中包含若干個變量(代表著配置信息),可以由本機直接通過C語言訪問,也可以由別的CANopen節(jié)點通過CAN網(wǎng)絡(luò)訪問,以此來實現(xiàn)CAN總線網(wǎng)絡(luò)系統(tǒng)中的信息交換,以及軟件系統(tǒng)對硬件系統(tǒng)的控制。
CANopenNode組件本身并不是一個完整的應(yīng)用程序,它包含一組實現(xiàn)CANopen協(xié)議棧的源碼和基于本身的樣例工程。如果要運行CANopenNode組件,還需要在一個具體的硬件平臺上進行適配,例如文本中即將用到的集成FlexCAN外設(shè)模塊的MM32F0140微控制器。
CANopenNode的開源站點上還開放了更多CANopen的功能組件,例如可以運行在Linux主機系統(tǒng)作為master的CANopenSocket項目,在多微控制器平臺上實現(xiàn)演示用例和測試工具的CANopenDemo項目,可以編輯對象字典生成C源碼文件的CAnopenEditor項目等等。
CANopenNode實現(xiàn)的工作流,如圖x所示。
figure-conode-workflow
圖x CANopenNode的工作流程其中,CAN總線接收線程和定時器周期執(zhí)行線程,可以在微控制器的中斷服務(wù)程序中實現(xiàn),主線線程可以在main()函數(shù)的主循環(huán)中實現(xiàn)。至于SDO客戶端和LSS(一個配置CANopen節(jié)點ID和比特率的服務(wù))客戶端,可以在應(yīng)用層的用戶程序中根據(jù)需要調(diào)用。
CANopenNode的源碼文件組織結(jié)構(gòu),如圖x所示。
figure-conode-source-files
圖x CANopenNode源碼文件的組織結(jié)構(gòu)CANopenNode項目的stack
目錄下分別實現(xiàn)了CANopen協(xié)議中的對象(通信過程),并封裝在各自獨立的源文件中。特別地,在stack/drvTemplate
目錄中,為開發(fā)者提供了一個向具體目標平臺移植CANopenNode的源碼模板,同時還提供了在多種不同微控制器平臺上移植CANopenNode的范例,例如stack/STM32
, stack/LPC1768
,stack/PIC32
等。
CANopenNode的移植接口
CANopenNode的源碼目錄中,專門為在具體微控制器平臺上實現(xiàn)移植提供了源碼模板,位于stack/drvTemplate
目錄中。本節(jié)簡要分析其中的代碼結(jié)構(gòu),為后續(xù)基于具體目標平臺實現(xiàn)移植奠定基礎(chǔ)。
stack/drvTemplate
目錄下包含四個源文件:CO_driver.h, CO_driver_target.h, eeprom.c和eeprom.h。
CO_driver_target.h
CO_driver_target.h源文件包含了支持如下功能的數(shù)據(jù)類型定義、函數(shù)原型和宏定義:
- 基本的數(shù)據(jù)類型
- CANopen消息的接收和發(fā)送緩沖區(qū)
- 同微控制器集成CAN外設(shè)模塊的接口
- CAN外設(shè)的接收和發(fā)送中斷函數(shù)的聲明(將用于在移植過程中嵌入硬件中斷服務(wù)程序中)
這個源文件定義了的CANopen的底層驅(qū)動程序,還定義了一些專用于優(yōu)化協(xié)議執(zhí)行過程的數(shù)據(jù)結(jié)構(gòu),它不再使用的CAN消息隊列,而是直接將數(shù)據(jù)連接CANopen設(shè)備的對象(通信過程)上,盡量提高響應(yīng)速度,并減少不必要的計算和內(nèi)存開銷。
CO_CANmodule_t結(jié)構(gòu)類型中,包含了一組接收消息對象(CO_CANrx_t類型)和一組發(fā)送消息對象(CO_CANtx_t類型),每個CANopen通信對象都有自己專屬的其中一個成員。例如,心跳消息生產(chǎn)者可以創(chuàng)建一個CANopen發(fā)送對象,它就需要在CO_CANtx_t數(shù)組中預(yù)留一個表項。同步模塊可能產(chǎn)生一個同步對象或是接收一個同步的對象,它就需要在CO_CANtx_t數(shù)組或者CO_CANrx_t數(shù)組中預(yù)留一個表項。
接收過程
在接收到CAN消息之前,CO_CANrx_t中的每個成員都必須被初始化,此時需要調(diào)用CO_CANrxBufferInit()函數(shù),例如,在CO_HBconsumer中就使用了CO_CANrx_t中的多個成員(需要監(jiān)控多個遠程節(jié)點),就需要多次調(diào)用CO_CANrxBufferInit()函數(shù),對每個CO_CANrx_t進行初始化。CO_CANrxBufferInit()函數(shù)的兩個主要參數(shù),一個是CAN ID,另一個是一個回調(diào)函數(shù)的指針,這兩個參數(shù)將被寫入到CO_CANrx_t數(shù)組中。其中的回調(diào)函數(shù)是根據(jù)具體功能模塊實現(xiàn)的,用以處理接收的幀消息,將必要的數(shù)據(jù)搬運到合適的內(nèi)存中,然后觸發(fā)其他任務(wù)以繼續(xù)處理接收數(shù)據(jù)?;卣{(diào)函數(shù)的程序必須要短小精悍,僅做少量必要的計算和數(shù)據(jù)搬運工作,以避免耽誤后續(xù)接收幀的時機。
接收CAN幀的操作將在CAN外設(shè)模塊的接收中斷服務(wù)中進行。當在接收中斷服務(wù)程序中捕獲到CAN消息后,程序首先將它的CAN ID同CO_CANrx_t數(shù)組中的成員進行匹配,如果匹配到預(yù)先配置好的CO_CANrx_t,就會執(zhí)行其中的回調(diào)函數(shù)。
回調(diào)函數(shù)有兩個傳入?yún)?shù):
- object - CO_CANrxBufferInit()函數(shù)注冊的一個指向傳輸對象的指針
- msg - 一個指向CO_CANrxMsg_t類型CAN消息的指針
回調(diào)函數(shù)可以返回CO_ReturnError_t類型的狀態(tài)值:
- CO_ERROR_NO
- CO_ERROR_RX_OVERFLOW
- CO_ERROR_RX_PDO_OVERFLOW
- CO_ERROR_RX_MSG_LENGTH
- CO_ERROR_RX_PDO_LENGTH。
發(fā)送過程
在發(fā)送CAN消息之前,CO_CANtx_t列表中的成員必須被CO_CANtxBufferInit()函數(shù)初始化。例如,心跳消息生產(chǎn)者就必須初始化它在CO_CANtx_t數(shù)組中的成員。CO_CANtxBufferInit()函數(shù)翻譯一個指向CO_CANtx_t類型結(jié)構(gòu)體的指針,其中包含了一個緩沖區(qū),可以存放即將要發(fā)送幀的數(shù)據(jù)。之后,可以通過調(diào)用CO_CANsend()函數(shù)啟動發(fā)送過程。如果恰巧微控制器硬件的發(fā)送緩沖區(qū)是可用的,就可以直接將發(fā)送消息從內(nèi)存中搬運到CAN外設(shè)的硬件緩沖區(qū)中等待發(fā)送,否則,CO_CANsend()函數(shù)將設(shè)定_bufferFull_
標志位為True,之后將通過發(fā)送中斷觸發(fā)的硬件發(fā)送緩沖區(qū)可用事件,觸發(fā)數(shù)據(jù)搬運過程并啟動發(fā)送。CO_CANtx_t中的數(shù)據(jù)在通過CAN外設(shè)發(fā)送出去之前,是不可改動的。這里CO_CANtx_t隊列中可能有多個成員的_bufferFull_
標志位為True,此時,編號更小的CO_CANtx_t將被優(yōu)先發(fā)送出去。
關(guān)鍵區(qū)域的函數(shù)
CANopenNode被設(shè)計基于多個線程運行,不同系統(tǒng)平臺對多線程的實現(xiàn)方式也不盡相同。在微控制器平臺,可以使用不同優(yōu)先級的中斷服務(wù)程序?qū)崿F(xiàn)多個線程。此時,需要將多個線程可能共同訪問的資源保護起來。一種簡單的實現(xiàn),可以在中斷服務(wù)程序或者后臺的調(diào)度器使用這些共享資源時,禁用對方,或者使用信號量等同步機制。
部分函數(shù)可以在不同的線程被調(diào)用:
- CO_driver.h中的CO_CANsend()函數(shù)
- CO_Emergency.h中的CO_errorReport()函數(shù)和CO_errorReset()函數(shù)
通常只有兩個線程會訪問到對象字典變量:一個是主線程,另一個是定時器線程。CANopenNode在主線程中運行CANopenNode的初始化過程和SDO服務(wù)端程序。PDO的程序運行在周期更短的定時器線程中,并且處理PDO的過程不能被主線程打斷。主線程必須保護定時器線程同時在訪問的對象字典變量,應(yīng)用層的程序也是如此。需要注意的是,并不是所有的對象字典變量可以被映射到PDO,所以這些不被PDO操作的變量是不需要被保護起來的。SDO服務(wù)端操作是會保護操作對象字典中的變量。
CAN接收線程對接收到的CAN消息幀進行簡單處理后,將它們寫入對應(yīng)的對象中,交由別的線程在后續(xù)繼續(xù)處理。這個過程不需要保護任何關(guān)鍵區(qū)域。但有一個例外,當同步消息出現(xiàn)在CANopen的總線上時, 需要臨時禁用CANrx(),直到所有的PDO都處理完畢。
這里需要開發(fā)者在移植CANopenNode到具體的微控制器平臺上時,需要實現(xiàn)保護關(guān)鍵區(qū)的宏函數(shù):
#define CO_LOCK_CAN_SEND() /**< Lock critical section in CO_CANsend() */
#define CO_UNLOCK_CAN_SEND()/**< Unlock critical section in CO_CANsend() */
#define CO_LOCK_EMCY() /**< Lock critical section in CO_errorReport() or CO_errorReset() */
#define CO_UNLOCK_EMCY() /**< Unlock critical section in CO_errorReport() or CO_errorReset() */
#define CO_LOCK_OD() /**< Lock critical section when accessing Object Dictionary */
#define CO_UNLOCK_OD() /**< Unock critical section when accessing Object Dictionary */
內(nèi)存同步函數(shù)
在接收CAN通信幀和處理消息的線程間同步消息緩沖區(qū)。當在中斷服務(wù)程序中運行接收函數(shù),則不需要進行任何同步操作,因為一旦中斷發(fā)生,CPU的使用權(quán)會自動從其它處理消息幀的線程切換到中斷服務(wù)程序。否則,需要使用一些同步機制,確保先接收到完整的CAN消息幀之后再處理它們。例如,使用GCC編譯器時,可以使用GCC編譯器內(nèi)置的內(nèi)存邊界函數(shù)__sync_synchronize()
,此時,只要將CANrxMemoryBarrier()函數(shù)映射到這個內(nèi)存邊界函數(shù)即可。
#define CANrxMemoryBarrier() {__sync_synchronize();}
CO_driver_target.h文件中定義了一組內(nèi)存同步相關(guān)的函數(shù):
/** Memory barrier */
#define CANrxMemoryBarrier()
/** Check if new message has arrived */
#define IS_CANrxNew(rxNew) ((uintptr_t)rxNew)
/** Set new message flag */
#define SET_CANrxNew(rxNew) {CANrxMemoryBarrier(); rxNew = (void*)1L;}
/** Clear new message flag */
#define CLEAR_CANrxNew(rxNew) {CANrxMemoryBarrier(); rxNew = (void*)0L;}
CO數(shù)據(jù)類型
CO_driver_target.h文件的后續(xù),還定了一些基本數(shù)據(jù)類型:
/**
* @defgroup CO_dataTypes Data types
* @{
*
* According to Misra C
*/
/* int8_t to uint64_t are defined in stdint.h */
typedef unsigned char bool_t; /**< bool_t */
typedef float float32_t; /**< float32_t */
typedef long double float64_t; /**< float64_t */
typedef char char_t; /**< char_t */
typedef unsigned char oChar_t; /**< oChar_t */
typedef unsigned char domain_t; /**< domain_t */
/** @} */
以及CANopenNode在操作CAN硬件驅(qū)動時涉及到的結(jié)構(gòu)體類型的定義,包括:
- CO_CANrxMsg_t - 接收CAN幀結(jié)構(gòu)體
- CO_CANrx_t - 接收消息對象
- CO_CANtx_t - 發(fā)送消息對象
- CO_CANmodule_t - CAN外設(shè)模塊對象
以及最后聲明了CO_CANinterrupt()函數(shù),便于開發(fā)者在移植時嵌入中斷服務(wù)程序:
void CO_CANinterrupt(CO_CANmodule_t *CANmodule);
CO_driver.c
CO_driver.c文件是CANopenNode對接微控制器的底層接口,在CO_driver.c文件的函數(shù)中,添加操作目標微控制器平臺包含CAN外設(shè)模塊在內(nèi)的電路系統(tǒng)的代碼,建立CANopenNode同具體微控制器平臺的綁定。CO_driver.c文件中實現(xiàn)了一些對CAN外設(shè)驅(qū)動進行抽象的函數(shù),如表x所示。
表x CANopenNode抽象的CAN外設(shè)驅(qū)動函數(shù)清單
在具體的目標微控制器平臺上移植CANopenNode時,需要結(jié)合具體的硬件CAN外設(shè)模塊,補充這些函數(shù)中對硬件的操作。
eeprom.h & eeprom.c
eeprom.h和eeprom.c文件,綁定了讀寫EEPROM存儲器的驅(qū)動程序,可以在CANopen協(xié)議運行的過程中,將對象字典保存在EEPROM存儲器中。在基本的移植中,可以不實現(xiàn)將對象字典存儲在外部存儲器的功能。
/**
* Eeprom object.
*/
typedef struct{
uint8_t *OD_EEPROMAddress; /**< From CO_EE_init_1() */
uint32_t OD_EEPROMSize; /**< From CO_EE_init_1() */
uint8_t *OD_ROMAddress; /**< From CO_EE_init_1() */
uint32_t OD_ROMSize; /**< From CO_EE_init_1() */
uint32_t OD_EEPROMCurrentIndex; /**< Internal variable controls the OD_EEPROM vrite */
bool_t OD_EEPROMWriteEnable; /**< Writing to EEPROM is enabled */
}CO_EE_t;
/**
* First part of eeprom initialization. Called after microcontroller reset.
*
* @param ee This object will be initialized.
* @param OD_EEPROMAddress Address of OD_EEPROM structure from object dictionary.
* @param OD_EEPROMSize Size of OD_EEPROM structure from object dictionary.
* @param OD_ROMAddress Address of OD_ROM structure from object dictionary.
* @param OD_ROMSize Size of OD_ROM structure from object dictionary.
*
* @return #CO_ReturnError_t: CO_ERROR_NO, CO_ERROR_DATA_CORRUPT (Data in eeprom corrupt) or
* CO_ERROR_CRC (CRC from MBR does not match the CRC of OD_ROM block in eeprom).
*/
CO_ReturnError_t CO_EE_init_1(
CO_EE_t *ee,
uint8_t *OD_EEPROMAddress,
uint32_t OD_EEPROMSize,
uint8_t *OD_ROMAddress,
uint32_t OD_ROMSize);
/**
* Second part of eeprom initialization. Called after CANopen communication reset.
*
* @param ee - This object.
* @param eeStatus - Return value from CO_EE_init_1().
* @param SDO - SDO object.
* @param em - Emergency object.
*/
void CO_EE_init_2(
CO_EE_t *ee,
CO_ReturnError_t eeStatus,
CO_SDO_t *SDO,
CO_EM_t *em);
/**
* Process eeprom object.
*
* Function must be called cyclically. It strores variables from OD_EEPROM data
* block into eeprom byte by byte (only if values are different).
*
* @param ee This object.
*/
void CO_EE_process(CO_EE_t *ee);
基于FlexCAN適配CANopenNode
(未完待續(xù))
一個CANopenNode的應(yīng)用樣例
(未完待續(xù))
總結(jié)
(未完待續(xù))
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19123瀏覽量
305143 -
接口
+關(guān)注
關(guān)注
33文章
8596瀏覽量
151145 -
移植
+關(guān)注
關(guān)注
1文章
379瀏覽量
28130 -
開源
+關(guān)注
關(guān)注
3文章
3348瀏覽量
42496 -
CANopen
+關(guān)注
關(guān)注
8文章
253瀏覽量
43586
發(fā)布評論請先 登錄
相關(guān)推薦
評論