Как читать в регистре хранения реле MiCOM с C# (ModBus RTU через связь RS-485) - PullRequest
0 голосов
/ 29 февраля 2020

Я пытался связать P C с MiCOM P127 Relay (master-slave) по протоколу Modbus RTU в RS-485. Я использовал Visual Studio C#, чтобы создать программу для чтения регистра хранения устройства. Я хочу прочитать с 0080-0081, => 0089 адрес из этого руководства

enter image description here enter image description here

Вот часть кода в Visual Studio

 public class ModbusRTUProtocol
{
    // Declares variables
    private byte slaveAddress = 1;
    private byte function = 3;// 
    private ushort startAddress = 128;//0080 hex => 128 dec
    private uint _NumberOfPoints = 3;

    private SerialPort serialPort1 = null;
    private List<Register> _Registers = new List<Register>();

    public ModbusRTUProtocol(uint pNumberOfPoints)
    {
        this.NumberOfPoints = pNumberOfPoints;
        for (int i = 0; i < this.NumberOfPoints; i++)
        {
            _Registers.Add(new Register() { Address = (ushort)(startAddress + i) }); // cast type to ushort.
        }
    }

    /// <summary>
    /// Starts Modbus RTU Service.
    /// </summary>
    public void Start()
    {
        try
        {
            serialPort1 = new SerialPort("COM7", 19200, Parity.None, 8, StopBits.One);
            if (serialPort1.IsOpen) serialPort1.Close();
            serialPort1.Open();
            ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
            {
                while (true)
                {
                    if (serialPort1.IsOpen)
                    {
                        byte[] frame = ReadHoldingRegistersMsg(slaveAddress, startAddress, function, NumberOfPoints);
                        serialPort1.Write(frame, 0, frame.Length);
                        Thread.Sleep(100); // Delay 100ms
                        if (serialPort1.BytesToRead >= 5)
                        {
                            byte[] bufferReceiver = new byte[this.serialPort1.BytesToRead];
                            serialPort1.Read(bufferReceiver, 0, serialPort1.BytesToRead);
                            serialPort1.DiscardInBuffer();

                            // Process data.
                            byte[] data = new byte[bufferReceiver.Length - 5];
                            Array.Copy(bufferReceiver, 3, data, 0, data.Length);
                            UInt16[] result = Word.ByteToUInt16(data);
                            for (int i = 0; i < result.Length; i++)
                            {
                                Registers[i].Value = result[i];
                            }
                        }
                    }
                    Thread.Sleep(20); // Delay 20ms
                }
            }));
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
    private byte[] ReadHoldingRegistersMsg(byte slaveAddress, ushort startAddress, byte function, uint numberOfPoints)
    {
        byte[] frame = new byte[8];
        frame[0] = slaveAddress;                // Slave Address
        frame[1] = function;                    // Function             
        frame[2] = (byte)(startAddress >> 8);   // Starting Address High
        frame[3] = (byte)startAddress;          // Starting Address Low            
        frame[4] = (byte)(numberOfPoints >> 8); // Quantity of Registers High
        frame[5] = (byte)numberOfPoints;        // Quantity of Registers Low
        byte[] crc = this.CalculateCRC(frame);  // Calculate CRC.
        frame[frame.Length - 2] = crc[0];       // Error Check Low
        frame[frame.Length - 1] = crc[1];       // Error Check High
        return frame;
    }

    private byte[] CalculateCRC(byte[] data)
    {
        ushort CRCFull = 0xFFFF; // Set the 16-bit register (CRC register) = FFFFH.
        byte CRCHigh = 0xFF, CRCLow = 0xFF;
        char CRCLSB;
        byte[] CRC = new byte[2];
        for (int i = 0; i < (data.Length) - 2; i++)
        {
            CRCFull = (ushort)(CRCFull ^ data[i]); // 

            for (int j = 0; j < 8; j++)
            {
                CRCLSB = (char)(CRCFull & 0x0001);
                CRCFull = (ushort)((CRCFull >> 1) & 0x7FFF);

                if (CRCLSB == 1)
                    CRCFull = (ushort)(CRCFull ^ 0xA001);
            }
        }
        CRC[1] = CRCHigh = (byte)((CRCFull >> 8) & 0xFF);
        CRC[0] = CRCLow = (byte)(CRCFull & 0xFF);
        return CRC;
    }

    public uint NumberOfPoints
    {
        get
        {
            return _NumberOfPoints;
        }

        set
        {
            _NumberOfPoints = value;
        }
    }

    public List<Register> Registers
    {
        get
        {
            return _Registers;
        }

        set
        {
            _Registers = value;
        }
    }
}

Источник здесь

Значение в этом приложении отличается от реального значения на устройстве enter image description here

Пожалуйста, помогите мне. Как мне отредактировать этот код, чтобы получить правильные данные? Спасибо

1 Ответ

1 голос
/ 01 марта 2020

Из руководства вы связали:

F18A Длинное целое без знака: цифра c данные: от 0 до (2E32 -1)

Итак, мы имеем дело с 32-битное число хранится в двух регистрах. Это означает, что нам нужно подумать о кодировании. стандарт Modbus гласит:

MODBUS использует представление "big-Endian" для адресов и элементов данных.

поэтому порядок байтов в регистре установлен (и это реализовано в вашей функции ReadHoldingRegistersMsg). Однако в нем не указано, как объединить несколько регистров, чтобы можно было обрабатывать большие числа. Я просмотрел предоставленное вами руководство и, возможно, не увидел упоминаний об этом, но попробовал две опции, указывающие на порядок little-endian (что означает, что второй регистр является старшей частью числа).

Получение данных со скриншота. Зарегистрируйте 0080 = 21210 и зарегистрируйте 0081 = 5. Предполагая, что используется формат с прямым порядком байтов, равный (5 * 65536) + 21210 = 348890 (примечание: это проще визуализировать в шестнадцатеричном формате hi = 0x05 , низкий = 0x52DA, результат = 0x552DA). Поскольку в документации указано, что эта цифра составляет 10 мВ, мы делим ее на 100 000, чтобы получить кв. 488 кВ (ожидаемые 3,5 кВ).

Проверка следующих двух; 0082 = 21789; 0083 = 5 (5 * 65536) + 21789 = 349 469. Преобразование в 3,49469 кВ (ожидаемое 3,5 кВ). Для полноты U C работает как 3.49007 кВ (ожидаемое 3,5 кВ).

Теперь давайте посмотрим на регистр 88 - 6.06 кВ; из ручного формата F1:

Целое число без знака: числовые данные от 0 до 65535

Таким образом, это должны быть прямые напряжения:

0088 - 6351 = 6,351 кВ (ожидаемое 6,06)
0089 - 6358 = 6,358 кВ (ожидаемое 6,07)
0090 - 6343 = 6,343 кВ (ожидаемое 6,06)

Приведенное выше не является точным совпадением с указанными вами значениями, но я подозреваю, что это может быть связано со временем (вам нужно будет провести некоторое тестирование). Надеюсь, это даст вам отправную точку.

...