三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

ZYNQ裸机开发实战:手把手教你移植开源CANopen协议栈CANFestival(附完整源码)

ZYNQ裸机开发实战:手把手教你移植开源CANopen协议栈CANFestival(附完整源码)

ZYNQ裸机开发实战:手把手教你移植开源CANopen协议栈CANFestival(附完整源码)

在嵌入式系统开发中,CAN总线因其高可靠性和实时性被广泛应用于工业控制、汽车电子等领域。而CANopen作为建立在CAN总线上的高层协议,为设备间的通信提供了标准化的解决方案。本文将详细介绍如何在ZYNQ平台上进行CANFestival协议栈的裸机移植,从环境搭建到功能测试,提供完整的实战指南。

1. 开发环境准备与基础配置

1.1 硬件平台选择与搭建

ZYNQ-7000系列SoC因其独特的ARM+FPGA架构,在工业控制领域广受欢迎。本次移植基于Xilinx ZYNQ-7020芯片,具体硬件配置如下:

  • 核心处理器:双核ARM Cortex-A9 @ 650MHz
  • CAN控制器:使用PS端的Xilinx CAN控制器
  • 开发环境:Vivado 2019.1 + Xilinx SDK
  • 调试工具:J-Link调试器 + CAN分析仪

硬件连接示意图如下:

[ZYNQ开发板] ---- [CAN收发器] ---- [CAN总线] | [USB-JTAG调试器]

1.2 软件工具链安装

确保开发环境中已安装以下必要工具:

# 基础工具 sudo apt-get install build-essential git # Xilinx工具链 # 下载Vivado 2019.1 WebPACK版本 # 安装时选择SDK和DocNav组件 # CAN分析工具 sudo apt-get install can-utils

提示:建议使用Linux环境进行开发,Windows用户可通过WSL或虚拟机获得类似体验

1.3 CANFestival源码获取

从官方仓库获取最新源码:

git clone https://hg.beremiz.org/canfestival cd canfestival

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

目录内容描述
src/协议栈核心实现
drivers/各平台驱动实现
examples/示例应用程序
objdictgen/对象字典生成工具

2. 定时器驱动设计与实现

2.1 CANFestival定时器机制解析

CANopen协议依赖精确的定时功能实现心跳、同步等关键机制。CANFestival采用"基准定时器+软件定时器"的双层架构:

  1. 基准定时器:硬件定时器,提供精确的时间基准
  2. 软件定时器:在基准上模拟多个逻辑定时器

关键数据结构:

typedef struct { UNS8 state; // 定时器状态 TIMEVAL val; // 剩余时间 TIMEVAL interval; // 周期值(0表示单次) // ...其他字段 } TIMER_HANDLE;

2.2 ZYNQ定时器驱动实现

使用ZYNQ的SCU私有定时器作为基准源,配置步骤如下:

  1. 初始化定时器
int TimerInit(uint32_t freq_hz) { XScuTimer_Config *ConfigPtr; ConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID); XScuTimer_CfgInitialize(&TimerInstance, ConfigPtr, ConfigPtr->BaseAddr); // 设置预分频和加载值 XScuTimer_SetPrescaler(&TimerInstance, PRESCALER_VALUE); XScuTimer_LoadTimer(&TimerInstance, LOAD_VALUE); // 启用自动重载和中断 XScuTimer_EnableAutoReload(&TimerInstance); XScuTimer_EnableInterrupt(&TimerInstance); return XST_SUCCESS; }
  1. 关键函数实现
  • SetTimer():设置下次触发时间
  • getElapsedTime():获取经过时间
  • 中断服务程序调用TimeDispatch()

2.3 定时器配置参数

timerscfg.h中定义关键参数:

#define TIMEVAL unsigned long #define TIMEVAL_MAX 0x00FFFFFF #define MS_TO_TIMEVAL(ms) (ms * 13000UL) #define US_TO_TIMEVAL(us) (us * 13UL)

注意:TIMEVAL_MAX设置需考虑32位整型溢出问题,建议保留最高位为0

3. CAN接口驱动适配

3.1 Xilinx CAN控制器初始化

ZYNQ PS端集成CAN控制器,初始化流程:

XCANPS_Config *ConfigPtr; ConfigPtr = XCanPs_LookupConfig(CAN_DEVICE_ID); XCanPs_CfgInitialize(&CanInstance, ConfigPtr, ConfigPtr->BaseAddr); // 设置波特率 XCanPs_SetBaudRatePrescaler(&CanInstance, CAN_BAUDRATE); XCanPs_SetBitTiming(&CanInstance, CAN_SYNCJUMP, CAN_SEG1, CAN_SEG2); // 启用中断 XCanPs_SetHandler(&CanInstance, XCANPS_HANDLER_RECV, (void *)CanRecvHandler); XCanPs_IntrEnable(&CanInstance, XCANPS_IXR_RXOK_MASK);

3.2 CAN发送/接收实现

发送函数需符合CANFestival接口规范:

UNS8 canSend(CAN_PORT notused, Message *m) { XCanPs_Frame TxFrame; TxFrame.Identifier = m->cob_id; TxFrame.DataLength = m->len; memcpy(TxFrame.Data, m->data, m->len); return XCanPs_Send(&CanInstance, &TxFrame); }

接收中断服务程序中调用协议栈处理:

void CanRecvHandler(void *CallBackRef) { XCanPs_Frame RxFrame; Message m; XCanPs_Recv(&CanInstance, &RxFrame); m.cob_id = RxFrame.Identifier; m.len = RxFrame.DataLength; memcpy(m.data, RxFrame.Data, m->len); canDispatch(&m); }

3.3 中断优先级配置

确保CAN和定时器中断互不抢占:

XScuGic_SetPriorityTriggerType(IntcInstance, TIMER_INT_ID, 0xA0, 0x3); XScuGic_SetPriorityTriggerType(IntcInstance, CAN_INT_ID, 0xA0, 0x3);

4. 协议栈配置与对象字典

4.1 关键配置文件定制

  1. applicfg.h- 类型定义和调试输出:
typedef uint8_t UNS8; typedef int8_t INTEGER8; // ...其他类型定义 #define MSG_ERR(...) xil_printf("ERROR: " __VA_ARGS__) #define MSG_WAR(...) xil_printf("WARN: " __VA_ARGS__)
  1. config.h- 协议栈参数:
#define SDO_TIMEOUT_MS 3000 #define MAX_NB_TIMER 16 #define NMT_MAX_NODE_ID 127

4.2 对象字典生成与使用

使用objdictgen工具生成基础字典:

python objdictgen/objdictgen.py

典型对象字典结构示例:

索引子索引名称类型访问权限
0x10000设备类型UNS32ro
0x10180身份信息RECORDro
0x18001TPDO1通信参数UNS32rw

4.3 回调函数实现

典型回调函数框架:

void heartbeatError(CO_Data* d, UNS8 nodeId) { MSG_ERR("Heartbeat timeout from node %d\n", nodeId); } void post_emcy(CO_Data* d, UNS8 nodeId, UNS16 errCode, UNS8 errReg) { MSG_WAR("EMCY from node %d: code=0x%04X\n", nodeId, errCode); }

5. 功能测试与调试技巧

5.1 基础通信测试

  1. 心跳测试
// 配置心跳生产者 setHeartbeatTime(d, 1000); // 1s周期 // 配置消费者 setNodeId(d, 1); setHeartbeatConsumerTime(d, 1, 1500); // 1.5s超时
  1. PDO传输测试
// 配置TPDO映射 UNS32 val = 0x12345678; writeLocalDict(d, 0x2100, 0x01, &val, sizeof(val)); sendPDOevent(d, 1);

5.2 常见问题排查

问题1:CAN报文发送失败

  • 检查物理层连接
  • 验证波特率设置
  • 确认CAN控制器初始化流程

问题2:定时器不触发

  • 验证基准定时器配置
  • 检查中断优先级设置
  • 确认TimeDispatch调用链路

5.3 性能优化建议

  1. 减少内存拷贝:直接操作CAN控制器缓冲区
  2. 中断优化:合并相关中断处理
  3. 对象字典优化:使用RAM镜像加速访问

6. 进阶功能实现

6.1 LSS协议支持

实现CANopen Layer Setting Services:

void lssSwitchState(CO_Data* d, UNS8 newState) { // 实现状态切换逻辑 } UNS8 lssStoreConfig(CO_Data* d) { // 保存配置到非易失存储 return 0; }

6.2 多协议栈实例

支持多个CANopen节点实例:

CO_Data node1_data, node2_data; void initMultipleNodes() { setNodeId(&node1_data, 1); setNodeId(&node2_data, 2); // 分别初始化 }

6.3 与FreeRTOS集成

在RTOS环境下的适配要点:

  1. 替换定时器驱动为RTOS定时器
  2. 添加临界区保护
  3. 任务间通信机制适配

7. 完整工程结构说明

最终项目目录结构:

canfestival_zynq/ ├── bsp/ # 硬件平台支持包 ├── canfestival/ # 协议栈源码 ├── drivers/ # 外设驱动 │ ├── can/ # CAN驱动 │ └── timer/ # 定时器驱动 ├── examples/ # 示例应用 ├── objdict/ # 对象字典文件 └── scripts/ # 构建脚本

关键文件说明:

文件作用
main.c应用入口
canfestival_port.c平台适配层
objdict.c自动生成的对象字典实现
Makefile工程构建配置

在ZYNQ实际项目中,我们通过这套实现成功将多个工业设备接入同一CANopen网络,最关键的发现是合理配置定时器中断优先级可显著提高通信可靠性。当遇到复杂问题时,建议使用CAN分析仪捕获原始报文配合协议栈日志进行联合分析。

← 返回列表