打印
[应用相关]

STM32 CANOpen点播

[复制链接]
2795|20
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 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. 置该节点到可操作状态。

使用特权

评论回复
5
dingbo95|  楼主 | 2018-7-16 20:53 | 只看该作者
设置网络波特率的步骤如下:
1. 将网络上所有LSS Slave切换到配置状态,通过‘switch Mode Global’服务实现。
2. 通过’Configure Bit Timing Parameters’服务将波特率广播到每个节点中。
3. 通过‘Activate Bit Timing Parameters’服务激活新设置。将整个网络波特率切换为新设置。
4. 置网络切换回可操作状态。

使用特权

评论回复
6
dingbo95|  楼主 | 2018-7-16 20:55 | 只看该作者
这里要注意:在‘Activate Bit Timing Parameters’服务中有个switch_delay参数,该参数指定相等长度的两个延迟周期长度,是为了避免总线上出现不同的位定时参数(波特率)。每个节点在收到命令后’switch_delay’毫秒后应用新的位定时参数。在应用新参数后,节点在第二个’switch_delay’毫秒定时到之前不发送任何报文。

使用特权

评论回复
7
dingbo95|  楼主 | 2018-7-16 20:58 | 只看该作者
数据存档文件,用于保存所有从节点的字典配置。

在从节点初始化时,从节点将上报boot_up报文。主节点收到boot_up报文后,将对从节点的字典和数据存档文件进行对比,如果不匹配,则需要通过sdo报文对从节点字典进行重新配置,并根据配置决定是否命令从节点将新的配置存入非易失性存储器。

使用特权

评论回复
8
dingbo95|  楼主 | 2018-7-16 20:59 | 只看该作者
主站可以为每个从站配置一个节点数据存档文件,节点数据存档文件的格式如图:

使用特权

评论回复
9
dingbo95|  楼主 | 2018-7-16 20:59 | 只看该作者
DCF入口
/* DCF入口 */
typedef struct
{
        UNS16 Index;                //索引
        UNS8 Subindex;        //子索引
        UNS32 Size;                        //字节数
        UNS8 *Data;                        //数据指针
}dcf_entry_t;


使用特权

评论回复
10
dingbo95|  楼主 | 2018-7-16 21:00 | 只看该作者
一些函数声明和定义
oid SaveNode(CO_Data *d, UNS8 nodeId);
static UNS8 read_consise_dcf_next_entry(CO_Data *d, UNS8 nodeId);
static UNS8 write_consise_dcf_next_entry(CO_Data *d, UNS8 nodeId);
UNS8 init_consise_dcf(CO_Data *d,UNS8 nodeId);

#ifdef _MSC_VER
#define inline _inline
#endif


使用特权

评论回复
11
dingbo95|  楼主 | 2018-7-16 21:01 | 只看该作者
本帖最后由 dingbo95 于 2018-7-16 21:03 编辑

启动节点

/* 启动节点 */
void start_node(CO_Data *d, UNS8 nodeId)
{
        /* 节点开始运行 */
        masterSendNMTstateChange(d, nodeId, NMT_Start_Node);
        /* 节点进入运行状态 */
        d->NMTable[nodeId] = Operational;
}









使用特权

评论回复
12
dingbo95|  楼主 | 2018-7-16 21:04 | 只看该作者

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

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

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

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

        return 2;
}


使用特权

评论回复
13
dingbo95|  楼主 | 2018-7-16 21:05 | 只看该作者

启动该节点并检查是否有节点还未进入运行态
/* 启动该节点并检查是否有节点还未进入运行态 */
void start_and_seek_node(CO_Data *d, UNS8 nodeId)
{
  UNS8 node;
  
        /* 启动该节点 */
        start_node(d, nodeId);
       
        /* 检查是否有节点还未进入运行态,如果有则检查是否要获取数据存档文件并启动节点 */
        for(node = 0; node < NMT_MAX_NODE_ID; node++)
        {
                if(d->NMTable[node] != Initialisation)
                        continue;
               
                /* 检查是否要获取数据存档文件并启动节点 */
                if(check_and_start_node(d, node) == 2)
                        return;
        }

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


使用特权

评论回复
14
dingbo95|  楼主 | 2018-7-16 21:05 | 只看该作者

检查数据存档文件sdo并判断下一步要做什么动作
/* 检查数据存档文件sdo并判断下一步要做什么动作 */
static void CheckSDOAndContinue(CO_Data *d, UNS8 nodeId)
{
        UNS32 abortCode = 0;
        UNS8 buf[4], match = 0;
        UNS32 size = 4;
       
        /* 如果状态为读取检查,说明已经开始读取 */
        if(d->dcf_status == DCF_STATUS_READ_CHECK)
        {
                /* 获取sdo读取到的结果 */
                if(getReadResultNetworkDict(d, nodeId, buf, &size, &abortCode) != SDO_FINISHED)
                        goto dcferror;
               
                /* 检查读取到的大小和本地配置的是否匹配 */
                if(size == d->dcf_size)
                {
                        match = 1;
                       
                        /* 检查读取到的内容和配置的是否匹配 */
                        while(size--)
                        {
                                if(buf[size] != d->dcf_data[size])
                                        match = 0;
                        }
                }
                /* 如果匹配,继续读取该节点数据存档文件中配置的下一个条目 */
                if(match)
                {
                        /* 如果已经读完该节点配置的数据存档文件中的所有条目 */
                        if(read_consise_dcf_next_entry(d, nodeId) == 0)
                        {
                                /* 开始并检查节点 */
                                start_and_seek_node(d, nodeId);
                        }
                }
                /* 如果读取的数据存档文件和配置的数据存档文件不匹配,则将配置数据存档文件写到节点字典中 */
                else
                {
                        /* 初始化节点nodeId的数据存档文件,写数据存档指针指向的条目数据并将指针偏移到下一个条目 */
                        if((init_consise_dcf(d, nodeId) == 0) || (write_consise_dcf_next_entry(d, nodeId) == 0))
                                goto dcferror;               
                       
                        /* 将状态配置为写 */
                        d->dcf_status = DCF_STATUS_WRITE;
                }
        }
        /* 如果状态为写 */
        else if(d->dcf_status == DCF_STATUS_WRITE)
        {
                /* 检查写字典结果是否成功 */
                if(getWriteResultNetworkDict(d, nodeId, &abortCode) != SDO_FINISHED)
                        goto dcferror;
               
                /* 写该节点数据存档文件中指针指向的条目的数据,并将指针偏移到该节点配置的下一个条目 */
                if(write_consise_dcf_next_entry(d, nodeId) == 0)
                {
#ifdef DCF_SAVE_NODE
                        /* 所有条目都写完,通知从站保存字典到非易失性存储器 */
                        SaveNode(d, nodeId);
                        /* 将状态设置为保存 */
                        d->dcf_status = DCF_STATUS_SAVED;
#else
                        /* 所有条目都写完,将状态设置为初始化 */
                        d->dcf_status = DCF_STATUS_INIT;
                        /* 启动该节点并检查是否有其它节点还未进入运行态,如果有则检查是否要获取数据存档文件并启动其它节点 */
                        start_and_seek_node(d,nodeId);
#endif
                }
        }
        /* 保存状态 */
        else if(d->dcf_status == DCF_STATUS_SAVED)
        {
                /* 检查是否写成功 */
                if(getWriteResultNetworkDict(d, nodeId, &abortCode) != SDO_FINISHED)
                        goto dcferror;
               
                /* 重启节点 */               
                masterSendNMTstateChange(d, nodeId, NMT_Reset_Node);
                /* 如果保存完该节点后,需要将数据存档入口状态设置初始化 */
                d->dcf_status = DCF_STATUS_INIT;
                /* 因为节点重启还会boot_up,因此将节点状态设置为未知状态 */
                d->NMTable[nodeId] = Unknown_state;
        }

        return;

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

        d->NMTable[nodeId] = Unknown_state;
}


使用特权

评论回复
15
dingbo95|  楼主 | 2018-7-16 21:06 | 只看该作者

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

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

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

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

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

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

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

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

        return 1;
DCF_finish:       
        return 0;
}


使用特权

评论回复
16
dingbo95|  楼主 | 2018-7-16 21:07 | 只看该作者

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

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

        /* 该节点的数据存档文件至少包含索引、子索引、和数据大小,共7个字节,并节点的数据存档文件已初始化条目数小于配置的条目数 */
        if((UNS8 *)d->dcf_cursor + 7 < (UNS8 *)dcfend && d->dcf_entries_count < nb_entries)
        {
#ifdef CANOPEN_BIG_ENDIAN
    dcf_entry->Index = *(d->dcf_cursor++) << 8 | *(d->dcf_cursor++);
#else
                /* 该条目索引 */
    memcpy(&dcf_entry->Index, d->dcf_cursor, 2);
                d->dcf_cursor += 2;
#endif
                /* 该条目子索引 */
                dcf_entry->Subindex = *(d->dcf_cursor++);
               
#ifdef CANOPEN_BIG_ENDIAN
    dcf_entry->Size = *(d->dcf_cursor++) << 24 | *(d->dcf_cursor++) << 16 | *(d->dcf_cursor++) << 8 | *(d->dcf_cursor++);
#else
                /* 该条目数据存储区大小 */
    memcpy(&dcf_entry->Size, d->dcf_cursor, 4);
    d->dcf_cursor += 4;
#endif
                /* 该条目数据存储区指针 */
    d->dcf_data = dcf_entry->Data = d->dcf_cursor;
                /* 该条目数据存储区大小 */
    d->dcf_size = dcf_entry->Size;
                /* 该节点下一条数据存档文件条目的指针 */
                d->dcf_cursor += dcf_entry->Size;
    /* 该节点的数据存档文件已初始化条目数加一 */
                d->dcf_entries_count++;

    return 1;
  }

  return 0;
}


使用特权

评论回复
17
dingbo95|  楼主 | 2018-7-16 21:07 | 只看该作者

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

        return 1;
}


使用特权

评论回复
18
dingbo95|  楼主 | 2018-7-16 21:09 | 只看该作者

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


使用特权

评论回复
19
dingbo95|  楼主 | 2018-7-16 21:10 | 只看该作者
通知从站保存字典到非易失性存储器
/* 通知从站保存字典到非易失性存储器 */
void SaveNode(CO_Data *d, UNS8 nodeId)
{
        UNS8 Ret;
        UNS32 data = 0x65766173;
       
        Ret = writeNetworkDictCallBackAI(d, nodeId, 0x1010, 1, 4, 0, (void *)&data, CheckSDOAndContinue, 0, 0);
        if(Ret)     
                MSG_ERR(0x1A04, "Erreur writeNetworkDictCallBackAI", Ret);
}



使用特权

评论回复
20
磨砂| | 2018-7-20 13:13 | 只看该作者
第一次听说这个服务 长知识了

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

52

主题

1197

帖子

5

粉丝