VC++ 三菱 PLC 串口通信软件开发指南
VC++ 串口通信程序,专门用于与三菱 PLC 进行通信。这个程序支持三菱 FX 系列、Q 系列等多种 PLC 型号,并实现了完整的通信协议。
一、系统架构
三菱PLC串口通信系统:
├── 通信层
│ ├── 串口配置与管理
│ ├── 数据帧封装/解析
│ ├── 超时处理
│ └── 错误检测
├── 协议层
│ ├── FX系列编程口协议
│ ├── MC协议(QnA兼容)
│ ├── A系列协议
│ └── Q系列协议
├── 应用层
│ ├── 设备监控
│ ├── 数据读写
│ ├── 程序上下载
│ └── 报警管理
└── 界面层
├── 串口配置界面
├── PLC监控界面
├── 数据监视界面
└── 诊断界面
二、核心代码实现
2.1 串口通信基类 (MitsubishiSerial.h)
#ifndef MITSUBISHI_SERIAL_H
#define MITSUBISHI_SERIAL_H
#include <windows.h>
#include <string>
#include <vector>
#include <queue>
#include <mutex>
#include <thread>
#include <atomic>
#include <functional>
#include <cstring>
#include <cstdint>
// 三菱PLC类型
enum MitsubishiPLCType {
PLC_FX = 0, // FX系列
PLC_Q, // Q系列
PLC_A, // A系列
PLC_L, // L系列
PLC_FX5U, // FX5U系列
PLC_QnA // QnA系列
};
// 软元件类型
enum SoftElementType {
SOFT_X = 0, // 输入继电器 X
SOFT_Y, // 输出继电器 Y
SOFT_M, // 辅助继电器 M
SOFT_D, // 数据寄存器 D
SOFT_T, // 定时器 T
SOFT_C, // 计数器 C
SOFT_S, // 状态继电器 S
SOFT_B, // 链接继电器 B
SOFT_W, // 链接寄存器 W
SOFT_R, // 文件寄存器 R
SOFT_Z, // 变址寄存器 Z
SOFT_V // 变址寄存器 V
};
// 通信结果
enum CommResult {
COMM_SUCCESS = 0, // 成功
COMM_TIMEOUT, // 超时
COMM_CRC_ERROR, // CRC错误
COMM_FORMAT_ERROR, // 格式错误
COMM_DEVICE_ERROR, // 设备错误
COMM_NOT_SUPPORTED, // 不支持
COMM_PORT_ERROR // 端口错误
};
// 数据帧结构
struct DataFrame {
uint8_t stx; // 起始符
uint8_t command; // 命令
uint16_t address; // 地址
uint16_t count; // 数量
uint8_t etx; // 结束符
uint8_t checksum; // 校验和
std::vector<uint8_t> data; // 数据
};
// 三菱PLC串口通信类
class MitsubishiSerial {
public:
MitsubishiSerial();
virtual ~MitsubishiSerial();
// 串口操作
bool OpenPort(const std::string& portName, DWORD baudRate = 9600);
void ClosePort();
bool IsPortOpen() const;
// 配置串口参数
void SetParity(BYTE parity);
void SetStopBits(BYTE stopBits);
void SetDataBits(BYTE dataBits);
void SetTimeout(DWORD readTimeout, DWORD writeTimeout);
// PLC操作
CommResult ReadDevice(SoftElementType type, uint16_t address,
uint16_t count, std::vector<int16_t>& values);
CommResult WriteDevice(SoftElementType type, uint16_t address,
const std::vector<int16_t>& values);
CommResult ReadBitDevice(SoftElementType type, uint16_t address,
uint16_t count, std::vector<bool>& values);
CommResult WriteBitDevice(SoftElementType type, uint16_t address,
const std::vector<bool>& values);
// 特殊功能
CommResult GetPLCType(std::string& plcType);
CommResult GetPLCSerialNumber(std::string& serialNumber);
CommResult GetPLCVersion(std::string& version);
CommResult ForceOn(SoftElementType type, uint16_t address);
CommResult ForceOff(SoftElementType type, uint16_t address);
// 回调函数
typedef std::function<void(CommResult)> CallbackFunc;
void SetCallback(CallbackFunc callback);
// 线程安全
void Lock();
void Unlock();
// 协议相关
void SetPLCType(MitsubishiPLCType type);
MitsubishiPLCType GetPLCType() const;
// 错误处理
std::string GetLastError() const;
protected:
// 协议实现(虚函数,由派生类实现)
virtual CommResult SendFrame(const DataFrame& frame) = 0;
virtual CommResult ReceiveFrame(DataFrame& frame) = 0;
virtual uint8_t CalculateChecksum(const DataFrame& frame) = 0;
virtual void BuildReadCommand(SoftElementType type, uint16_t address,
uint16_t count, DataFrame& frame) = 0;
virtual void BuildWriteCommand(SoftElementType type, uint16_t address,
const std::vector<int16_t>& values,
DataFrame& frame) = 0;
// 通用方法
bool SendData(const uint8_t* data, DWORD length);
bool ReceiveData(uint8_t* data, DWORD length, DWORD timeout);
void ClearBuffer();
// 地址转换
uint16_t ConvertAddress(SoftElementType type, uint16_t address);
std::string GetElementName(SoftElementType type);
private:
// 串口句柄
HANDLE hComm;
DCB dcb;
COMMTIMEOUTS timeouts;
// 线程安全
std::mutex commMutex;
std::atomic<bool> isOpen;
// PLC类型
MitsubishiPLCType plcType;
// 错误记录
std::string lastError;
// 回调函数
CallbackFunc callbackFunc;
// 线程
std::thread monitorThread;
std::atomic<bool> monitorRunning;
// 接收缓冲区
std::queue<uint8_t> receiveQueue;
// 私有方法
void MonitorThread();
void ProcessReceivedData();
void SetLastError(const std::string& error);
};
#endif // MITSUBISHI_SERIAL_H
2.2 FX系列PLC实现 (MitsubishiFXSerial.h 和 MitsubishiFXSerial.cpp)
MitsubishiFXSerial.h
#ifndef MITSUBISHI_FX_SERIAL_H
#define MITSUBISHI_FX_SERIAL_H
#include "MitsubishiSerial.h"
// FX系列PLC专用协议
class MitsubishiFXSerial : public MitsubishiSerial {
public:
MitsubishiFXSerial();
virtual ~MitsubishiFXSerial();
protected:
// 实现基类虚函数
virtual CommResult SendFrame(const DataFrame& frame) override;
virtual CommResult ReceiveFrame(DataFrame& frame) override;
virtual uint8_t CalculateChecksum(const DataFrame& frame) override;
virtual void BuildReadCommand(SoftElementType type, uint16_t address,
uint16_t count, DataFrame& frame) override;
virtual void BuildWriteCommand(SoftElementType type, uint16_t address,
const std::vector<int16_t>& values,
DataFrame& frame) override;
private:
// FX系列专用命令
enum FXCommand {
CMD_READ = 0x30, // 读取
CMD_WRITE = 0x31, // 写入
CMD_FORCE_ON = 0x37, // 强制ON
CMD_FORCE_OFF = 0x38, // 强制OFF
CMD_READ_STATUS = 0x39, // 读取状态
CMD_RESET = 0x40 // 复位
};
// FX系列地址映射
uint16_t GetFXAddress(SoftElementType type, uint16_t address);
// 数据转换
void ConvertToASCII(const uint8_t* binary, uint8_t* ascii, size_t length);
void ConvertFromASCII(const uint8_t* ascii, uint8_t* binary, size_t length);
// 协议常量
static const uint8_t STX = 0x02; // 起始符
static const uint8_t ETX = 0x03; // 结束符
};
#endif // MITSUBISHI_FX_SERIAL_H
MitsubishiFXSerial.cpp
#include "MitsubishiFXSerial.h"
#include <sstream>
#include <iomanip>
MitsubishiFXSerial::MitsubishiFXSerial() {
SetPLCType(PLC_FX);
}
MitsubishiFXSerial::~MitsubishiFXSerial() {
ClosePort();
}
CommResult MitsubishiFXSerial::SendFrame(const DataFrame& frame) {
std::lock_guard<std::mutex> lock(commMutex);
// 构建发送数据
std::vector<uint8_t> sendData;
// 添加STX
sendData.push_back(STX);
// 添加命令
sendData.push_back(frame.command);
// 添加地址(转换为ASCII)
uint8_t addrHi = (frame.address >> 8) & 0xFF;
uint8_t addrLo = frame.address & 0xFF;
uint8_t addrAscii[4];
ConvertToASCII(&addrHi, addrAscii, 1);
ConvertToASCII(&addrLo, addrAscii + 2, 1);
sendData.insert(sendData.end(), addrAscii, addrAscii + 4);
// 添加数量(转换为ASCII)
uint8_t countHi = (frame.count >> 8) & 0xFF;
uint8_t countLo = frame.count & 0xFF;
uint8_t countAscii[4];
ConvertToASCII(&countHi, countAscii, 1);
ConvertToASCII(&countLo, countAscii + 2, 1);
sendData.insert(sendData.end(), countAscii, countAscii + 4);
// 添加ETX
sendData.push_back(ETX);
// 添加校验和
uint8_t checksum = CalculateChecksum(frame);
uint8_t checksumAscii[2];
ConvertToASCII(&checksum, checksumAscii, 1);
sendData.insert(sendData.end(), checksumAscii, 2);
// 发送数据
if (!SendData(sendData.data(), static_cast<DWORD>(sendData.size()))) {
SetLastError("Failed to send data");
return COMM_PORT_ERROR;
}
return COMM_SUCCESS;
}
CommResult MitsubishiFXSerial::ReceiveFrame(DataFrame& frame) {
std::lock_guard<std::mutex> lock(commMutex);
// 接收数据
uint8_t buffer[256];
DWORD bytesRead = 0;
// 查找STX
uint8_t byte;
int timeoutCount = 0;
while (true) {
if (!ReceiveData(&byte, 1, 100)) {
if (++timeoutCount > 50) { // 5秒超时
SetLastError("Receive timeout waiting for STX");
return COMM_TIMEOUT;
}
continue;
}
if (byte == STX) break;
}
// 接收命令
if (!ReceiveData(&frame.command, 1, 1000)) {
SetLastError("Failed to receive command");
return COMM_TIMEOUT;
}
// 接收地址(ASCII格式)
uint8_t addrAscii[4];
if (!ReceiveData(addrAscii, 4, 1000)) {
SetLastError("Failed to receive address");
return COMM_TIMEOUT;
}
// 转换地址
uint8_t addrBinary[2];
ConvertFromASCII(addrAscii, addrBinary, 2);
frame.address = (addrBinary[0] << 8) | addrBinary[1];
// 接收数量(ASCII格式)
uint8_t countAscii[4];
if (!ReceiveData(countAscii, 4, 1000)) {
SetLastError("Failed to receive count");
return COMM_TIMEOUT;
}
// 转换数量
uint8_t countBinary[2];
ConvertFromASCII(countAscii, countBinary, 2);
frame.count = (countBinary[0] << 8) | countBinary[1];
// 接收数据
if (frame.count > 0) {
size_t dataSize = frame.count * 2; // 每个数据2字节
uint8_t dataAscii[dataSize * 2]; // ASCII格式是二进制的两倍
if (!ReceiveData(dataAscii, static_cast<DWORD>(dataSize * 2), 1000)) {
SetLastError("Failed to receive data");
return COMM_TIMEOUT;
}
frame.data.resize(dataSize);
ConvertFromASCII(dataAscii, frame.data.data(), dataSize);
}
// 接收ETX
if (!ReceiveData(&frame.etx, 1, 1000)) {
SetLastError("Failed to receive ETX");
return COMM_TIMEOUT;
}
// 接收校验和
uint8_t checksumAscii[2];
if (!ReceiveData(checksumAscii, 2, 1000)) {
SetLastError("Failed to receive checksum");
return COMM_TIMEOUT;
}
// 转换校验和
uint8_t checksumBinary[1];
ConvertFromASCII(checksumAscii, checksumBinary, 1);
frame.checksum = checksumBinary[0];
// 验证校验和
uint8_t calculatedChecksum = CalculateChecksum(frame);
if (calculatedChecksum != frame.checksum) {
SetLastError("Checksum error");
return COMM_CRC_ERROR;
}
return COMM_SUCCESS;
}
uint8_t MitsubishiFXSerial::CalculateChecksum(const DataFrame& frame) {
uint8_t sum = frame.command;
sum += (frame.address >> 8) & 0xFF;
sum += frame.address & 0xFF;
sum += (frame.count >> 8) & 0xFF;
sum += frame.count & 0xFF;
for (size_t i = 0; i < frame.data.size(); i++) {
sum += frame.data[i];
}
sum += frame.etx;
return sum & 0xFF;
}
void MitsubishiFXSerial::BuildReadCommand(SoftElementType type, uint16_t address,
uint16_t count, DataFrame& frame) {
frame.stx = STX;
frame.command = CMD_READ;
frame.address = GetFXAddress(type, address);
frame.count = count;
frame.etx = ETX;
frame.checksum = CalculateChecksum(frame);
}
void MitsubishiFXSerial::BuildWriteCommand(SoftElementType type, uint16_t address,
const std::vector<int16_t>& values,
DataFrame& frame) {
frame.stx = STX;
frame.command = CMD_WRITE;
frame.address = GetFXAddress(type, address);
frame.count = static_cast<uint16_t>(values.size());
frame.etx = ETX;
// 转换数据
frame.data.resize(values.size() * 2);
for (size_t i = 0; i < values.size(); i++) {
frame.data[i * 2] = (values[i] >> 8) & 0xFF;
frame.data[i * 2 + 1] = values[i] & 0xFF;
}
frame.checksum = CalculateChecksum(frame);
}
uint16_t MitsubishiFXSerial::GetFXAddress(SoftElementType type, uint16_t address) {
switch (type) {
case SOFT_X: return address; // X0-X377 (八进制)
case SOFT_Y: return address; // Y0-Y377 (八进制)
case SOFT_M: return address; // M0-M3071
case SOFT_D: return address; // D0-D7999
case SOFT_T: return address; // T0-T255
case SOFT_C: return address; // C0-C255
case SOFT_S: return address; // S0-S4095
default: return address;
}
}
void MitsubishiFXSerial::ConvertToASCII(const uint8_t* binary, uint8_t* ascii, size_t length) {
static const char hexChars[] = "0123456789ABCDEF";
for (size_t i = 0; i < length; i++) {
ascii[i * 2] = hexChars[(binary[i] >> 4) & 0x0F];
ascii[i * 2 + 1] = hexChars[binary[i] & 0x0F];
}
}
void MitsubishiFXSerial::ConvertFromASCII(const uint8_t* ascii, uint8_t* binary, size_t length) {
for (size_t i = 0; i < length; i++) {
uint8_t hi = ascii[i * 2];
uint8_t lo = ascii[i * 2 + 1];
if (hi >= '0' && hi <= '9') hi -= '0';
else if (hi >= 'A' && hi <= 'F') hi = hi - 'A' + 10;
else if (hi >= 'a' && hi <= 'f') hi = hi - 'a' + 10;
if (lo >= '0' && lo <= '9') lo -= '0';
else if (lo >= 'A' && lo <= 'F') lo = lo - 'A' + 10;
else if (lo >= 'a' && lo <= 'f') lo = lo - 'a' + 10;
binary[i] = (hi << 4) | lo;
}
}
2.3 MC协议实现(Q系列)(MitsubishiMCSerial.h 和 MitsubishiMCSerial.cpp)
MitsubishiMCSerial.h
#ifndef MITSUBISHI_MC_SERIAL_H
#define MITSUBISHI_MC_SERIAL_H
#include "MitsubishiSerial.h"
// MC协议(QnA兼容)
class MitsubishiMCSerial : public MitsubishiSerial {
public:
MitsubishiMCSerial();
virtual ~MitsubishiMCSerial();
protected:
// 实现基类虚函数
virtual CommResult SendFrame(const DataFrame& frame) override;
virtual CommResult ReceiveFrame(DataFrame& frame) override;
virtual uint8_t CalculateChecksum(const DataFrame& frame) override;
virtual void BuildReadCommand(SoftElementType type, uint16_t address,
uint16_t count, DataFrame& frame) override;
virtual void BuildWriteCommand(SoftElementType type, uint16_t address,
const std::vector<int16_t>& values,
DataFrame& frame) override;
private:
// MC协议命令
enum MCCommand {
CMD_BATCH_READ = 0x0401, // 批量读取
CMD_BATCH_WRITE = 0x1401, // 批量写入
CMD_RANDOM_READ = 0x0403, // 随机读取
CMD_RANDOM_WRITE = 0x1402, // 随机写入
CMD_MONITOR = 0x0801, // 监视
CMD_TEST = 0x1901, // 测试
CMD_REMOTE_RUN = 0x1001, // 远程RUN
CMD_REMOTE_STOP = 0x1002, // 远程STOP
CMD_REMOTE_RESET = 0x1003, // 远程RESET
CMD_REMOTE_PAUSE = 0x1004 // 远程PAUSE
};
// MC协议子命令
enum MCSubCommand {
SUBCMD_BIT = 0x0001, // 位设备
SUBCMD_WORD = 0x0000 // 字设备
};
// 地址转换
uint16_t GetMCAddress(SoftElementType type, uint16_t address);
// 协议常量
static const uint8_t ST_HEADER = 0x50; // 副头部
static const uint8_t ST_NETWORK = 0x00; // 网络号
static const uint8_t ST_PC = 0xFF; // PC号
static const uint8_t ST_MODULE = 0x03; // 模块IO号
static const uint8_t ST_MULTIDROP = 0x00; // 多点站号
};
#endif // MITSUBISHI_MC_SERIAL_H
MitsubishiMCSerial.cpp
#include "MitsubishiMCSerial.h"
MitsubishiMCSerial::MitsubishiMCSerial() {
SetPLCType(PLC_Q);
}
MitsubishiMCSerial::~MitsubishiMCSerial() {
ClosePort();
}
CommResult MitsubishiMCSerial::SendFrame(const DataFrame& frame) {
std::lock_guard<std::mutex> lock(commMutex);
// 构建MC协议帧
std::vector<uint8_t> sendData;
// 副头部
sendData.push_back(ST_HEADER);
// 网络号、PC号、模块IO号、多点站号
sendData.push_back(ST_NETWORK);
sendData.push_back(ST_PC);
sendData.push_back(ST_MODULE);
sendData.push_back(ST_MULTIDROP);
// 请求数据长度(小端序)
uint16_t dataLength = static_cast<uint16_t>(frame.data.size() + 12); // 固定部分12字节
sendData.push_back(dataLength & 0xFF);
sendData.push_back((dataLength >> 8) & 0xFF);
// CPU监视定时器
sendData.push_back(0x00); // 10秒
sendData.push_back(0x00);
// 命令
sendData.push_back(frame.command & 0xFF);
sendData.push_back((frame.command >> 8) & 0xFF);
// 子命令
sendData.push_back(0x00); // 字设备
sendData.push_back(0x00);
// 起始地址
sendData.push_back(frame.address & 0xFF);
sendData.push_back((frame.address >> 8) & 0xFF);
sendData.push_back((frame.address >> 16) & 0xFF);
sendData.push_back((frame.address >> 24) & 0xFF);
// 设备点数
sendData.push_back(frame.count & 0xFF);
sendData.push_back((frame.count >> 8) & 0xFF);
// 数据
sendData.insert(sendData.end(), frame.data.begin(), frame.data.end());
// 发送数据
if (!SendData(sendData.data(), static_cast<DWORD>(sendData.size()))) {
SetLastError("Failed to send MC protocol data");
return COMM_PORT_ERROR;
}
return COMM_SUCCESS;
}
CommResult MitsubishiMCSerial::ReceiveFrame(DataFrame& frame) {
std::lock_guard<std::mutex> lock(commMutex);
// 接收响应
uint8_t buffer[1024];
DWORD bytesRead = 0;
// 接收副头部
if (!ReceiveData(buffer, 1, 1000)) {
SetLastError("Failed to receive MC header");
return COMM_TIMEOUT;
}
if (buffer[0] != ST_HEADER) {
SetLastError("Invalid MC header");
return COMM_FORMAT_ERROR;
}
// 接收网络号等
if (!ReceiveData(buffer, 4, 1000)) {
SetLastError("Failed to receive network info");
return COMM_TIMEOUT;
}
// 接收数据长度
uint16_t dataLength;
if (!ReceiveData(reinterpret_cast<uint8_t*>(&dataLength), 2, 1000)) {
SetLastError("Failed to receive data length");
return COMM_TIMEOUT;
}
// 接收结束代码
uint16_t endCode;
if (!ReceiveData(reinterpret_cast<uint8_t*>(&endCode), 2, 1000)) {
SetLastError("Failed to receive end code");
return COMM_TIMEOUT;
}
// 检查结束代码
if (endCode != 0x0000) {
std::ostringstream oss;
oss << "MC protocol error: 0x" << std::hex << endCode;
SetLastError(oss.str());
return COMM_DEVICE_ERROR;
}
// 接收数据
if (dataLength > 2) {
frame.data.resize(dataLength - 2);
if (!ReceiveData(frame.data.data(), static_cast<DWORD>(frame.data.size()), 1000)) {
SetLastError("Failed to receive data");
return COMM_TIMEOUT;
}
}
return COMM_SUCCESS;
}
uint8_t MitsubishiMCSerial::CalculateChecksum(const DataFrame& frame) {
// MC协议使用CRC-16
uint16_t crc = 0xFFFF;
const uint8_t* data = frame.data.data();
size_t length = frame.data.size();
for (size_t i = 0; i < length; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return static_cast<uint8_t>(crc & 0xFF);
}
void MitsubishiMCSerial::BuildReadCommand(SoftElementType type, uint16_t address,
uint16_t count, DataFrame& frame) {
frame.command = CMD_BATCH_READ;
frame.address = GetMCAddress(type, address);
frame.count = count;
// 构建数据部分
frame.data.clear();
}
void MitsubishiMCSerial::BuildWriteCommand(SoftElementType type, uint16_t address,
const std::vector<int16_t>& values,
DataFrame& frame) {
frame.command = CMD_BATCH_WRITE;
frame.address = GetMCAddress(type, address);
frame.count = static_cast<uint16_t>(values.size());
// 构建数据部分
frame.data.resize(values.size() * 2);
for (size_t i = 0; i < values.size(); i++) {
frame.data[i * 2] = values[i] & 0xFF;
frame.data[i * 2 + 1] = (values[i] >> 8) & 0xFF;
}
}
uint16_t MitsubishiMCSerial::GetMCAddress(SoftElementType type, uint16_t address) {
switch (type) {
case SOFT_X: return 0x0098; // X区
case SOFT_Y: return 0x009C; // Y区
case SOFT_M: return 0x0090; // M区
case SOFT_D: return 0x0000; // D区
case SOFT_T: return 0x00C8; // T区
case SOFT_C: return 0x00CC; // C区
case SOFT_S: return 0x00A0; // S区
default: return 0x0000;
}
}
2.4 主程序实现 (main.cpp)
#include "MitsubishiFXSerial.h"
#include "MitsubishiMCSerial.h"
#include <iostream>
#include <iomanip>
#include <conio.h>
// 控制台颜色
enum ConsoleColor {
BLACK = 0,
BLUE = 1,
GREEN = 2,
CYAN = 3,
RED = 4,
MAGENTA = 5,
BROWN = 6,
LIGHTGRAY = 7,
DARKGRAY = 8,
LIGHTBLUE = 9,
LIGHTGREEN = 10,
LIGHTCYAN = 11,
LIGHTRED = 12,
LIGHTMAGENTA = 13,
YELLOW = 14,
WHITE = 15
};
void SetConsoleColor(ConsoleColor textColor, ConsoleColor bgColor = BLACK) {
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hConsole, (bgColor << 4) | textColor);
}
void ResetConsoleColor() {
SetConsoleColor(LIGHTGRAY, BLACK);
}
// 显示菜单
void ShowMenu() {
SetConsoleColor(LIGHTCYAN, BLACK);
std::cout << "==============================================" << std::endl;
std::cout << " 三菱PLC串口通信测试程序 v1.0" << std::endl;
std::cout << "==============================================" << std::endl;
ResetConsoleColor();
SetConsoleColor(YELLOW, BLACK);
std::cout << "\n选择PLC类型:" << std::endl;
std::cout << "1. FX系列 (编程口协议)" << std::endl;
std::cout << "2. Q系列 (MC协议)" << std::endl;
std::cout << "3. A系列" << std::endl;
std::cout << "4. L系列" << std::endl;
std::cout << "5. FX5U系列" << std::endl;
ResetConsoleColor();
SetConsoleColor(LIGHTGREEN, BLACK);
std::cout << "\n选择操作:" << std::endl;
std::cout << "a. 读取D寄存器" << std::endl;
std::cout << "b. 写入D寄存器" << std::endl;
std::cout << "c. 读取M继电器" << std::endl;
std::cout << "d. 写入M继电器" << std::endl;
std::cout << "e. 强制ON" << std::endl;
std::cout << "f. 强制OFF" << std::endl;
std::cout << "g. 读取PLC信息" << std::endl;
std::cout << "h. 监控模式" << std::endl;
std::cout << "i. 退出" << std::endl;
ResetConsoleColor();
}
// 测试函数
void TestFXSerial() {
MitsubishiFXSerial fxSerial;
// 打开串口
SetConsoleColor(LIGHTBLUE, BLACK);
std::cout << "\n正在连接FX PLC..." << std::endl;
ResetConsoleColor();
if (!fxSerial.OpenPort("COM1", 9600)) {
SetConsoleColor(LIGHTRED, BLACK);
std::cout << "无法打开串口 COM1!" << std::endl;
ResetConsoleColor();
return;
}
SetConsoleColor(LIGHTGREEN, BLACK);
std::cout << "串口已打开!" << std::endl;
ResetConsoleColor();
// 读取D100-D109
std::vector<int16_t> values;
CommResult result = fxSerial.ReadDevice(SOFT_D, 100, 10, values);
if (result == COMM_SUCCESS) {
SetConsoleColor(LIGHTGREEN, BLACK);
std::cout << "\n读取D100-D109成功:" << std::endl;
for (size_t i = 0; i < values.size(); i++) {
std::cout << "D" << 100 + i << " = " << values[i] << std::endl;
}
ResetConsoleColor();
} else {
SetConsoleColor(LIGHTRED, BLACK);
std::cout << "读取失败! 错误代码: " << result << std::endl;
std::cout << "错误信息: " << fxSerial.GetLastError() << std::endl;
ResetConsoleColor();
}
// 写入D100
std::vector<int16_t> writeValues = {1234};
result = fxSerial.WriteDevice(SOFT_D, 100, writeValues);
if (result == COMM_SUCCESS) {
SetConsoleColor(LIGHTGREEN, BLACK);
std::cout << "\n写入D100 = 1234 成功!" << std::endl;
ResetConsoleColor();
} else {
SetConsoleColor(LIGHTRED, BLACK);
std::cout << "写入失败!" << std::endl;
ResetConsoleColor();
}
// 读取M100-M109
std::vector<bool> bitValues;
result = fxSerial.ReadBitDevice(SOFT_M, 100, 10, bitValues);
if (result == COMM_SUCCESS) {
SetConsoleColor(LIGHTGREEN, BLACK);
std::cout << "\n读取M100-M109成功:" << std::endl;
for (size_t i = 0; i < bitValues.size(); i++) {
std::cout << "M" << 100 + i << " = " << (bitValues[i] ? "ON" : "OFF") << std::endl;
}
ResetConsoleColor();
}
fxSerial.ClosePort();
}
void TestMCSerial() {
MitsubishiMCSerial mcSerial;
// 打开串口
SetConsoleColor(LIGHTBLUE, BLACK);
std::cout << "\n正在连接Q PLC..." << std::endl;
ResetConsoleColor();
if (!mcSerial.OpenPort("COM1", 9600)) {
SetConsoleColor(LIGHTRED, BLACK);
std::cout << "无法打开串口 COM1!" << std::endl;
ResetConsoleColor();
return;
}
SetConsoleColor(LIGHTGREEN, BLACK);
std::cout << "串口已打开!" << std::endl;
ResetConsoleColor();
// 读取D100-D109
std::vector<int16_t> values;
CommResult result = mcSerial.ReadDevice(SOFT_D, 100, 10, values);
if (result == COMM_SUCCESS) {
SetConsoleColor(LIGHTGREEN, BLACK);
std::cout << "\n读取D100-D109成功:" << std::endl;
for (size_t i = 0; i < values.size(); i++) {
std::cout << "D" << 100 + i << " = " << values[i] << std::endl;
}
ResetConsoleColor();
} else {
SetConsoleColor(LIGHTRED, BLACK);
std::cout << "读取失败! 错误代码: " << result << std::endl;
std::cout << "错误信息: " << mcSerial.GetLastError() << std::endl;
ResetConsoleColor();
}
mcSerial.ClosePort();
}
int main() {
// 设置控制台标题
SetConsoleTitle(L"三菱PLC串口通信测试程序");
// 清屏
system("cls");
int plcType = 0;
char choice;
while (true) {
ShowMenu();
SetConsoleColor(LIGHTCYAN, BLACK);
std::cout << "\n请输入选择: ";
ResetConsoleColor();
std::cin >> choice;
if (choice == 'i' || choice == 'I') {
break;
}
if (plcType == 0) {
// 选择PLC类型
switch (choice) {
case '1': plcType = 1; break;
case '2': plcType = 2; break;
case '3': plcType = 3; break;
case '4': plcType = 4; break;
case '5': plcType = 5; break;
default:
SetConsoleColor(LIGHTRED, BLACK);
std::cout << "无效的选择!" << std::endl;
ResetConsoleColor();
break;
}
} else {
// 执行操作
switch (choice) {
case 'a':
if (plcType == 1) TestFXSerial();
else if (plcType == 2) TestMCSerial();
break;
case 'b':
// 写入操作
break;
case 'c':
// 读取M继电器
break;
case 'd':
// 写入M继电器
break;
case 'e':
// 强制ON
break;
case 'f':
// 强制OFF
break;
case 'g':
// 读取PLC信息
break;
case 'h':
// 监控模式
break;
default:
SetConsoleColor(LIGHTRED, BLACK);
std::cout << "无效的操作!" << std::endl;
ResetConsoleColor();
break;
}
}
std::cout << "\n按任意键继续...";
_getch();
system("cls");
}
SetConsoleColor(LIGHTGREEN, BLACK);
std::cout << "\n程序已退出。" << std::endl;
ResetConsoleColor();
return 0;
}
2.5 实用工具类 (PlcUtils.h)
#ifndef PLC_UTILS_H
#define PLC_UTILS_H
#include <string>
#include <vector>
#include <map>
#include <functional>
#include <memory>
// PLC工具类
class PlcUtils {
public:
// 地址解析
static bool ParseAddress(const std::string& addressStr,
SoftElementType& type, uint16_t& address);
// 数据类型转换
static int16_t BytesToInt16(const uint8_t* bytes);
static uint16_t BytesToUInt16(const uint8_t* bytes);
static int32_t BytesToInt32(const uint8_t* bytes);
static uint32_t BytesToUInt32(const uint8_t* bytes);
static float BytesToFloat(const uint8_t* bytes);
static double BytesToDouble(const uint8_t* bytes);
static void Int16ToBytes(int16_t value, uint8_t* bytes);
static void UInt16ToBytes(uint16_t value, uint8_t* bytes);
static void Int32ToBytes(int32_t value, uint8_t* bytes);
static void UInt32ToBytes(uint32_t value, uint8_t* bytes);
static void FloatToBytes(float value, uint8_t* bytes);
static void DoubleToBytes(double value, uint8_t* bytes);
// 数据格式化
static std::string FormatValue(int16_t value, const std::string& format = "");
static std::string FormatValue(uint16_t value, const std::string& format = "");
static std::string FormatValue(int32_t value, const std::string& format = "");
static std::string FormatValue(uint32_t value, const std::string& format = "");
static std::string FormatValue(float value, const std::string& format = "");
static std::string FormatValue(double value, const std::string& format = "");
// 日志
static void LogMessage(const std::string& message, bool error = false);
static void LogCommResult(CommResult result);
// 验证
static bool ValidateAddress(SoftElementType type, uint16_t address);
static bool ValidateData(const std::vector<int16_t>& values);
// 配置管理
static bool LoadConfig(const std::string& filename);
static bool SaveConfig(const std::string& filename);
private:
static std::map<std::string, SoftElementType> elementTypeMap;
static std::vector<std::string> logMessages;
};
#endif // PLC_UTILS_H
三、MFC界面版本
3.1 主对话框类 (CMitsubishiPlcDlg.h)
#pragma once
#include "afxwin.h"
#include "MitsubishiFXSerial.h"
#include "MitsubishiMCSerial.h"
// CMitsubishiPlcDlg 对话框
class CMitsubishiPlcDlg : public CDialogEx
{
DECLARE_DYNAMIC(CMitsubishiPlcDlg)
public:
CMitsubishiPlcDlg(CWnd* pParent = nullptr); // 标准构造函数
virtual ~CMitsubishiPlcDlg();
// 对话框数据
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_MITSUBISHIPLC_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
private:
// 控件变量
CComboBox m_comboPort;
CComboBox m_comboBaudRate;
CComboBox m_comboPLCType;
CEdit m_editAddress;
CEdit m_editCount;
CEdit m_editValue;
CListBox m_listLog;
CStatic m_staticStatus;
// PLC对象
std::unique_ptr<MitsubishiSerial> m_plc;
MitsubishiPLCType m_selectedPLCType;
// 线程
std::thread m_monitorThread;
std::atomic<bool> m_monitorRunning;
// 方法
void UpdateControls();
void LogMessage(const CString& message, bool error = false);
void UpdateStatus(const CString& status);
// 回调
void OnCommCallback(CommResult result);
public:
afx_msg void OnBnClickedBtnConnect();
afx_msg void OnBnClickedBtnDisconnect();
afx_msg void OnBnClickedBtnRead();
afx_msg void OnBnClickedBtnWrite();
afx_msg void OnBnClickedBtnForceOn();
afx_msg void OnBnClickedBtnForceOff();
afx_msg void OnBnClickedBtnMonitor();
afx_msg void OnCbnSelchangeComboPlcType();
afx_msg void OnTimer(UINT_PTR nIDEvent);
};
3.2 资源文件 (resource.h 和 MitsubishiPlc.rc)
// resource.h
#pragma once
#define IDD_MITSUBISHIPLC_DIALOG 101
#define IDC_COMBO_PORT 1001
#define IDC_COMBO_BAUDRATE 1002
#define IDC_COMBO_PLCTYPE 1003
#define IDC_EDIT_ADDRESS 1004
#define IDC_EDIT_COUNT 1005
#define IDC_EDIT_VALUE 1006
#define IDC_LIST_LOG 1007
#define IDC_STATIC_STATUS 1008
#define IDC_BTN_CONNECT 1009
#define IDC_BTN_DISCONNECT 1010
#define IDC_BTN_READ 1011
#define IDC_BTN_WRITE 1012
#define IDC_BTN_FORCEON 1013
#define IDC_BTN_FORCEOFF 1014
#define IDC_BTN_MONITOR 1015
参考代码 使用VC++软件对三菱PLC进行串口通信的软件编程 www.youwenfan.com/contentcsv/72051.html
四、项目配置
4.1 Visual Studio 项目设置
-
创建 Win32 控制台应用程序或 MFC 应用程序
-
添加上述所有源文件
-
配置项目属性:
- C/C++ → 常规 → 附加包含目录:添加项目目录
- C/C++ → 代码生成 → 运行库:选择 /MT 或 /MD
- 链接器 → 输入 → 附加依赖项:添加 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib
4.2 使用示例
// 简单使用示例
int main() {
// 创建FX PLC对象
MitsubishiFXSerial plc;
// 打开串口
if (!plc.OpenPort("COM1", 9600)) {
std::cout << "无法打开串口!" << std::endl;
return -1;
}
// 读取D100的值
std::vector<int16_t> values;
CommResult result = plc.ReadDevice(SOFT_D, 100, 1, values);
if (result == COMM_SUCCESS) {
std::cout << "D100 = " << values[0] << std::endl;
} else {
std::cout << "读取失败: " << plc.GetLastError() << std::endl;
}
// 写入D100
std::vector<int16_t> writeValues = {1234};
result = plc.WriteDevice(SOFT_D, 100, writeValues);
if (result == COMM_SUCCESS) {
std::cout << "写入成功!" << std::endl;
}
// 关闭串口
plc.ClosePort();
return 0;
}
五、注意事项
- 串口参数:三菱PLC通常使用 9600 波特率,7 数据位,1 停止位,偶校验
- 接线:使用 RS232 交叉线连接计算机和PLC
- 协议选择:FX系列使用编程口协议,Q系列使用MC协议
- 地址范围:不同PLC型号的地址范围不同,需要参考具体手册
- 超时设置:建议设置合理的超时时间,避免程序假死
- 错误处理:每次通信后检查返回结果,处理错误情况