CiA 401 — 通用 I/O 模块
CiA 401 (IEC 61131-9) 是基于 CoE 的通用数字/模拟 I/O 设备协议。
CiA 401 运行在 CoE (SDO) 之上,仅当从站支持 CoE 时可用。
通过 slave.CoE.CiA401 访问。从站不支持 CiA 401 时为 null。
IO 属性
通过索引器直接读写 IO 通道,所有通道号从 0 开始。
| 类别 | 属性 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 数字 | DI[n] | bool | 只读 | 数字输入通道 n |
| DO[n] | bool | 读写 | 数字输出通道 n | |
| 模拟 | AI[n] | int | 只读 | 模拟输入通道 n |
| AO[n] | short | 读写 | 模拟输出通道 n |
示例:
var io = slave.CoE.CiA401;
if (io == null) return;
// 数字 IO
bool sensor = io.DI[0];
io.DO[0] = true;
io.DO[1] = sensor;
// 模拟 IO
int adc = io.AI[0];
io.AO[0] = 16384;
高频率 I/O 控制应使用 PDO 映射 而非 SDO。SDO 适合参数配置和诊断。
DI
public DIAccessor DI { get; }
数字输入索引器。DI[channel] 按通道号 (从 0 开始) 读取单通道高/低电平,内部按 8 通道一组转 SDO 0x6000。
相关结构:
public class DIAccessor
{
public bool this[int channel] { get; } // 只读, true = 高电平
}
示例:
bool ch0 = io.DI[0];
bool ch5 = io.DI[5];
DO
public DOAccessor DO { get; }
数字输出索引器。DO[channel] 既可读回上次写入的状态,也可设置目标状态 (写 SDO 0x6200),不影响同组其他通道。
相关结构:
public class DOAccessor
{
public bool this[int channel] { get; set; }
}
示例:
io.DO[0] = true;
io.DO[1] = io.DI[0];
bool current = io.DO[0];
AI
public AIAccessor AI { get; }
模拟输入索引器。AI[channel] 读 SDO 0x6400(子索引 = 通道 + 1),按返回长度自动适应 16/32 位,返回有符号原始值。如需无符号 16 位或无符号 32 位,使用 ReadAIUnsigned / ReadAI32Unsigned。
相关结构:
public class AIAccessor
{
public int this[int channel] { get; } // 只读
}
示例:
int adc0 = io.AI[0];
double voltage = adc0 * 10.0 / 32768;
AO
public AOAccessor AO { get; }
模拟输出索引器。AO[channel] 读写 16 位有符号值 (SDO 0x6411)。32 位高精度设备使用 WriteAO32 / ReadAO32。
相关结构:
public class AOAccessor
{
public short this[int channel] { get; set; }
}
示例:
io.AO[0] = 16384; // 写入 50% 量程
short echo = io.AO[0]; // 读回当前设定值
方法 API
索引器内部调用以下方法,也可直接使用。所有写入方法在 SDO 写入失败时抛 InvalidOperationException(从站配置未改变)。
ReadDI
public bool ReadDI(int channel)
读取数字输入。内部按 8 通道一组自动计算组号和位偏移。
参数:
channel— 通道号(从 0 开始)
WriteDO
public void WriteDO(int channel, bool state)
设置数字输出,读改写同组字节,不影响同组其他通道。同组旧值读失败时抛 InvalidOperationException,不按 0 基线改写本组。
参数:
channel— 通道号(从 0 开始)state— 目标状态
ReadDO
public bool ReadDO(int channel)
读取数字输出状态(SDO 0x6200,按 8 通道一组)。读失败返回 false 且与真值不可区分,判失败请用 TryReadDO。
参数:
channel— 通道号(从 0 开始)
WriteVirtualDI(int diIndex, bool state)
public void WriteVirtualDI(int diIndex, bool state)
写入虚拟 DI 命令(0x2120:01-04,厂商对象)。该对象 SDO 写会被 abort(0x06020000),方法内部自动回退 PDO 写入;SDO 与 PDO 均失败时抛 InvalidOperationException。
参数:
diIndex— 虚拟 DI 子索引(1-4 → 0x2120:01-04)state— 目标状态(true = 1)
ReadAI
public int ReadAI(int channel)
读取模拟输入值。自动适应 16 位和 32 位设备。
参数:
channel— 通道号(从 0 开始)
WriteAO
public void WriteAO(int channel, short value)
写入模拟输出值。
参数:
channel— 通道号(从 0 开始)value— 16 位输出值
ReadAO
public short ReadAO(int channel)
读取 16 位模拟输出值(SDO 0x6411)。读失败返回 0 且与真值不可区分,判失败请用 TryReadAO。
参数:
channel— 通道号(从 0 开始)
ReadAIUnsigned
public ushort ReadAIUnsigned(int channel)
读取模拟输入值(无符号 16 位)。适用于 AI 数据范围为 0~65535 的设备。
参数:
channel— 通道号(从 0 开始)
返回值:
ushort— 无符号 16 位模拟值
ReadAI32Unsigned
public uint ReadAI32Unsigned(int channel)
读取模拟输入值(无符号 32 位)。适用于高精度 AI 设备,数据范围 0~4294967295。
参数:
channel— 通道号(从 0 开始)
返回值:
uint— 无符号 32 位模拟值
示例:
var io = slave.CoE.CiA401;
// 有符号读取 (默认, 可能高位值变负数)
int aiSigned = io.ReadAI(0);
// 无符号 16 位读取 (0~65535)
ushort aiU16 = io.ReadAIUnsigned(0);
// 无符号 32 位读取 (高精度设备)
uint aiU32 = io.ReadAI32Unsigned(0);
模拟输入中断与限值
ReadGlobalInterruptEnable / WriteGlobalInterruptEnable
public bool ReadGlobalInterruptEnable()
public void WriteGlobalInterruptEnable(bool enable)
读取或设置模拟输入全局中断使能 (0x6423)。
参数:
enable—true= 使能,false= 禁用
ReadAIUpperLimit / WriteAIUpperLimit
public int ReadAIUpperLimit(int channel)
public void WriteAIUpperLimit(int channel, short value)
读取或写入模拟输入中断上限值 (0x6424)。当 AI 值超过上限时触发中断。
参数:
channel— 通道号(从 0 开始)value— 上限值
ReadAILowerLimit / WriteAILowerLimit
public int ReadAILowerLimit(int channel)
public void WriteAILowerLimit(int channel, short value)
读取或写入模拟输入中断下限值 (0x6425)。当 AI 值低于下限时触发中断。
参数:
channel— 通道号(从 0 开始)value— 下限值
示例:
var io = slave.CoE.CiA401;
// 启用全局中断
io.WriteGlobalInterruptEnable(true);
// 设置通道 0 的上下限
io.WriteAIUpperLimit(0, 30000);
io.WriteAILowerLimit(0, 1000);
// 读取当前限值配置
int upper = io.ReadAIUpperLimit(0);
int lower = io.ReadAILowerLimit(0);
bool enabled = io.ReadGlobalInterruptEnable();
批量 IO 方法
除逐通道操作外,还支持 16/32 位批量读写,一次操作多个通道。
ReadDI16(int group)
public ushort ReadDI16(int group)
读取 16 位数字输入(16 个通道)。
参数:
group— 组号(子索引,从 1 开始,每 16 个通道为一组:组 1 = 通道 0-15)
返回值:
ushort— 16 位输入值,每位对应一个通道
ReadDI32(int group)
public uint ReadDI32(int group)
读取 32 位数字输入(32 个通道)。
参数:
group— 组号(子索引,从 1 开始,每 32 个通道为一组:组 1 = 通道 0-31)
返回值:
uint— 32 位输入值,每位对应一个通道
示例:
var io = slave.CoE.CiA401;
ushort di16 = io.ReadDI16(1); // 读取通道 0-15
uint di32 = io.ReadDI32(1); // 读取通道 0-31
bool ch5 = (di16 & (1 << 5)) != 0; // 检查通道 5
ReadDO16 / WriteDO16
public ushort ReadDO16(int group)
public void WriteDO16(int group, ushort value)
读取或写入 16 位数字输出(16 个通道)。
参数:
group— 组号(子索引,从 1 开始,每 16 个通道为一组:组 1 = 通道 0-15)value— 16 位输出值
ReadDO32 / WriteDO32
public uint ReadDO32(int group)
public void WriteDO32(int group, uint value)
读取或写入 32 位数字输出(32 个通道)。
参数:
group— 组号(子索引,从 1 开始,每 32 个通道为一组:组 1 = 通道 0-31)value— 32 位输出值
示例:
var io = slave.CoE.CiA401;
// 16 位批量操作
io.WriteDO16(1, 0x00FF); // 通道 0-7 输出高,8-15 输出低
ushort do16 = io.ReadDO16(1); // 读回当前值
// 32 位批量操作
io.WriteDO32(1, 0x0000FFFF); // 通道 0-15 输出高
uint do32 = io.ReadDO32(1); // 读回当前值
ReadAO32 / WriteAO32
public int ReadAO32(int channel)
public void WriteAO32(int channel, int value)
读取或写入 32 位模拟输出值。适用于高精度模拟输出设备。
参数:
channel— 通道号(从 0 开始)value— 32 位输出值
示例:
var io = slave.CoE.CiA401;
io.WriteAO32(0, 65536); // 写入 32 位高精度值
int ao = io.ReadAO32(0); // 读回当前值
读失败判别(Try 前缀方法)
普通读取方法(ReadDI / ReadAI / ReadDO16 等)读失败时返回 0 / false / Hold 占位,与真值不可区分,不可作为工艺状态参与联锁。以下 Try 前缀方法返回 false 表示读取失败(out 参数无意义),返回 true 时 out 参数才是真值。
| 类别 | 方法 | 返回类型 | 读写 | 说明 |
|---|---|---|---|---|
| 数字 | TryReadDI(int channel, out bool value) | bool | 只读 | 数字输入单通道(0x6000) |
| 数字 | TryReadDI16(int group, out ushort value) | bool | 只读 | 16 位数字输入组(0x6001,组号 = 子索引,从 1 开始) |
| 数字 | TryReadDI32(int group, out uint value) | bool | 只读 | 32 位数字输入组(0x6020,组号 = 子索引,从 1 开始) |
| 数字 | TryReadDO(int channel, out bool value) | bool | 只读 | 数字输出单通道回读(0x6200) |
| 数字 | TryReadDO16(int group, out ushort value) | bool | 只读 | 16 位数字输出组回读(0x6201) |
| 数字 | TryReadDO32(int group, out uint value) | bool | 只读 | 32 位数字输出组回读(0x6220) |
| 模拟 | TryReadAI(int channel, out int value) | bool | 只读 | 模拟输入,自动适应 16/32 位(0x6400) |
| 模拟 | TryReadAIUnsigned(int channel, out ushort value) | bool | 只读 | 模拟输入无符号 16 位(0x6400) |
| 模拟 | TryReadAI32Unsigned(int channel, out uint value) | bool | 只读 | 模拟输入无符号 32 位(0x6400) |
| 模拟 | TryReadAIUpperLimit(int channel, out int value) | bool | 只读 | 模拟输入中断上限(0x6424) |
| 模拟 | TryReadAILowerLimit(int channel, out int value) | bool | 只读 | 模拟输入中断下限(0x6425) |
| 模拟 | TryReadAO(int channel, out short value) | bool | 只读 | 16 位模拟输出回读(0x6411) |
| 模拟 | TryReadAO32(int channel, out int value) | bool | 只读 | 32 位模拟输出回读(0x6412) |
| 错误 | TryGetDOErrorMode(int group, out CiA401ErrorMode mode) | bool | 只读 | 数字输出错误模式(0x6206) |
| 错误 | TryGetDOErrorValue(int group, out byte value) | bool | 只读 | 数字输出安全值(0x6207) |
示例:
var io = slave.CoE.CiA401;
// 分判「读失败」与「真值 0」
if (io.TryReadAI(0, out int ai))
Console.WriteLine($"AI0 = {ai}");
else
Console.WriteLine("AI0 读取失败, 不可当测量值使用");
数字输入配置
SetDIPolarity / GetDIPolarity
public void SetDIPolarity(int group, byte polarity)
public byte GetDIPolarity(int group)
设置或读取数字输入极性 (0x6002)。可按位反转输入信号逻辑。
参数:
group— 组号(子索引,从 1 开始)polarity— 极性掩码,1 = 反转对应通道
示例:
var io = slave.CoE.CiA401;
io.SetDIPolarity(1, 0x0F); // 反转通道 0-3 的极性
byte pol = io.GetDIPolarity(1); // 读取当前极性配置
SetDIFilter / GetDIFilter
public void SetDIFilter(int group, byte filterEnable)
public byte GetDIFilter(int group)
设置或读取数字输入滤波使能 (0x6003)。用于消除输入信号抖动。
参数:
group— 组号(子索引,从 1 开始)filterEnable— 滤波使能掩码
示例:
var io = slave.CoE.CiA401;
io.SetDIFilter(1, 10); // 设置滤波使能
byte flt = io.GetDIFilter(1); // 读取当前滤波配置
错误状态读取
GetDOErrorMode / GetDOErrorValue
public CiA401ErrorMode GetDOErrorMode(int group)
public byte GetDOErrorValue(int group)
读取数字输出错误模式和错误安全值的当前配置。读失败返回占位值(Hold / 0)并记日志,与真值不可区分;判失败请用 TryGetDOErrorMode / TryGetDOErrorValue。
参数:
group— 组号(从 0 开始)
示例:
var io = slave.CoE.CiA401;
var mode = io.GetDOErrorMode(0);
byte safeVal = io.GetDOErrorValue(0);
Console.WriteLine($"错误模式: {mode}, 安全值: 0x{safeVal:X2}");
错误处理
DOErrorMode
public void DOErrorMode(int group, CiA401ErrorMode mode)
设置数字输出错误模式。当 EtherCAT 通信丢失时,决定输出如何响应。
参数:
group— 组号(从 0 开始,每 8 个通道为一组:组 0 = 通道 0-7,组 1 = 通道 8-15)mode— 错误模式
相关结构:
public enum CiA401ErrorMode : byte
{
Hold = 0, // 保持当前输出值
SafeValue = 1, // 切换到预设的安全值
}
DOErrorValue
public void DOErrorValue(int group, byte value)
设置数字输出通信丢失时的安全值。仅在 SafeValue 模式下生效。
参数:
group— 组号(从 0 开始)value— 安全输出值(8 位,每位对应一个通道)
AOErrorMode
public void AOErrorMode(int channel, CiA401ErrorMode mode)
设置模拟输出错误模式。
参数:
channel— 通道号(从 0 开始)mode— 错误模式
AOErrorValue
public void AOErrorValue(int channel, short value)
设置模拟输出通信丢失时的安全值。仅在 SafeValue 模式下生效。
参数:
channel— 通道号(从 0 开始)value— 16 位安全输出值
示例:
io.DOErrorMode(0, CiA401ErrorMode.SafeValue);
io.DOErrorValue(0, 0x00);
io.AOErrorMode(0, CiA401ErrorMode.SafeValue);
io.AOErrorValue(0, 0);
标准对象索引常量
可搭配 slave.CoE.SDORead / SDOWrite 直接访问:
| 常量 | 值 | 说明 |
|---|---|---|
| OD_VIRTUAL_DI | 0x2120 | 虚拟 DI 命令(厂商对象,须走 PDO) |
| OD_DI | 0x6000 | 数字输入 |
| OD_DI_16BIT | 0x6001 | 16 位数字输入 |
| OD_DI_POLARITY | 0x6002 | 数字输入极性 |
| OD_DI_FILTER | 0x6003 | 数字输入滤波使能 |
| OD_DI_INTERRUPT | 0x6005 | 数字输入中断触发 |
| OD_DI_INTERRUPT_EDGE | 0x6006 | 数字输入中断边沿 |
| OD_DI_32BIT | 0x6020 | 32 位数字输入 |
| OD_DO | 0x6200 | 数字输出 |
| OD_DO_16BIT | 0x6201 | 16 位数字输出 |
| OD_DO_POLARITY | 0x6202 | 数字输出极性 |
| OD_DO_ERROR_MODE | 0x6206 | 数字输出错误模式 |
| OD_DO_ERROR_VALUE | 0x6207 | 数字输出错误值 |
| OD_DO_32BIT | 0x6220 | 32 位数字输出 |
| OD_AI | 0x6400 | 模拟输入 |
| OD_AI_SI_UNIT | 0x6420 | 模拟输入量程 |
| OD_AI_GLOBAL_INTERRUPT | 0x6423 | 模拟输入全局中断使能 |
| OD_AI_UPPER_LIMIT | 0x6424 | 模拟输入中断上限 |
| OD_AI_LOWER_LIMIT | 0x6425 | 模拟输入中断下限 |
| OD_AO | 0x6411 | 模拟输出 |
| OD_AO_32BIT | 0x6412 | 32 位模拟输出 |
| OD_AO_SI_UNIT | 0x6430 | 模拟输出量程 |
| OD_AO_ERROR_MODE | 0x6443 | 模拟输出错误模式 |
| OD_AO_ERROR_VALUE | 0x6444 | 模拟输出错误值 |
完整示例
var io = slave.CoE.CiA401;
if (io == null) return;
// 数字 IO
bool sensor = io.DI[0];
bool limit = io.DI[5];
io.DO[0] = true;
io.DO[1] = sensor;
// 模拟 IO
int adc = io.AI[0];
double temp = adc * 0.1;
io.AO[0] = (short)(temp > 50 ? 0 : 16384);
// 安全配置
io.DOErrorMode(0, CiA401ErrorMode.SafeValue);
io.DOErrorValue(0, 0x00);