1. 程式人生 > >CanOpen協議【CanFestival】移植到STM32

CanOpen協議【CanFestival】移植到STM32

轉自http://bbs.21ic.com/icview-878522-1-1.html

前段時間學習了CanOpen協議,到網上下載的CanFestival3-10原始碼,移植到VC、QT、STM32等平臺,由於網上的資源較少,走了不少彎路,移植好使用過程中才逐漸暴露出各種問題,比如OD字串傳輸、心跳時間不準確等等,現在已經解決了遇到的所有問題,移植出來的工程能夠完好支援CanOpen協議,花了點時間,整理出一個簡單易用的移植方法說明,也寫了一些比較實用的除錯工具,本來還想整理SDO、PDO、EDS檔案裝載等相關知識的,可惜比較忙,等什麼時候有空了再整理其他的吧!先把移植的貼上來,希望能幫到大家。
如果是第一次,整個移植過程還比較麻煩,需要有耐心,按照下面說的一步步來肯定可以的,移植成功一次後,再移植到其他平臺就非常輕鬆了。


到網上下載CanFestival原始碼CanFestival-3-1041153c5fd2,解壓出來,並將資料夾名字改為CanFestival-3-10,我們移植需要用到的原始檔在CanFestival-3-10\src目錄下,標頭檔案在CanFestival-3-10\include目錄下。


CanFestival-3-10\src下的檔案如下圖所示:

CanFestival-3-10\include下的檔案如下圖所示:

接下來開始移植:
步驟一:在新建好的工程目錄下新建資料夾CanFestival,再在CanFestival下新建資料夾driver、inc和src,再在inc資料夾下面新建stm32資料夾(我這裡主要以移植到stm32為例說明,如果是移植到VC或其他平臺下,這裡也可以命名為其他名字,如vc)。
步驟二:將CanFestival-3-10\src目錄下的dcf.c、emcy.c、lifegrd.c、lss.c、nmtMaster.c、nmtSlave.c、objacces.c、pdo.c、sdo.c、states.c、sync.c、timer.c共12個檔案拷貝到CanFestival\src目錄下;將CanFestival-3-10\include目錄下的所有.h檔案共19個檔案全部拷貝到CanFestival\inc目錄下,再把CanFestival-3-10\examples\AVR\Slave目錄下的ObjDict.h檔案拷貝過來,一共20個;將CanFestival-3-10\include\AVR目錄下的applicfg.h、canfestival.h、config.h、timerscfg.h共4個頭檔案拷貝到canfestival\inc\stm32目錄下;將CanFestival-3-10\examples\TestMasterSlave目錄下的TestSlave.c、TestSlave.h、TestMaster.h、TestMaster.c拷貝到canfestival\driver目錄下,並在該目錄下新建stm32_canfestival.c檔案。
步驟三:將CanFestival\src目錄下的所有.c檔案新增到工程;將canfestival\driver目錄下的stm32_canfestival.c檔案新增到工程;如果實現的是從裝置,再將canfestival\driver目錄下的TestSlave.c檔案新增到工程,如果實現的是主裝置,則將TestMaster.c檔案新增到工程;
步驟四:將檔案目錄canfestival\inc、canfestival\inc\stm32、canfestival\driver等路徑新增到工程包含路徑。
步驟五:在stm32_canfestival.c中包含標頭檔案#include "canfestival.h",並定義如下函式:
void setTimer(TIMEVAL value)
{
}
TIMEVAL getElapsedTime(void)
{
return 1;
}
unsigned char canSend(CAN_PORT notused, Message *m)
{
return 1;
}
可以先定義一個空函式,等到編譯都通過了之後,再往裡面新增內容,這幾個函式都是定義來供canfestival原始碼呼叫的,如果找不到這幾個函式編譯就會報錯。
步驟六:通過以上幾步,所有的檔案都弄齊了,但是編譯一定會出現報錯,註釋或刪除掉config.h檔案中的如下幾行就能編譯通過:
#include <inttypes.h>
#include <avr\io.h>
#include <avr\interrupt.h>
#include <avr/pgmspace.h>
#include <avr\sleep.h>
#include <avr\wdt.h>
如果還有其他報錯,那有可能是因為不同原始碼版本、不同平臺、不同人遇到的錯誤也會不相同,這裡的過程只能做一定的參考,不一定完全相同,解決這些錯誤需要有一定的除錯功底,需要根據編譯出錯提示來進行修改對應地方,一般都是有些函式沒宣告或者某個標頭檔案沒有包含或者包含了一些不必要的標頭檔案而該檔案不存在或者是一些變數型別不符合需定義之類的,如果能夠擺平所有的編譯出錯,那麼移植就算成功了,如果你被編譯出錯擺平了,那麼遊戲就結束,沒得玩了。
步驟七:解決了所有的編譯錯誤後,接下來實現剛才定義的3個空函式,函式void setTimer(TIMEVAL value)主要被原始碼用來定時的,時間到了就需要呼叫一下函式TimeDispatch(),函式TIMEVAL getElapsedTime(void)主要被原始碼用來查詢距離下一個定時觸發還有多少時間,unsigned char canSend(CAN_PORT notused, Message *m)函式主要被原始碼用來發一個CAN包的,需要呼叫驅動來將一個CAN包發出去。
我們在stm32_canfestival.c檔案裡定義幾個變數如下:
unsigned int TimeCNT=0;//時間計數
unsigned int NextTime=0;//下一次觸發時間計數
unsigned int TIMER_MAX_COUNT=70000;//最大時間計數
static TIMEVAL last_time_set = TIMEVAL_MAX;//上一次的時間計數
setTimer和getElapsedTime函式實現如下:
//Set the next alarm //
void setTimer(TIMEVAL value)
{
NextTime=(TimeCNT+value)%TIMER_MAX_COUNT;
}


// Get the elapsed time since the last occured alarm //
TIMEVAL getElapsedTime(void)
{
int ret=0;
ret = TimeCNT> last_time_set ? TimeCNT - last_time_set : TimeCNT + TIMER_MAX_COUNT - last_time_set;
last_time_set = TimeCNT;
return ret;
}
另外還要開一個1毫秒的定時器,每1毫秒呼叫一下下面這個函式。
void timerForCan(void)
{
TimeCNT++;
if (TimeCNT>=TIMER_MAX_COUNT)
{
TimeCNT=0;
}
if (TimeCNT==NextTime)
{
TimeDispatch();
}
}
can發包函式canSend跟CAN驅動有關,CAN通道可以使用真實的CAN匯流排,也可以使用虛擬的CAN通道(如檔案介面、網路通道等等)。


啟動時初始化:
在初始化的檔案裡(比如main.c)新增以下幾行程式碼
#include "TestSlave.h"
unsigned char nodeID=0x21;
extern CO_Data TestSlave_Data;
在呼叫函式(比如main函式)裡呼叫以下程式碼初始化
setNodeId(&TestSlave_Data, nodeID);
setState(&TestSlave_Data, Initialisation); // Init the state
其中T estSlave_Data在TestSlave.c中定義
然後開啟呼叫TimerForCan()的1毫秒定時器,在接收CAN資料那裡呼叫一下原始碼函式canDispatch(&TestSlave_Data, &m);
canfestival原始碼就可以跑了,如果需要跟主裝置聯調,還要實現canSend函式,這個與平臺的Can驅動相關。


Stm32平臺下的驅動實現:
開啟一個1毫秒定時器,可參考如下程式碼,呼叫一下函式TIM4_start();即可:
/* TIM4 configure */
static void TIM4_Configuration(void)
{
/* 時鐘及分頻設定 */


TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* Time Base configuration */
/* 72M / 72 = 1us */
// 這個就是預分頻係數,當由於為0時表示不分頻所以要減1
TIM_TimeBaseStructure.TIM_Prescaler =72-1; //72000 - 1;
//計數模式:向上計數
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//這個就是自動裝載的計數值,由於計數是從0開始的
//TIM_TimeBaseStructure.TIM_Period =0xffff;//
TIM_TimeBaseStructure.TIM_Period =0x03e8;//1ms
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//重新計數的起始值
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;


TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);


// TIM IT enable
TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE); //開啟溢位中斷


// TIM enable counter
TIM_Cmd(TIM4, ENABLE);//計數器使能,開始工作


}


static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
/* Enable the TIM4 global Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}


static void RCC_Configuration(void)
{


RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);


/* TIM4 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);


/* clock enable */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA ,ENABLE);



}


void TIM4_start(void)
{
RCC_Configuration();


/* configure TIM4 for remote and encoder */
NVIC_Configuration();
TIM4_Configuration();
}


void TIM4_IRQHandler(void)
{
if (TIM_GetITStatus(TIM4, TIM_IT_CC1) != RESET)
{
//printf("enter tim4");
TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);
}
TimerForCan();
}
canSend函式實現如下:
unsigned char canSend(CAN_PORT notused, Message *m)
{
uint32_t i;
CanTxMsg *ptx_msg=&TxMessage;
ptx_msg->StdId = m->cob_id;


if(m->rtr)
ptx_msg->RTR = CAN_RTR_REMOTE;
else
ptx_msg->RTR = CAN_RTR_DATA;


ptx_msg->IDE = CAN_ID_STD;
ptx_msg->DLC = m->len;
for(i = 0; i < m->len; i++)
ptx_msg->Data = m->data;


if( CAN_Transmit( CAN1, ptx_msg )==CAN_NO_MB)
{
return 0xff;
}
else
{
return 0x00;
}
}
其中CAN_Transmit為stm32提供的庫函式,在stm32f10x_can.c中定義。
在使用stm32之前需要初始化一下CAN
void CAN_Config(void)
{
/* CAN register init */
CAN_DeInit(CAN1);
CAN_DeInit(CAN2);
CAN_StructInit(&CAN_InitStructure);

/* CAN1 cell init */
CAN_InitStructure.CAN_TTCM = DISABLE;
CAN_InitStructure.CAN_ABOM = DISABLE;
CAN_InitStructure.CAN_AWUM = DISABLE;
CAN_InitStructure.CAN_NART = DISABLE;
CAN_InitStructure.CAN_RFLM = DISABLE;
CAN_InitStructure.CAN_TXFP = DISABLE;
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
//Fpclk=72M/2/CAN_Prescaler
//BitRate=Fpclk/((CAN_SJW+1)*((CAN_BS1+1)+(CAN_BS2+1)+1));
//1M
/*CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_3tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_5tq;
CAN_InitStructure.CAN_Prescaler = 4;*/
//125K
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq;
CAN_InitStructure.CAN_BS2 = CAN_BS2_7tq;
CAN_InitStructure.CAN_Prescaler = 18;

CAN_Init(CAN1, &CAN_InitStructure);
CAN_Init(CAN2, &CAN_InitStructure);

/* CAN1 filter init */
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = 0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);

/* CAN2 filter init */
CAN_FilterInitStructure.CAN_FilterNumber = 14;
CAN_FilterInit(&CAN_FilterInitStructure);

}

Can 接收中斷實現:
void CAN1_RX0_IRQHandler(void)
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
//接收處理
m.cob_id=RxMessage.StdId;

if(RxMessage.RTR == CAN_RTR_REMOTE)
m.rtr=1;
else if(RxMessage.RTR == CAN_RTR_DATA)
m.rtr=0;
m.len=RxMessage.DLC;
for(i = 0; i < RxMessage.DLC; i++)
m.data=RxMessage.Data;

canDispatch(&TestSlave_Data, &m);
}








移植到VC或其他C++平臺說明:
由於原始碼全是c檔案,如果要移植到C++平臺,需要將以上所有涉及的.c檔案改成.cpp檔案,

如果是移植到MFC,則還要在cpp檔案中包含標頭檔案
#include "stdafx.h"
移植到VC等一些比較牛的編譯器下面時,由於檢查得更嚴格,所以編譯還會出現一些指標不匹配的問題,如:pdo.cpp檔案的145、216、332行就會報錯,只要強制轉換一下指標就能通過,如將
pwCobId = d->objdict[offset].pSubindex[1].pObject;
改成
pwCobId = (unsigned long *)d->objdict[offset].pSubindex[1].pObject;
即可通過。
還有407行由於程式碼跨平臺出現些亂碼錯誤,將
MSG_ERR (0x1948, " Couldn't build TPDO n�", numPdo);
改成
MSG_ERR (0x1948, " Couldn't build TPDO \n", numPdo);
即可。

這時編譯還不能通過,需修改除了dcf.h和canfestival.h以外的所有標頭檔案,在開頭加上
#ifdef __cplusplus
extern "C" {
#endif
標頭檔案結尾加上
#ifdef __cplusplus
};
#endif
例如:data.c改成data.cpp後,data.h中新增位置如下:
#ifndef __data_h__
#define __data_h__

#ifdef __cplusplus
extern "C" {
#endif
//省略掉中間內容
#ifdef __cplusplus
};
#endif

#endif /* __data_h__ */

另外,原始碼檔案檔案還有一個錯誤,這個錯誤在keil裡表現不出來,在VC裡就會導致出錯,花了些時間才找到這些錯誤。如下:
檔案dcf.cpp第40行,將
extern UNS8 _writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index,
UNS8 subIndex, UNS8 count, UNS8 dataType, void *data, SDOCallback_t Callback, UNS8 endianize);
改成
extern UNS8 _writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index,
UNS8 subIndex, UNS32 count, UNS8 dataType, void *data, SDOCallback_t Callback, UNS8 endianize);

移植到VC或QT時,由於電腦沒有CAN介面,這時要麼用USB-CAN,要麼得使用虛擬的CAN匯流排通道,Linux下面有虛擬的CAN匯流排,windows下沒有,只能通過走檔案介面或網口來虛擬CAN匯流排。