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 步:
- 把
nboard_shm.h和nboard_shm.c放到你的项目目录 - 在你的程序中
#include "nboard_shm.h" - 程序启动时调用
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 / RTD:memcpy方式访问- N 板通过
io_type判断
同时要遵循"先检查,再写入"原则:写入前必须先检查模块是否在线、通道范围是否正确,以及模块类型是否与写入方式匹配;检查不通过时,不得继续写入共享内存。
3. 不要修改接口文件
nboard_shm.h 和 nboard_shm.c 是固定接口文件:
- 不要删除字段
- 不要改字段顺序
- 不要改字段类型
- 不要改连接参数
4. 不要去写内部字段
业务程序通常只应该使用这些字段:
is_start--- 检查模块是否在线io_type--- 检查模块类型sub_io[k].io_num--- 通道数量sub_io[k].chan_data[]--- 通道数据
不要直接去操作下面这些内部字段:
filter_dataorder_dataset_datacfgwritten
六、总结
把 N 板集成到你自己的程序里,最小步骤就是三步:
-
引入接口文件 将
nboard_shm.h和nboard_shm.c复制到你的项目目录。这两个文件是固定的接口定义,不要修改。 -
启动时调用
nboard_connect()连接共享内存 在main()函数的最开始处调用nboard_connect()。连接失败意味着无法访问 N 板数据。 -
在业务逻辑里读写数据 通过
nboard_nodes()获取节点数组指针,按nodes[slot].sub_io[0].chan_data[channel]路径访问。写入前必须先校验:槽位在线(is_start)、模块类型匹配(io_type)。整数通道直接赋值,浮点通道用memcpy。
本文定位为示例教程,联系钡铼技术售后获取配套接口文档。