跳到主要内容

过程数据

通过 PnSProcessDataInputsMappingRO / PnSProcessDataOutputsMapping 把整幅过程映像映射成结构体,通过 PnSProcessDataIn* / PnSProcessDataOut*PnSServiceReadBool 这类按偏移 / 按地址的口读写单个值。

过程数据就是控制器与设备之间每周期自动搬运的那段字节:写 Q(设备 → 控制器),读 I(控制器 → 设备)。最省事的用法是把这段字节一次映射成一个 #pragma pack(push, 1) 结构体,之后读写字段就是直接读写过程映像本身 —— 0 拷贝,不用拷贝、不用刷新:读到的永远是当前周期,写下的下个周期发出。

I 区只读:控制器每周期整段覆盖输入映像,写它静默无效(下一拍被冲掉,也不报错)—— 要写过程映像只有 Q 区一条路。Q 区反过来是设备唯一可写方向,改字节即改进程映像,内核每周期原样取走,没有任何提交调用

零拷贝只有整幅过程映像的映射这一条路,出口两种形态:类型化的结构体映射(PnSProcessDataInputsMappingRO / PnSProcessDataOutputsMapping),以及共享区的原始净荷字节口(pns_shm_i_payload / pns_shm_q_payload,见「共享区直连」)。

除此以外的入口都是拷贝:按偏移取(PnSProcessDataInI16 一族)、按项取(PnSProcessDataItemIn / Out + 项上定宽读写)、按地址取(I0.0 / IW2 / QD4)。它们的好处是不用先建结构体,代价是每次调用都把那一小段字节拷出来再按大端解释。

总线周期由内核完成。事件流报的是连接 / 状态 / 链路 / IO 质量这类运行态(见事件);净荷的变化按你自己的节拍去取(PnSServiceRead / PnSServiceGetIo / 映射视图)。调度(循环 / 定时器 / 线程)由你自己定。

结构体映射契约(0 拷贝那条路)
  • 必须 #pragma pack(push, 1):过程映像是线上字节流,不能有对齐填充。SDK 只给指针 + 长度,不校验打包与尺寸 —— 没 pack 的结构体会整体错位且不报错,用 _Static_assert(sizeof(T) == 打包尺寸) 在编译期钉死。
  • 判据三缺一即禁止映射rc == PNS_OK && *p != NULL && *n >= sizeof(T)。失败时 *p = NULL*n = 0,不会留旧指针。
  • 多字节是网络大端(PROFINET 网络序):映射出来的是线上字节,把 int16_t / int32_t / float 字段当宿主数值直接用会反序 —— 要宿主数值走按偏移的类型化口(它们替你按大端解码),或解析字节字段。
  • 方向:Q 区(PnSProcessDataOutputsMapping)可写;I 区(PnSProcessDataInputsMappingRO)只读。
  • 窗口起点是映射那一次算出来的:I 侧净荷起点 = 先剥掉 2 个 IOPS 前缀,再看净荷开头连续有几个 0x80 再剥(最多 4 个)——所以它取决于当次区内容,不是常量;能用的窗口比 PnSServiceGetIo 回的 I 长少 2–6 字节(Q 侧是固定 +3、不扫描)。判据只看 *n,别自己写死偏移。
  • 映射从净荷第 0 字节起:要映射中段,在结构体开头放一段占位数组(uint8_t _skip[k];)顶开;或改用 PnSServiceReadArea 按偏移取字节。
  • 生命周期:映射在会话存续期有效,PnSServiceClose 后失效;Stop 只停刷新不卸映射(能读回旧值),但 Stop 之后新的映射调用一律 PNS_ERR_NO_STACK。地址稳定性不在承诺里 —— 生命周期是真的,偏移不是。
  • Q 区映射是单 owner:读可并发,写请调用方自行串行化
Input / Output 与 I / Q 对着看

GSDML 里那 20 字节的 Input(设备 → 控制器)就是 SDK 的 Q;GSDML 里那 32 字节的 Output(控制器 → 设备)就是 SDK 的 I。

设备是固定单模块(槽 1 / 子槽 1),C 口的函数签名里没有 slot / subslot 参数 —— pns_service_address_t 里的 slot / subslot 恒为 1,不需要调用方填。

使用案例

日常使用

① 结构体映射(0 拷贝,持续用):进数据交换后映射一次,之后当变量读写。

#include "pns.h"

#pragma pack(push, 1)
typedef struct { uint8_t present; int16_t recipe; uint8_t flag; } from_plc_t; /* I:控制器 → 设备 */
typedef struct { uint8_t ready; uint8_t busy; } to_plc_t; /* Q:设备 → 控制器 */
#pragma pack(pop)

_Static_assert(sizeof(from_plc_t) == 4, "缺 #pragma pack(push, 1)");
_Static_assert(sizeof(to_plc_t) == 2, "缺 #pragma pack(push, 1)");

pns_service_handle_t session = NULL;
if (PnSServiceConnectRealtime(&session) != PNS_OK) return 1; /* 实时附着(占用唯一门) */
if (PnSServiceStart(session) != PNS_OK)
{ PnSServiceClose(session); return 1; } /* 启用 IO 设备 */

const uint8_t *i = NULL; volatile uint8_t *q = NULL; size_t ni = 0, nq = 0;
if (PnSProcessDataInputsMappingRO(session, &i, &ni) != PNS_OK || i == NULL || ni < sizeof(from_plc_t))
{ PnSServiceClose(session); return 1; } /* I:只读映射 */
if (PnSProcessDataOutputsMapping(session, &q, &nq) != PNS_OK || q == NULL || nq < sizeof(to_plc_t))
{ PnSServiceClose(session); return 1; } /* Q:可写映射 */

const from_plc_t *inp = (const from_plc_t *)i; /* 映射一次,之后当变量用 */
to_plc_t *outp = (to_plc_t *)q;

outp->ready = 1; /* 写下的下个周期发出,无需提交调用 */
uint8_t present = inp->present; /* 读到的永远是当前周期 */

字段宽度大于 1 字节又想要宿主数值时,别直接读结构体字段(那是大端字节),走按偏移的类型化口:

int16_t recipe = 0;
if (PnSProcessDataInI16(session, 1, &recipe) == PNS_OK) /* 偏移 1:按大端解码成宿主 int16 */
printf("recipe=%d\n", recipe);

② 单次读取(一次性取):要整段字节,或只要一个字段。

uint8_t buf[64]; uint16_t len = sizeof(buf);
PnSServiceRead(session, buf, sizeof(buf), &len); /* I 整段:一次读出,同一拍自洽 */

int16_t recipe = 0;
PnSProcessDataInI16(session, 1, &recipe); /* I:偏移 1 的 int16,大端 */

pns_process_data_item_t item = PnSProcessDataItemIn(session, 0); /* 见 ④:项(下标)形态 */
uint16_t v = 0;
PnSProcessDataItemReadU16(&item, &v); /* 项上读 2 字节,大端 */

字符串字段在过程映像里是定长字节(char 数组),一般不带结尾 0:

typedef struct { char part_no[16]; } from_plc_text_t;   /* 定义放进 #pragma pack(push, 1) */
const from_plc_text_t *txt = (const from_plc_text_t *)i; /* i 同 ①,同一份映射 */

char part_no[17];
memcpy(part_no, txt->part_no, 16);
part_no[16] = '\0';

③ 按地址读写(照着工程软件里的地址写):区域字母 + 字节偏移 + 位。

bool done = false;
PnSServiceReadBool(session, "I0.0", &done); /* 读位:控制器 → 设备 */
int16_t recipe = 0;
PnSServiceReadInt16(session, "IW2", &recipe); /* 读字:大端 */
int32_t counter = 0;
PnSServiceReadInt32(session, "ID4", &counter); /* 读双字:大端 */

PnSServiceWriteBool(session, "Q0.0", true); /* 写位(读改写同字节其它位) */
PnSServiceWriteInt16(session, "QW2", 123); /* 写字:大端 */
PnSServiceWriteInt32(session, "QD4", 10000); /* 写双字:大端 */

④ 下标取元素(项形态):项 = 会话 + 偏移 + 方向;在同一坐标上取定宽标量。项是拷贝,不是视图。

pns_process_data_item_t in_item  = PnSProcessDataItemIn(session, 1);   /* I 区只读项 */
pns_process_data_item_t out_item = PnSProcessDataItemOut(session, 0); /* Q 区可写项 */

int16_t recipe = 0;
if (PnSProcessDataItemReadI16(&in_item, &recipe) == PNS_OK) /* 大端解码 */
printf("recipe=%d\n", recipe);

if (PnSProcessDataItemWriteU16(&out_item, 123) == PNS_OK) /* 当场提交到共享区 */
printf("已写入 Q 偏移 0\n");

if (PnSProcessDataItemWriteU16(&in_item, 1) == PNS_ERR_NOT_SUPPORTED) /* I 项不可写,如实报错 */
printf("I 区只读\n");

周期变化

同一份映射放进你自己的循环直接用(内核每周期搬运,读到的就是当前拍)。

while (running) {                                /* 你自己的周期任务,节拍自定 */
outp->ready = (uint8_t)(inp->present + 1); /* 读当前拍 → 算 → 写下拍发出 */
Sleep(10); /* 拍间等待自备:Sleep 来自 <windows.h>,本 SDK 头不引入 */
}

映射在循环外取一次仍然是推荐用法:省掉每拍一次解析,内核每周期搬过程数据,不需要任何额外调用。代价要说清:净荷起点是映射那一次算出来的(见上面契约),一旦偏移漂移,你读到的是错位的字节 —— 不抛异常,*n >= sizeof(T) 这种尺寸门也拦不住。所以取值范围自检留在自己手里;要定位稳定的字节,用按偏移 / 按地址的口。Stop 后映射仍可读(读回旧值),PnSServiceClose 后映射失效。

按偏移 / 按项 / 按地址的口每次调用都直接取当前值,所以一次循环里对同一字节的多次取可能落在两个不同的总线周期。要一拍之内多字段互相自洽(例如"这一拍的 present 和 recipe 必须来自同一帧"),用 PnSServiceRead 取整段快照,或直接读 ① 的结构体映射(结构体映射每次读都是当前映像,不需要重取)。

结构体映射(零拷贝)

PnSProcessDataInputsMappingRO()

static inline pns_result_t PnSProcessDataInputsMappingRO(
pns_service_handle_t session,
const uint8_t ** p,
size_t * n);

I 区(控制器 → 设备)的 0 拷贝映射,出参是 const uint8_t ** —— 类型上就把写口封死。它是 I 区唯一推荐的映射入口。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • PNS_ERR_INVALID_PARAMpnNULL
  • PNS_ERR_NO_STACK — 会话未 Start 或已 Stop,文案:服务未 Start 或已 Stop: 过程映像/钉页不可用 (禁止 overlay 未运行的过程映像), 请先 PnSServiceStart
  • PNS_ERR_NATIVE — 过程映像不可用,文案:GlobalIO 未映射, 无法进行过程数据 IO (SDK IO 禁 HTTP);或净荷为空
  • 失败时 *p = NULL*n = 0 一并写实,不会把上一次的旧指针留给调用方
  • *n剥掉 IOPS 前缀后的净荷字节数,比整区长(PnSServiceGetIo 回的 I 长)少前缀字节;判据三缺一就不要拿它当结构体用
  • 起点是映射那一次算出来的(剥 2 个 IOPS,再扫描净荷开头连续的 0x80,最多 4 个):窗口比 I 整区长少 2–6 字节,偏移不是常量。不要把它当固定偏移存起来复用,也不要跨次映射缓存自己算的字节偏移 —— 要稳定偏移的定位就用按偏移 / 按地址的口
  • 映射存续期有效:PnSServiceClose 后失效;Stop 只停刷新不卸映射,但 Stop 之后新的映射调用返回 PNS_ERR_NO_STACK(旧映射仍指同一页,还能读)
  • 映射的是线上字节 = 网络大端;任意线程可读

示例:

const uint8_t *i = NULL;
size_t n = 0;
pns_result_t rc = PnSProcessDataInputsMappingRO(session, &i, &n);

if (rc == PNS_OK && i != NULL && n >= sizeof(from_plc_t)) {
const from_plc_t *inp = (const from_plc_t *)i; /* 钉在过程映像上 */
printf("present=%u\n", inp->present); /* 读到的永远是当前周期 */
} else {
printf("映射未就绪: rc=%d (%s)\n", (int)rc, PnSServiceLastError(session));
}

PnSProcessDataOutputsMapping()

pns_result_t PnSProcessDataOutputsMapping(
pns_service_handle_t session,
volatile uint8_t ** p,
size_t * n);

Q 区(设备 → 控制器)的 0 拷贝映射:这是过程映像唯一可写方向,改 *p 指向的字节即改进程映像,内核每周期原样取走(不按脏标记门控),无需任何提交调用。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • PNS_ERR_INVALID_PARAMpnNULL
  • PNS_ERR_NO_STACK — 会话未 Start 或已 Stop,文案:服务未 Start 或已 Stop: 过程映像/钉页不可用 (禁止 overlay 未运行的过程映像), 请先 PnSServiceStart
  • PNS_ERR_NATIVE — 过程映像不可用(GlobalIO 未映射, 无法进行过程数据 IO (SDK IO 禁 HTTP)),或映射自身失败,文案:过程数据输出映像不可用
  • 失败时 *p = NULL*n = 0
  • 净荷窗口 = 输出区 + PPM 槽 1 固定前缀(+3,是 0 长子模块的 IOPS 状态字节,不是槽 1 净荷,固定 3 字节不扫描),与 PnSServiceWriteArea / PnSProcessDataWriteStruct 的写入落点逐字节一致;*n 是剥前缀后的净荷长
  • 并发契约:读可并发;写请调用方自行串行化;同一映像同一时刻只允许一个可写映射
  • 多字节字段写宿主 int16_t / int32_t / float 前须自行转大端(或改用 PnSProcessDataWriteStruct / 按偏移的 PnSProcessDataOut* 一族)

示例:

volatile uint8_t *q = NULL;
size_t n = 0;
pns_result_t rc = PnSProcessDataOutputsMapping(session, &q, &n);

if (rc == PNS_OK && q != NULL && n >= sizeof(to_plc_t)) {
to_plc_t *outp = (to_plc_t *)q;
outp->ready = 1; /* 改字节即改过程映像,下个周期发出 */
outp->busy = 0;
} else {
printf("映射未就绪: rc=%d (%s)\n", (int)rc, PnSServiceLastError(session));
}

PnSProcessDataInputsMapping()

pns_result_t PnSProcessDataInputsMapping(
pns_service_handle_t session,
volatile uint8_t ** p,
size_t * n);

PnSProcessDataInputsMappingRO 同一页、同一长度、同一失败语义,区别只在出参类型是 volatile uint8_t **(这是既有签名,保留不动)。只读消费(解码 / 判断 / 转发)请用 RO 版本;拿到 volatile 指针不等于 I 区可写 —— 写它静默无效。

返回值:

  • pns_result_t — 与 PnSProcessDataInputsMappingRO 逐条相同

说明:

  • PNS_ERR_INVALID_PARAM / PNS_ERR_NO_STACK / PNS_ERR_NATIVE 的判据与文案同 RO 版本,失败时 *p = NULL*n = 0
  • 两个版本的指针指向同一页的同一偏移,判据也相同:rc == PNS_OK && *p != NULL && *n >= sizeof(T)

示例:

volatile uint8_t *raw = NULL;
size_t n = 0;
if (PnSProcessDataInputsMapping(session, &raw, &n) == PNS_OK && raw != NULL && n >= 2) {
uint16_t sw = (uint16_t)(((uint16_t)raw[0] << 8) | (uint16_t)raw[1]); /* 大端自行拆字节 */
printf("sw=0x%04X\n", sw);
}

pns_shm_i_payload() / pns_shm_q_payload()

pns_result_t pns_shm_i_payload(pns_shm_map_t * map, volatile uint8_t ** p, size_t * n);
pns_result_t pns_shm_q_payload(pns_shm_map_t * map, volatile uint8_t ** p, size_t * n);

直连映射句柄上的 0 拷贝净荷映射,与 PnSProcessDataInputsMappingRO / PnSProcessDataOutputsMapping 同语义同落点(剥前缀算法一致),只是不经过服务会话。起点同样按当次区内容剥出来(I 侧剥 2 个 IOPS + 扫描 0x80,窗口比整区少 2–6 字节;Q 侧固定 +3),判据只看 *n

返回值:

  • pns_result_tPNS_OK 成功;失败(未映射 / 净荷为空)时 *p = NULL*n = 0,此时禁止映射

说明:

  • pns_shm_i_payload 给 I 区净荷(只读方向,控制器每周期整段覆盖);pns_shm_q_payload 给 Q 区净荷(可写方向,改字节即改进程映像,内核不按脏标记门控,无需提交调用)
  • Q 侧净荷窗口同样带那 3 字节固定前缀口径:与 pns_shm_write_to_plc 的落点同一偏移
  • *n剥前缀后的净荷字节数,比 pns_shm_read_from_plc 回的整区长少前缀字节
  • *ppns_shm_unmap 之前有效
  • 结构体请按此视图映射 —— 映射的是线上字节(PROFINET 网络大端),多字节字段当宿主 int / short 用会反序

示例:

#pragma pack(push, 1)
typedef struct { uint8_t present; uint8_t flag; } from_plc_t;
typedef struct { uint8_t ready; uint8_t busy; } to_plc_t;
#pragma pack(pop)

volatile uint8_t *i = NULL, *q = NULL;
size_t ni = 0, nq = 0;
if (pns_shm_i_payload(shm, &i, &ni) == PNS_OK && i != NULL && ni >= sizeof(from_plc_t)
&& pns_shm_q_payload(shm, &q, &nq) == PNS_OK && q != NULL && nq >= sizeof(to_plc_t)) {
const from_plc_t *inp = (const from_plc_t *)i;
to_plc_t *outp = (to_plc_t *)q;
outp->ready = inp->present ? 1 : 0;
}

直接访问(不映射也能用)

不建映射也能取单个值。这一节的口每次调用都拷一段字节出来,不是视图 —— 要"源头变字段自己变"只有上面的整幅映射。

PnSProcessDataIn*() 一族(按偏移读 I)

static inline pns_result_t PnSProcessDataInI16(
pns_service_handle_t session,
uint16_t offset,
int16_t * p_value);

In* 一族:在 I 区(控制器 → 设备)的字节偏移 offset 处读一个定宽标量,多字节一律网络大端解码。偏移即字节偏移,从净荷第 0 字节起算(0 = 映射视图的第 0 字节,不做任何 +1 调整)。

返回值:

  • pns_result_tPNS_OK 成功,值写进 *p_value;其余取值见下

说明:

  • PNS_ERR_INVALID_PARAMp_valueNULL;区域越界(offset + 宽度 超出 I 区,文案 区域越界: 偏移 %u + 长度 %u 超出过程映像区 %u 字节);会话为 NULL
  • PNS_ERR_NO_STACK — 会话未 Start 或已 Stop
  • PNS_ERR_NATIVE — 过程映像不可用(通道未就绪)
  • 失败时 *p_value 不改写,不会留下半解出来的值
  • 不强制 2 / 4 对齐:offset 就是字节偏移,偏移 3 读 I16 合法(代价是跨了两个字节边界)
  • 这是拷贝:每次调用都从共享区取当前值,一端是值不是指针

示例:

int16_t recipe = 0;
if (PnSProcessDataInI16(session, 1, &recipe) == PNS_OK)
printf("recipe=%d\n", recipe); /* 偏移 1 起 2 字节,大端 */
else
printf("读失败: %s\n", PnSServiceLastError(session));

相关结构:

/* 读 I(控制器 → 设备):全部 static inline,逐字取自 pns_process_data.h */
PnSProcessDataInI8 (session, offset, int8_t * v); /* 1 字节 有符号 */
PnSProcessDataInU8 (session, offset, uint8_t * v); /* 1 字节 无符号 */
PnSProcessDataInI16(session, offset, int16_t * v); /* 2 字节 有符号 大端 */
PnSProcessDataInU16(session, offset, uint16_t * v); /* 2 字节 无符号 大端 */
PnSProcessDataInI32(session, offset, int32_t * v); /* 4 字节 有符号 大端 */
PnSProcessDataInU32(session, offset, uint32_t * v); /* 4 字节 无符号 大端 */
PnSProcessDataInI64(session, offset, int64_t * v); /* 8 字节 有符号 大端 */
PnSProcessDataInU64(session, offset, uint64_t * v); /* 8 字节 无符号 大端 */
PnSProcessDataInF32(session, offset, float * v); /* 4 字节 IEEE754 大端 */
PnSProcessDataInF64(session, offset, double * v); /* 8 字节 IEEE754 大端 */
PnSProcessDataInBool(session, offset, bool * v); /* 1 字节 按 bit0 判真:0x00=false, 0x01=true, 0x02/0x80=false */
PnSProcessDataInBit (session, offset, int bit, bool * v); /* 1 字节 的第 bit 位,bit 0..7(0 = LSB),越界 INVALID_PARAM */

PnSProcessDataOut*() 一族(按偏移写 Q)

static inline pns_result_t PnSProcessDataOutI16(
pns_service_handle_t session,
uint16_t offset,
int16_t value);

Out* 一族:把值按定宽大端编好,写到 Q 区(设备 → 控制器)的字节偏移 offset 处。数据当场提交到过程映像共享区,返回 PNS_OK ⟺ 字节已在共享区,没有延迟提交、没有提交调用

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • PNS_ERR_INVALID_PARAM — 区域越界(offset + 宽度 超出 Q 区净荷窗口,文案 区域越界: 偏移 %u + 长度 %u 超出过程映像净荷区 %u 字节);会话为 NULL
  • PNS_ERR_NO_STACK — 会话未 Start 或已 Stop
  • PNS_ERR_NATIVE — 过程映像不可用
  • 越界不静默短写:一个字节都没落进去,如实报 PNS_ERR_INVALID_PARAM,不做"部分覆盖 + 报成功"
  • PnSProcessDataOutBit 是读改写:先读回同字节,只改目标位,其余位保持原值
  • PnSProcessDataOutBooltrue = 0x01false = 0x00(整字节,且只产生规范值)

示例:

if (PnSProcessDataOutI16(session, 0, 123) == PNS_OK)   /* Q 偏移 0 起 2 字节,大端 */
printf("已提交\n");

PnSProcessDataOutBit(session, 2, 0, true); /* Q 偏移 2 的第 0 位:只改这一位 */

相关结构:

/* 写 Q(设备 → 控制器):全部 static inline,逐字取自 pns_process_data.h */
PnSProcessDataOutI8 (session, offset, int8_t v); /* 1 字节 有符号 */
PnSProcessDataOutU8 (session, offset, uint8_t v); /* 1 字节 无符号 */
PnSProcessDataOutI16(session, offset, int16_t v); /* 2 字节 有符号 大端 */
PnSProcessDataOutU16(session, offset, uint16_t v); /* 2 字节 无符号 大端 */
PnSProcessDataOutI32(session, offset, int32_t v); /* 4 字节 有符号 大端 */
PnSProcessDataOutU32(session, offset, uint32_t v); /* 4 字节 无符号 大端 */
PnSProcessDataOutI64(session, offset, int64_t v); /* 8 字节 有符号 大端 */
PnSProcessDataOutU64(session, offset, uint64_t v); /* 8 字节 无符号 大端 */
PnSProcessDataOutF32(session, offset, float v); /* 4 字节 IEEE754 大端 */
PnSProcessDataOutF64(session, offset, double v); /* 8 字节 IEEE754 大端 */
PnSProcessDataOutBool(session, offset, bool v); /* 1 字节 整字节覆盖:true=0x01 / false=0x00 */
PnSProcessDataOutBit (session, offset, int bit, bool v); /* 读改写同字节第 bit 位(0..7),其余位不变 */

PnSProcessDataCopyInputsTo() / PnSProcessDataCopyToOutputs()

static inline pns_result_t PnSProcessDataCopyInputsTo(
pns_service_handle_t session,
uint8_t * dest,
uint16_t dest_len,
uint16_t * copied);
static inline pns_result_t PnSProcessDataCopyToOutputs(
pns_service_handle_t session,
const uint8_t * src,
uint16_t src_len,
uint16_t * written);

I 区整区 → 你的缓冲(快照拷贝),Q 区从偏移 0 起短写(允许短于整区)。两条都是快照,不是映射。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • CopyInputsTocopiedNULLPNS_ERR_INVALID_PARAMdestNULL 只查长度(*copied 回写 I 整区长,返回 PNS_OK);dest_len < 需要的长度PNS_ERR_INVALID_PARAM*copied 已回写所需长度,不半帧拷贝
  • CopyToOutputswrittenNULLsrcNULLsrc_len == 0PNS_ERR_INVALID_PARAM*written 先被清 0);成功时 *written = src_len
  • 两条都会把底层的过程映像判据透出来:会话未 Start / 已 Stop → PNS_ERR_NO_STACK;通道未就绪 → PNS_ERR_NATIVE
  • 拷出来的缓冲是你的内存:拷完之后不跟着总线变,可以慢慢解析、跨线程传阅;要"多字段同一拍自洽"时这是首选来源

示例:

uint8_t i_buf[64];
uint16_t copied = 0;
if (PnSProcessDataCopyInputsTo(session, i_buf, sizeof(i_buf), &copied) == PNS_OK)
printf("这一拍 %u 字节\n", copied);
else
printf("需要 %u 字节的缓冲\n", copied); /* 缓冲过小时 copied 是所需长度 */

uint8_t q_head[2] = { 0x01, 0x00 };
uint16_t written = 0;
PnSProcessDataCopyToOutputs(session, q_head, sizeof(q_head), &written); /* 短写 Q 前 2 字节 */

PnSProcessDataReadStruct() / PnSProcessDataWriteStruct()

static inline pns_result_t PnSProcessDataReadStruct(
pns_service_handle_t session,
void * dest,
uint16_t len);
static inline pns_result_t PnSProcessDataWriteStruct(
pns_service_handle_t session,
const void * src,
uint16_t len);

把 I 区前 len 字节拷进一个打包结构体,或把一个打包结构体写到 Q 区前 len 字节。快照拷贝,不是映射 —— 名字里的 Struct 指"打包结构体",不指活视图。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • dest / srcNULLlen == 0PNS_ERR_INVALID_PARAM
  • 越界、未 Start、通道未就绪的码与文案同按偏移一族(区域越界: …PNS_ERR_NO_STACKPNS_ERR_NATIVE
  • 拷出来的是线上字节:结构体里的多字节字段仍是大端,要宿主数值请自己按大端解码(或用 PnSProcessDataIn* 一族)
  • 写侧是允许短写的整段覆盖:len 可以小于 Q 区整长,只覆盖前 len 字节

示例:

#pragma pack(push, 1)
typedef struct { uint8_t present; int16_t recipe_be; uint8_t flag; } from_plc_t;
#pragma pack(pop)

from_plc_t snap;
if (PnSProcessDataReadStruct(session, &snap, (uint16_t)sizeof snap) == PNS_OK) {
const uint8_t *rb = (const uint8_t *)&snap.recipe_be;
int16_t recipe = (int16_t)(((uint16_t)rb[0] << 8) | (uint16_t)rb[1]); /* 自行按大端解码 */
printf("present=%u recipe=%d\n", snap.present, recipe);
}

to_plc_t out = { 1, 0 };
PnSProcessDataWriteStruct(session, &out, (uint16_t)sizeof out); /* 覆盖 Q 区前 2 字节 */

PnSProcessDataItemIn() / PnSProcessDataItemOut()

static inline pns_process_data_item_t PnSProcessDataItemIn(pns_service_handle_t session, uint16_t offset);
static inline pns_process_data_item_t PnSProcessDataItemOut(pns_service_handle_t session, uint16_t offset);

取一个"项":把(会话,偏移,方向)三样打包成一个值。In 项是 I 区只读项(控制器 → 设备),Out 项是 Q 区可读影子 + 可写项(设备 → 控制器)。项与 PnSProcessDataInU16(session, offset, …) 同一坐标:字节 0 = 净荷第 0 字节,不做任何 +1 调整。

返回值:

  • pns_process_data_item_t — 按值返回的项(session / offset / writable),不失败:参数合不合法要等真正读写时才判

说明:

  • 取项本身不做任何 IO,也不碰会话;sessionNULL 也能取到项,随后读写时如实返回 PNS_ERR_INVALID_PARAM
  • 不持有过程映像、也不缓存值:每次读都是"读那一刻"的当前值,每次写都是当场提交
  • 项是拷贝入口,不是视图(0 拷贝只有整幅映射那一条路)

示例:

pns_process_data_item_t item = PnSProcessDataItemIn(session, 1);   /* I 区偏移 1,只读 */
uint16_t v = 0;
if (PnSProcessDataItemReadU16(&item, &v) == PNS_OK)
printf("v=%u\n", v);

相关结构:

typedef struct pns_process_data_item
{
pns_service_handle_t session; /* 会话句柄(可为 NULL,读写时如实报错) */
uint16_t offset; /* 净荷字节偏移:0 = In[0] / Out[0] */
bool writable; /* false = In 项(I 区只读)/ true = Out 项(Q 区可读影子 + 可写) */
} pns_process_data_item_t;

pns_process_data_item_read() / pns_process_data_item_write()

static inline pns_result_t pns_process_data_item_read(const pns_process_data_item_t * item, uint16_t n, uint8_t * buf);
static inline pns_result_t pns_process_data_item_write(const pns_process_data_item_t * item, const uint8_t * buf, uint16_t n);

项上整段读 / 写 n 字节:In 项读 I 区,Out 项读 Q 影子(你最近写进去的那份,不是控制器回执),写Out。这是同一族里唯一按字节数工作的两个口(其余都是定宽标量)。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • item / bufNULLn == 0PNS_ERR_INVALID_PARAM(先判参数,再碰会话)
  • In 项调用写 → PNS_ERR_NOT_SUPPORTED(I 区由控制器每周期整段覆盖,写它无效,不静默当成功)
  • 越界、未 Start、通道未就绪:码与文案同底层整区口(区域越界: … / PNS_ERR_NO_STACK / PNS_ERR_NATIVE
  • 写是当场提交,返回 PNS_OK ⟺ 字节已在共享区
  • 只要按偏移读 Q 影子、不建项时用 pns_process_data_read_q(session, offset, n, buf)(与 pns_process_data_read_i 同形,区域换成输出区;Out 项读回走的就是这一条)

示例:

uint8_t raw[2] = { 0 };
if (pns_process_data_item_read(&item, 2, raw) == PNS_OK)
printf("raw=0x%02X%02X\n", raw[0], raw[1]); /* 大端:高字节在前 */

if (pns_process_data_item_write(&out_item, raw, 2) == PNS_OK) /* Out 项:当场提交 */
printf("Q 偏移 0 起 2 字节已提交\n");
if (pns_process_data_item_write(&item, raw, 2) == PNS_ERR_NOT_SUPPORTED) /* In 项:I 区只读 */
printf("In 项不可写\n");

PnSProcessDataItemRead*() 一族

static inline pns_result_t PnSProcessDataItemReadU16(
const pns_process_data_item_t * item,
uint16_t * p_value);

项上按定宽读一个标量,多字节大端解码,In / Out 项都能读(Out 项读的是 Q 影子,写完当场读回即同值)。

返回值:

  • pns_result_tPNS_OK 成功,值写进 *p_value;其余取值见下

说明:

  • itemp_valueNULLPNS_ERR_INVALID_PARAM
  • 越界(offset + 宽度 超出该方向窗口)→ PNS_ERR_INVALID_PARAM,文案 区域越界: 偏移 %u + 长度 %u 超出过程映像区 %u 字节
  • 会话未 Start / 已 Stop → PNS_ERR_NO_STACK;通道未就绪 → PNS_ERR_NATIVE
  • 失败时 *p_value 不改写
  • ReadBoolBit0bit0 判真:0x00false0x01true、非规范字节 0x02 / 0x80false。要按整字节 0 / 非 0 判真假,用 PnSProcessDataItemReadU8 取原始字节自己比
  • ReadBitbit0..7(0 = LSB),越界在碰会话之前就返回 PNS_ERR_INVALID_PARAM

示例:

int16_t recipe = 0;
if (PnSProcessDataItemReadI16(&item, &recipe) == PNS_OK)
printf("recipe=%d\n", recipe);

bool flag = false;
if (PnSProcessDataItemReadBoolBit0(&item, &flag) == PNS_OK)
printf("bit0=%s\n", flag ? "true" : "false"); /* 0x02 / 0x80 也是 false */

bool b3 = false;
if (PnSProcessDataItemReadBit(&item, 8, &b3) == PNS_ERR_INVALID_PARAM)
printf("位号必须在 0..7\n");

相关结构:

/* 项上读(In 项读 I / Out 项读 Q 影子):static inline,逐字取自 pns_process_data.h */
PnSProcessDataItemReadU8 (item, uint8_t * v); /* 1 字节 无符号 */
PnSProcessDataItemReadI8 (item, int8_t * v); /* 1 字节 有符号 */
PnSProcessDataItemReadU16(item, uint16_t * v); /* 2 字节 无符号 大端 */
PnSProcessDataItemReadI16(item, int16_t * v); /* 2 字节 有符号 大端 */
PnSProcessDataItemReadU32(item, uint32_t * v); /* 4 字节 无符号 大端 */
PnSProcessDataItemReadI32(item, int32_t * v); /* 4 字节 有符号 大端 */
PnSProcessDataItemReadU64(item, uint64_t * v); /* 8 字节 无符号 大端 */
PnSProcessDataItemReadI64(item, int64_t * v); /* 8 字节 有符号 大端 */
PnSProcessDataItemReadF32(item, float * v); /* 4 字节 IEEE754 大端 */
PnSProcessDataItemReadF64(item, double * v); /* 8 字节 IEEE754 大端 */
PnSProcessDataItemReadBoolBit0(item, bool * v); /* 1 字节 bit0 判真:0x00=false, 0x01=true, 0x02/0x80=false */
PnSProcessDataItemReadBit(item, int bit, bool * v); /* 1 字节 的第 bit 位(0..7,0 = LSB) */

PnSProcessDataItemWrite*() 一族

static inline pns_result_t PnSProcessDataItemWriteU16(
const pns_process_data_item_t * item,
uint16_t value);

项上按定宽写一个标量,多字节大端编码,OutIn 项一律返回 PNS_ERR_NOT_SUPPORTED,一个字节都不写。写完当场提交到共享区。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • In 项 → PNS_ERR_NOT_SUPPORTED(判方向在碰会话之前,不做无谓读)
  • itemNULLPNS_ERR_INVALID_PARAM
  • 越界 → PNS_ERR_INVALID_PARAM,文案 区域越界: 偏移 %u + 长度 %u 超出过程映像净荷区 %u 字节不静默短写
  • 会话未 Start / 已 Stop → PNS_ERR_NO_STACK;通道未就绪 → PNS_ERR_NATIVE
  • WriteBoolBit0 是整字节覆盖,且只产生规范值:true = 0x01false = 0x00
  • WriteBit 是读改写:只改目标位,同字节其余位保持原值;bit 越界 → PNS_ERR_INVALID_PARAM

示例:

if (PnSProcessDataItemWriteU16(&out_item, 123) == PNS_OK)
printf("已提交 Q 偏移 0\n");

uint16_t back = 0;
PnSProcessDataItemReadU16(&out_item, &back); /* Q 影子读回:写完当场读即同值 */
printf("读回 %u\n", back);

if (PnSProcessDataItemWriteBit(&out_item, 3, true) == PNS_OK) /* 只置第 3 位,其余位不动 */
printf("已置第 3 位\n");

相关结构:

/* 项上写(仅 Out 项;In 项 → PNS_ERR_NOT_SUPPORTED):static inline,逐字取自 pns_process_data.h */
PnSProcessDataItemWriteU8 (item, uint8_t v); /* 1 字节 无符号 */
PnSProcessDataItemWriteI8 (item, int8_t v); /* 1 字节 有符号 */
PnSProcessDataItemWriteU16(item, uint16_t v); /* 2 字节 无符号 大端 */
PnSProcessDataItemWriteI16(item, int16_t v); /* 2 字节 有符号 大端 */
PnSProcessDataItemWriteU32(item, uint32_t v); /* 4 字节 无符号 大端 */
PnSProcessDataItemWriteI32(item, int32_t v); /* 4 字节 有符号 大端 */
PnSProcessDataItemWriteU64(item, uint64_t v); /* 8 字节 无符号 大端 */
PnSProcessDataItemWriteI64(item, int64_t v); /* 8 字节 有符号 大端 */
PnSProcessDataItemWriteF32(item, float v); /* 4 字节 IEEE754 大端 */
PnSProcessDataItemWriteF64(item, double v); /* 8 字节 IEEE754 大端 */
PnSProcessDataItemWriteBoolBit0(item, bool v); /* 1 字节 整字节覆盖:true=0x01 / false=0x00 */
PnSProcessDataItemWriteBit(item, int bit, bool v); /* 读改写同字节第 bit 位(0..7),其余位不变 */

按地址读写(区域 + 字节 + 位)

不想自己算偏移、想照着工程软件里的地址写时用这一族:区域字母 + 字节偏移 + 位

地址写法是什么读写口
I0.0 / Q0.0位地址:第 0 字节的第 0 位,位号只能 0–7PnSServiceReadBool / PnSServiceWriteBool
IB0 / QB0字节(区域后可省类型字符:I0 与 IB0 同义)PnSServiceReadBytes / PnSServiceWriteBytes
IW2 / QW2字:2 字节,大端PnSServiceReadInt16 / PnSServiceWriteInt16
ID4 / QD4双字:4 字节,大端PnSServiceReadInt32 / PnSServiceWriteInt32
  • 不强制 2 / 4 对齐IW3 / ID2 都合法,偏移就是字节偏移,字 / 双字不会替你补齐。
  • 位地址只落在该偏移的这一个字节:位号 0..7(0 = LSB),要跨字节定位就自己把字节偏移加上去。
  • 不支持 M 区 / DB 区M0.0 / DB1.DBW0 一律解析失败返回 PNS_ERR_INVALID_PARAM,不做映射、不猜。
  • I 区只写不支持:写 I 侧返回 PNS_ERR_NOT_SUPPORTED,不静默丢。
  • 大小写不敏感(i0.0 / q1.2 等价),地址前导空格 / 制表符会被跳过。
  • 偏移是十进制,最大 0xFFFF;超过即解析失败。
  • 失败时具体文案读 PnSServiceLastError(session)(例如 地址越界: 偏移 4 + 长度 2 超出过程映像区 20 字节):错误码只区分大类,越界 / 语法 / 方向这些细节都在文案里。
  • Q 区的读回是本地影子值(你最近写进去的那份,不是控制器的回执);I 区的读是控制器发来的当前值。

PnSServiceParseAddress()

pns_result_t PnSServiceParseAddress(
const char * text,
pns_service_address_t * p_out);

只解析地址字符串,不碰过程映像:把 I0.0 / IB0 / IW2 / ID4 / Q… 解析成结构化的区域 + 偏移 + 位 + 宽度。想让自己的代码先校验地址、或者要把地址算成字节偏移时用它。

返回值:

  • pns_result_tPNS_OK 成功(p_out 已填好);PNS_ERR_INVALID_PARAM 地址非法

说明:

  • PNS_ERR_INVALID_PARAM 的触发面:text / p_outNULL;空串;DB 开头;区域字母不是 I / QM 也不支持);位号不在 0..7;偏移不是十进制或超过 0xFFFF;位地址缺偏移(如 I.0
  • 成功时 slot / subslot 恒为 1,bit-1 表示非位寻址;length 是宽度,位地址 = 1(跨 1 字节),B/无类型字符 = 1,W = 2,D = 4
  • 解析成功不代表一定能读:越界要等真去读写时才报

示例:

pns_service_address_t addr;
if (PnSServiceParseAddress("IW3", &addr) == PNS_OK) {
printf("area=%d offset=%u length=%d bit=%d\n",
(int)addr.area, addr.byte_offset, addr.length, addr.bit); /* 不强制 2 字节对齐 */
}
if (PnSServiceParseAddress("M0.0", &addr) != PNS_OK)
printf("M 区不支持\n");

相关结构:

typedef enum pns_service_area
{
PNS_SERVICE_AREA_UNKNOWN = 0,
PNS_SERVICE_AREA_INPUT = 1, /* I 区: 只读 (控制器 -> 设备) */
PNS_SERVICE_AREA_OUTPUT = 2, /* Q 区: 只写 (设备 -> 控制器) */
PNS_SERVICE_AREA_MEMORY = 3, /* M 区: 不支持 (解析失败, 不映射) */
PNS_SERVICE_AREA_DB = 4, /* DB 区: 不支持 (解析失败, 不映射) */
} pns_service_area_t;

typedef struct pns_service_address
{
pns_service_area_t area; /* 区域 */
uint16_t slot; /* 槽位号 (DB 编号; I/Q/M 区固定 1) */
uint16_t subslot; /* 子槽位号 (固定 1) */
uint16_t byte_offset; /* 字节偏移 */
int bit; /* 位号 (0-7); -1 = 非位寻址 */
int length; /* 数据宽度 (位 = 1 字节跨度, 字节 = 1, 字 = 2, 双字 = 4) */
} pns_service_address_t;

PnSServiceReadBool() / PnSServiceWriteBool()

pns_result_t PnSServiceReadBool(
pns_service_handle_t session,
const char * address,
bool * p_value);
pns_result_t PnSServiceWriteBool(
pns_service_handle_t session,
const char * address,
bool value);

读 / 写位地址(I0.0 / Q0.0)。读 I 区是控制器发来的当前值,读 Q 区读回你最近写入的本机影子值。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • PNS_ERR_INVALID_PARAMp_valueNULL(读);地址非法(含 M / DB、位号越界、缺位号 —— 例如 IB0 没有位号,读位会报参数非法);地址越界(文案 地址越界: 偏移 %u + 长度 %d 超出过程映像区 %u 字节
  • PNS_ERR_NOT_SUPPORTED — 写 I 侧(I0.0 这类)
  • PNS_ERR_NO_STACK — 会话未 Start 或已 Stop
  • PNS_ERR_NATIVE — 过程数据通道未就绪
  • 写是读改写:先读回该字节、只翻目标位、再整字节写回,同字节其它位保持不变
  • 读失败时 *p_value 先被置 false,不会留下旧值

示例:

bool done = false;
if (PnSServiceReadBool(session, "I0.0", &done) == PNS_OK)
printf("done=%d\n", done);

if (PnSServiceWriteBool(session, "Q0.0", true) == PNS_OK)
printf("Q0.0 已置位\n");
if (PnSServiceWriteBool(session, "I0.0", true) == PNS_ERR_NOT_SUPPORTED)
printf("I 区只读\n");

PnSServiceReadInt16() / PnSServiceWriteInt16()

pns_result_t PnSServiceReadInt16(
pns_service_handle_t session,
const char * address,
int16_t * p_value);
pns_result_t PnSServiceWriteInt16(
pns_service_handle_t session,
const char * address,
int16_t value);

按字地址(IW2 / QW2)读 / 写 2 字节整数,大端

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • 地址必须是 W 形式且非位寻址IB0(1 字节)或 I0.0(位地址)一律 PNS_ERR_INVALID_PARAMIW3 合法(不强制 2 字节对齐)
  • 越界(offset + 2 超出该方向)→ PNS_ERR_INVALID_PARAM,文案 地址越界: 偏移 %u + 长度 %d 超出过程映像区 %u 字节
  • 写 I 侧 → PNS_ERR_NOT_SUPPORTED;未 Start / 已 Stop → PNS_ERR_NO_STACK;通道未就绪 → PNS_ERR_NATIVE
  • 读失败时 *p_value 先被置 0
  • 写是整 2 字节覆盖,编码为大端(不在局部做读改写)

示例:

int16_t recipe = 0;
if (PnSServiceReadInt16(session, "IW2", &recipe) == PNS_OK)
printf("recipe=%d\n", recipe);

PnSServiceWriteInt16(session, "QW2", 123); /* 大端落到 Q 偏移 2 */

PnSServiceReadInt32() / PnSServiceWriteInt32()

pns_result_t PnSServiceReadInt32(
pns_service_handle_t session,
const char * address,
int32_t * p_value);
pns_result_t PnSServiceWriteInt32(
pns_service_handle_t session,
const char * address,
int32_t value);

按双字地址(ID4 / QD4)读 / 写 4 字节整数,大端

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • 地址必须是 D 形式且非位寻址IW2 / I0.0 一律 PNS_ERR_INVALID_PARAMID2 合法(不强制 4 字节对齐)
  • 失败码与文案同 PnSServiceReadInt16地址越界: … / PNS_ERR_NOT_SUPPORTED / PNS_ERR_NO_STACK / PNS_ERR_NATIVE
  • 读失败时 *p_value 先被置 0

示例:

int32_t counter = 0;
if (PnSServiceReadInt32(session, "ID4", &counter) == PNS_OK)
printf("counter=%d\n", counter);

PnSServiceWriteInt32(session, "QD4", 10000);

PnSServiceReadBytes() / PnSServiceWriteBytes()

pns_result_t PnSServiceReadBytes(
pns_service_handle_t session,
const pns_service_address_t * p_addr,
uint8_t * p_data);
pns_result_t PnSServiceWriteBytes(
pns_service_handle_t session,
const pns_service_address_t * p_addr,
const uint8_t * p_data);

按解析好的地址结构读写原始字节 —— 上面那几个地址化口内部走的就是这两个。要自己拿 PnSServiceParseAddress 的结果做批量判断,或者地址是拼出来的,可以直接用它们。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • 读:session / p_addr / p_dataNULLPNS_ERR_INVALID_PARAM;区域不是 I / QPNS_ERR_INVALID_PARAMlength 不在 1..4PNS_ERR_INVALID_PARAM;越界 → PNS_ERR_INVALID_PARAM,文案 地址越界: 偏移 %u + 长度 %d 超出过程映像区 %u 字节
  • 读:会话未 Start → PNS_ERR_NO_STACK(文案 服务未 Start 或已 Stop: …);通道未就绪 → PNS_ERR_NATIVE
  • 写:区域为 IPNS_ERR_NOT_SUPPORTED;区域不是 QPNS_ERR_INVALID_PARAM;未 Start → PNS_ERR_NO_STACKlength 不在 1..4PNS_ERR_INVALID_PARAM
  • 写越界按净荷窗口核对,文案 地址越界: 偏移 %u + 长度 %d 超出过程映像净荷区 %u 字节不做静默短写冒充成功
  • 两条都不会替你判地址语义:p_addrarea 直接决定读哪边写哪边,别拿 pns_service_address_t 手工拼出 area = PNS_SERVICE_AREA_MEMORY 再用

示例:

pns_service_address_t addr;
uint8_t word[2] = { 0 };

if (PnSServiceParseAddress("QW2", &addr) == PNS_OK
&& PnSServiceReadBytes(session, &addr, word) == PNS_OK) /* Q 影子读回 */
printf("QW2 当前值 = 0x%02X%02X\n", word[0], word[1]);

word[0] = 0x00; word[1] = 0x7B; /* 123,大端 */
PnSServiceWriteBytes(session, &addr, word);

整区入口(整段 / 短写)

PnSServiceRead()

pns_result_t PnSServiceRead(
pns_service_handle_t session,
uint8_t * p_data,
uint16_t cap,
uint16_t * p_len);

读 I 区整段(控制器 → 设备)到你的缓冲 —— 一次调用给出同一拍自洽的一份字节。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • p_lenNULLPNS_ERR_INVALID_PARAM
  • p_dataNULL / cap0 只查长度:*p_len 回写 I 整区长,返回 PNS_OK
  • 缓冲过小 → PNS_ERR_INVALID_PARAM*p_len 回写所需长度,不半帧拷贝
  • 会话未 Start / 已 Stop → PNS_ERR_NO_STACK;通道未就绪 → PNS_ERR_NATIVE
  • 长度是该方向的整区长,比映射出来的净荷长多前缀字节(映射那侧的 *n 已剥前缀);长度由运行配置决定,不要写死 20 / 32

示例:

uint8_t i_buf[64];
uint16_t n = sizeof(i_buf);
if (PnSServiceRead(session, i_buf, sizeof(i_buf), &n) == PNS_OK)
printf("这一拍 %u 字节\n", n);
else
printf("需要 %u 字节的缓冲\n", n); /* 长度已回写 */

uint16_t need = 0;
PnSServiceRead(session, NULL, 0, &need); /* 只查长度 */

PnSServiceWrite()

pns_result_t PnSServiceWrite(
pns_service_handle_t session,
const uint8_t * p_data,
uint16_t length);

从偏移 0 起短写 Q 区(设备 → 控制器):length 允许短于整区,只覆盖前 length 字节。当场提交到共享区,返回 PNS_OK ⟺ 字节已在共享区。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • p_dataNULLlength == 0PNS_ERR_INVALID_PARAM(空数据不当成功)
  • 越界(length 超出 Q 区净荷窗口)→ PNS_ERR_INVALID_PARAM,文案 区域越界: 偏移 0 + 长度 %u 超出过程映像净荷区 %u 字节
  • 会话未 Start / 已 Stop → PNS_ERR_NO_STACK;通道未就绪 → PNS_ERR_NATIVE
  • 短写只覆盖前缀,不动的尾部字节保持原值(不是整区清零)

示例:

uint8_t q[2] = { 0x01, 0x00 };
if (PnSServiceWrite(session, q, sizeof(q)) == PNS_OK)
printf("Q 前 2 字节已提交\n");

PnSServiceReadArea()

pns_result_t PnSServiceReadArea(
pns_service_handle_t session,
pns_service_area_t area,
uint16_t offset,
uint16_t length,
uint16_t * p_len, uint8_t * p_data);

区域 + 偏移 + 长度整段读:PNS_SERVICE_AREA_INPUT 读 I,PNS_SERVICE_AREA_OUTPUT 读 Q 影子。不知道净荷有多长、只想要中间一段时用它。

返回值:

  • pns_result_tPNS_OK 成功,*p_len = 实际字节数;其余取值见下

说明:

  • session / p_len / p_dataNULLPNS_ERR_INVALID_PARAM
  • area 不是 PNS_SERVICE_AREA_INPUT / PNS_SERVICE_AREA_OUTPUT(含 M / DB)→ PNS_ERR_INVALID_PARAM
  • length == 0*p_len = 0 且返回 PNS_OK(空读直接成功)
  • 会话未 Start / 已 Stop → PNS_ERR_NO_STACK
  • 越界 → PNS_ERR_INVALID_PARAM,文案 区域越界: 偏移 %u + 长度 %u 超出过程映像区 %u 字节*p_len 置 0
  • 缓冲过小 → PNS_ERR_INVALID_PARAM*p_len 回写所需长度(不半帧拷贝
  • 每次直读共享区当前值

示例:

uint8_t part[2]; uint16_t pn = sizeof(part);
if (PnSServiceReadArea(session, PNS_SERVICE_AREA_INPUT, 4, 2, &pn, part) == PNS_OK)
printf("I 偏移 4 起 2 字节:0x%02X%02X\n", part[0], part[1]);

PnSServiceWriteArea()

pns_result_t PnSServiceWriteArea(
pns_service_handle_t session,
pns_service_area_t area,
uint16_t offset,
const uint8_t * p_data,
uint16_t length);

区域 + 偏移 + 长度部分覆盖写 Q 区。这是"我自己合并好了一段、一次写进去"的入口。当场提交,返回 PNS_OK ⟺ 字节已在共享区。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • 写 I 区(PNS_SERVICE_AREA_INPUT)→ PNS_ERR_NOT_SUPPORTED(I 区只读)
  • area 不是 PNS_SERVICE_AREA_INPUT / PNS_SERVICE_AREA_OUTPUTPNS_ERR_INVALID_PARAMM / DB 不支持)
  • session / p_dataNULLPNS_ERR_INVALID_PARAM
  • length == 0PNS_ERR_INVALID_PARAM(空数据)
  • 会话未 Start / 已 Stop → PNS_ERR_NO_STACK
  • 越界 → PNS_ERR_INVALID_PARAM,文案 区域越界: 偏移 %u + 长度 %u 超出过程映像净荷区 %u 字节不靠底层截断兜底(截断 + 报成功会让人以为整段已落共享区)
  • 一次只写一段连续区域;不相邻的改动请自己合并成一次写,或分多次调(每次调用都是一次提交)

示例:

uint8_t q[2] = { 0x00, 0x7B };
if (PnSServiceWriteArea(session, PNS_SERVICE_AREA_OUTPUT, 2, q, sizeof(q)) == PNS_OK)
printf("Q 偏移 2 起 2 字节已提交\n");

if (PnSServiceWriteArea(session, PNS_SERVICE_AREA_INPUT, 0, q, sizeof(q)) == PNS_ERR_NOT_SUPPORTED)
printf("I 区只读\n");

PnSServiceGetIo()

pns_result_t PnSServiceGetIo(
pns_service_handle_t session,
uint16_t * p_input_len, uint8_t * p_input,
uint16_t * p_output_len, uint8_t * p_output);

一次把两个方向都取回本机缓冲。不想分两次调用时用它,也可以只取长度。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • 出参名与承载区域对着看:第 1 组 p_input_len / p_input 装的是服务端输出区 = 地址化 Q(设备 → 控制器);第 3 组 p_output_len / p_output 装的是服务端输入区 = 地址化 I(控制器 → 设备)PnSServiceRead 读 I 走的就是第 3 组出参
  • 两组出参都可传 NULL 表示"这一组不要";但只给缓冲不给容量(p_input_len == NULL && p_input != NULL,或输出组同理)→ PNS_ERR_INVALID_PARAM
  • p_*_len 入 = 缓冲容量,出 = 实际字节数;容量为 0 只查长度
  • 某一组缓冲过小 → PNS_ERR_INVALID_PARAM*p_*_len 回写所需长度,文案 过程映像 Q 缓冲过小: 需要 %u 字节, 容量 %u / 过程映像 I 缓冲过小: 需要 %u 字节, 容量 %u不半帧拷贝
  • 会话未 Start / 已 Stop → PNS_ERR_NO_STACK;通道未就绪 → PNS_ERR_NATIVE
  • 长度不要写死 20 / 32

示例:

uint8_t i[64], q[64]; uint16_t ilen = sizeof(i), qlen = sizeof(q);
if (PnSServiceGetIo(session, &qlen, q, &ilen, i) == PNS_OK) /* 第 1 组 = Q,第 3 组 = I */
printf("Q %u 字节 / I %u 字节\n", qlen, ilen);

uint16_t q_need = 0, i_need = 0;
PnSServiceGetIo(session, &q_need, NULL, &i_need, NULL); /* 只查两边长度 */

共享区直连(同一份过程映像的另一条路)

pns_shm_* 一族直接读写过程映像,不必建服务会话。判据(未映射 / 未连接)与失败语义与上面那条路一致,区别在于它多一个"允许短写"的写入行为。

pns_shm_read_from_plc() / pns_shm_read_to_plc_shadow()

pns_result_t pns_shm_read_from_plc(pns_shm_map_t * map,
uint8_t * buf, size_t buf_cap, size_t * p_len);
pns_result_t pns_shm_read_to_plc_shadow(pns_shm_map_t * map,
uint8_t * buf, size_t buf_cap, size_t * p_len);

读输入区(I,控制器 → 设备)与读输出区影子(Q,你最近写进去的那份)。

返回值:

  • pns_result_tPNS_OK 成功;PNS_ERR_INVALID_PARAM 缓冲容量不足(*p_len 已回写所需长度);PNS_ERR_NO_STACK 未映射

说明:

  • 已剥离线上控制字前缀;pns_shm_read_from_plc 返回长度恒 = 输入区长(剥前缀前):净荷前置、尾部补 0(真零数据返回同长全 0,不假装没数据)
  • pns_shm_read_to_plc_shadowpns_shm_write_to_plc 对称:写什么读回什么;输出区尚未就绪时 *p_len = 0 且返回 PNS_OK
  • bufNULL 只查长度(返回 PNS_OK*p_len 仍写回所需长度)
  • 本族的读是快照,可能与运行时并发更新撕裂;要同一拍自洽就一次读够

示例:

uint8_t i_buf[64]; size_t n = 0;
if (pns_shm_read_from_plc(shm, i_buf, sizeof(i_buf), &n) == PNS_OK)
printf("I %zu 字节\n", n);

size_t qn = 0;
pns_shm_read_to_plc_shadow(shm, NULL, 0, &qn); /* 只查影子长度 */

pns_shm_write_to_plc()

pns_result_t pns_shm_write_to_plc(pns_shm_map_t * map,
uint32_t offset,
const uint8_t * data, size_t len);

从净荷偏移 offset 起写 Q 区(设备 → 控制器)。内部先写数据再置提交标志。

返回值:

  • pns_result_tPNS_OK 成功(含短写);PNS_ERR_INVALID_PARAM 参数非法 / dataNULL / 偏移越界 / 一个字节都写不进;PNS_ERR_NO_STACK 未映射

说明:

  • 允许短写:写不下时按可容纳长度截断,仍返回 PNS_OK(实际落盘长度可用 pns_shm_read_to_plc_shadow 复核)。要越界即报错的口,用 PnSServiceWriteArea / PnSProcessDataOut* 一族
  • len == 0 时仅置提交标志,不拷数据
  • 调用方之间同一句柄不保证互斥,多线程写请自行串行化

示例:

uint8_t q[2] = { 0x01, 0x00 };
if (pns_shm_write_to_plc(shm, 0, q, sizeof(q)) == PNS_OK) {
uint8_t back[2]; size_t n = 0;
pns_shm_read_to_plc_shadow(shm, back, sizeof(back), &n); /* 复核实际落盘 */
}

pns_shm_connected() / pns_shm_is_mapped()

int pns_shm_connected(pns_shm_map_t * map);
int pns_shm_is_mapped(const pns_shm_map_t * map);

两个本地判据:映射是否还在,控制器周期数据是否正常。取自运行时看门狗字段,不假绿。

返回值:

  • intpns_shm_connected1 = 控制器周期数据正常;0 = 未连接 / 安全态 / 未映射
  • intpns_shm_is_mapped1 = 已映射;0 = NULL 或未映射

说明:

  • 这两个函数不产生 IO,也不阻塞;周期循环里先判 pns_shm_connected 再决定用数据还是走安全态
  • 未连接时读到的仍是映像里的字节,但它是停止刷新前的旧值,别当当前拍用

示例:

while (running) {
if (!pns_shm_connected(shm)) { /* 未连接 / 安全态 */
Sleep(10); /* 拍间等待自备:Sleep 来自 <windows.h>,本 SDK 头不引入 */
continue;
}
pns_shm_read_from_plc(shm, i_buf, sizeof(i_buf), &n);
/* ... 算 ... */
pns_shm_write_to_plc(shm, 0, q, sizeof(q));
Sleep(10); /* 等待下一拍(同上) */
}

记录索引空间(非周期数据)

非周期数据走记录索引,索引号的空间是分段的:本 SDK 有入口的只有用户区记录与 I&M 两段,这两条路互不覆盖。

索引段是什么本 SDK 走哪
0x0000 – 0x7FFF用户 / 厂商区:控制器写进来、由设备侧保存的应用参数记录PnSServiceGetRecords
0x8000 起标准索引区(期望 / 实际标识、诊断、替代值、端口统计这类标准化索引)本 SDK 不提供通用记录读口;诊断走 PnSServiceGetDiag / PnSServiceGetIoStatus(见诊断
0xAFF0 – 0xAFF4I&M 标识:I&M0(0xAFF0)与 I&M1–4(0xAFF1–0xAFF4)PnSServiceGetIm
  • I&M 不在用户记录口里:用户区记录只收 0x0000–0x7FFF 这一段,I&M(0xAFF0 起)由协议栈自行处理,PnSServiceGetRecords 永远看不到 I&M 索引。要设备标识就走 PnSServiceGetIm
  • 记录这条链是只读观测:控制器写给设备的记录由设备侧接收保存,SDK 负责读出来给你看,没有"从 SDK 写记录给控制器"的入口。
  • 记录与过程数据无关:写记录不会改变周期映像,读记录也不会。

PnSServiceGetRecords()

pns_result_t PnSServiceGetRecords(
pns_service_handle_t session,
pns_service_user_record_t * p_items,
size_t items_cap,
size_t * p_count);

取用户区记录表(索引 0x0000–0x7FFF 那一段),两段式调用。

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • session / p_countNULLPNS_ERR_INVALID_PARAM
  • 只查数量:p_itemsNULLitems_cap 必须为 0p_itemsNULLitems_cap == 0 也非法
  • 实际条数多于容量 → PNS_ERR_INVALID_PARAM,已填充的条目仍然有效,*p_count 回写实际条数,文案 用户区记录缓冲过小: 实际 %u 条, 容量 %u
  • 服务不可达 → PNS_ERR_NO_CONNECTION;服务端错误(非 200)→ PNS_ERR_NATIVE。走的是服务控制面请求,不是过程数据通道
  • 字段缺失按 0 / 空处理,不虚构;data_hex 超长如实截断(NUL 结尾)
  • 字段与逐项契约见诊断(本页只定位索引空间)

示例:

size_t count = 0;
PnSServiceGetRecords(session, NULL, 0, &count); /* 先查条数 */

pns_service_user_record_t recs[8];
if (PnSServiceGetRecords(session, recs, 8, &count) == PNS_OK) {
for (size_t k = 0; k < count; k++)
printf("idx=0x%04X slot=%u len=%u\n", recs[k].index, recs[k].slot, recs[k].length);
}

PnSServiceGetIm()

pns_result_t PnSServiceGetIm(
pns_service_handle_t session,
pns_service_im_info_t * p_im);

取设备标识(I&M0 的生效值 + I&M1–I&M3 字段 + 工程标识字段)—— 这就是 I&M 索引段(0xAFF0–0xAFF4)在本 SDK 里的唯一入口

返回值:

  • pns_result_tPNS_OK 成功;其余取值见下

说明:

  • session / p_imNULLPNS_ERR_INVALID_PARAM
  • 服务不可达 → PNS_ERR_NO_CONNECTION;服务端错误(非 200)→ PNS_ERR_NATIVE
  • I&M1–I&M3 已由原生栈启用并真读(栈运行中 im14_supported = 1im_supported 列出实际启用集):主站没写过时读到的是出厂占位值(I&M1 功能标签 Darra、安装位置 Darra PnS 20I/32O、I&M2 日期、I&M3 描述),不是空串;I&M4 未启用,im4_signature_hex 不承诺有值
  • 栈未运行 / I&M1–4 不可读时这些字段为空串、im14_supported = 0,订货号 / 序列号 / 软硬件版本是原生栈固定默认值镜像 —— 缘由都如实写在 im_note 里,不虚构能力
  • 字段缺失保持零值(空串 / 0),不编;字符串字段 NUL 结尾,超长如实截断
  • 字段清单与逐项含义见诊断

示例:

pns_service_im_info_t im;
if (PnSServiceGetIm(session, &im) == PNS_OK) {
printf("vendor=0x%04X device=0x%04X station=%s\n",
im.vendor_id, im.device_id, im.station_name);
printf("I&M: %s (I&M1-4 supported=%d)\n", im.im_supported, im.im14_supported);
}

字节数怎么拿

长度由运行配置决定,不要写死 20 / 32(改过组态就变)。

想拿走哪
I 整区长度PnSServiceRead(session, NULL, 0, &n)(p_data 传 NULL 只查长度,不拷数据)
Q 净荷窗口长度PnSServiceGetIo(session, &q_need, NULL, NULL, NULL)(第 1 组出参装的是 Q,已剥 3 字节固定前缀,与 Q 映射的 *n 同口径,不是整区长度)
两边一起取PnSServiceGetIo(session, &q_len, q, &i_len, i)(Q 组是净荷窗口、I 组是整区长度),任一出参可传 NULL 只查长度
映射净荷长度映射出参 *n(已剥 IOPS 前缀,比整区长度少前缀字节)
两个方向的配置长度PnSServiceGetDiag 的 input_area_length / output_area_length
共享区直连的长度pns_shm_read_from_plc / pns_shm_read_to_plc_shadow 的 *p_len(buf 传 NULL 只查长度)

判据三缺一就不要拿映射当结构体用:rc == PNS_OK && *p != NULL && *n >= sizeof(T)

通道未就绪时的行为

过程数据通道(共享区过程映像)没就绪时,各口一律如实报错:

  • 会话没 Start / 已经 Stop:PNS_ERR_NO_STACK,文案 服务未 Start 或已 Stop: 过程映像/钉页不可用 (禁止 overlay 未运行的过程映像), 请先 PnSServiceStart
  • 映像没映射 / 净荷为空:PNS_ERR_NATIVE,文案 GlobalIO 未映射, 无法进行过程数据 IO (SDK IO 禁 HTTP)
  • 具体文案读 PnSServiceLastError(session)(进程级兜底 pns_last_error()

IOPS / IOCS 这类质量字节走服务面的 /api/io,与过程数据是两条独立的路径,见诊断

布尔只在最低位

PnSProcessDataInBool / PnSProcessDataItemReadBoolBit0bit0 判真:字节 0x00false0x01true,而 0x020x80 都读 false —— 这不是"非 0 即真",很多人第一次会踩。要按整字节 0 / 非 0 判真假,用 PnSProcessDataInU8 / PnSProcessDataItemReadU8 取原始字节自己比;本 SDK 不提供第二个"非 0 即真"的布尔口。写侧对称:OutBool / WriteBoolBit0 只产生 0x01 / 0x00

别把 "写 I" 当成能用的功能

I 区由控制器每周期整段覆盖,写 I 区的字节静默无效(下一拍被冲掉)。所以:PnSProcessDataInputsMappingRO 给的是 const 指针;PnSServiceWriteArea / PnSServiceWriteBool / PnSProcessDataItemWrite* 碰到 I 侧一律 PNS_ERR_NOT_SUPPORTED。要写过程映像走 Q 区。