欧姆龙NJ/NX无协议串口接收:从配置到ST状态机解析
2026/9/5 23:24:49 网站建设 项目流程

在欧姆龙 NJ/NX 系列上做串口通信,很多工程师第一次都会卡在同一个地方:网口、EtherCAT、运动控制都配得好好的,但面对一个只出 RS-232/RS-485 信号的扫码枪、称重仪表或者老式传感器时,突然不知道数据该怎么收了。NJ501 作为机器自动化控制器,本身大量项目都是围绕 EtherCAT 轴和 EtherNet/IP 组网展开的,真正的“裸串口”接收反而成了视频教程里最容易讲不清楚、也最容易让新人踩坑的部分。

先说一个明确判断:无协议串口通信,并不是把 PLC 的串口参数填好、接上两根线,就能在自己的程序里等到一串完整的报文。它只是在物理层和数据链路层帮你把字节收了下来,至于多少字节算一帧、从哪个字节开始、中间有没有校验、结尾怎么判断,这些“应用层规则”全部要由你的 PLC 程序自己实现。换句话说,无协议模式解决的是“怎么把线上电平变成 CPU 里的 BYTE”,而不是“怎么把 BYTE 变成业务数据”。

这篇文章就围绕 NJ501 的无协议串口接收来展开。你会看到无协议通信的使用场景、硬件连接与 Sysmac Studio 中的基础配置、接收程序的完整 ST 设计思路、用 PC 模拟外部设备发送报文的验证方法,以及实际项目里常见的故障排查手段。看完之后,你至少能自己搭出一个“收到一帧原始报文并解析出来”的最小可运行工程。

1. 这篇文章真正要解决的问题

很多 NJ501 初学者拿着 Sysmac Studio,第一反应是找一个类似“串口接收”的现成功能块,把串口调试助手发过来的数据直接读进一个字符串变量里。但在欧姆龙 NJ/NX 的编程模型里,硬件串口的数据不是简单通过一个指令就能变成高级语言字符串的。它的处理链路更长:串口单元负责接收字节,CPU 负责从通信单元的数据区或事件标志里拿到这些字节,然后用户程序负责判断“一帧是否完整”。

所以,如果不提前设计帧格式,程序很容易出现以下几种现象:

  1. 串口助手那边显示发送成功,PLC 这边一个字节都看不到。
  2. PLC 能看到字节,但每次数量不对,有时多一个、有时少一个。
  3. 收到一帧后,程序没反应,因为接收完成标志只保持了一个扫描周期。
  4. 外部设备发来的字节是 ASCII 码,结果程序按十六进制数去解析,得到完全错误的数据。

这些问题都不是“设备坏了”,而是对无协议接收模型的理解不到位。无协议接收的核心从来不是“怎么收字节”,而是“怎么判断一帧已经到齐了”。这是一道典型的状态机设计题,跟用 CP1H、CJ2M 还是 NJ501 关系不大,真正的差异只是平台上的配置方式和地址映射不同。

这篇文章不会给你一个“填进去就能用”的万能代码,因为外部设备的报文格式千差万别。但我会提供一套可以迁移的接收状态机程序框架,以及从配置、接帧、校验到排错的完整思路。如果你正在做扫码枪数据采集、电子秤读数、RFID 读写器数据接入,或者任何靠裸串口主动上报数据的项目,这套方法可以直接拿过去改。

2. 无协议串口通信的核心概念与适用场景

2.1 什么是无协议通信

无协议通信在欧姆龙手册里的意思是:PLC 的串口不按 Modbus-RTU、Modbus-ASCII 或 Host Link 等固定协议进行报文解析,而是把收到的每个字节原样交给用户程序处理。

用一句话解释:

无协议模式下的串口单元,相当于一个“字节搬运工”。它只保证波特率、数据位、停止位、校验方式正确,不关心这串字节到底是什么内容。

也正因为如此,它可以适配通信协议未知或者不标准的设备。外部仪器厂家只要告诉你“设备每隔 100ms 向串口发送一帧十六进制数据”,你就能通过无协议模式来接。

2.2 无协议接收的两种工作方式

在实际设备对接中,无协议接收通常有两种场景,一定要先分清楚。

第一种是外部设备主动上报。扫码枪扫到条码后主动发一串 ASCII 字符,称重仪表稳定后周期发送当前重量,RFID 读写器读到标签后主动上传标签号。这种场景下,PLC 端主要是“接收方”,不需要在每次接收前先发命令。

第二种是问答式。PLC 先向设备发送一个读取命令,设备收到后返回一组数据。虽然整体可以算“无协议通信”,但程序里需要把发送命令、等待应答、超时重试这一套逻辑加进去。

本文的视频讲解内容主要针对第一种主动上发场景。它的好处是 PLC 侧逻辑更简单,难点在于帧边界判断和粘包处理。问答式场景可以在主动上报的基础之上,再把“发送请求”状态加进去,核心接收状态机是通用的。

2.3 什么时候应该用无协议,什么时候不该用

无协议不是万能的。如果外部设备支持 Modbus-RTU,优先用 Modbus-RTU 主站功能。因为 Modbus 有标准的 CRC 校验、功能码和地址访问机制,PLC 侧有现成库,稳定性更好。

无协议适合的是:

  • 设备只支持自由格式报文,没有标准总线协议。
  • 设备协议是厂家私有格式,而且没有提供官方驱动。
  • 数据量不大,一帧在几十个字节以内。
  • 传输距离短,通信频次不高。

不适合的场景:

  • 需要高可靠性 CRC 校验的场合,而你又不想在 PLC 里手写校验算法。
  • 设备协议本身就是 Modbus,却硬要用无协议模式自己解析。
  • 长距离、强干扰环境下的 RS-232 传输。RS-232 的抗干扰能力有限,这种情况建议改 RS-485 或加隔离转换器。

实际项目中,无协议通信最大的隐藏成本不是硬件,而是“应用层协议维护”。每种设备一个帧格式,你需要把每种格式都固化成清晰的程序结构,否则后续维护非常痛苦。

3. 硬件连接与环境准备

3.1 需要准备的硬件

做 NJ501 无协议串口通信接收,不能想当然地认为 CPU 本体上一定有串口。不同型号的 NJ501 CPU 在接口配置上有差异,这一点必须先看硬件手册确认。

从工程通用角度来看,需要准备以下设备:

设备说明
NJ501 系列 CPU作为控制器,执行 ST/梯形图程序
串行通信单元或带串口的通信模块用于提供 RS-232C/RS-422A/RS-485 物理接口
外部串口设备扫码枪、仪表、传感器等,能主动发送数据
编程连接线NJ501 与电脑之间通过 USB 或 EtherNet/IP 连接
RS-232/RS-485 通信线连接外部设备与串口单元,注意针脚定义
Sysmac Studio 软件NJ/NX 系列的编程和组态软件

要特别提醒:不要在没有确认串口单元型号和 CPU 兼容性的情况下直接采购。NJ501 扩展串行通信单元时,需要确认 Sysmac Studio 版本是否支持该单元,CPU 固件版本也不能太低。以你实际拿到的硬件手册为准,不同批次和型号可能有不小差异。

3.2 RS-232 接线的基本规则

RS-232 是最常用的无协议接口之一,接线时最容易犯的错误是把发送和接收接反。

PLC 侧的串口单元一般会标注 TXD、RXD、SG(信号地)等引脚。连接外部设备时遵循一个原则:

  • PLC 的 TXD 接外部设备的 RXD。
  • PLC 的 RXD 接外部设备的 TXD。
  • SG 信号地一定要接,否则通信可能不稳定。

如果使用 DB9 转接端子,还要注意 2、3 脚是否交叉。很多电工习惯“同号相接”,这在 RS-232 上往往是错的。视频里经常强调的一句话是:

RS-232 是 DTE 和 DCE 之间的交叉连接,不是直连。

如果调试时总是收不到数据,第一步先用万用表确认 TX/RX 有没有交叉接反,而不是先怀疑 PLC 配置。

3.3 软件环境准备

Sysmac Studio 是欧姆龙 NJ/NX 系列统一使用的编程软件,支持梯形图、结构化文本 ST、连续功能图等 IEC 61131-3 语言。做无协议接收程序时,推荐使用 ST 来写接收状态机,因为它对分支和状态表达更清晰。

软件环境要注意三点:

  1. Sysmac Studio 的版本尽量与 NJ501 CPU 固件版本匹配,低版本软件可能无法识别高版本固件创建的项目。
  2. 建立新工程时,在“设备”里选择 CPU 的具体型号,比如 NJ501-□□□□,选错型号会导致组态信息不一致。
  3. 如果使用仿真功能,需要了解 Sysmac Studio 的模拟器只能仿真逻辑任务,不能直接模拟真实串口收发。无协议接收调试最好还是接硬件。

如果还没有安装 Sysmac Studio,可以通过欧姆龙官网的技术支持页面获取试用版或正式版安装包。安装时关闭杀毒软件,安装路径不要带中文,否则后续编译时偶尔会出现奇怪的文件访问错误。

4. Sysmac Studio 无协议接收配置

4.1 添加串行通信单元

在 Sysmac Studio 的工程树中,通常是在“配置和设置”或“CPU/扩展机架”里完成串行通信单元的添加。如果你用的是 CPU 本体的扩展能力,需要把实际的串行单元添加到设备组态中。

添加时重点确认两个信息:

  1. 单元型号是否正确,最好与硬件铭牌完全一致。
  2. 单元占用的单元号/节点号是否与其他单元冲突。

添加完成后,Sysmac Studio 会自动为通信单元分配 CIO/DM 等地址。这些地址会在你编写接收程序时用到,不需要手工去背偏移量,软件分配出来的地址才是当前工程环境下的真实地址。

4.2 设置无协议模式与串口参数

打开串行通信单元的设置页面后,把通信模式选为“无协议”或“Non-procedure”。

接下来需要设置串口参数,这些参数必须和外部设备完全一致:

参数常见值说明
波特率9600、19200、115200必须与外部设备一致
数据位8大多数设备都使用 8 位数据
停止位1 或 2根据设备手册设置
校验位无、偶校验、奇校验必须与设备一致
流控无协议场景下一般不用硬件流控
接收结束条件结束码/字节数/间隔时间根据帧格式选择

很多外部设备出厂默认是 9600、8、N、1。如果设备文档丢失,可以在串口调试助手里用常见波特率逐个试。不要小看这一步,现场很大比例的通信失败都是因为波特率不一致,而不是程序错误。

4.3 接收结束方式的两种思路

无协议接收最难的一个配置项是“什么时候认为一帧数据接收完成”。欧姆龙串口单元通常支持基于结束码、接收字节数、字符间隔等不同策略。

  • 结束码方式:设备每帧末尾带 CR、LF、0x0D、0x0A 之类固定字节,收到结束码就代表一帧结束。适合 ASCII 文本型设备。
  • 固定字节数方式:设备每帧长度固定,比如总是 8 个字节,收到第 8 个字节就认为一帧完成。适合二进制仪表。
  • 字符间隔方式:如果帧长不固定、又没有结束码,可以在串口空闲超过一定时间后认为一帧收完,比如“字节间隔超过 20ms 判定为帧结束”。

你需要根据设备协议来选择。视频里经常看到的坑是:明明数据已经进入PLC,但因为结束条件设置不对,接收完成标志永远不置位,程序里自然看不到结果。

4.4 接收缓冲与字节提取

无协议通信单元接收到的数据会进入通信单元的接收缓冲区。用户程序不能把这些数据当成一个大字符串直接读取,而是要通过地址映射或接收事件逐个字节地取出来。

这里需要强调一个工程原则:

不要在主程序里大量轮询整个缓冲区,这样既占用扫描周期,又容易漏掉短暂有效的完成标志。比较好的做法是:在合适的周期任务或事件任务中,把“新收到字节”的信号和“接收缓冲区首地址”之间的转换做成一个独立接口,再用状态机逐字节处理。

Sysmac Studio 中,实际地址会以全局变量的形式出现在 I/O 映射表里。由于不同通信单元的地址分配不同,本文不写死具体地址。真实的做法是:

  1. 在全局变量表中找到接收完成标志。
  2. 找到接收缓冲区对应的字节型变量或字型变量。
  3. 编写一个适配程序,把接收完成信号转换成“本周期有新字节”的布尔量和一个 BYTE 型数据。

完成这一步之后,后面所有接收解析逻辑都可以与具体硬件解耦,程序的可移植性会大幅提高。

5. 解析程序设计:用 ST 实现接收状态机

5.1 先确定假设的帧格式

为了让示例程序不依赖某种具体设备,我定义一个简单的报文格式。实际项目中,你需要把这段改成外部设备手册上的真实协议。

假设外部设备每帧报文为:

字节位置内容说明
第 1 字节0xAA帧头 STX
第 2 字节LEN数据区长度,1 字节
之后 LEN 字节DATA业务数据,范围 0 到 128 字节
倒数第 2 字节XOR校验字节,从 LEN 到 DATA 最后一个字节的异或结果
最后 1 字节0x55帧尾 ETX

例如,要发送的数据是11 22,那么:

  • LEN = 0x02
  • XOR = 0x02 XOR 0x11 XOR 0x22 = 0x31
  • 整帧就是AA 02 11 22 31 55

在程序里,我们不依赖 Sysmac Studio 某个隐藏的系统功能,而是把无协议接收看成“逐字节喂给状态机”的过程。每次串口收到一个新字节,就把这个字节交给状态机运行一次,状态机会自动判断当前处于帧头、长度、数据、校验还是帧尾阶段。

5.2 定义数据类型

首先在 Sysmac Studio 中定义一帧解析结果的数据结构,方便后续程序统一引用。

TYPE St_ReceiveFrame : STRUCT bValid : BOOL; (* 本帧是否校验通过 *) bError : BOOL; (* 本帧是否接收出错 *) uDataLen : UINT; (* 数据区实际长度 *) aData : ARRAY[0..127] OF BYTE; (* 解析后的数据区 *) END_STRUCT END_TYPE

这段代码的作用是给“解析结果”定义一个标准容器。只要外部设备的数据长度不超过 128 字节,都可以存放在这个结构体里。如果实际数据长度更长,需要把数组上限改大,同时注意接收缓冲区的容量限制。

5.3 编写逐字节接收状态机

下面是一个完整的 ST 功能块。它不关心数据从哪个串口来,只要求外部每个周期把新字节通过bByteRcvdbyData输入进来。

FUNCTION_BLOCK FB_SerialFrameRx VAR_INPUT bEn : BOOL; (* 功能块使能 *) bByteRcvd : BOOL; (* 本周期有新字节到达 *) byData : BYTE; (* 新到达的字节 *) END_VAR VAR_OUTPUT bFrameDone : BOOL; (* 一帧完整接收完成 *) bFrameErr : BOOL; (* 帧格式错误 *) stFrame : St_ReceiveFrame; (* 解析结果 *) END_VAR VAR eState : INT := 0; (* 当前状态 *) iDataLen : INT := 0; (* 本帧数据长度 *) iDataIdx : INT := 0; (* 已经接收的数据计数器 *) byXor : BYTE; (* 异或校验累加值 *) END_VAR

功能块的执行逻辑如下。状态 0 等待帧头,状态 1 读取长度,状态 2 接收数据,状态 3 校验,状态 4 等待帧尾。

IF NOT bEn THEN eState := 0; bFrameDone := FALSE; bFrameErr := FALSE; RETURN; END_IF; IF NOT bByteRcvd THEN RETURN; END_IF; CASE eState OF 0: (* 等待帧头 0xAA *) bFrameDone := FALSE; bFrameErr := FALSE; IF byData = 16#AA THEN eState := 1; END_IF; 1: (* 读取数据长度 LEN *) iDataLen := byData; byXor := byData; IF iDataLen > 128 THEN eState := 5; ELSIF iDataLen = 0 THEN eState := 3; ELSE iDataIdx := 0; eState := 2; END_IF; 2: (* 接收数据区 *) stFrame.aData[iDataIdx] := byData; byXor := byXor XOR byData; iDataIdx := iDataIdx + 1; IF iDataIdx >= iDataLen THEN eState := 3; END_IF; 3: (* 校验字节比较 *) IF byData = byXor THEN eState := 4; ELSE stFrame.bError := TRUE; eState := 5; END_IF; 4: (* 等待帧尾 0x55 *) IF byData = 16#55 THEN stFrame.bValid := TRUE; stFrame.uDataLen := iDataLen; bFrameDone := TRUE; eState := 0; ELSE stFrame.bError := TRUE; eState := 5; END_IF; 5: (* 错误状态,回到等待帧头 *) bFrameErr := TRUE; stFrame.bError := TRUE; eState := 0; END_CASE;

这个状态机最需要注意

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询