CANopen从站协议在stm32分析和说明.docx
《CANopen从站协议在stm32分析和说明.docx》由会员分享,可在线阅读,更多相关《CANopen从站协议在stm32分析和说明.docx(18页珍藏版)》请在冰豆网上搜索。
![CANopen从站协议在stm32分析和说明.docx](https://file1.bdocx.com/fileroot1/2023-2/2/7106cdb6-7816-4a8a-918d-587c43361a4b/7106cdb6-7816-4a8a-918d-587c43361a4b1.gif)
CANopen从站协议在stm32分析和说明
1CANopen是一种架构在控制局域网路
(ControlAreaNetwork,CAN)上的高层通讯协定,其协议在嵌入式系统及单片机上广泛使用,是工业控制常用到的一种现场总线。
依靠CANopen协议集的支持,可以对不同的从站设备通过总线进行配置和系统重构。
相信在不久的将来随着国对CANopen协议的研究深入,CANopen协议会在各个领域有广泛的应用。
CANopen是OSI模型中的网络层以上(包括网络层)的协定。
CANopen支持网络管理、设备监控及节点间的通讯,其中包括一个简易的传输层,可处理资料的传送。
数据的传输和接收都基于CAN总线。
如图1,通常多个从站设备靠CANopen网络传输数据给一个CANopen主站设备。
CANopen需要有对象字典,SDO(服务数据对象)处理功能,PDO(过程数据对象)处理功能,定时器,NMT(网络管理)处理功能等。
2CANopen对象字典介绍及设计
对象字典(od:
objectdictionary)是CANopen协议的核心。
对象字典(od:
objectdictionary)是一个有序的对象组;每个对象采用一个16位的索引值来寻址,为了允许访问数据结构中的单个元素,同时定义了一个8位的子索引。
通过接收主站发送的SDO(服务数据对象)报文,可以设置从站的对象字典,主要对象字典请参见表1。
从站在做事件处理时通常会读取对象字典,根据对象字典里的数据进行事件处理。
譬如从站的1017索引是记录从站发送心跳包的时间间隔。
当从站程序运行时并且从站是准备、停止、运行状态时,程序会查找1017索引的0号子索引里的数据进行处理。
如果里面有数据的话(假设数据为2000),程序则会根据数据所设置的时间通过定时器判断来每2000毫秒发送心跳包。
表1从站主要对象字典介绍
索引:
对象16位元的位址。
数据的类型:
一个代表对象的类型,可以是阵列、纪录或只是一个变量。
类型:
变量的类型。
属性:
提供此是否可读/可写的资料,有下列四种:
可读/写、只读、唯写、只读常数。
以下是建立6003索引的代码案例,并且里面的数据是(2.78593)*100000的代码片
UNS32AIdata=(2.78593)*100000;//要写入索引数据
staticUNS8highestSubIndex_6003=0;//子索引为:
1个(从0开始计数)
subindexIndex6003[]={
{RW,uint32,sizeof(UNS32),(void*)&AIdata}
};//建立索引的读写属性,数据类型,数据大小,索引数据
3NMT节点管理介绍及设计
NMT(网络管理,Networkmanagement)
会定义(设备部)从站的状态变更命令(如启动设备或停止设备)、侦测远端设备故障情形等。
通常从站都由主机通过NMT报文来启动、停止和重启。
每一个从站还必须配有一个单独的设备标识符,即从站节点ID。
(从站节点ID一般在程序启动后,节点初始化状态时设置。
)
节点可分为4种状态,初始化状态(Initialisatio),预操作状态(Pre_operational),操作状态(Operational)和停止状态(Stopped)。
主站发送来的NMT格式一般为00000105,其中0000是NMT功能码,代表主站发来的是NMT报文,01是要将从站节点设定为操作状态(参考表2),05是从站节点的ID。
表2状态码表
当程序启动或者复位,一开始都是初始
化状态,此状态会进行节点部设置,如设置从站节点ID及一些索引数据的初始化,完成这些操作后,程序进入预操作状态,在预操作状态下,主站通常会通过SDO报文设置从站对象字典,包括心跳时间的设定,同步功CANOPEN从站协议在stm32f103zet6单片机上的实现-给人_文库下载.wenkuxiazai./doc/90842bd76bec0975f565e20c-2.html能设置,数据存储映射设置等,当设置完毕后,会发送NMT节点管理报文将从站设为操作状态,此时从站节点如果已经设
置了同步功能,当从站节点收到主站发送过
来的同步报文后(通常是收到的报文是0080,需要从站根据1005对象字典的数据来确定可识别的同步报文ID,若数据为0080,那么收到0080的报文后发送从站状态数据)会返回目前从站的状态数据。
NMT主要涉及的代码为:
proceedNMTstateChange(Message*m)函数:
此函数主要功能是根据主站发送的设定状态码来设置从站节点的状态,代码片段如下:
if(((*m).data[1]==0)||((*m).data[1]==bDeviceNodeId)){//判断报文是否是发给本从站
switch((*m).data[0]){//解析报文caseNMT_Start_Node:
if((nodeState==Pre_operational)||(nodeState==Stopped))
nodeState=Operational;break;
caseNMT_Stop_Node:
if(nodeState==Pre_operational||nodeState==Operational)
nodeState=Stopped;break;
caseNMT_Enter_PreOperational:
if(nodeState==Operational||nodeState==Stopped)
nodeState=Pre_operational;break;
caseNMT_Reset_Node:
nodeState=Initialisation;break;
4心跳功能介绍及设计
所谓“心跳”,指的是主站/从站之间的一种通信。
采用心跳机制的好处在于,如果从站设备发生故障(如断电,重启等),会停止发送心跳报文,若主站一段时间没有收到心跳报文,主站设备就会检测到从站发生了故障。
(心跳报文:
CANopen设备将根据主站给从站设置心跳时间间隔参数(索引1017h)的周期发送心跳报文。
)
Stm32f103zet6单片机置定时器用于计算心跳报文发送间隔,假设对象字典设置的数据报文发送间隔为2000毫秒,就可以将定时器设定为每500毫秒累计一次数值501,当累加数值大于2000时则发送心跳报文。
心跳报文数据格式为07057F。
0705根据协议计算可以知道是从站节点ID为5。
(根据CANopen协议设定,心跳报文格式为”CommunicationObjectIdentifier”COB-ID+NODE-ID+1位状态码,心跳报文的COB-ID为0x700,0705等于0x700+0x05,则NODE-ID等于0x05),7F代表从站状态为预操作状态(详见表3)。
当发送心跳报文后,数值清0,数值再次每500毫秒累加一次数值501,当累加到大于2000数值时则发送心跳报文,以此无限循环。
图2心跳报文及定时器工作流程图表3心跳报文状态码对照表
心跳功能主要涉及的函数代码为:
定时器设置函数TIM3_Configuration:
此函数主要功能为每500毫秒触发一次定时器函数,代码片段:
voidTIM3_Configuration(void){
TIM_TimeBaseInitTypeDefTIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period=9999;
TIM_TimeBaseStructure.TIM_Prescaler=3599;//公式(1+3599)/72M*(1+9999)=0.5
TIM_TimeBaseStructure.TIM_ClockDivision=0;
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
触发的定时器函数则进行数值累加,代码片段:
voidTIM3_IRQHandler(void){
if(TIM_GetITStatus(TIM3,TIM_IT_Update)!
=RESET){
/*ClearTIM3updateinterrupt*/TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
GPIOF->ODR^=GPIO_Pin_7;//每次让连接PF7管脚的LED灯亮进行状态切换
a=a+501;//每次累加数值501}}
另外涉及的heartbeatMGR心跳函数:
此函数功能是对定时器累加的数值做判断,当累加的数值达到对象字典的数值时则发送心跳报文,代码片段:
if(should_time)//这个变量为1017索引的值,通过读取对象字典函数getODentry()获取
{if((a>=should_time)){msg.cob_id.w=bDeviceNodeId+0x700;
msg.len=(UNS8)0x01;msg.rtr=0;msg.data[0]=nodeState;
can_send(&msg);//发送心跳报文a=0;//发送完毕后将定时器累加的数值清0
}}
5SDO服务数据对象介绍及设计
SDO服务数据对象可用来设置及读取远端节点的对象字典其中的资料。
当主站要设置从站的对象字典的数据时,需要先发送一个SDO报文,当从站(stm32f103zet6)接收到报文后,会进行报文解析进而形成一个响应报文反馈给主站。
参考以下流程图:
图3从站处理主站SDO流程图
CANopen发送的SDO报文包括11位元的ID、远端传输请求(RTR)位元及大小不超过8位元的资料。
表4报文介绍
图3处理写入报文示例
图4处理读取报文示例
从图3看出,从站先接收到主站发送过来的报文06052B1700D0070000;其中0605是主站的设置05从站节点的ID(根据CANopen协议设定,主站发送的SDO报文格式为,COB-ID+NODE-ID+8位DATA,主站SDO报文的COB-ID为0x600,0605等于0x600+0x05,则NODE-ID等于0x05,即设定要设置的从站节点为5号节点),2B是代表设置的数据长度为2个字节(2F代表设置的数据长度为1个字节,27代表设置的数据长度为2个字节,23代表设置的数据长度为4个字节),1710则代表要设置1017索引,后面的00代表设置0号索引,D0070000代表设置的2个数据为D007。
(因为只设置2个字节数据长度,所以后面的0000可以忽略。
)如图3所示,当经过SDO报文处理后,程序会反馈一个SDO响应报文05856017000000000000;其中0585是代表此数据为5号从站返回的数据(从站发送SDO响应报文格式为COB-ID+NODE-ID+8位数据,从站SDO响应报文的COB-ID为0x580,0585等于0x580+0x05,则NODE-ID等于0x05,即此数据为5号从站返回的数据),60代表响应写入成功命令字(若是80的话则是写入错误),1710代表是对1017索引的反馈,00000000代表反馈响应数据成功(若返回失败则返回错误码如:
00000206,则代表是对象字典不存在,此错误代码可以通过代码去设置)。
图3是处理设置报文(download)示例。
图4是处理读取报文(upload)示例,如图主站发送的报文的第三个字节是40,代表请求读取数据命令字。
1710代表是读取1017索引的数据,从站的响应报文4B代表索引的数据为2个字节(4F代表索引的数据为1个字节,47代表索引的数据为3个字节,43代表索引的数据为4个字节),数据为D007。
SDO功能主要涉及的函数代码为:
proceedSDO(message*m)函数:
此函数主要功能是处理SDO报文并将其封装成响应SDO报文,代码片段:
if((nodeState==Operational)||(nodeState==Pre_operational))
{
sdo.nodeId=(UNS8)(GET_NODE_ID((*m)));
MSG_WAR1(0x3A19,"ReceivedSDOfornodeId:
",sdo.nodeId);
sdo.len=(*m).len;if(sdo.len>0)
sdo.body.SCS=m->data[0];//封装SDO报文
for(i=1;idata[i];
报文通过sendSDO(s_SDOsdo)函数发送:
此函数主要功能是将封装后的SDO报文发送,代码片段:
m.cob_id.w=*pwCobId;m.rtr=DONNEES;m.len=8;
if(sdo.len>0)
m.data[0]=sdo.body.SCS;for(i=1;ifor(i=sdo.len;i<8;i++)m.data[i]=0;//将封装的报文转换成CAN协议帧发送returncan_send(&m);
6PDO过程数据对象及设计
过程数据对象(PDO)协定可用来在节点之间交换即时的资料。
PDO分为两种:
传送用的TPDO及接收用的RPDO。
一个节点的TPDO是将资料由此节点传输到其他节点,而RPDO则是接收由其他节点传输的资料。
从站(stm32f103zet6)目前只设置了TPDO用于传输从站的数据及传输错误信息,TPDO和RPDO从站可通过一个TPDO传送最多8字节资料给一设备。
一个PDO可以由对象字典中几个不同索引的资料组成,规划方式则是透过对象字典中对应PDO映射及PDO参数的索引。
PDO可以用同步或异步的方式传送:
同
步的PDO是从站接收到主站发送的SYNC讯息后触发,而异步的PDO是由从站部达到一定条件或其他外部条件触发。
同时从站报错事件也是由PDO发送。
图5从站同步传输数据
图5是从站同步传输数据的报文,当从
站收到主站发送的0080报文时,则进入同步传输数据函数,从站会将之前主站设置好的索引的数据发送给主站,0181是由主站设置的同步传输标识ID,02676F则是3个索引里的数值,传换成十进制数为157551。
同步传输涉及的函数代码为:
Proceedsync(Message*m),此函数功能是封装同步传输PDO报文,代码片段为:
if(index==0x2000&&subIndex==1)//将2000索引的1号子索引的数值赋给pdo的报文
{
*p=x;}
if(index==0x2000&&subIndex==3)////将2000索引的3号子索引的数值赋给pdo的报文{
*p=y;}
if(index==0x2000&&subIndex==4)////将2000索引的4号子索引的数值赋给pdo的报文{
*p=z;}if(objDict==OD_SUCCESSFUL){
MSG_WAR1(0x3011,"Mappeddatafoundsizebytes:
",*pSize);
if(sizeData!
=*pSize){MSG_WAR1(0x2052,"Sizeofdatadifferentthan(sizeinmapping/8):
",sizeData);
}memcpy(&process_var.data[offset],pMappedAppObject,sizeData);//封装pdo报文
图6从站发送错误事件
图6是一个从站发送错误事件的报文,当从站部程序出现异常时(譬如取不到索引数据等),从站会进行报错,这里错误码和容可以自己配置。
0183是由主站设置的发送错误事件标识ID,551F0000代表错误码,CDAB0000代表错误的数据。
发送错误事件涉及的函数代码为:
MSG_ERR(num,str,val),此函数功能是将错误码和错误数值存入由主站设置的特定索引并封装成PDO报文发送(封装由sendPDOevent函数完成),代码片段为:
MSG_ERR(0x1F55,"ErrorForTest",0xABCD);//可发送图6的错误报文
MSG_ERR(num,str,val)//函数原型{if(nodeState==Operational){
canopenErrNB=num;canopenErrVAL=val;
sendPDOevent(0,&canopenEr
rNB);//发送pdo报文
}
}
图7从站异步传输数据
图7是一个异步传输数据报文,当部达到一定条件是,会自动发送PDO报文,笔者将条件设定为如果时间在每小时56分时则发送PDO报文,0182是主站设置给从站发送异步数据的标识ID,数据为当前时间戳,(0x86转换成十进制是56。
)
异步传输数据所涉及函数也是sendPDOevent函数,此函数功能是将数据
if(minutes==56){
sendPDOevent(0,&minutes);}
7CANOPEN从站协议在STM32F103ZET6单片机上的实现
7.1硬件设置
首先需要对管脚进行设置,由原理图(见图8)可以看出CAN数据传输端CANTX接PB9管脚,CAN数据接收端CANRX接PB8管脚。
需要编写gpio_config()函数进行配置,代码片段:
/*ConfigureCANpin:
RX*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//输入模式
GPIO_Init(GPIOB,&GPIO_InitStructure);/*ConfigureCANpin:
TX*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//输出模式
GPIO_Init(GPIOB,&GPIO_InitStructure);GPIO_PinRemapConfig(GPIO_Remap1_CAN1,ENABLE);//重映射
图8STM32F103ZET6CAN原理图
硬件方面设置完毕后,对程序进行设计,程序主要分为两大部分。
7.2主程序介绍及设计
第一部分为main函数部分:
程序运行时,进行初始化操作(设置节点ID、中断服务、管脚、发送、接收和波特率等),完成后进入对节点对状态进行检测和处理的无限循环程序:
从站启动运行后会有四个状态,分别是初始化、预运行、运行、停止。
代码中分别用initialization、pre_operational、operational、stopped四种状态来表示,四个状态功能函数分别是initialization(),preoperational(),operational(),stopped(),这些功能函数的主要作用是发送当前状态的心跳报文。
用switch语句实现不同状态之间的切换及函数处理。
以下是主程序流程图:
图9主程序流程图
代码框架大致为:
While
(1){
switch(nodestatus){caseinitialization:
initialization();break;
casepre_operational:
pre_operational();break;
caseoperational:
operational();break;casestopped:
stopped();break;
}}
7.3中断服务程序介绍及设计第二部分为中断服务程序:
从节点上电启动后,中断等待来自主节点的管理报文,当报文到来的时候,基于中断的接收报文机制产生中断,中断由stm32的硬件进入voidusb_lp_can_rx0_irqhandler(void)处理接收报文。
can接收中断触发了CAN报文接收函数canreceive(&m)。
然后通过不同功能码来实现报文的解析和处理。
功能函数包括:
proceednmtstatechange()函数,处理主站NMT报文,改变从节点状态;proceedsync()函数,用于接收同步报文;proceedpdo()函数,处理pdo报文;proceedsdo()函数,处理sdo报文。
通过功能函数的解析后来执行相应的处理。
从而来实现对节点的控制。
以下是中断服务程序流程图:
图10中断服务程序流程图
代码框架大致为:
CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);
if(RxMessage.StdId==0x600+bDeviceNodeId)//检查是否是发给本站的报文
{
if((RxMessage.Data[0]==0x2F)||
(RxMessage.Data[0]==0x2B)||(RxMessage.Data[0]==0x27)||(RxMessage.Data[0]==0x23)||(RxMessage.Data[0]==0x40)||(RxMessage.Data[0]==0x60))//检查功能码
{proceedSDO(0,&m);//处理SDO报文
}
if(RxMessage.StdId==0x0000)//检查功能码
{
proceedNMTstateChange(&m);//处理NMT报文
}
if(RxMessage.StdId==0x0080)检查功能码
{pro