A C library for Mitsubishi PLC communication over Ethernet using MC Protocol (QnA-compatible 3E frame), with both binary and ASCII modes.
- Language: C
- Platforms: Windows and Linux
- Protocol: MC Protocol 3E frame (binary and ASCII)
- Typical tested hardware: QJ71E71 network module, FX5U
The library exposes typed read/write APIs, PLC remote control APIs, and batch APIs. It also includes communication timeout/retry configuration and thread-safety improvements for request/response transaction serialization on the same socket.
- PLC connect/disconnect (binary and ASCII)
- Typed single-value read/write
- Batch read/write for common scalar types
- Remote run/stop/reset and PLC type query
- Configurable send/receive timeout and retry strategy
- Request-response transaction serialization per file descriptor
- Protocol-switchable stress test in
main.c(same-fd and multi-fd)
melsec_mc_net/: core library source and headersdocs/: protocol/API notesapp/: build artifacts/dependency files for the Make-based flowmain.cinmelsec_mc_net/: example and test entry
flowchart LR
App[User Application]
Bin[Binary API<br/>melsec_mc_bin.h]
Ascii[ASCII API<br/>melsec_mc_ascii.h]
Batch[Batch API<br/>melsec_mc_bin_batch.h]
Core[Address Parsing + Command Packing<br/>melsec_mc_comm.c / melsec_helper.c]
Transport[Transport + Timeout + Retry<br/>socket.c / network_init.c]
PLC[PLC / Ethernet Module]
App --> Bin
App --> Ascii
App --> Batch
Bin --> Core
Ascii --> Core
Batch --> Core
Core --> Transport
Transport --> PLC
README keeps the high-level interaction model. For frame-level field layouts and binary/ASCII protocol details, see docs/melsec_mc_bin_ascii协议api手册.md.
From repository root:
make clean
makeThe default Make flow produces mc_bin_test at repository root.
Open melsec_mc_net/melsec_mc_net.sln in Visual Studio and build the target project.
Include headers:
#include "melsec_mc_bin.h"
#include "melsec_mc_ascii.h"
#include "typedef.h"Initialize network stack (recommended cross-platform entry):
#include "network_init.h"
if (mc_network_init() != MC_ERROR_CODE_SUCCESS) {
return -1;
}Connect and disconnect (binary):
int fd = mc_connect("192.168.1.10", 6000, 0, 0);
if (fd < 0) {
return -1;
}
mc_disconnect(fd);
mc_network_cleanup();Connect and disconnect (ASCII):
int fd = mc_ascii_connect("192.168.1.10", 6000, 0, 0);
if (fd < 0) {
return -1;
}
mc_ascii_disconnect(fd);Read and write examples:
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 is caller-owned and NUL-terminated */
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) {
/* use plc_type */
mc_free_string(plc_type);
}int mc_connect(char* ip_addr, int port, byte network_addr, byte station_addr);
bool mc_disconnect(int fd);- Scalar read APIs such as
mc_read_shortand batch APIs such asmc_read_int32_batchwrite into caller-provided buffers; the library does not allocate output memory for those APIs. mc_read_string,mc_ascii_read_string,mc_read_plc_type, andmc_ascii_read_plc_typeallocate heap memory for the returned string.- These string-returning APIs set the output pointer to
NULLon failure. On success they return a caller-owned, NUL-terminated buffer that should be released withmc_free_string()ormc_free_buffer().
Memory helper APIs:
void mc_free_buffer(void* buffer);
void mc_free_string(char* value);Example:
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);
}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 allocates the returned buffer. The caller must free *val after a successful call.
Prefer mc_free_string(*val) or mc_free_buffer(*val).
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);Header: melsec_mc_bin_batch.h
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);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 allocates the returned model string. The caller must free *type after a successful call.
Prefer mc_free_string(*type) or mc_free_buffer(*type).
ASCII mode uses the same semantic API set with mc_ascii_ prefix, for example:
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 follows the same ownership contract as mc_read_string.
Header: melsec_mc_ascii.h
Header: socket.h
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);The API accepts address strings such as:
M100X1D100W1A0ZR200TN10
Supported device addresses include M/X/Y/D/W/L/F/V/B/R/SN/SS/SC/ZR/Z/TC/TS/TN/CN/CS/CC.
For easier migration and one-to-one mapping, the full address list is kept below:
- Internal relay: M
- Input relay: X
- Output relay: Y
- Data register: D
- Link register: W
- Latch relay: L
- Annunciator: F
- Edge relay: V
- Link relay: B
- File register: R
- Accumulative timer current value: SN
- Accumulative timer contact: SS
- Accumulative timer coil: SC
- File register ZR area: ZR
- Index register: Z
- Timer coil: TC
- Timer contact: TS
- Timer current value: TN
- Counter current value: CN
- Counter contact: CS
- Counter coil: CC
- Requests on the same file descriptor are serialized at transaction level (send + receive) to prevent response interleaving across threads.
- You can still run parallel workloads across different connections.
sequenceDiagram
participant Caller as User Code
participant API as mc_read_* / mc_write_*
participant Lock as Per-fd Transaction Lock
participant Transport as socket.c
participant PLC as PLC
Caller->>API: read/write request
API->>API: validate parameters + parse address
API->>API: build MC core command
API->>API: pack binary/ascii frame
API->>Lock: lock(fd)
Lock-->>API: transaction granted
API->>Transport: send request
Transport->>PLC: protocol frame
PLC-->>Transport: response frame
Transport-->>API: response payload
API->>API: parse response + decode value
API->>Lock: unlock(fd)
API-->>Caller: scalar result / heap string
Note over Lock: Calls on the same fd are serialized as one transaction.
Note over Caller,PLC: Calls using different fds can still run concurrently.
melsec_mc_net/main.c supports stress testing with protocol selection:
- Set
test_protocolto"bin"or"ascii" - Same test workflow is used for both modes:
run_same_fd_stressandrun_multi_fd_stress.
- Remote control commands (run/stop/reset/type) may be unsupported by some simulators or restricted by PLC-side permissions/configuration.
- Please configure the PLC Ethernet module and MC communication parameters before use.
- Connection fails: verify IP, port, PLC network/station number, firewall, and PLC Ethernet module settings.
- Timeout or intermittent errors: tune
send_timeout_ms,recv_timeout_ms, and retry parameters. - Incorrect data: verify device address type and base (decimal/hex by device family).
- Copyright (c) 2022-2026 wqliceman. All rights reserved.
- GitHub: iceman
- Email: wqliceman@gmail.com
See LICENSE.