VC++ 三菱 PLC 串口通信软件开发指南

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.hMitsubishiFXSerial.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.hMitsubishiMCSerial.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.hMitsubishiPlc.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 项目设置

  1. 创建 Win32 控制台应用程序或 MFC 应用程序

  2. 添加上述所有源文件

  3. 配置项目属性:

    • 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;
}

五、注意事项

  1. 串口参数:三菱PLC通常使用 9600 波特率,7 数据位,1 停止位,偶校验
  2. 接线:使用 RS232 交叉线连接计算机和PLC
  3. 协议选择:FX系列使用编程口协议,Q系列使用MC协议
  4. 地址范围:不同PLC型号的地址范围不同,需要参考具体手册
  5. 超时设置:建议设置合理的超时时间,避免程序假死
  6. 错误处理:每次通信后检查返回结果,处理错误情况

专注于matlab/simulink,电子电路,编程