PureBasic - форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » PureBasic - форум » Вопросы по PureBasic » Modbus RTU - не получаю данные


Modbus RTU - не получаю данные

Сообщений 1 страница 13 из 13

1

Всем доброго времени суток.

Делаю Modbus RTU клиента для опроса одного устройства.
В терминале видно, что программа шлет запрос, но ответ от устройства не совсем то что нужно.
Кое что взял со сторонних форумов.
Где что упустил - поправьте пожалуйста:

Код:
; Настройки Modbus
Define deviceAddress.b = 6
Define registerStart.b = 0    ; Начальный Holding Register
Define registerCount.b = 10   ; Количество Holding Registers для опроса

; Настройки COM-порта

#ComPort = 1; Идентификатор COM порта
Define baudRate.b = 9600      ; Скорость передачи данных
Define parity.b = 0           ; Четность: 0 - нет, 1 - нечетность, 2 - четность
Define dataBits.b = 8         ; Количество бит данных
Define stopBits.b = 1         ; Количество стоп-битов

; Функция расчета CRC-16
Procedure.i CalculateCRC(buffer, length)
  Define.i crc = $FFFF
  Define.i i, j
  For i = 0 To length - 1
    crc = crc ! PeekB(buffer + i) ; Используем оператор XOR (!)
    For j = 0 To 7
      If (crc & $0001) <> 0
        crc = (crc >> 1) ! $A001
      Else
        crc = crc >> 1
      EndIf
    Next
  Next
  ProcedureReturn crc
EndProcedure


; Инициализация COM-порта
;If OpenSerialPort(comPort, "COM1", baudRate, parity, dataBits, stopBits)
 If OpenSerialPort(#ComPort, "COM1", baudRate, parity, 8, stopBits, #PB_SerialPort_NoHandshake, 255, 255)
  ; Отправка запроса на чтение Holding Registers
  Define requestSize.b = 8
  Define responseSize.b = 50
  Dim requestBuffer.b(requestSize)
  Dim responseBuffer.b(responseSize)
  
  ; Создание запроса (Формат: [Адрес устройства] [Функция] [Начальный регистр] [Количество регистров])
  requestBuffer(0) = deviceAddress
  requestBuffer(1) = 3   ; Функция "Чтение Holding Registers"
  PokeW(@requestBuffer(2), registerStart)    ; Начальный регистр
  PokeW(@requestBuffer(4), registerCount)    ; Количество регистров
  
  ; Рассчитать CRC
  Define crc = CalculateCRC(@requestBuffer(0), requestSize - 2)
  PokeW(@requestBuffer(requestSize - 2), crc) ; CRC-16
  
  ; Отправка запроса
  WriteSerialPortData(#ComPort, @requestBuffer(0), requestSize)
  
  ; Ожидание ответа
  If ReadSerialPortData(#ComPort, @responseBuffer(0), responseSize) >0
    ; Проверка CRC
    If CalculateCRC(@responseBuffer(0), responseSize - 2) = PeekW(@responseBuffer(responseSize - 2))
      ; Обработка ответа
      For i = 0 To registerCount - 1
        Define registerValue = PeekW(@responseBuffer(3 + i * 2))
        Debug "Register " + Str(i) + ": " + Str(registerValue)
      Next
    Else
      Debug "CRC Check Failed"
    EndIf
  Else
    Debug "No Response from Device"
  EndIf
  
  CloseSerialPort(comPort)
Else
  Debug "Failed to Open Serial Port"
EndIf

0

2

newmayer написал(а):

; Функция расчета CRC-16

Должны быть другие типы переменных иначе результат может быть не правильным.

Код:
; Функция расчета CRC-16
Procedure.u CalculateCRC(*buffer, length)
  Protected crc.u = $FFFF
  Protected.i i, j
  For i = 0 To length - 1
    crc = crc ! PeekA(*buffer + i) ; Используем оператор XOR (!)
    For j = 0 To 7
      If (crc & $0001) <> 0
        crc = (crc >> 1) ! $A001
      Else
        crc = crc >> 1
      EndIf
    Next
  Next
  ProcedureReturn crc
EndProcedure
newmayer написал(а):

; Создание запроса (Формат: [Адрес устройства] [Функция] [Начальный регистр] [Количество регистров])
  requestBuffer(0) = deviceAddress
  requestBuffer(1) = 3   ; Функция "Чтение Holding Registers"
  PokeW(@requestBuffer(2), registerStart)    ; Начальный регистр
  PokeW(@requestBuffer(4), registerCount)    ; Количество регистров

Это лучше оформить в виде структуры

Код:
Structure RTU_Cmd
  Address.a   ; Адрес устройства
  Func.a      ; Функция
  StartReg.u  ; Начальный регистр
  CountReg.u  ; Количество регистров
EndStructure

Заполнение и отправка данных

Код:
*Cmd.RTU_Cmd = @requestBuffer(0)

With *Cmd
  \Address = deviceAddress
  \Func = 3                 ; Функция "Чтение Holding Registers"
  \StartReg = registerStart ; Начальный регистр
  \CountReg = registerCount ; Количество регистров
EndWith

Тип .b замените на .a
Там где должно быть 2 байта, используйте тип .u а не .w за исключением случаев когда число знаковое.

0

3

Немного доработал код.
Теперь все читает корректно Holding Registers - функция 3 с адреса 1 по 10

После приема данных получаю

Register 0: 32001
Register 1: 31489
Register 2: 32257
Register 3: 24320
Register 4: 24576
Register 5: 21760
Register 6: 56576
Register 7: 56320
Register 8: 56064
Register 9: 12800

После преобразования получаю реальные данные

Register 1: 381 - вольт
Register 2: 379 - вольт
Register 3: 382 - вольт
Register 4: 95 - ток потребления
Register 5: 96 - ток потребления
Register 6: 85 - ток потребления
Register 7: 221 - фаза А относительно нуля
Register 8: 220 - фаза В относительно нуля
Register 9: 219 - фаза С относительно нуля
Register 10: 50 - частота в герцах

Код:
; Функция расчета CRC-16
Procedure.u CalculateCRC(*buffer, length)
  Protected crc.u = $FFFF
  Protected.i i, j
  For i = 0 To length - 1
    crc = crc ! PeekA(*buffer + i) ; Используем оператор XOR (!)
    For j = 0 To 7
      If (crc & $0001) <> 0
        crc = (crc >> 1) ! $A001
      Else
        crc = crc >> 1
      EndIf
    Next
  Next
  ProcedureReturn crc
EndProcedure

; Настройки Modbus
Define.i deviceAddress = 6
Define.i registerStart = 0    ; Начальный Holding Register
Define.i registerCount = 10      ; Количество Holding Registers для опроса

; Настройки COM-порта
#ComPort = 1
Define.i baudRate = 9600      ; Скорость передачи данных
Define.i parity = #PB_SerialPort_NoParity ; Четность
Define.i dataBits = 8         ; Количество бит данных
Define.i stopBits = 1         ; Количество стоп-битов

; Основной код программы
If OpenSerialPort(#ComPort, "COM1", baudRate, parity, 8, stopBits, #PB_SerialPort_NoHandshake, 255, 255)
  ; Отправка запроса на чтение Holding Registers
  Define.i requestSize = 8
  Define.i responseSize = 25
  Dim requestBuffer.a(requestSize)
  Dim responseBuffer.a(responseSize)
  
  ; Создание запроса (Формат: [Адрес устройства] [Функция] [Начальный регистр старший] [Начальный регистр младший] [Количество регистров старший] [Количество регистров младший])
  requestBuffer(0) = deviceAddress                      ; Адрес устройства
  requestBuffer(1) = 3                                 ; Функция "Чтение Holding Registers"
  requestBuffer(2) = (registerStart >> 8) & $FF         ; Начальный регистр старший байт
  requestBuffer(3) = registerStart & $FF                ; Начальный регистр младший байт
  requestBuffer(4) = (registerCount >> 8) & $FF         ; Количество регистров старший байт
  requestBuffer(5) = registerCount & $FF                ; Количество регистров младший байт
  
  ; Рассчитать CRC
  Define.i crc = CalculateCRC(@requestBuffer(0), requestSize - 2)
  PokeW(@requestBuffer(requestSize - 2), crc) ; CRC-16 (в конец запроса)
  
  ; Отправка запроса
  WriteSerialPortData(#ComPort, @requestBuffer(0), requestSize)
  
  ; Ожидание ответа
  If ReadSerialPortData(#ComPort, @responseBuffer(0), responseSize) > 0
    ; Обработка ответа
    For i.i = 0 To registerCount - 1
      Define.i registerValue = PeekU(@responseBuffer(3 + i * 2))
      Debug "Register " + Str(i) + ": " + Str(registerValue)
    Next
    Define.i registerValue
    
  ;Преобразование данных в понятный вид
registerValue = (PeekA(@responseBuffer(3)) << 8) | PeekA(@responseBuffer(4))
Debug "Register 1: " + Str(registerValue)

registerValue = (PeekA(@responseBuffer(5)) << 8) | PeekA(@responseBuffer(6))
Debug "Register 2: " + Str(registerValue)

registerValue = (PeekA(@responseBuffer(7)) << 8) | PeekA(@responseBuffer(8))
Debug "Register 3: " + Str(registerValue)

registerValue = (PeekA(@responseBuffer(9)) << 8) | PeekA(@responseBuffer(10))
Debug "Register 4: " + Str(registerValue)

registerValue = (PeekA(@responseBuffer(11)) << 8) | PeekA(@responseBuffer(12))
Debug "Register 5: " + Str(registerValue)

registerValue = (PeekA(@responseBuffer(13)) << 8) | PeekA(@responseBuffer(14))
Debug "Register 6: " + Str(registerValue)

registerValue = (PeekA(@responseBuffer(15)) << 8) | PeekA(@responseBuffer(16))
Debug "Register 7: " + Str(registerValue)

registerValue = (PeekA(@responseBuffer(17)) << 8) | PeekA(@responseBuffer(18))
Debug "Register 8: " + Str(registerValue)

registerValue = (PeekA(@responseBuffer(19)) << 8) | PeekA(@responseBuffer(20))
Debug "Register 9: " + Str(registerValue)

registerValue = (PeekA(@responseBuffer(21)) << 8) | PeekA(@responseBuffer(22))
Debug "Register 10: " + Str(registerValue)
  Else
    Debug "No Response from Device"
  EndIf
  
  CloseSerialPort(#ComPort)
Else
  Debug "Failed to Open Serial Port"
EndIf

Помогите "Преобразование данных " засунуть в цикл, для упрощения и минимизации кода

Ну и по рекомендации Пётр буду еще дорабатывать

0

4

newmayer написал(а):

Немного доработал код.
Теперь все читает корректно Holding Registers - функция 3 с адреса 1 по 10

После приема данных получаю

Register 0: 32001
Register 1: 31489
Register 2: 32257
Register 3: 24320
Register 4: 24576
Register 5: 21760
Register 6: 56576
Register 7: 56320
Register 8: 56064
Register 9: 12800

После преобразования получаю реальные данные

Register 1: 381 - вольт
Register 2: 379 - вольт
Register 3: 382 - вольт
Register 4: 95 - ток потребления
Register 5: 96 - ток потребления
Register 6: 85 - ток потребления
Register 7: 221 - фаза А относительно нуля
Register 8: 220 - фаза В относительно нуля
Register 9: 219 - фаза С относительно нуля
Register 10: 50 - частота в герцах

Помогите "Преобразование данных " засунуть в цикл, для упрощения и минимизации кода

Ну и по рекомендации Пётр буду еще дорабатывать

Кстати проверку CRC пока что убрал, а то какая то каша получается

0

5

В общем готовое решение такое:
Принимаем данные в Holding Register

Если какие то замечания пишите.

Код:
; Функция расчета CRC-16
Procedure.u CalculateCRC(*buffer, length)
  Protected crc.u = $FFFF
  Protected.i i, j
  For i = 0 To length - 1
    crc = crc ! PeekA(*buffer + i) ; Используем оператор XOR (!)
    For j = 0 To 7
      If (crc & $0001) <> 0
        crc = (crc >> 1) ! $A001
      Else
        crc = crc >> 1
      EndIf
    Next
  Next
  ProcedureReturn crc
EndProcedure

Global Dim registerValue(22)

; Настройки Modbus
Define.i deviceAddress = 3
Define.i registerStart = 0    ; Начальный Holding Register
Define.i registerCount = 10   ; Количество Holding Registers для опроса

; Настройки COM-порта
#ComPort = 1
Define.i baudRate = 9600      ; Скорость передачи данных
Define.i parity = #PB_SerialPort_NoParity ; Четность
Define.i dataBits = 8                     ; Количество бит данных
Define.i stopBits = 1                     ; Количество стоп-битов

; Основной код программы
If OpenSerialPort(#ComPort, "COM1", baudRate, parity, 8, stopBits, #PB_SerialPort_NoHandshake, 255, 255)
  ; Отправка запроса на чтение Holding Registers
  Define.i requestSize = 8
  Define.i responseSize = 25
  Dim requestBuffer.a(requestSize)
  Dim responseBuffer.a(responseSize)
  Repeat 
    ; Создание запроса (Формат: [Адрес устройства] [Функция] [Начальный регистр старший] [Начальный регистр младший] [Количество регистров старший] [Количество регистров младший])
    
    requestBuffer(0) = deviceAddress                      ; Адрес устройства
    requestBuffer(1) = 3                                  ; Функция "Чтение Holding Registers"
    requestBuffer(2) = (registerStart >> 8) & $FF         ; Начальный регистр старший байт
    requestBuffer(3) = registerStart & $FF                ; Начальный регистр младший байт
    requestBuffer(4) = (registerCount >> 8) & $FF         ; Количество регистров старший байт
    requestBuffer(5) = registerCount & $FF                ; Количество регистров младший байт
    
    ; Рассчитать CRC
    Define.i crc = CalculateCRC(@requestBuffer(0), requestSize - 2)
    PokeW(@requestBuffer(requestSize - 2), crc) ; CRC-16 (в конец запроса)
    
    
    
    ; Отправка запроса
    WriteSerialPortData(#ComPort, @requestBuffer(0), requestSize)
    
    ; Ожидание ответа
    If AvailableSerialPortInput(#ComPort) >= requestSize
      If ReadSerialPortData(#ComPort, @responseBuffer(0), responseSize) > 0
        ; Обработка ответа
        For i.i = 0 To registerCount - 1
          Define.i registerValue = PeekU(@responseBuffer(3 + i * 2))
        Next
        
        ;Dim registerValue(22)
        Dim inv_(22)
        ;Преобразование данных в понятный вид
        For r.b = 3 To 22 Step 2
          r1.b = r + 1
          registerValue = (PeekA(@responseBuffer(r)) << 8) | PeekA(@responseBuffer(r1))  
          Debug "Register " + Str(i) + ": " + Str(registerValue) ; Проверяем полученные данные
        Next 
        
      Else
        Debug "No Response from Device"
      EndIf
    Else
      Debug "Not data available on serial port"
    EndIf
    
    Delay(1000)
    
    
  Until registerValue(21) > 60
  
  CloseSerialPort(#ComPort)
Else
  Debug "Failed to Open Serial Port"
EndIf

Отредактировано newmayer (29.08.2024 09:33:28)

0

6

newmayer написал(а):

В общем готовое решение такое:
Принимаем данные в Holding Register

Если какие то замечания пишите.

Проверка на CRC нету!!!

0

7

Попробуйте с такой процедурой https://www.purebasic.fr/english/viewto … 56#p626456

Код:
Procedure.u ModBus_CalcCRC(*Ptr, Len.i)
  Protected CRC_Value.u
  Protected i.i
  CRC_Value.u = $FFFF
  Len - 1 ; means Len = Len - 1
  For i = 0 To Len
    CRC_Value = (CRC_Value >> 8) ! PeekU(?ModBus_CRCTable + (PeekA(*Ptr + i) ! (CRC_Value & $FF)) << 1)
  Next i
  ProcedureReturn CRC_Value
EndProcedure

0

8

Пётр написал(а):

Попробуйте с такой процедурой https://www.purebasic.fr/english/viewto … 56#p626456

Спасибо попробую.

0

9

newmayer написал(а):

newmayer написал(а):

    Немного доработал код.
    Теперь все читает корректно Holding Registers - функция 3 с адреса 1 по 10

    После приема данных получаю

    Register 0: 32001
    Register 1: 31489
    Register 2: 32257
    Register 3: 24320
    Register 4: 24576
    Register 5: 21760
    Register 6: 56576
    Register 7: 56320
    Register 8: 56064
    Register 9: 12800

    После преобразования получаю реальные данные

    Register 1: 381 - вольт
    Register 2: 379 - вольт
    Register 3: 382 - вольт
    Register 4: 95 - ток потребления
    Register 5: 96 - ток потребления
    Register 6: 85 - ток потребления
    Register 7: 221 - фаза А относительно нуля
    Register 8: 220 - фаза В относительно нуля
    Register 9: 219 - фаза С относительно нуля
    Register 10: 50 - частота в герцах

    Помогите "Преобразование данных " засунуть в цикл, для упрощения и минимизации кода

    Ну и по рекомендации Пётр буду еще дорабатывать

newmayer, а не подскажете, что это за устройство такое интересное ???
Если не секрет конечно. Оно "на борту" уже имеет UART ???

0

10

Это Солнечный трехфазный инвертор Deye
у него на борту есть RS485 ModBus RTU
Делаю для себя программулю для опроса инверторов.

У меня таких около 50 шт раскиданы нескольким территориям
Есть идея сделать что то на подобие СКАДЫ с клиент сервером

Отредактировано newmayer (30.08.2024 08:51:38)

0

11

newmayer

спасибо за ответ. Нет, это немного не то. Инвертор мне не нужен, а только измеритель напряжения, тока, частоты.

0

12

Пар написал(а):

newmayer

спасибо за ответ. Нет, это немного не то. Инвертор мне не нужен, а только измеритель напряжения, тока, частоты.

Что за утстройство у Вас? Если интересно - то можно попробовать адаптировать программу для Вас. С возможностью визуализации данных.

0

13

newmayer написал(а):

Что за утстройство у Вас? Если интересно - то можно попробовать адаптировать программу для Вас. С возможностью визуализации данных.

newmayer, ОГРОМНОЕ спасибо за желание помочь !    :cool:

Но я пока что только прикидываю стоит ли вообще затевать это мероприятие. Устройство - распред. щиток с АВР'ом (промышленный). Чем с него можно снять параметры сети для дальнейшей обработки полученных данных уже в своей программе ? Сеть - трёхфазная, два ввода + ДГУ. Потребители в сумме "кушают" не слабо. Общая потребляемая мощность исчисляется...  скажем так...  порядок - десятки кВт. Но дело тут даже не в этом и не в получении разрешения на проведение данных работ, хотя...  это тоже немаловажный фактор, если не сказать решающий!
Просматривая этот форум я давно уже заприметил и положил себе в закладки (до лучших времён) одну замечательную тему. Там наш Уважаемый Пётр поделился замечательной идеей + программой + схемой подключения, как можно получать данные о температуре от внешнего датчика. В общем, как говорится, было бы желание. Так вот...  я и хотел собрать все данные воедино. Меня интересовали: температура, влажность и собственно параметры сети. В той теме Пётр также предлагал задействовать "малинку" aka raspberry pi, чтобы не гонять цельный компьютер. Это целесообразно, если данные нужны 24/7. А это именно мой случай. Плюс объект удалённый. Так, что "малинка" как нельзя кстати здесь приходится. Но посмотрев цены на неё... я немного опечалился, честно говоря. Да и жирно это получится, если её использовать только для снятия данных о температуре окружающей среды. Плюс ко всему - Linux, с которым мне ещё не приходилось сталкиваться. Как-то так, если вкратце.

0

Похожие темы


Вы здесь » PureBasic - форум » Вопросы по PureBasic » Modbus RTU - не получаю данные