『7x24小时有问必答』
  

CANopen NMT:搞懂 NMT 运行流程

@[toc]

先给结论

NMT 是 CANopen 的网络管理状态机控制协议,Heartbeat 是配套的节点在线与状态上报机制。在 CANopenNode 里,CO_NMT_Heartbeat.c/h  不是单纯“收一帧命令就切状态”的代码,而是把以下功能合在一个对象  CO_NMT_t  里:
1.  NMT slave / NMT consumer:接收 CAN-ID  0x000  的 NMT 命令,目标 Node-ID 为  0  或本节点时才处理。
2.  Heartbeat producer:用 CAN-ID  0x700 + nodeId  周期发送本节点 NMT 状态;初始化阶段还会发 boot-up message。
3.  NMT 状态机执行点:真正状态切换不在 CAN 接收回调里完成,而是在  CO_NMT_process()  周期调用中完成。
4.  错误条件联动:可根据 CAN bus off、Heartbeat consumer 错误、Error register mask,把 Operational 自动降到 Pre-operational 或 Stopped。
5.  复位请求返回Reset node  /  Reset communication  不在 NMT 模块里直接重启 MCU,而是通过  CO_NMT_process()  返回  CO_RESET_APP  /  CO_RESET_COMM  交给应用层执行。
源码运行链路可以压缩成:
CAN-ID 0x000 NMT command

      -> CO_NMT_receive() 校验 DLC 和 Node-ID

      -> NMT->internalCommand = command

      -> CO_NMT_process() 周期执行

      -> 切换 NMT 状态或返回 resetCommand

      -> 发送 boot-up / heartbeat:0x700 + nodeId, data[0] = NMT state

---

1. 先看协议:NMT 解决什么问题

1.1 NMT 的定位

CANopen 节点不是上电后立刻就能随便发 PDO。协议把设备运行分成几个 NMT 状态,主站或管理端通过 NMT 命令控制节点进入启动、停止、预运行、运行或复位流程。
在工程上,NMT 主要解决三件事:
问题
NMT/Heartbeat 的做法
什么时候允许 PDO 工作
只有 Operational 状态下 PDO 才工作
主站如何启动、停止或复位从站
发送 CAN-ID  0x000  的 NMT command
主站如何知道从站当前在线状态
从站周期发送 Heartbeat,数据字节携带 NMT state
CANopenNode 官方仓库说明它支持 NMT slave、simple NMT master,以及 heartbeat producer/consumer error control。CiA 的 NMT 公开说明也明确:NMT 协议由 active NMT manager 发送,接收后会强制 CANopen 设备转入被命令的 NMT 状态;报文是单帧 2 字节,CAN-ID 为  0

1.2 NMT 命令帧格式

NMT command 固定使用最高优先级的标准 CAN-ID:
字段
值/含义
CAN-ID
0x000
DLC
2
Byte 0
NMT command specifier
Byte 1
目标 Node-ID;0  表示广播所有节点
常用命令值在  CO_NMT_command_t  中直接定义:
CANopenNode 枚举
数值
协议含义
CO_NMT_ENTER_OPERATIONAL0x01
Start remote node,进入 Operational
CO_NMT_ENTER_STOPPED0x02
Stop remote node,进入 Stopped
CO_NMT_ENTER_PRE_OPERATIONAL0x80
Enter pre-operational
CO_NMT_RESET_NODE0x81
Reset node,完整节点复位
CO_NMT_RESET_COMMUNICATION0x82
Reset communication,仅通信部分复位
示例:
# 启动所有节点进入 Operational

CAN-ID = 0x000, DLC = 2, Data = 01 00

# 让节点 5 进入 Pre-operational

CAN-ID = 0x000, DLC = 2, Data = 80 05

# 让节点 5 执行通信复位

CAN-ID = 0x000, DLC = 2, Data = 82 05

1.3 Heartbeat 与 boot-up 报文格式

Heartbeat 是错误控制协议的一部分,用于确认网络参与者仍然在线,并且仍处于预期的 NMT FSA 状态。
字段
值/含义
CAN-ID
0x700 + Node-ID
DLC
1
Byte 0
当前 NMT state
状态值在  CO_NMT_internalState_t  中定义:
状态
数值
运行含义
CO_NMT_INITIALIZING0
初始化中;也用于 boot-up message 的数据字节
CO_NMT_PRE_OPERATIONAL127 / 0x7F
预运行;SDO 等对象可用,PDO 不工作
CO_NMT_OPERATIONAL5 / 0x05
运行;PDO 开始工作
CO_NMT_STOPPED4 / 0x04
停止;只保留少量通信能力
CO_NMT_UNKNOWN-1
Heartbeat consumer 侧用于表示未知状态
注意:boot-up message 和 heartbeat 使用同一个 CAN-ID 规则。boot-up 是节点初始化完成时发出的状态值  0,后续 heartbeat 则周期性发送当前状态。

1.4 四个状态要先分清

CANopenNode 在头文件注释里给出了状态边界:
状态
CANopenNode 注释中的语义
对 STM32 从机的直接影响
Initializing
CANopen 初始化完成前的活动状态
还没进入正常 CANopen 通信流程
Pre-operational
除 PDO 外所有 CANopen 对象可用
常用于 SDO 配置、参数下载、映射检查
Operational
PDO 也可用
正式跑过程数据
Stopped
只保留 Heartbeat producer 和 NMT consumer
应用输出应进入安全/停止策略

---

2. 再看源码:文件分工和阅读顺序

建议阅读顺序如下:
顺序
位置
先看什么
目的
1
CO_NMT_Heartbeat.h
  顶部 Doxygen
模块说明、NMT/Heartbeat 报文内容
建立协议边界
2
CO_NMT_internalState_t
NMT 状态值
对照 heartbeat byte0
3
CO_NMT_command_t
NMT 命令值
对照 NMT command byte0
4
CO_NMT_reset_cmd_t
返回给应用的复位动作
理解 reset 不在模块内部直接执行
5
CO_NMT_control_t
  宏
NMTcontrol
  的位域
理解自启动、错误降级、错误恢复
6
CO_NMT_t
状态、定时器、CAN buffer、回调
理解运行时对象保存了什么
7
CO_NMT_init()
初始化对象、OD 0x1017、CAN RX/TX
理解初始化阶段做了什么
8
CO_NMT_receive()
接收 NMT 命令
理解接收回调只做预处理
9
CO_NMT_process()
状态机主逻辑
这是运行流程核心
10
CO_NMT_sendCommand()
可选 simple NMT master 发送能力
理解  CO_CONFIG_NMT_MASTER  不是完整主站

---

3.  CO_NMT_Heartbeat.h:先把协议值和对象结构看懂

3.1  CO_CONFIG_NMT  默认配置

头文件中默认配置是:
#ifndef  CO_CONFIG_NMT

#define  CO_CONFIG_NMT (CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE | CO_CONFIG_GLOBAL_FLAG_TIMERNEXT)

#endif
含义:如果工程没有自己定义  CO_CONFIG_NMT,默认打开两个公共能力:
flag
含义
CO_CONFIG_GLOBAL_FLAG_CALLBACK_PRE
收到 NMT CAN 帧预处理后可触发回调,常用于 RTOS 唤醒处理任务
CO_CONFIG_GLOBAL_FLAG_TIMERNEXTCO_NMT_process()
  可给出下一次建议调用时间  timerNext_us
如果想让本节点也能主动发 NMT command,需要额外打开  CO_CONFIG_NMT_MASTER。这只表示“simple NMT master 发送能力”,不是完整主站配置器。

3.2  CO_NMT_internalState_t:Heartbeat 发送的就是这个值

typedef  enum  {

      CO_NMT_UNKNOWN =  -1,

      CO_NMT_INITIALIZING =  0,

      CO_NMT_PRE_OPERATIONAL =  127,

      CO_NMT_OPERATIONAL =  5,

      CO_NMT_STOPPED =  4

} CO_NMT_internalState_t;
CO_NMT_process()  发送 heartbeat 时会把当前状态写到  HB_TXbuff->data[0]。所以调试 CAN 报文时看到:
0x705   [1]   7F    # 节点 5 处于 Pre-operational

0x705   [1]   05    # 节点 5 处于 Operational

0x705   [1]   04    # 节点 5 处于 Stopped

0x705   [1]   00    # boot-up message / Initializing

3.3  CO_NMT_command_t:NMT 命令 byte0 的来源

typedef  enum  {

      CO_NMT_NO_COMMAND =  0,

      CO_NMT_ENTER_OPERATIONAL =  1,

      CO_NMT_ENTER_STOPPED =  2,

      CO_NMT_ENTER_PRE_OPERATIONAL =  128,

      CO_NMT_RESET_NODE =  129,

      CO_NMT_RESET_COMMUNICATION =  130

} CO_NMT_command_t;
CO_NMT_receive()  从 NMT command 报文的  data[0]  读出这个值;如果本节点启用了 simple master,CO_NMT_sendCommand()  也会把这个值写回  NMT_TXbuff->data[0]

3.4  CO_NMT_reset_cmd_t:复位命令为什么是返回值

typedef  enum  {

      CO_RESET_NOT =  0,

      CO_RESET_COMM =  1,

      CO_RESET_APP =  2,

      CO_RESET_QUIT =  3

} CO_NMT_reset_cmd_t;
这体现了 CANopenNode 的边界:协议栈识别“需要复位”,但不直接决定 MCU 怎么复位。
•  CO_RESET_COMM:应用进入通信复位段,重建 CANopen 通信对象。
•  CO_RESET_APP:应用执行完整设备复位,常见做法是 MCU reset 或回到主初始化入口。
•  CO_RESET_NOT:正常运行。

3.5  NMTcontrol:不是协议帧,而是本地策略开关

NMTcontrol  是传给  CO_NMT_init()  的本地控制位域,用于决定自启动和错误行为:
作用
CO_NMT_ERR_REG_MASK
低 8 位作为 Error register mask
CO_NMT_STARTUP_TO_OPERATIONAL
初始化后直接进入 Operational,否则进入 Pre-operational
CO_NMT_ERR_ON_BUSOFF_HB
Operational 下遇到 CAN bus off 或 heartbeat consumer 错误时降级
CO_NMT_ERR_ON_ERR_REG
Operational 下 Error register 命中 mask 时降级
CO_NMT_ERR_TO_STOPPED
错误触发时降到 Stopped,否则降到 Pre-operational
CO_NMT_ERR_FREE_TO_OPERATIONAL
错误消失后可从 Pre-operational 自动恢复到 Operational
这一组宏是源码学习时很容易漏掉的点。它解释了一个现象:节点没有收到新的 NMT command,也可能因为本地错误自动从 Operational 掉到 Pre-operational 或 Stopped。

---

4.  CO_NMT_init():初始化时把协议入口全部接好

CO_NMT_init()  必须在 communication reset section 调用。它主要做 6 件事。

4.1 清空对象并写入初始状态

源码先  memset(NMT, 0, sizeof(CO_NMT_t)),然后设置:
NMT->operatingState = CO_NMT_INITIALIZING;

NMT->operatingStatePrev = CO_NMT_INITIALIZING;

NMT->nodeId = nodeId;

NMT->NMTcontrol = NMTcontrol;

NMT->em = em;

NMT->HBproducerTimer = (uint32_t)firstHBTime_ms *  1000U;
这里的关键是:初始状态固定为  CO_NMT_INITIALIZING,后面第一次  CO_NMT_process()  会发送 boot-up message,然后再进入 Pre-operational 或 Operational。

4.2 读取并扩展 OD  0x1017 Producer heartbeat time

0x1017  是 producer heartbeat time。CO_NMT_init()  会读取它,并保存到微秒单位:
OD_get_u16(OD_1017_ProducerHbTime,  0, &HBprodTime_ms,  true);

NMT->HBproducerTime_us = (uint32_t)HBprodTime_ms *  1000U;
随后它给  0x1017  安装 OD extension:
NMT->OD_1017_extension.object = NMT;

NMT->OD_1017_extension.read = OD_readOriginal;

NMT->OD_1017_extension.write = OD_write_1017;

OD_extension_init(OD_1017_ProducerHbTime, &NMT->OD_1017_extension);
这样运行时如果通过 SDO 写  0x1017OD_write_1017()  会同步更新  HBproducerTime_us,并把  HBproducerTimer  清零,使新的 heartbeat 周期立即生效。

4.3 配置 NMT 命令接收

CO_CANrxBufferInit(NMT_CANdevRx, NMT_rxIdx, CANidRxNMT,  0x7FF,  false, (void*)NMT, CO_NMT_receive);
典型  CANidRxNMT  是  0x000。这一步把 CAN 接收 buffer 和  CO_NMT_receive()  绑定起来。

4.4 可选配置 simple NMT master 发送

只有  CO_CONFIG_NMT_MASTER  打开时,才会编译和初始化:
NMT->NMT_TXbuff = CO_CANtxBufferInit(NMT_CANdevTx, NMT_txIdx, CANidTxNMT,  false,  2,  false);
这对应发送 CAN-ID  0x000、DLC 2 的 NMT command。

4.5 配置 Heartbeat 发送

NMT->HB_TXbuff = CO_CANtxBufferInit(HB_CANdevTx, HB_txIdx, CANidTxHB,  false,  1,  false);
典型  CANidTxHB  是  0x700 + nodeId,DLC 为 1。

---

5.  CO_NMT_receive():接收回调只做“预处理”

CO_NMT_receive()  的逻辑很短,但很关键:
uint8_t  DLC = CO_CANrxMsg_readDLC(msg);

const  uint8_t* data = CO_CANrxMsg_readData(msg);

CO_NMT_command_t command = (CO_NMT_command_t)data[0];

uint8_t  nodeId = data[1];

if  ((DLC ==  2U) && ((nodeId ==  0U) || (nodeId == NMT->nodeId))) {

      NMT->internalCommand = command;

      ... optional callback ...

}
这里有三个结论:
1.  NMT 命令必须 DLC=2,否则不处理。
2.  Node-ID 为 0 是广播,或者等于本节点 Node-ID 才处理。
3.  接收回调不直接切状态,只写  NMT->internalCommand,真正动作留给  CO_NMT_process()
这种设计适合裸机和 RTOS:CAN 中断/驱动回调里只做极短预处理,复杂状态切换和可能触发的 reset 交给主循环或任务上下文。

---

6.  CO_NMT_process():NMT 运行流程核心

CO_NMT_process()  是整个模块最重要的函数。它的执行顺序可以拆成 7 层。

6.1 更新 Heartbeat 定时器

NMT->HBproducerTimer = (NMT->HBproducerTimer > timeDifference_us)

                                ? (NMT->HBproducerTimer - timeDifference_us)

                                :  0U;
timeDifference_us  是应用每次调用时传入的时间差。NMT 模块不自己读硬件定时器,而是依赖外部调度传入 elapsed time。

6.2 发送 boot-up 或 heartbeat

发送条件是:
当前是 Initializing

0x1017 不为 0,并且 heartbeat timer 到期或 NMT 状态变化
发送内容是:
NMT->HB_TXbuff->data[0] = (uint8_t)NMTstateCpy;

CO_CANsend(NMT->HB_CANdevTx, NMT->HB_TXbuff);
如果当前状态是  CO_NMT_INITIALIZING,这帧就是 boot-up message。发完后,源码再根据  CO_NMT_STARTUP_TO_OPERATIONAL  决定初始运行状态:
NMTstateCpy = (NMTcontrol & CO_NMT_STARTUP_TO_OPERATIONAL)

                  ? CO_NMT_OPERATIONAL

                  : CO_NMT_PRE_OPERATIONAL;
所以:
NMTcontrol
  设置
boot-up 后进入
未设置  CO_NMT_STARTUP_TO_OPERATIONAL
Pre-operational
设置  CO_NMT_STARTUP_TO_OPERATIONAL
Operational

6.3 处理收到的内部命令

internalCommand  可能来自两个入口:
•  CO_NMT_receive():总线上收到 NMT command。
•  CO_NMT_sendCommand():本节点作为 simple NMT master 发送命令时,如果目标是自己或广播,也会写入本地  internalCommand
处理逻辑:
switch  (NMT->internalCommand) {

       case  CO_NMT_ENTER_OPERATIONAL:

            NMTstateCpy = CO_NMT_OPERATIONAL;

             break;

       case  CO_NMT_ENTER_STOPPED:

            NMTstateCpy = CO_NMT_STOPPED;

             break;

       case  CO_NMT_ENTER_PRE_OPERATIONAL:

            NMTstateCpy = CO_NMT_PRE_OPERATIONAL;

             break;

       case  CO_NMT_RESET_NODE:

            resetCommand = CO_RESET_APP;

             break;

       case  CO_NMT_RESET_COMMUNICATION:

            resetCommand = CO_RESET_COMM;

             break;

}

NMT->internalCommand = CO_NMT_NO_COMMAND;
注意:CO_NMT_RESET_NODE  和  CO_NMT_RESET_COMMUNICATION  不会在这里调用  NVIC_SystemReset()。源码只设置返回值,应用层必须处理这个返回值。

6.4 根据错误条件自动降级或恢复

源码读取 Emergency 模块中的错误状态:
ErrBusOff = CO_isError(NMT->em, CO_EM_CAN_TX_BUS_OFF);

ErrHbCons = CO_isError(NMT->em, CO_EM_HEARTBEAT_CONSUMER);

ErrHbConsRemote = CO_isError(NMT->em, CO_EM_HB_CONSUMER_REMOTE_RESET);
然后结合  NMTcontrol  判断:
busOff_HB = CO_NMT_ERR_ON_BUSOFF_HB && (busOff 或 heartbeat consumer 错误)

errRegMasked = CO_NMT_ERR_ON_ERR_REG && (Error register 命中 NMTcontrol 低 8 位 mask)
最终策略:
当前状态
条件
结果
Operational
busOff_HB
  或  errRegMasked
降级到 Pre-operational,或在  CO_NMT_ERR_TO_STOPPED  设置时降到 Stopped
Pre-operational
CO_NMT_ERR_FREE_TO_OPERATIONAL
  设置,且错误条件全部消失
自动恢复到 Operational
这解释了调试中常见的“明明没发 NMT stop,节点为什么掉到 Pre-op”:通常是  NMTcontrol  配置让错误寄存器或 heartbeat consumer 错误参与了 NMT 状态控制。

6.5 可选状态变化回调

如果打开  CO_CONFIG_NMT_CALLBACK_CHANGE,状态变化后会调用:
NMT->pFunctNMT(NMTstateCpy);
这适合应用层统一处理状态进入/退出,比如进入 Stopped 时关闭输出、进入 Operational 时允许业务周期运行。

6.6 可选  timerNext_us

如果打开  CO_CONFIG_FLAG_TIMERNEXTCO_NMT_process()  会根据 heartbeat timer 更新  timerNext_us,供外部调度器决定下一次最晚什么时候再调用。

6.7 更新状态并返回 reset 命令

最后:
NMT->operatingState = NMTstateCpy;

if  (NMTstate !=  NULL) {

      *NMTstate = NMTstateCpy;

}

return  resetCommand;
应用层应该保存或检查  NMTstate,并处理  resetCommand

---

7.  CO_NMT_sendCommand():simple NMT master 不是完整主站

CO_NMT_sendCommand()  只有在  CO_CONFIG_NMT_MASTER  打开时才编译。它做两件事。

7.1 如果目标包含自己,先写本地  internalCommand

if  ((nodeID ==  0U) || (nodeID == NMT->nodeId)) {

      NMT->internalCommand = command;

}
这样本节点广播启动所有节点时,自己也会在下一次  CO_NMT_process()  中执行同一命令。

7.2 发送 NMT command 到总线

NMT->NMT_TXbuff->data[0] = (uint8_t)command;

NMT->NMT_TXbuff->data[1] = nodeID;

return  CO_CANsend(NMT->NMT_CANdevTx, NMT->NMT_TXbuff);
这只负责发  0x000  NMT command,不包含:
• 自动扫描节点;
• 自动配置对象字典;
• 自动写 PDO 映射;
• 自动监控所有 heartbeat;
• 自动恢复故障节点。
所以  CO_CONFIG_NMT_MASTER  的准确理解是:让本节点具备发送 NMT command 的能力,不是把 STM32 从机“变成完整 CANopen 主站”。

---

8. STM32 从机里应该怎么用 NMT

8.1 最小运行链路

普通 STM32 CANopen 从机通常只需要默认 NMT slave + Heartbeat producer:
1. 初始化 CAN/FDCAN 驱动和 CANopen 对象

2. 在 communication reset 段调用 CO_NMT_init()

3. 主循环或 RTOS task 周期调用 CO_NMT_process()

4. 根据返回的 resetCommand 决定是否通信复位或整机复位

5. 根据 NMTstate 决定应用是否允许输出、是否允许 PDO 业务
伪代码:
for  (;;) {

       uint32_t  dt_us = get_elapsed_us();

       uint32_t  timerNext_us = UINT32_MAX;

      CO_NMT_internalState_t nmtState;

      CO_NMT_reset_cmd_t reset = CO_NMT_process(CO->NMT, &nmtState, dt_us, &timerNext_us);

       if  (reset == CO_RESET_COMM) {

             /* exit to communication reset section */

             break;

      }

       if  (reset == CO_RESET_APP) {

             /* execute application reset policy, for example MCU reset */

            system_reset();

      }

       if  (nmtState == CO_NMT_OPERATIONAL) {

             /* allow normal process-data behavior */

      }  else  {

             /* keep outputs safe or skip operational-only behavior */

      }

}

8.2  0x1017 Producer heartbeat time  怎么配

0x1017  的单位是毫秒,CANopenNode 读出后换算成微秒保存到  HBproducerTime_us
建议调试阶段先设置一个容易观察的值,例如:
0x1017 = 1000 ms
这样节点每秒发一次 heartbeat。CAN 分析仪上应该能看到:
0x700 + nodeId   [1]   7F / 05 / 04
如果  0x1017 = 0,周期 heartbeat 被禁用;但初始化阶段的 boot-up message 仍由  CO_NMT_process()  在 Initializing 状态下发送。

8.3 主站不发 Start 时,为什么 PDO 不工作

如果没有设置  CO_NMT_STARTUP_TO_OPERATIONAL,节点 boot-up 后默认进入 Pre-operational。这个状态下 SDO 可用,但 PDO 不工作。
这时有两种选择:
方式
行为
适用场景
主站发送  01 nodeId  或  01 00
从站进入 Operational
规范主站管理流程
本地设置  CO_NMT_STARTUP_TO_OPERATIONAL
boot-up 后自动进入 Operational
简单单节点、实验或固定系统

8.4 收到 reset command 后该做什么

NMT reset 命令不是“协议栈内部立即复位”。应用必须检查  CO_NMT_process()  返回值:
返回值
应用侧动作
CO_RESET_COMM
退出当前通信循环,重新初始化 CANopen 通信对象,通常不需要重启 MCU
CO_RESET_APP
执行完整应用复位策略,可能包括 MCU reset
CO_RESET_NOT
正常继续

---

9. 一句话总结

CO_NMT_Heartbeat.c/h  的核心不是“发心跳”这么简单,而是:用  CO_NMT_t  保存本节点 NMT 状态和 heartbeat 定时器,用  CO_NMT_receive()  把总线 NMT 命令转成内部命令,再由  CO_NMT_process()  在主循环/任务上下文中统一完成 boot-up、heartbeat、状态切换、错误降级和复位请求返回。

---

10. 资料依据

类型
资料
本文使用点
协议公开说明
CiA:Network management
NMT command 由 active NMT manager 发送;CAN-ID 0;DLC 2;byte0 为命令,byte1 为 node-ID;node-ID 为 0 表示所有节点
协议公开说明
CiA:Error control protocols
Heartbeat 用于确认网络参与者仍在线且处于预期 NMT FSA 状态
官方实现资料
CANopenNode:NMT and Heartbeat Doxygen
CO_NMT_init()
CO_NMT_process()、回调、CO_NMT_sendCommand()  的官方语义
官方实现资料
CANopenNode:CO_NMT_Heartbeat.h  Doxygen
NMT 状态、NMT command、Heartbeat/NMT 报文内容、NMTcontrol  宏
官方仓库
CANopenNode GitHub
CANopenNode 是 ANSI C 协议栈,支持 NMT slave、simple NMT master、Heartbeat producer/consumer
上传源码
CO_NMT_Heartbeat.h
  /  CO_NMT_Heartbeat.c
本文源码流程、状态值、命令值、初始化与 process 逻辑
上传规范译注
《CiA301 V4.2.0(中文注释版)》
NMT、boot-up、状态机、Heartbeat/Node guarding、对象  0x1017  的章节定位
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码