跳至内容

【canopen】移植篇:源码移植

🍵移植篇:源码移植

拷贝源码文件

在目标工程下建立CANOpen文件夹,新建CANOpen/src,CANOpen/inc,CANOpen/driver文件夹。新建driver/can_timer.c、driver/can_driver.c。

  • 将源码CanFestival-3/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个文件文件拷贝到CANOpen/src。

    0.1

  • 将源码CanFestival-3/include中.h文件拷贝到CANOpen/inc。将CanFestival-3-10\include\AVR目录下的applicfg.h、canfestival.h、config.h、timerscfg.h共4个头文件拷贝到CANOpen/inc目录下;

    由于FREERTOS源码中包含了与canfestival同名的timers.c和timers.h,可以将canfestival中的改为timer.c/.h以免冲突。

    0.2

  • 将字典文件拷贝到driver目录下

  • 新建CO_slave.c,设置回调函数

    * CO_Data *OD_Data = &SillySlave_Data;//SillySlave_Data在字典.c最后一行
    
    void CO_slave_initialisation(CO_Data *d)
    {
    
    }
    
    void CO_slave_preOperational(CO_Data *d)
    {
        printf("CO_slave_preOperational\n");
    }
    
    void CO_slave_operational(CO_Data *d)
    {
        printf("CO_slave_operational\n");
    }
    
    void CO_slave_stopped(CO_Data *d)
    {
        printf("CO_slave_stopped\n");
    }
    
    void CO_slave_post_sync(CO_Data *d)
    {
        printf("CO_slave_post_sync: \n");
    }
    
    void CO_slave_post_TPDO(CO_Data *d)
    {
        printf("CO_slave_post_TPDO: \n");
        printf("LifeSignal = %u\n", LifeSignal);
    }
    
    void CO_slave_post_emcy(CO_Data *d, UNS8 nodeID, UNS16 errCode, UNS8 errReg, const UNS8 errSpec[5])
    {
        //    printf("Slave received EMCY message. Node: %2.2xh  ErrorCode: %4.4x  ErrorRegister: %2.2xh\n", nodeID, errCode, errReg);
    }
    
    void CO_slave_heartbeatError(CO_Data *d, UNS8 heartbeatID)
    {
        //    printf("CO_slave_heartbeatError %d\n", heartbeatID);
    }
    
    UNS32 CO_slave_storeODSubIndex(CO_Data *d, UNS16 wIndex, UNS8 bSubindex)
    {
        /*TODO :
         * - call getODEntry for index and subindex,
         * - save content to file, database, flash, nvram, ...
         *
         * To ease flash organisation, index of variable to store
         * can be established by scanning d->objdict[d->ObjdictSize]
         * for variables to store.
         *
         * */
        //    printf("CO_slave_storeODSubIndex : %4.4x %2.2xh\n", wIndex,  bSubindex);
        return 0;
    }
    
    int canopen_init(void)
    {
    
        OD_Data->heartbeatError = CO_slave_heartbeatError;
        OD_Data->initialisation = CO_slave_initialisation;
        OD_Data->preOperational = CO_slave_preOperational;
        OD_Data->operational = CO_slave_operational;
        OD_Data->stopped = CO_slave_stopped;
        OD_Data->post_sync = CO_slave_post_sync;
        OD_Data->post_TPDO = CO_slave_post_TPDO;
        OD_Data->storeODSubIndex = (void *)CO_slave_storeODSubIndex;
        OD_Data->post_emcy = (void *)CO_slave_post_emcy;
    
        HAL_TIM_Base_Start_IT(&htim13);//开启定时器
    
        setNodeId(OD_Data, NODE_SLAVE);
        setState(OD_Data, Initialisation);
    
        return 0;
    
    }
  • 在工程中添加.c文件,包含.h的路径。

编译工程

源码需要我们配置几个函数来运行协议栈。

  • 在can_timer.c中添加几个空函数。

    #include "canfestival.h"
    void setTimer(TIMEVAL value)
    {
    }
    TIMEVAL getElapsedTime(void)
    {
          return 1;
    }
    can_driver.c中添加
    unsigned char canSend(CAN_PORT notused, Message *m)
    {
          return 1;
    }
  • 注释或删除掉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>
  • 若编译时dcf.c中报错inline内联函数未定义,只需声明一下原函数即可。

    0.3

  • 编译解决其他报错问题,直至编译通过

接口配置

上方定义了几个空函数,函数void setTimer(TIMEVAL value)主要被源码用来定时的,时间到了就需要调用一下函数TimeDispatch(),函数TIMEVAL getElapsedTime(void)主要被源码用来查询距离下一个定时触发还有多少时间,unsigned char canSend(CAN_PORT notused, Message *m)函数主要被源码用来发一个CAN包的,需要调用驱动来将一个CAN包发出去。

在can_timer.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;
}

在1ms定时器中断中调用以下函数

void timerForCan(void)
{
        TimeCNT++;
        if (TimeCNT>=TIMER_MAX_COUNT)
        {
                TimeCNT=0;
        }
        if (TimeCNT==NextTime)
        {
                TimeDispatch();
        }
}
  • cansend就不多说了,根据芯片、例程配置就行了。
  • 接下来来到了巨坑时刻!!!!

    按道理说上述接口配置完成后协议栈就应该运行起来了,但是我在写这篇时定时时间总是不准确,折腾的我精神衰弱,无数次怀疑接口是不是写错了,定时器是不是不准确,甚至自己根据多个工程案例写接口,直到有一天看到评论区一位救命恩人说timerscfg.h下的MS_TO_TIMEVAL
    US_TO_TIMEVAL
    需要根据定时情况更改。然后我立刻查看了工程,果然canfestival源码中定义的是

    // The timer is incrementing every 8 us.
    #define MS_TO_TIMEVAL(ms) ((ms) * 125)
    #define US_TO_TIMEVAL(us) ((us)>>3)

    意思是时钟每两次中断之间时间间隔为8us,然而我们的定时器是1ms,将其改为

    // The timer is incrementing every 1000 us.
    #define MS_TO_TIMEVAL(ms) ((ms) * 1)
    #define US_TO_TIMEVAL(us) ((us)/1000)

    完美运行!!!

    在此告诫自己遇到问题还是要不限范围搜索资料,同时做好工作笔记
    ,以免以后再用时又得从头再来,浪费青春

发送评论