跳到主要内容
avatar logoavatar logo
主页资料关于导航
ctrlK
avatar logoavatar logo
  • 手册列表
  • 📊 嵌入式札记
    • 📈CANOpen
      • 🥛原理篇:CAN与CANOpen基础
      • ☕移植篇:CAN配置
      • 🫖移植篇:时钟配置
      • 🍵移植篇:源码移植
    • 🎛️esp
    • 📟软路由
  • ☁️ 云原生
  • 💻 边缘计算
  • 🤖 神经网络
  • 📝 小芝士
  • 拷贝源码文件
  • 编译工程
  • 接口配置

🍵移植篇:源码移植

拷贝源码文件​

在目标工程下建立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)

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

🍾ESP32🫖移植篇:时钟配置
  • 拷贝源码文件
  • 编译工程
  • 接口配置