N板共享内存集成教程

N板共享内存集成教程

前言

本文演示如何把 N 板的共享内存接口,集成到你自己开发的 C 程序里。你可以在自己的程序中直接读取 N 板输入数据,或者控制 N 板输出,只需要按步骤接入,就能跑通。


一、集成原理与示例目标

有一个后台程序 iolibn,它负责与 N 板硬件通信、采集输入数据、下发输出数据,并把最新的 I/O 数据实时更新到一块共享内存中。你的程序不需要直接处理底层硬件通信,只需要连接这块共享内存,然后从中读取输入数据或向其中写入输出数据即可。

我们用一个简单的示例来说明:控制 Slot1 的 N2162 模块(16路 DO),逐个置位 DO 通道,然后再全部复位。


二、文件结构

本教程采用 头文件 + 接口实现 + 用户程序 的三文件结构,避免每个 .c 文件都重复定义结构体:

makefile 复制代码
your_project/
├── nboard_shm.h      ← 接口头文件(结构体定义 + 函数声明)
├── nboard_shm.c      ← 接口实现(连接函数实现)
└── my_app.c          ← 你的程序(只 include 头文件)
文件 作用 是否可修改
nboard_shm.h 定义结构体、常量、函数声明 不可修改
nboard_shm.c 实现共享内存连接函数 不可修改
my_app.c 你的业务代码 自由修改

三、改造思路

把自己的程序接入 N 板,最小只需要 3 步:

  1. nboard_shm.hnboard_shm.c 放到你的项目目录
  2. 在你的程序中 #include "nboard_shm.h"
  3. 程序启动时调用 nboard_connect(),然后通过 nboard_nodes() 读写数据

第 1 步:引入接口文件

将以下两个文件放到你的项目目录:

nboard_shm.h(接口头文件):

c 复制代码
#ifndef __NBOARD_SHM_H__
#define __NBOARD_SHM_H__

#include <stdint.h>
#include <sys/types.h>
#include <sys/time.h>

#ifdef __cplusplus
extern "C" {
#endif

/* ===== 常量定义(固定值) ===== */
#define MAX_NODE_NUM   33
#define MAX_DATA_NUM   32
#define SUB_IO_NUM     4
#define AIN_CHAN_MAX   8
#define CALIB_MAX      10
#define FILT_NUM       20

/* ===== 设备类型枚举(固定值) ===== */
#define DEV_DO      1
#define DEV_DI      2
#define DEV_AO      3
#define DEV_AI      4
#define DEV_RTD     8
#define DEV_DIO     10

/* ===== 共享内存连接参数(固定值) ===== */
#define NBOARD_SHM_KEY_PATH  "/usr"
#define NBOARD_SHM_KEY_ID    1

/* ===== 结构体定义(固定值,不可修改字段顺序/类型/数量) ===== */

typedef struct calib_cfg {
    float    calib_x, calib_y, calib_k, calib_b;
    uint8_t  used;
    uint8_t  resver[3];
} calib_cfg_t;

typedef struct aio_cfg {
    int   mode, filter;
    float min, max, offset;
    int   cali_falg, count;
} aio_cfg_t;

typedef struct aio_calib_pack {
    calib_cfg_t calib[CALIB_MAX + 1];
} aio_calib_pack_t;

typedef struct aio_cfg_pack {
    aio_cfg_t        aio_cfg_data[AIN_CHAN_MAX];
    aio_calib_pack_t calib_pack[AIN_CHAN_MAX];
} aio_cfg_pack_t;

typedef struct hsc_pwm_cfg {
    uint8_t  edge[4];
    uint32_t freq;
    uint8_t  phase[8];
} hsc_pwm_cfg_t;

typedef struct tc_cfg {
    uint8_t  mode, cj_offset;
    uint16_t cj_reg;
} tc_cfg_t;

typedef struct sub_info {
    uint8_t  io_type, io_num, chan_mode, reg_type, reg_len, data_len;
    uint32_t start_addr, start_addr2, end_addr;
    uint32_t chan_data[MAX_DATA_NUM];
    uint32_t order_data[MAX_DATA_NUM];
    float    set_data[MAX_DATA_NUM];
    int eip_in_start, eip_in_end, eip_out_start, eip_out_end;
    int iec104_baseaddr, iec104_addr[MAX_DATA_NUM];
    int   scale;
    float rtd_cur;
    union {
        hsc_pwm_cfg_t  hsc_pwm;
        tc_cfg_t       tc_cfg[AIN_CHAN_MAX];
        aio_cfg_pack_t aio;
    } cfg;
    int32_t  filter_data[AIN_CHAN_MAX][FILT_NUM];
    uint32_t filter_count[AIN_CHAN_MAX];
    uint32_t filter_start;
} sub_info_t;

typedef struct mode_info {
    uint8_t  io_type, io_num, chan_mode, hw_ver;
    uint8_t  is_reg, is_start, sub_num;
    sub_info_t sub_io[SUB_IO_NUM];
    uint8_t  in_len, out_len;
    int slave_id, slot_id, delay, err_count;
    uint8_t  cfg_set, io_ver, io_status, upgrading, reload_flag;
    char     io_name[32];
    uint8_t  cfg_need, is_last;
    char     written[MAX_DATA_NUM];
    struct timeval start_time;
    int      test_time, take_time;
    char     use_default;
    int cmd_cfg, read_cfg;
} mode_info_t;

/* ===== 函数声明 ===== */

/* 连接N板共享内存,返回 0=成功, -1=失败,整个进程只需调用一次 */
int nboard_connect(void);

/* 获取共享内存节点数组指针 */
mode_info_t *nboard_nodes(void);

#ifdef __cplusplus
}
#endif

#endif /* __NBOARD_SHM_H__ */

nboard_shm.c(接口实现):

c 复制代码
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "nboard_shm.h"

static mode_info_t *__nodes = NULL;

int nboard_connect(void)
{
    key_t key = ftok(NBOARD_SHM_KEY_PATH, NBOARD_SHM_KEY_ID);
    int shmid = shmget(key, MAX_NODE_NUM * sizeof(mode_info_t), 0666);
    if (shmid < 0) {
        perror("nboard_connect: shmget");
        return -1;
    }

    __nodes = (mode_info_t *)shmat(shmid, NULL, 0);
    if ((long)__nodes == -1) {
        perror("nboard_connect: shmat");
        return -1;
    }

    return 0;
}

mode_info_t *nboard_nodes(void)
{
    return __nodes;
}

这两个文件是固定接口文件,不要修改里面的内容,直接复制到你的项目目录即可。


第 2 步:编写你的程序

创建 my_app.c,只需 #include "nboard_shm.h",不需要重复定义结构体:

c 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "nboard_shm.h"

int main(void)
{
    /* 连接共享内存 */
    if (nboard_connect() != 0) {
        printf("连接 N板共享内存失败\n");
        return -1;
    }

    mode_info_t *nodes = nboard_nodes();

    /* 扫描所有在线模块 */
    int slot;
    for (slot = 1; slot < MAX_NODE_NUM; slot++) {
        mode_info_t *m = &nodes[slot];
        if (!m->is_start)
            continue;

        printf("Slot%d: 型号=%s, io_type=%d, sub_num=%d\n",
               slot, m->io_name, m->io_type, m->sub_num);
    }

    return 0;
}

这样程序启动后,就先完成"接入 N 板"的动作,并扫描显示所有在线模块。


第 3 步:编译并运行

bash 复制代码
# 本机编译(如设备为aarch64)
gcc -o my_app my_app.c nboard_shm.c -lm

运行后会看到类似输出:

ini 复制代码
Slot1: 型号=N2162, io_type=1, sub_num=1

第 4 步:在业务逻辑里控制输出

下面把业务逻辑加进去。这个示例演示:扫描所有在线模块,找到 DO 模块后逐个置位,然后复位。

在执行任何写操作前,必须先确认模块类型与写入方式匹配。比如说:

  • 写 DO 前应先确认该槽位确实是 DO 模块
  • 写 AO 前应先确认该槽位确实是 AO 模块
  • 如果检查不通过,程序应直接报错或退出,不得继续写入共享内存;如果不做这些检查,可能会引发异常日志暴增,严重时甚至会直接填满磁盘空间。
c 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "nboard_shm.h"

int main(void)
{
    if (nboard_connect() != 0) {
        printf("连接 N板共享内存失败\n");
        return -1;
    }

    mode_info_t *nodes = nboard_nodes();

    /* 扫描所有在线模块,找到 DO 模块 */
    int do_slot = -1;
    int slot;
    for (slot = 1; slot < MAX_NODE_NUM; slot++) {
        mode_info_t *m = &nodes[slot];
        if (!m->is_start)
            continue;

        printf("Slot%d: 型号=%s, io_type=%d, sub_num=%d\n",
               slot, m->io_name, m->io_type, m->sub_num);

        /* 找到第一个 DO 模块 */
        if (do_slot < 0 && m->io_type == DEV_DO) {
            do_slot = slot;
        }
    }

    if (do_slot < 0) {
        printf("未找到 DO 模块\n");
        return -1;
    }

    /* 控制 DO: 逐个置位 */
    sub_info_t *do_sub = &nodes[do_slot].sub_io[0];
    printf("\n找到 DO 模块: Slot%d [%s], 通道数=%d\n",
           do_slot, nodes[do_slot].io_name, do_sub->io_num);

    int ch;
    for (ch = 0; ch < do_sub->io_num; ch++) {
        do_sub->chan_data[ch] = 1;
        printf("  DO ch%d -> 1\n", ch + 1);
        usleep(200000);
    }

    sleep(2);

    /* 全部复位 */
    for (ch = 0; ch < do_sub->io_num; ch++) {
        do_sub->chan_data[ch] = 0;
    }
    printf("所有 DO 已复位\n");

    return 0;
}

这段代码的作用是扫描所有在线模块,找到第一个 DO 模块后,逐个置位其所有通道,然后全部复位。


四、为什么写 AO 电压要用 memcpy

N 板里有两类通道数据,分别是整数型通道和浮点型通道。对于整数型通道,可以直接读写:

c 复制代码
/* 读 DI */
int value = s->chan_data[channel];

/* 写 DO */
s->chan_data[channel] = 1;

但对于 AO 这样的浮点型通道,不能直接写成:

c 复制代码
s->chan_data[0] = 3.5f;   // 错误

正确方法必须是:

c 复制代码
float voltage = 3.5f;
memcpy(&s->chan_data[0], &voltage, sizeof(float));

原因是:

  • chan_data[] 的元素类型本身是 uint32_t
  • 对 AI/AO/RTD 来说,里面存的是 float 的二进制内容
  • 所以要用 memcpy 把这 4 个字节按 float 的方式写进去

如何判断是整数还是浮点?

N 板没有 float_type 字段(Y 板有),而是通过 io_type 来判断:

io_type 模块 数据类型 读写方式
DEV_DO (1) DO 整数 直接赋值
DEV_DI (2) DI 整数 直接赋值
DEV_AO (3) AO 浮点 memcpy
DEV_AI (4) AI 浮点 memcpy
DEV_RTD (8) RTD 浮点 memcpy
DEV_DIO (10) DIO 整数 直接赋值

五、注意事项

1. 不要使用 slot 0

共享内存里:

  • nodes[0] 是预留位
  • nodes[1] 才是 Slot1(第一个模块)
  • nodes[2] 是 Slot2
  • 最大支持 nodes[32]

2. 注意通道类型

不是所有通道都能直接当整数读写。记住这个规则:

  • DI / DO / DIO:整数方式访问
  • AI / AO / RTDmemcpy 方式访问
  • N 板通过 io_type 判断

同时要遵循"先检查,再写入"原则:写入前必须先检查模块是否在线、通道范围是否正确,以及模块类型是否与写入方式匹配;检查不通过时,不得继续写入共享内存。

3. 不要修改接口文件

nboard_shm.hnboard_shm.c 是固定接口文件:

  • 不要删除字段
  • 不要改字段顺序
  • 不要改字段类型
  • 不要改连接参数

4. 不要去写内部字段

业务程序通常只应该使用这些字段:

  • is_start --- 检查模块是否在线
  • io_type --- 检查模块类型
  • sub_io[k].io_num --- 通道数量
  • sub_io[k].chan_data[] --- 通道数据

不要直接去操作下面这些内部字段:

  • filter_data
  • order_data
  • set_data
  • cfg
  • written

六、总结

把 N 板集成到你自己的程序里,最小步骤就是三步:

  1. 引入接口文件nboard_shm.hnboard_shm.c 复制到你的项目目录。这两个文件是固定的接口定义,不要修改。

  2. 启动时调用 nboard_connect() 连接共享内存main() 函数的最开始处调用 nboard_connect()。连接失败意味着无法访问 N 板数据。

  3. 在业务逻辑里读写数据 通过 nboard_nodes() 获取节点数组指针,按 nodes[slot].sub_io[0].chan_data[channel] 路径访问。写入前必须先校验:槽位在线(is_start)、模块类型匹配(io_type)。整数通道直接赋值,浮点通道用 memcpy

本文定位为示例教程,联系钡铼技术售后获取配套接口文档。

相关推荐
Hhy_11071 小时前
《C++深度解构02》类和对象(上)——定义规范、内存对齐与 this 指针全解
c语言·开发语言·c++·学习·visual studio
多弗朗皮卡丘11 小时前
C语言梦开始的地方7:函数
c语言·开发语言
Herbert_hwt16 小时前
C语言零基础入门:循环控制与数据类型详解
c语言·数据结构·算法
Tyler_TXZ17 小时前
C++C语言之——二叉树
c语言·开发语言·数据结构·c++·二叉树
小羊先生car17 小时前
F429-HAL-RS485(回环/双机实验)(2026/8/16)
c语言·单片机·嵌入式硬件·软件构建
白狐_79820 小时前
408 数据结构|线索二叉树两题详解:先序线索化后的空链域 + 中序前驱/后继判断
c语言·数据结构·链表
GeekZHR20 小时前
C语言指针进阶补充6:动态内存管理、mem系列内存函数、复杂指针声明,一次补齐指针的“三大盲区“
java·c语言·算法·指针
caimouse21 小时前
ReactOS 图形系统分析(43):API 裁剪 — cliprgn.c
c语言·开发语言
caimouse1 天前
ReactOS 图形系统分析(40):文本子系统 — text.c
c语言·开发语言