STM32F103裸机移植CanFestival-3全记录:从源码下载到心跳包测试(附对象字典生成工具避坑)
2026/6/5 5:36:01 网站建设 项目流程

STM32F103裸机移植CanFestival-3全记录:从源码下载到心跳包测试(附对象字典生成工具避坑)

在工业自动化领域,CANopen协议因其高可靠性和灵活性成为设备间通信的首选方案之一。对于嵌入式开发者而言,如何在资源受限的STM32F103平台上实现CANopen从站功能,是一个既具挑战性又充满实践价值的课题。本文将详细记录一个完整的移植过程,从源码获取到最终通信测试,特别针对裸机环境下的特殊问题进行深入探讨。

1. 环境准备与源码获取

1.1 开发环境配置

在开始移植前,需要准备以下基础环境:

  • 硬件平台:STM32F103C8T6最小系统板(俗称"蓝板")
  • 开发工具:Keil MDK-ARM 5.32
  • 调试工具:ST-Link V2仿真器
  • CAN分析仪:PCAN-USB或ZLG的CAN盒

提示:虽然STM32F103系列内部时钟精度足够CAN通信使用,但建议外接8MHz晶振以获得更稳定的时钟源。

1.2 CanFestival源码获取与结构分析

CanFestival官方源码托管在Mercurial仓库,获取方式如下:

hg clone https://hg.beremiz.org/CanFestival-3

源码目录结构关键部分说明:

CanFestival-3/ ├── drivers/ # 各平台驱动实现 ├── examples/ # 示例代码 ├── include/ # 公共头文件 ├── objdictgen/ # 对象字典生成工具 └── src/ # 核心协议栈源码

对于裸机移植,我们需要重点关注src/目录下的核心文件和include/中的头文件。特别需要注意的是,CanFestival默认设计为支持多平台,因此需要针对STM32进行特定配置。

2. 工程搭建与基础移植

2.1 工程目录结构设计

合理的目录结构能显著提升项目管理效率,建议采用如下布局:

Project/ ├── CMSIS/ # ST官方库文件 ├── Drivers/ # 外设驱动 ├── CanFestival/ │ ├── inc/ # 头文件 │ ├── src/ # 源码文件 │ └── driver/ # 平台特定驱动 └── User/ # 用户代码

2.2 关键文件移植与修改

从CanFestival源码中需要移植的文件包括:

  • 核心协议栈文件(复制到CanFestival/src/):

    • dcf.cemcy.clifegrd.cnmtSlave.cobjacces.cpdo.csdo.cstates.csync.ctimer.c
  • 头文件(复制到CanFestival/inc/):

    • canfestival.hconfig.htimerscfg.h

在移植过程中,裸机环境下常见的两个编译错误及解决方法:

  1. 内联函数报错: 在dcf.c中找到以下两个函数,添加static修饰符:

    static inline UNS8 _getSubIndex(UNS16 index, UNS8 subindex) {...} static inline void _setSubIndex(UNS16 index, UNS8 subindex, UNS8 value) {...}
  2. 定时器相关宏冲突: 修改timerscfg.h中的宏定义:

    #define TIMER_HANDLE int #define TIMER_NONE -1

2.3 基础驱动实现

driver/stm32_canfestival.c中需要实现三个关键函数:

// 定时器设置函数 void setTimer(TIMEVAL value) { NextTime = (TimeCNT + value) % TIMER_MAX_COUNT; } // 获取已过去的时间 TIMEVAL getElapsedTime(void) { int ret = TimeCNT > last_time_set ? TimeCNT - last_time_set : TimeCNT + TIMER_MAX_COUNT - last_time_set; last_time_set = TimeCNT; return ret; } // CAN消息发送函数 unsigned char canSend(CAN_PORT notused, Message *m) { return CAN1_Send_Msg((Message *)m); }

3. CAN驱动与定时器配置

3.1 CAN外设初始化

STM32的CAN控制器初始化需要特别注意波特率设置。对于常见的1Mbps速率,配置如下:

CAN_InitTypeDef CAN_InitStructure; CAN_InitStructure.CAN_TTCM = DISABLE; CAN_InitStructure.CAN_ABOM = ENABLE; CAN_InitStructure.CAN_AWUM = ENABLE; CAN_InitStructure.CAN_NART = DISABLE; CAN_InitStructure.CAN_RFLM = DISABLE; CAN_InitStructure.CAN_TXFP = DISABLE; CAN_InitStructure.CAN_Mode = CAN_Mode_Normal; CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 = CAN_BS1_3tq; CAN_InitStructure.CAN_BS2 = CAN_BS2_2tq; CAN_InitStructure.CAN_Prescaler = 4; // APB1时钟为36MHz时 CAN_Init(CAN1, &CAN_InitStructure);

3.2 定时器配置

CanFestival需要1ms精度的定时器来维护协议栈时间基准。使用STM32的TIM2定时器配置示例:

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period = 35999; // 72MHz/72000 = 1KHz TIM_TimeBaseStructure.TIM_Prescaler = 71; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_Cmd(TIM2, ENABLE);

定时器中断服务程序中调用timerForCan()函数:

void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); timerForCan(); } }

4. 对象字典生成与配置

4.1 对象字典生成工具搭建

CanFestival自带的objdictgen工具基于Python开发,搭建步骤如下:

  1. 安装Python 2.7(工具暂不支持Python 3)
  2. 安装依赖库:
    pip install wxPython pyserial
  3. 运行工具:
    python objdictgen/objdictgen.py

注意:在Windows 10上运行时,可能会遇到wxPython兼容性问题,建议使用兼容模式运行。

4.2 对象字典配置实践

创建从站对象字典时的关键配置项:

参数项推荐设置说明
节点ID1-127确保网络中唯一
心跳生产者时间1000ms建议初始值
PDO通信参数传输类型0xFE异步制造商特定事件
映射参数0x2000-0x5FFF区域用户自定义变量区域

常见问题及解决方案:

  1. 工具闪退

    • 检查Python路径是否包含中文
    • 尝试以管理员身份运行
  2. 生成的代码编译错误

    • 确保选择了正确的目标平台(STM32)
    • 检查头文件包含路径

4.3 对象字典集成技巧

生成的对象字典包含两个关键文件:

  • ObjDict.c:变量存储和对象字典实现
  • ObjDict.h:对象字典声明和从站数据结构

在工程中使用时,可以修改ObjDict.c中的变量指针,指向应用中的实际变量:

/* 原始定义 */ UNS32 Obj2000_00 = 0x0; UNS32 Obj2000_01 = 0x0; /* ... */ /* 修改为指向应用变量 */ UNS32 *Obj2000_00_ptr = &app_var1; UNS32 *Obj2000_01_ptr = &app_var2; /* ... */

5. 通信测试与问题排查

5.1 初始化流程

main()函数中,CANopen协议栈的初始化只需三个关键函数:

/* CAN硬件初始化 */ CAN_Configuration(); /* CanFestival初始化 */ setNodeId(&SLAVE_Data, 0x01); // 设置节点ID setState(&SLAVE_Data, Initialisation); setState(&SLAVE_Data, Pre_operational);

5.2 心跳包测试

如果配置了心跳生产者,可以使用CAN分析仪观察心跳报文。典型的心跳报文格式:

字段说明
COB-ID0x700+NodeID节点1为0x701
数据[0]状态字节0x05表示运行状态

常见心跳包问题及解决方法:

  1. 无心跳包发出

    • 检查lifegrd.c是否包含在工程中
    • 确认SetHeartbeatTime()函数被正确调用
  2. 心跳间隔不稳定

    • 检查定时器中断是否正常触发
    • 确认没有其他高优先级中断阻塞

5.3 PDO通信测试

测试RPDO接收功能的步骤:

  1. 配置主站发送RPDO,COB-ID为0x200+NodeID
  2. 发送包含目标数据的PDO报文
  3. 在从站中检查对象字典对应变量是否更新

一个典型的RPDO报文示例(通过CAN分析仪发送):

# 使用python-can发送RPDO1 import can bus = can.interface.Bus(channel='can0', bustype='socketcan') msg = can.Message( arbitration_id=0x201, # 节点1的RPDO1 data=[0x01, 0x02, 0x03, 0x04], is_extended_id=False ) bus.send(msg)

5.4 常见问题排查表

现象可能原因解决方法
无法接收到任何报文CAN过滤器配置错误设置为不过滤模式
能收不能发CAN发送邮箱满检查发送函数返回值
对象字典访问超时SDO服务器未正确初始化确认sdo.c包含在工程中
心跳包能发但状态不对状态机未正确迁移检查setState()调用顺序

在实际项目中,我遇到最棘手的问题是CAN发送偶尔失败,最终发现是PCB布局问题导致CAN信号质量差。通过增加终端电阻和缩短布线长度解决了这一问题。另一个经验是,对象字典生成工具生成的代码可能需要手动调整变量对齐方式,特别是在使用非32位变量时。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询