[应用相关] STM32 CANOpen点播

[复制链接]
3583|20
 楼主| dingbo95 发表于 2018-7-16 20:51 | 显示全部楼层 |阅读模式
本帖最后由 dingbo95 于 2018-7-16 20:58 编辑

LSS提供查询和改变CANopen模块底层参数的功能,LSS Slave用于某个CANopen模块,LSS Master处理整个CAN网络。
LSS功能占用两个接口,占用2021(for master)和2020(for slave)两个标识符。属于主从式服务,类似于NMT的方式,CANopen网络中同时只能有一个节点能提供LSS Master服务,并且该节点同时也必须是NMT Master。网络上的其他节点都提供LSS Slave服务。
LSS地址跟节点的地址不同,它理论上是全球唯一的,包括vendor-id(制造商ID)、product-code(产品码)、revision-number(修订码)和serial-number(序列号),LSS地址由CANopen身份对象(1018H)标识。一般要求LSS Master节点要预先知道网络上的各节点的LSS地址。
 楼主| dingbo95 发表于 2018-7-16 20:51 | 显示全部楼层
LSS服务可以按功能划分为三部分:
    切换状态服务(switch mode services)为LSS Master和LSS Slave的逻辑连接提供途径。用来改变LSS Slave的状态。
        切换全局状态:该服务用于网络上所有LSS Slave节点在可操作状态和配置状态之间切换。
        切换选中状态:该服务切换属性与LSS_address相等的LSS地址节点进入配置状态。

    配置服务(configuration services)用来配置LSS Slave的底层参数。该服务仅在配置状态可用。
        配置节点地址:通过该服务LSS Master节点配置一个LSS Slave节点的NMT-address。
        配置位定时参数:通过设置位定时参数服务,LSS Master节点将新的位定时参数设到LSS Slave中。
        激活位定时参数:通过该服务激活LSS Master由配置位定时参数服务所设置的位定时参数。
        存储配置参数:存储配置参数服务实际上是用于将配置参数存入非易失性存储器。

    查询服务(inquiry services)为LSS Maste确定底层参数提供途径。该服务仅在配置状态可用。
        查询LSS地址:该服务允许确定一个在配置状态下的LSS Slave节点的LSS-address参数。
        查询NMT地址:该服务允许确定一个在配置状态下的LSS Slave节点的NMT-address参数。
 楼主| dingbo95 发表于 2018-7-16 20:52 | 显示全部楼层
身份识别服务(identification services)。
        LSS识别远程Slave节点:LSS Master请求所有LSS Slave节点,LSS地址和LSS_Address_sel一致,给主机发送LSS Identify Slave服务标识自己。
         LSS标识Slave:在使用LSS Identify Remote Slaves服务后,LSS Slave表明匹配本地LSS地址的LSS_Address_sel一致,给主机发送标识自己。
 楼主| dingbo95 发表于 2018-7-16 20:52 | 显示全部楼层
设置某个节点地址的步骤如下:
1. 通过LSS地址去将希望设置ID的节点切换到配置状态(相当于NMT的停止状态),当然是通过LSS服务(Switch Mode Selective)。
2. 然后通过’Configure Node-ID’服务设置其节点地址。
3. 置该节点到可操作状态。
 楼主| dingbo95 发表于 2018-7-16 20:53 | 显示全部楼层
设置网络波特率的步骤如下:
1. 将网络上所有LSS Slave切换到配置状态,通过‘switch Mode Global’服务实现。
2. 通过’Configure Bit Timing Parameters’服务将波特率广播到每个节点中。
3. 通过‘Activate Bit Timing Parameters’服务激活新设置。将整个网络波特率切换为新设置。
4. 置网络切换回可操作状态。
 楼主| dingbo95 发表于 2018-7-16 20:55 | 显示全部楼层
这里要注意:在‘Activate Bit Timing Parameters’服务中有个switch_delay参数,该参数指定相等长度的两个延迟周期长度,是为了避免总线上出现不同的位定时参数(波特率)。每个节点在收到命令后’switch_delay’毫秒后应用新的位定时参数。在应用新参数后,节点在第二个’switch_delay’毫秒定时到之前不发送任何报文。
 楼主| dingbo95 发表于 2018-7-16 20:58 | 显示全部楼层
数据存档文件,用于保存所有从节点的字典配置。

在从节点初始化时,从节点将上报boot_up报文。主节点收到boot_up报文后,将对从节点的字典和数据存档文件进行对比,如果不匹配,则需要通过sdo报文对从节点字典进行重新配置,并根据配置决定是否命令从节点将新的配置存入非易失性存储器。
 楼主| dingbo95 发表于 2018-7-16 20:59 | 显示全部楼层
主站可以为每个从站配置一个节点数据存档文件,节点数据存档文件的格式如图:
1.jpeg
 楼主| dingbo95 发表于 2018-7-16 20:59 | 显示全部楼层
DCF入口
  1. /* DCF入口 */
  2. typedef struct
  3. {
  4.         UNS16 Index;                //索引
  5.         UNS8 Subindex;        //子索引
  6.         UNS32 Size;                        //字节数
  7.         UNS8 *Data;                        //数据指针
  8. }dcf_entry_t;


 楼主| dingbo95 发表于 2018-7-16 21:00 | 显示全部楼层
一些函数声明和定义
  1. oid SaveNode(CO_Data *d, UNS8 nodeId);
  2. static UNS8 read_consise_dcf_next_entry(CO_Data *d, UNS8 nodeId);
  3. static UNS8 write_consise_dcf_next_entry(CO_Data *d, UNS8 nodeId);
  4. UNS8 init_consise_dcf(CO_Data *d,UNS8 nodeId);

  5. #ifdef _MSC_VER
  6. #define inline _inline
  7. #endif


 楼主| dingbo95 发表于 2018-7-16 21:01 | 显示全部楼层
本帖最后由 dingbo95 于 2018-7-16 21:03 编辑

启动节点

  1. /* 启动节点 */
  2. void start_node(CO_Data *d, UNS8 nodeId)
  3. {
  4.         /* 节点开始运行 */
  5.         masterSendNMTstateChange(d, nodeId, NMT_Start_Node);
  6.         /* 节点进入运行状态 */
  7.         d->NMTable[nodeId] = Operational;
  8. }









 楼主| dingbo95 发表于 2018-7-16 21:04 | 显示全部楼层

* 该函数被post_SlaveBootup函数调用,检查是否要获取数据存档文件并启动节点 */

  1. UNS8 check_and_start_node(CO_Data *d, UNS8 nodeId)
  2. {
  3.         /* 如果该节点的数据存档文件正在初始化,则直接退出 */
  4.         if(d->dcf_status != DCF_STATUS_INIT)
  5.                 return 0;

  6.         /* 初始化节点nodeId的数据存档文件,并读取该节点的数据存档文件中配置的所有条目的数据 */
  7.         if((init_consise_dcf(d, nodeId) == 0) || (read_consise_dcf_next_entry(d, nodeId) == 0))
  8.         {
  9.                 /* 如果没有配置该节点数据存档文件,则直接启动该节点 */
  10.                 start_node(d, nodeId);

  11.                 return 1;
  12.         }
  13.         /* 开始读取该节点的数据存档文件后,将状态设置为读取检查 */
  14.         d->dcf_status = DCF_STATUS_READ_CHECK;

  15.         return 2;
  16. }


 楼主| dingbo95 发表于 2018-7-16 21:05 | 显示全部楼层

启动该节点并检查是否有节点还未进入运行态
  1. /* 启动该节点并检查是否有节点还未进入运行态 */
  2. void start_and_seek_node(CO_Data *d, UNS8 nodeId)
  3. {
  4.   UNS8 node;
  5.   
  6.         /* 启动该节点 */
  7.         start_node(d, nodeId);
  8.        
  9.         /* 检查是否有节点还未进入运行态,如果有则检查是否要获取数据存档文件并启动节点 */
  10.         for(node = 0; node < NMT_MAX_NODE_ID; node++)
  11.         {
  12.                 if(d->NMTable[node] != Initialisation)
  13.                         continue;
  14.                
  15.                 /* 检查是否要获取数据存档文件并启动节点 */
  16.                 if(check_and_start_node(d, node) == 2)
  17.                         return;
  18.         }

  19.   /* 所有如果已经有从站进入运行态,则必须将主站设置为运行态 */
  20.         setState(d, Operational);
  21. }


 楼主| dingbo95 发表于 2018-7-16 21:05 | 显示全部楼层

检查数据存档文件sdo并判断下一步要做什么动作
  1. /* 检查数据存档文件sdo并判断下一步要做什么动作 */
  2. static void CheckSDOAndContinue(CO_Data *d, UNS8 nodeId)
  3. {
  4.         UNS32 abortCode = 0;
  5.         UNS8 buf[4], match = 0;
  6.         UNS32 size = 4;
  7.        
  8.         /* 如果状态为读取检查,说明已经开始读取 */
  9.         if(d->dcf_status == DCF_STATUS_READ_CHECK)
  10.         {
  11.                 /* 获取sdo读取到的结果 */
  12.                 if(getReadResultNetworkDict(d, nodeId, buf, &size, &abortCode) != SDO_FINISHED)
  13.                         goto dcferror;
  14.                
  15.                 /* 检查读取到的大小和本地配置的是否匹配 */
  16.                 if(size == d->dcf_size)
  17.                 {
  18.                         match = 1;
  19.                        
  20.                         /* 检查读取到的内容和配置的是否匹配 */
  21.                         while(size--)
  22.                         {
  23.                                 if(buf[size] != d->dcf_data[size])
  24.                                         match = 0;
  25.                         }
  26.                 }
  27.                 /* 如果匹配,继续读取该节点数据存档文件中配置的下一个条目 */
  28.                 if(match)
  29.                 {
  30.                         /* 如果已经读完该节点配置的数据存档文件中的所有条目 */
  31.                         if(read_consise_dcf_next_entry(d, nodeId) == 0)
  32.                         {
  33.                                 /* 开始并检查节点 */
  34.                                 start_and_seek_node(d, nodeId);
  35.                         }
  36.                 }
  37.                 /* 如果读取的数据存档文件和配置的数据存档文件不匹配,则将配置数据存档文件写到节点字典中 */
  38.                 else
  39.                 {
  40.                         /* 初始化节点nodeId的数据存档文件,写数据存档指针指向的条目数据并将指针偏移到下一个条目 */
  41.                         if((init_consise_dcf(d, nodeId) == 0) || (write_consise_dcf_next_entry(d, nodeId) == 0))
  42.                                 goto dcferror;               
  43.                        
  44.                         /* 将状态配置为写 */
  45.                         d->dcf_status = DCF_STATUS_WRITE;
  46.                 }
  47.         }
  48.         /* 如果状态为写 */
  49.         else if(d->dcf_status == DCF_STATUS_WRITE)
  50.         {
  51.                 /* 检查写字典结果是否成功 */
  52.                 if(getWriteResultNetworkDict(d, nodeId, &abortCode) != SDO_FINISHED)
  53.                         goto dcferror;
  54.                
  55.                 /* 写该节点数据存档文件中指针指向的条目的数据,并将指针偏移到该节点配置的下一个条目 */
  56.                 if(write_consise_dcf_next_entry(d, nodeId) == 0)
  57.                 {
  58. #ifdef DCF_SAVE_NODE
  59.                         /* 所有条目都写完,通知从站保存字典到非易失性存储器 */
  60.                         SaveNode(d, nodeId);
  61.                         /* 将状态设置为保存 */
  62.                         d->dcf_status = DCF_STATUS_SAVED;
  63. #else
  64.                         /* 所有条目都写完,将状态设置为初始化 */
  65.                         d->dcf_status = DCF_STATUS_INIT;
  66.                         /* 启动该节点并检查是否有其它节点还未进入运行态,如果有则检查是否要获取数据存档文件并启动其它节点 */
  67.                         start_and_seek_node(d,nodeId);
  68. #endif
  69.                 }
  70.         }
  71.         /* 保存状态 */
  72.         else if(d->dcf_status == DCF_STATUS_SAVED)
  73.         {
  74.                 /* 检查是否写成功 */
  75.                 if(getWriteResultNetworkDict(d, nodeId, &abortCode) != SDO_FINISHED)
  76.                         goto dcferror;
  77.                
  78.                 /* 重启节点 */               
  79.                 masterSendNMTstateChange(d, nodeId, NMT_Reset_Node);
  80.                 /* 如果保存完该节点后,需要将数据存档入口状态设置初始化 */
  81.                 d->dcf_status = DCF_STATUS_INIT;
  82.                 /* 因为节点重启还会boot_up,因此将节点状态设置为未知状态 */
  83.                 d->NMTable[nodeId] = Unknown_state;
  84.         }

  85.         return;

  86. dcferror:
  87.         MSG_ERR(0x1A01, "SDO error in consise DCF", abortCode);
  88.         MSG_WAR(0x2A02, "server node : ", nodeId);

  89.         d->NMTable[nodeId] = Unknown_state;
  90. }


 楼主| dingbo95 发表于 2018-7-16 21:06 | 显示全部楼层

初始化节点nodeId的数据存档文件
  1. /* 初始化节点nodeId的数据存档文件 */
  2. UNS8 init_consise_dcf(CO_Data *d, UNS8 nodeId)
  3. {
  4.         UNS32 errorCode;
  5.         ODCallback_t *Callback;
  6.         UNS8 *dcf;

  7.         /* 查找节点的数据存档文件入口 */
  8.         d->dcf_odentry = (*d->scanIndexOD)(0x1F22, &errorCode, &Callback);
  9.         if(errorCode != OD_SUCCESSFUL)
  10.                 goto DCF_finish;

  11.         /* 节点号不能大于数据存档入口配置的条目数 */
  12.         if(nodeId > d->dcf_odentry->bSubCount)
  13.                 goto DCF_finish;

  14.         /* 该节点数据存档入口配置的字节大小,为零说明没有配置 */
  15.         if(!d->dcf_odentry->pSubindex[nodeId].size)
  16.                 goto DCF_finish;

  17.         /* 该节点的数据存档文件入口配置指针 */
  18.         dcf = *(UNS8 **)d->dcf_odentry->pSubindex[nodeId].pObject;

  19.         /* 初始化数据存档文件条目的偏移指针(数据存档文件配置的前4字节是条目数) */
  20.         d->dcf_cursor = dcf + 4;

  21.         /* 初始化数据存档文件已初始化条目计数器 */
  22.         d->dcf_entries_count = 0;

  23.         /* 数据存档文件状态设为初始化 */
  24.         d->dcf_status = DCF_STATUS_INIT;

  25.         return 1;
  26. DCF_finish:       
  27.         return 0;
  28. }


 楼主| dingbo95 发表于 2018-7-16 21:07 | 显示全部楼层

获取该节点数据存档文件中指针指向的条目的索引、子索引、数据指针、数据大小,并将指针偏移到下一个条目
  1. /* 获取该节点数据存档文件中指针指向的条目的索引、子索引、数据指针、数据大小,并将指针偏移到下一个条目 */
  2. UNS8 get_next_DCF_data(CO_Data *d, dcf_entry_t *dcf_entry, UNS8 nodeId)
  3. {
  4.   UNS8 *dcfend;
  5.   UNS32 nb_entries;
  6.   UNS32 szData;
  7.   UNS8 *dcf;
  8.        
  9.         /* DCF入口是否配置 */
  10.   if(!d->dcf_odentry)
  11.      return 0;
  12.   
  13.         /* 每个节点作为一个子条目存在,节点号不能大于子条目数 */
  14.         if(nodeId > d->dcf_odentry->bSubCount)
  15.      return 0;

  16.         /* 该节点的数据存档文件配置的字节大小 */
  17.         szData = d->dcf_odentry->pSubindex[nodeId].size;
  18.         /* 该节点的数据存档文件配置指针 */
  19.   dcf = *(UNS8 **)d->dcf_odentry->pSubindex[nodeId].pObject;
  20.         /* 该节点的数据数据存档文件条目数(数据存档文件配置的前4字节) */
  21.   nb_entries = UNS32_LE(*((UNS32 *)dcf));
  22.   /* 该节点数据存档文件的结束指针 */
  23.         dcfend = dcf + szData;

  24.         /* 该节点的数据存档文件至少包含索引、子索引、和数据大小,共7个字节,并节点的数据存档文件已初始化条目数小于配置的条目数 */
  25.         if((UNS8 *)d->dcf_cursor + 7 < (UNS8 *)dcfend && d->dcf_entries_count < nb_entries)
  26.         {
  27. #ifdef CANOPEN_BIG_ENDIAN
  28.     dcf_entry->Index = *(d->dcf_cursor++) << 8 | *(d->dcf_cursor++);
  29. #else
  30.                 /* 该条目索引 */
  31.     memcpy(&dcf_entry->Index, d->dcf_cursor, 2);
  32.                 d->dcf_cursor += 2;
  33. #endif
  34.                 /* 该条目子索引 */
  35.                 dcf_entry->Subindex = *(d->dcf_cursor++);
  36.                
  37. #ifdef CANOPEN_BIG_ENDIAN
  38.     dcf_entry->Size = *(d->dcf_cursor++) << 24 | *(d->dcf_cursor++) << 16 | *(d->dcf_cursor++) << 8 | *(d->dcf_cursor++);
  39. #else
  40.                 /* 该条目数据存储区大小 */
  41.     memcpy(&dcf_entry->Size, d->dcf_cursor, 4);
  42.     d->dcf_cursor += 4;
  43. #endif
  44.                 /* 该条目数据存储区指针 */
  45.     d->dcf_data = dcf_entry->Data = d->dcf_cursor;
  46.                 /* 该条目数据存储区大小 */
  47.     d->dcf_size = dcf_entry->Size;
  48.                 /* 该节点下一条数据存档文件条目的指针 */
  49.                 d->dcf_cursor += dcf_entry->Size;
  50.     /* 该节点的数据存档文件已初始化条目数加一 */
  51.                 d->dcf_entries_count++;

  52.     return 1;
  53.   }

  54.   return 0;
  55. }


 楼主| dingbo95 发表于 2018-7-16 21:07 | 显示全部楼层

写该节点数据存档文件中指针指向的条目的数据,并将指针偏移到该节点配置的下一个条目
  1. /* 写该节点数据存档文件中指针指向的条目的数据,并将指针偏移到该节点配置的下一个条目 */
  2. static UNS8 write_consise_dcf_next_entry(CO_Data* d, UNS8 nodeId)
  3. {
  4.         UNS8 Ret;
  5.         dcf_entry_t dcf_entry;
  6.        
  7.         /* 获取该节点数据存档文件中指针指向的条目的索引、子索引、数据指针、数据大小,并将指针偏移到下一个条目 */
  8.         if(!get_next_DCF_data(d, &dcf_entry, nodeId))
  9.                 return 0;
  10.        
  11.         /* 将数据写过去 */
  12.         Ret = writeNetworkDictCallBackAI(d, nodeId, dcf_entry.Index, dcf_entry.Subindex, (UNS8)dcf_entry.Size,
  13.                                                                                                                                          0, dcf_entry.Data, CheckSDOAndContinue, 0, 0);
  14.         if(Ret)
  15.                         MSG_ERR(0x1A02,"Erreur writeNetworkDictCallBackAI",Ret);

  16.         return 1;
  17. }


 楼主| dingbo95 发表于 2018-7-16 21:09 | 显示全部楼层

读取该节点数据存档文件中指针指向的条目的数据,并将指针偏移到该节点配置的下一个条目
  1. /* 读取该节点数据存档文件中指针指向的条目的数据,并将指针偏移到该节点配置的下一个条目 */
  2. static UNS8 read_consise_dcf_next_entry(CO_Data *d, UNS8 nodeId)
  3. {
  4.         UNS8 Ret;
  5.         dcf_entry_t dcf_entry;
  6.        
  7.         /* 获取该节点数据存档文件中指针指向的条目的索引、子索引、数据指针、数据大小,并将指针偏移到下一个条目 */
  8.         if(!get_next_DCF_data(d, &dcf_entry, nodeId))
  9.                 return 0;
  10.        
  11.         /* 读取该节点数据存放文件中指针指向的条目的数据 */
  12.         Ret = readNetworkDictCallbackAI(d, nodeId, dcf_entry.Index, dcf_entry.Subindex, 0, CheckSDOAndContinue, 0);
  13.         if(Ret)
  14.                 MSG_ERR(0x1A03,"Erreur readNetworkDictCallbackAI", Ret);
  15.    
  16.         return 1;
  17. }


 楼主| dingbo95 发表于 2018-7-16 21:10 | 显示全部楼层
通知从站保存字典到非易失性存储器
  1. /* 通知从站保存字典到非易失性存储器 */
  2. void SaveNode(CO_Data *d, UNS8 nodeId)
  3. {
  4.         UNS8 Ret;
  5.         UNS32 data = 0x65766173;
  6.        
  7.         Ret = writeNetworkDictCallBackAI(d, nodeId, 0x1010, 1, 4, 0, (void *)&data, CheckSDOAndContinue, 0, 0);
  8.         if(Ret)     
  9.                 MSG_ERR(0x1A04, "Erreur writeNetworkDictCallBackAI", Ret);
  10. }



磨砂 发表于 2018-7-20 13:13 | 显示全部楼层
第一次听说这个服务 长知识了
您需要登录后才可以回帖 登录 | 注册

本版积分规则

52

主题

1197

帖子

5

粉丝
快速回复 在线客服 返回列表 返回顶部