基于STM32F103移植canfestival协议栈(从站)CANopen

CAN open是一个基于CAN串行总线的网络传输系统的应用层协议,遵循ISO/OSI协议。CAN现场总线只是实现了OSI七层架构中的物理层和数据链路层,而canopen协议是基于他之上的一个应用层协议,它规定了(包含了)OSI七层架构的网络层以上的通信规则(个人理解)

27edfecf255649839483f420fc9c52a4.jpeg99d2e2292bd14de2aba45829b48e5e33.jpeg  

二、通信对象理解

(1)网络管理对象(NMT)

包括Boot-up消息,Heartbeat协议(心跳节点)及NMT消息,基于主从通信模式, NMT 用于管理和监控网络中的各个节点,主要实现三种功能:节点状态控制、错误控制和节点启动。

(2)服务数据对象(SDO)

l 包括接收 SDO(R-SDO) 和发送 SDO(T-SDO)。

l 通过使用索引和子索引, SDO 使客户机能够访问设备对象字典中的项。

l SDO 通过 CAL 中多元域的 CMS 对象来实现,允许传送任何长度的数据,当数据超过 4 个字节时分拆成几个报文。

l 协议是确认服务类型,为每个消息生成一个应答。 SDO 请求和应答报文总是包含 8 个字节。

(3)过程数据对象(PDO)

·包括接收 PDO(RPDO) 和发送 PDO(TPDO)。

·用来传输实时数据,数据从一个创建者传到一个或多个接收者。数据传送限制在 1 到 8 个字节。

·每个 CANopen 设备包含 8 个缺省的 PDO 通道, 4 个发送 PDO 通道和 4 个接收 PDO 通道。

·PDO 包含同步和异步两种传输方式,由该 PDO 对应的通信参数决定。

·PDO 消息的内容是预定义的,由该 PDO 对应的映射参数决定。

(4)同步对象(SYNC)

同步对象是由 CANopen 主站周期性地广播到 CAN 总线的报文,用来实现基本的网络时钟信号,每个设备可以根据自己的配置,决定是否使用该事件来跟其它网络设备进行同步通信。

(5)紧急报文(EMCY)

设备内部通信故障或者应用故障时发送的报文

三、对象字典

1)对象字典概述

CANopen对象字典(OD: Object Dictionary)是CANopen协议最为核心的概念。所谓的对象字典就是一个有序的对象组,描述了对应CANopen节点的所有参数,包括通讯数据的存放位置也列入其索引,这个表变成可以传递形式就叫做EDS文件(电子数据文档Electronic Data Sheet)。

理解eg:对象字典就相当于菜单(OD),比如说你去奶茶店点奶茶的时候。你看到店里的菜单上有很多品种,如果茶,奶茶,咖啡等(OD中的对象),他们前面有一个编号(就相当于索引),然后这些类型下面又细分出奶茶的名字,如波霸奶茶,柠檬茶,而他们前面也同样有着编号(就相当于子索引)。而后你看到这么多,不知道喝什么,此时你突然看到有套餐(相当于PDO、SDO),这样你就可以点一个套餐了,而套餐包含的样式有限(64bit),然后你告诉服务员,我要套餐A(已经映射好OD中的对象的PDO),此时服务员听到后(PDO、SDO发送成功)(而后面的工作人员给你制作套餐的过程就有点像我们PDO,SDO组装发送的过程)他就给你上 了一个套餐A的菜品。

四、移植CANfestival协议站

a48bc06eb20e41928a59f67a7c2ea80c.jpeg

(1)移植CANfestival-3协议栈源码(网上资源很多,可以自己去下载,我也上传了)

新建inc、src、driver三个文件夹用来存放canfetsival的核心文件以及驱动文件;

文件包含:include中的19个.h文件以及example/AVR/Slave下的ObjDict.h文件//实现从机功能的时候要用到

以及将include中的AVR更名为stm32,并保留其中的applicfg.h、canfestival.h、config.h、timerscfg.h四个头文件。移植到inc这个文件夹里面(头文件)

将src里面的.c文件全部移植到新建的src文件里面;(symbols.c文件也要去除 )

(2)建立canopen_drive文件夹,用来存放驱动源文件(如canopen_drv.c、TestSlave.c/.h文件;底层驱动文件);

为什么要建canopen_drive?里面又该存放什么源文件?

答:①应该是为了添加相应的接口函数,供canfestival.h调用,如果没有的话,移植过来的协议栈源码就会报错。如下图:

709d8dd066484b7a8f81c7177db0e9a0.jpeg

canSend() ,作用是发送can数据,最终canfestival库发送数据,都会调用canSend()函数;

getElapsedTime(),作用是软件定时器的修正(通过一个硬件定时器,来模拟多个软件定时器).

setTimer(),作用是改变硬件定时器的溢出周期

②应该放一个canopen_drv.c(名字自定义,我的为了方便起见就这样命名了)

canopen_drv.c含canSend()、canReciver()、canChangeBaudRate();供canfestival.h调用;含setTimer()、getElapsedTime()供timer.h调用(协议栈里的);

以上就完成了协议栈的移植;

(3)建立bsp文件夹,用来存放与timer、can相关的源文件和头文件,基础工程;初始化TIM定时器,CAN通信的基础配置,以及一些IO口的初始化;可以移植正点原子的定时器,和CAN收发实验的那些基础配置;

(4)建立对象字典文件TestSlave.c、TestSlave.h通过软件来建立,并保存在canopen_drive里面,方便后续的调用

总结:移植思想:①CAN、定时器硬件相关的操作代码②对象字典资源生成

根据编译错误找到config.h把下面这些注释掉或者删掉

3c09fbabe308439e9ae12a3277b144e1.jpeg

还有把dfc里面的inline删掉,这样就还剩前面三个错误了;缺少的那三个函数;

c23333bcf6474a71b31f57a98a41d569.jpeg

缺啥补啥。借鉴了网上的大佬们的代码(这个放在canopen_drv里面)

#include "canfestival.h"

#include "timer.h"

unsigned int TimeCNT=0;//时间计数

unsigned int NextTime=0;//下一次触发时间计数

unsigned int TIMER_MAX_COUNT=70000;//最大时间计数

static TIMEVAL last_time_set = TIMEVAL_MAX;//上一次的时间计数

unsigned char canSend(CAN_PORT notused, Message *m)

{

                        return 0;

}

unsigned char canChangeBaudRate(CAN_PORT port, char* baud)

{

        return 0;

}

//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;

}

//1s的定时器驱动

void timerForCan(void)

{

        TimeCNT++;

        if (TimeCNT>=TIMER_MAX_COUNT)

        {

                TimeCNT=0;

        }

        if (TimeCNT==NextTime)

        {

                TimeDispatch();

        }

}

//这个是canSend函数。实现PDO、SDO发送

unsigned char Can_Send_Msg(unsigned char* msg,unsigned char len)

{

  unsigned char mbox;

        unsigned int i = 0;

  CanTxMsg TxMessage;

  TxMessage.StdId = 0x12; // 标准标识符为 0x12

  TxMessage.ExtId = 0x12; // 设置扩展标示符(29 位)

  TxMessage.IDE = CAN_Id_Standard; // 标准帧

  TxMessage.RTR = CAN_RTR_Data; // 数据帧

  TxMessage.DLC = len; // 要发送的数据长度

  for(i=0;i<len;i++)

        TxMessage.Data = msg;

        mbox= CAN_Transmit(CAN1, &TxMessage);

        i=0;

  while((CAN_TransmitStatus(CAN1, mbox)!= CAN_TxStatus_Ok)&&(i<0xfff))i++; //等待结束

   if(i>=0xfff)return 1;

   return 0;

}

//CAN接收 中断服务函数

void USB_LP_CAN1_RX0_IRQHandler(void)

{

        CanRxMsg RxMessage;

        int i=0;

        CAN_Receive(CAN1, 0, &RxMessage);

        for(i=0;i<8;i++)

        printf("rxbuf[%d]:%d\r\n",i,RxMessage.Data);

}

五、配置对象字典

文件--新建--选择从站(我这里搭建从站)--命名(TestSlave)--选择心跳--确认

de86d109349e4633ac4f520fa0c9c148.jpeg

3948977df7ba4cda82daf880eaa12d14.jpeg

设置产生心跳时间

42cadbdbab7f485596dc172d13f5065d.jpeg

设置从站的实验变量(如输出灯的状态,点灯)

bdd8caa81af64dacad2b50062e269190.jpeg

映射PDO参数

13f3d9d4bbfd4384bf2a4f921bc8e85e.jpeg

文件--保存(命名TestSlave)--文件--建立词典--保存(路径存放在driver文件夹里面)

然后就可以在自己的工程里面添加从站的.c/.h文件了我这里加错了文件夹,不过影响不大

4c7f2f294f484433adbba03b84702758.jpeg

然后添加代码测试

void test(void){

        setNodeId(&TestSlave_Data, nodeID);

        setState(&TestSlave_Data, Initialisation); // Init the state

        setState(&TestSlave_Data,Pre_operational);

        setState(&TestSlave_Data,NMT_Start_Node);

        if(getWriteResultNetworkDict(&TestSlave_Data,0x01,&abortCode)==SDO_FINISHED)

        {

                        if(DI1==0x22){

                                        LED0=0;

                        }else{

                                LED0=1;

                        }

                        if(DI1==0x33)

                        {

                                LED1=0;

                        }else{

                                LED0=0;

                                LED1=1;

                                delay_ms(300); //延时300ms

                                LED0=1;

                                LED1=0;

                                delay_ms(300); //延时300ms

                        }

        }

}

int main(void)

{        

                Uart_Config();

          CAN_Config_init(1,1,3,2,6);

          Timer_Config(4999,7199); //500ms 10K

                printf("hello canopen!\r\n");

                delay_init(); //延时函数初始化         

          LED_Init(); //初始化与LED连接的硬件接口

          test();

                while(1)

   {

                        if(timeflag == 1)

                        {

        timeflag = 0;

        for(i=0;i<8;i++)

        {

          canbuf=i; //填充发送缓冲区

                                }

        res = Can_Send_Msg(canbuf,8); //发送 8 个字节

        if(res==0 ) printf("Send OK!\r\n");

        else printf("Send Error!\r\n");

      }      

     }

}下面是运行的结果;但卡住了,不知道该怎么去设置应用层的实现了。有没有大佬指导一下

f5f12f2e0114469e9d9f1a74a9ec2f07.jpeg

部分代码借鉴该大佬的https://bbs.21ic.com/icview-3018978-1-1.html坛很好的贴子,大家可以去看看

本人原创,有些借鉴了别的大佬整理出来的,码字不易,转载请注明地址,21电子网也有发。

STM32 CANOpen是指基于STMicroelectronics的STM32系列单片机,使用CANOpen协议进行通信的一种应用。CANOpen是一种基于CAN总线的开放性、高效性的通信协议,广泛应用于多领域的自动化控制系统中。 STM32是STMicroelectronics开发的一系列32位微控制器的品牌,它具有高性能、低功耗、丰富的外设接口等特点,广泛应用于工业控制、汽车电子、智能家居等领域。 STM32与CANOpen的结合,可以实现在工控、机械控制、机器人、电动汽车等各种应用中,实现各个节点之间的通信和数据交换。 通过STM32的内置CAN控制器和CANOpen协议栈,可以方便地实现CAN总线的物理层和数据链路层协议,具备相应的通信功能。CANOpen协议提供了一套标准的通信对象字典(Communication Object Dictionary, COD),用于定义节点之间的通信参数和功能,从而实现数据的传输和节点的控制。开发者只需要根据自己的需求配置相应的COD参数和功能,即可实现节点之间的通信。 在STM32 CANOpen的应用中,通常会存在一个主节点和多个从节点。主节点负责对从节点进行管理和控制,通过发送CAN帧来向从节点发送指令或请求数据。从节点则负责执行主节点的指令,并将执行结果或所需数据通过CAN帧返回给主节点。借助于CANOpen的报文和状态确认机制,节点之间的通信具有高效、可靠的特性。 总之,STM32 CANOpen通过将STM32系列单片机和CANOpen协议结合在一起,提供了一套方便、高效、可靠的通信解决方案,广泛应用于各种自动化控制系统中,为工业和汽车电子等领域的应用提供了强大的支持。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值