# melsec_mc_net **Repository Path**: s-pms/melsec_mc_net ## Basic Information - **Project Name**: melsec_mc_net - **Description**: Melsec PLC MC-Binary protocol - **Primary Language**: C - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 4 - **Created**: 2023-12-05 - **Last Updated**: 2026-10-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # melsec_mc_net [English](README.md) | [简体中文](README.zh-CN.md) 一个基于 C 语言的三菱 PLC 以太网通信库,采用 MC 协议(QnA 兼容 3E 帧,支持 Binary / ASCII 两种模式)。 ## 项目概览 - 语言:C - 平台:Windows / Linux - 协议:MC Protocol 3E Frame(Binary / ASCII) - 常见测试设备:QJ71E71 网络模块、FX5U 当前代码已提供类型化读写接口、批量读写接口、PLC 远程控制接口,以及通信超时/重试配置能力。并且已增强同一连接上的事务级串行化,避免多线程并发导致响应串包。 ## 核心能力 - PLC 连接与断开(Binary / ASCII) - 多类型单值读写 - 常用类型批量读写 - 远程运行/停止/复位与 PLC 型号读取 - 发送/接收超时与重试参数配置 - 同一 fd 的请求-响应事务串行化 - `main.c` 支持按协议切换的同流程压测(same-fd / multi-fd) ## 目录结构 - `melsec_mc_net/`:核心源码与头文件 - `docs/`:协议/API 说明 - `app/`:Make 流程相关中间产物目录 - `melsec_mc_net/main.c`:示例与测试入口 ## 架构总览 ```mermaid flowchart LR App[用户应用] Bin[Binary API
melsec_mc_bin.h] Ascii[ASCII API
melsec_mc_ascii.h] Batch[批量 API
melsec_mc_bin_batch.h] Core[地址解析 + 命令打包
melsec_mc_comm.c / melsec_helper.c] Transport[传输层 + 超时 + 重试
socket.c / network_init.c] PLC[PLC / 以太网模块] App --> Bin App --> Ascii App --> Batch Bin --> Core Ascii --> Core Batch --> Core Core --> Transport Transport --> PLC ``` README 只保留高层协作关系。帧字段顺序、Binary/ASCII 差异等低层协议细节,请查看 `docs/melsec_mc_bin_ascii协议api手册.md`。 ## 构建方式 ### Linux / WSL(Make) 在仓库根目录执行: ```bash make clean make ``` 默认会在仓库根目录生成 `mc_bin_test`。 ### Windows(Visual Studio) 使用 Visual Studio 打开 `melsec_mc_net/melsec_mc_net.sln` 并编译对应工程。 ## 基础使用 头文件: ```c #include "melsec_mc_bin.h" #include "melsec_mc_ascii.h" #include "typedef.h" ``` 推荐先初始化网络环境: ```c #include "network_init.h" if (mc_network_init() != MC_ERROR_CODE_SUCCESS) { return -1; } ``` 连接与断开(Binary): ```c int fd = mc_connect("192.168.1.10", 6000, 0, 0); if (fd < 0) { return -1; } mc_disconnect(fd); mc_network_cleanup(); ``` 连接与断开(ASCII): ```c int fd = mc_ascii_connect("192.168.1.10", 6000, 0, 0); if (fd < 0) { return -1; } mc_ascii_disconnect(fd); ``` 读写示例: ```c short s = 0; mc_read_short(fd, "D100", &s); mc_write_int32(fd, "D200", 12345); char* text = NULL; if (mc_read_string(fd, "D300", 8, &text) == MC_ERROR_CODE_SUCCESS) { /* text 由调用方持有,且为 NUL 结尾 */ printf("text=%s\n", text); mc_free_string(text); } char* plc_type = NULL; if (mc_read_plc_type(fd, &plc_type) == MC_ERROR_CODE_SUCCESS) { /* 使用 plc_type */ mc_free_string(plc_type); } ``` ## 公开 API ### 连接接口 ```c int mc_connect(char* ip_addr, int port, byte network_addr, byte station_addr); bool mc_disconnect(int fd); ``` ### 内存所有权 - `mc_read_short` 这类标量读取接口,以及 `mc_read_int32_batch` 这类批量接口,都是写入调用方提供的缓冲区,库本身不会为输出结果分配内存。 - `mc_read_string`、`mc_ascii_read_string`、`mc_read_plc_type`、`mc_ascii_read_plc_type` 会为返回字符串分配堆内存。 - 这几类字符串接口在失败时会把输出指针设为 `NULL`;成功时返回由调用方持有的 NUL 结尾字符串,建议调用方使用 `mc_free_string()` 或 `mc_free_buffer()` 释放。 内存释放辅助接口: ```c void mc_free_buffer(void* buffer); void mc_free_string(char* value); ``` 示例: ```c char* value = NULL; mc_error_code_e ret = mc_read_string(fd, "D300", 8, &value); if (ret == MC_ERROR_CODE_SUCCESS) { printf("value=%s\n", value); mc_free_string(value); } ``` ### 单值读取 ```c mc_error_code_e mc_read_bool(int fd, const char* address, bool* val); mc_error_code_e mc_read_short(int fd, const char* address, short* val); mc_error_code_e mc_read_ushort(int fd, const char* address, ushort* val); mc_error_code_e mc_read_int32(int fd, const char* address, int32* val); mc_error_code_e mc_read_uint32(int fd, const char* address, uint32* val); mc_error_code_e mc_read_int64(int fd, const char* address, int64* val); mc_error_code_e mc_read_uint64(int fd, const char* address, uint64* val); mc_error_code_e mc_read_float(int fd, const char* address, float* val); mc_error_code_e mc_read_double(int fd, const char* address, double* val); mc_error_code_e mc_read_string(int fd, const char* address, int length, char** val); ``` `mc_read_string` 会分配返回缓冲区,成功后建议调用 `mc_free_string(*val)` 或 `mc_free_buffer(*val)`。 ### 单值写入 ```c mc_error_code_e mc_write_bool(int fd, const char* address, bool val); mc_error_code_e mc_write_short(int fd, const char* address, short val); mc_error_code_e mc_write_ushort(int fd, const char* address, ushort val); mc_error_code_e mc_write_int32(int fd, const char* address, int32 val); mc_error_code_e mc_write_uint32(int fd, const char* address, uint32 val); mc_error_code_e mc_write_int64(int fd, const char* address, int64 val); mc_error_code_e mc_write_uint64(int fd, const char* address, uint64 val); mc_error_code_e mc_write_float(int fd, const char* address, float val); mc_error_code_e mc_write_double(int fd, const char* address, double val); mc_error_code_e mc_write_string(int fd, const char* address, int length, const char* val); ``` ### 批量接口 头文件:`melsec_mc_bin_batch.h` ```c mc_error_code_e mc_read_bool_batch(int fd, const char* address, int length, bool* values); mc_error_code_e mc_read_short_batch(int fd, const char* address, int length, short* values); mc_error_code_e mc_read_ushort_batch(int fd, const char* address, int length, ushort* values); mc_error_code_e mc_read_int32_batch(int fd, const char* address, int length, int32* values); mc_error_code_e mc_read_uint32_batch(int fd, const char* address, int length, uint32* values); mc_error_code_e mc_read_float_batch(int fd, const char* address, int length, float* values); mc_error_code_e mc_write_bool_batch(int fd, const char* address, int length, const bool* values); mc_error_code_e mc_write_short_batch(int fd, const char* address, int length, const short* values); mc_error_code_e mc_write_ushort_batch(int fd, const char* address, int length, const ushort* values); mc_error_code_e mc_write_int32_batch(int fd, const char* address, int length, const int32* values); mc_error_code_e mc_write_uint32_batch(int fd, const char* address, int length, const uint32* values); mc_error_code_e mc_write_float_batch(int fd, const char* address, int length, const float* values); ``` ### PLC 控制与信息 ```c mc_error_code_e mc_remote_run(int fd); mc_error_code_e mc_remote_stop(int fd); mc_error_code_e mc_remote_reset(int fd); mc_error_code_e mc_read_plc_type(int fd, char** type); ``` `mc_read_plc_type` 会分配 PLC 型号字符串,成功后建议调用 `mc_free_string(*type)` 或 `mc_free_buffer(*type)`。 ### ASCII 接口 ASCII 模式接口与 binary 语义一致,函数名前缀为 `mc_ascii_`,例如: ```c int mc_ascii_connect(char* ip_addr, int port, byte network_addr, byte station_addr); bool mc_ascii_disconnect(int fd); mc_error_code_e mc_ascii_read_int32(int fd, const char* address, int32* val); mc_error_code_e mc_ascii_write_double(int fd, const char* address, double val); mc_error_code_e mc_ascii_read_string(int fd, const char* address, int length, char** val); ``` `mc_ascii_read_string` 的所有权规则与 `mc_read_string` 相同。 头文件:`melsec_mc_ascii.h` ### 通信参数配置 头文件:`socket.h` ```c typedef struct { int send_timeout_ms; int recv_timeout_ms; int retry_count; int retry_interval_ms; } mc_comm_config_t; mc_error_code_e mc_set_comm_config(int fd, mc_comm_config_t config); mc_error_code_e mc_get_comm_config(int fd, mc_comm_config_t* config); ``` ## 地址格式 支持如下一类地址字符串: - `M100` - `X1` - `D100` - `W1A0` - `ZR200` - `TN10` 支持的设备地址包含:M/X/Y/D/W/L/F/V/B/R/SN/SS/SC/ZR/Z/TC/TS/TN/CN/CS/CC。 为便于对照与迁移,保留完整地址列表如下: | 序号 | 描述 | 地址类型 | | :--: | :----------------- | :------: | | 1 | 中间继电器 | M | | 2 | 输入继电器 | X | | 3 | 输出继电器 | Y | | 4 | 数据寄存器 | D | | 5 | 链接寄存器 | W | | 6 | 锁存继电器 | L | | 7 | 报警器 | F | | 8 | 边沿继电器 | V | | 9 | 链接继电器 | B | | 10 | 文件寄存器 | R | | 11 | 累计定时器的当前值 | SN | | 12 | 累计定时器的触点 | SS | | 13 | 累计定时器的线圈 | SC | | 14 | 文件寄存器 ZR 区 | ZR | | 15 | 变址寄存器 | Z | | 16 | 定时器的线圈 | TC | | 17 | 定时器的触点 | TS | | 18 | 定时器的当前值 | TN | | 19 | 计数器的当前值 | CN | | 20 | 计数器的触点 | CS | | 21 | 计数器的线圈 | CC | ## 线程安全说明 - 同一 fd 上的请求按事务级串行化(发送 + 接收在同一锁中完成),可避免多线程响应交叉。 - 不同连接间可并发执行。 ### 请求生命周期 ```mermaid sequenceDiagram participant Caller as 用户代码 participant API as mc_read_* / mc_write_* participant Lock as 按 fd 的事务锁 participant Transport as socket.c participant PLC as PLC Caller->>API: 发起读写请求 API->>API: 参数校验 + 地址解析 API->>API: 构建 MC 核心命令 API->>API: 打包 Binary/ASCII 帧 API->>Lock: lock(fd) Lock-->>API: 获得事务执行权 API->>Transport: 发送请求 Transport->>PLC: 协议报文 PLC-->>Transport: 响应报文 Transport-->>API: 返回响应载荷 API->>API: 解析响应 + 解码结果 API->>Lock: unlock(fd) API-->>Caller: 标量结果 / 堆字符串 Note over Lock: 同一 fd 上的调用会按完整事务串行化。 Note over Caller,PLC: 不同 fd 的调用仍可并发执行。 ``` ## 压测入口 `melsec_mc_net/main.c` 已支持按协议切换压测: - 在 `test_protocol` 中设置 `"bin"` 或 `"ascii"` - 两种协议均走同一压测流程:`run_same_fd_stress` 和 `run_multi_fd_stress`。 ## 使用限制 - 部分仿真器或 PLC 侧配置可能不支持远程控制命令(run/stop/reset/type)。 - 使用前需在 PLC 侧正确配置以太网模块与 MC 协议参数。 ## 常见问题 - 无法连接:检查 IP、端口、network/station、PLC 侧参数和防火墙。 - 超时/偶发失败:根据网络质量调整 timeout 和 retry 参数。 - 数据异常:检查软元件地址类型与地址进制是否匹配。 ## 版权信息 - Copyright (c) 2022-2026 wqliceman. All rights reserved. - GitHub 用户名:iceman - 邮箱:[wqliceman@gmail.com](mailto:wqliceman@gmail.com) ## 许可证 见 `LICENSE`。