Не могу разобраться с Modbus RTU и 485 интерфейсом ...
- Войдите на сайт для отправки комментариев
Пт, 14/10/2016 - 17:37
Всем привет!
есть (как мне казалась) простая задача: удаленный мониторинг и примитивное управление термостатом с 485 интерфейсом. Собрал стенд, прогнал телеграммы, подключив USB-RS485 конвертер, через TERMITE (Modbus RTU terminal), все хорошо. В комментах, в начале программы, я указал образец сеанса связи ....
Теперь начал дружить контроллер с Ардуиной, но не тут то было. Не понимают они друг друга хоть убей. Вот код (начал с примитива, что бы понять, работает ли данный механизм), который почему то работает по принципу: туда дуй, а от туда ... В общем, уважаемые форумчане, если у кого был опыт работы с Modbus RTU, подскажите, в чем моя ошибка?
// // // все эксперименты на Arduino MEGA2560 // в арсенале: Ардуино Мега 2560, TTL to RS-485 module, программатор AVRISP MK II, термостат Pego EXPERT NANO 3CF // настройки порта термостата: 9600/None/8/One // протокол обмена: Modbus RTU (Req type = Raw, CRC order MSB -> LSB) // боевая задача: удаленно смотреть t и менять уставку (либо +30С либо +40С) // телеграммы термостата известны: // запрос температуры : 01 03 01 00 00 01 85 F6 // ответ термостата : 01 03 02 01 0C B9 D1 (01 0С = 268 = 26.8С) // запрос уставки : 01 03 03 00 00 01 84 4E // ответ термостата : 01 03 02 01 2C B8 09 (01 2С = 300 = 30.0С) // изменение уставки : 01 06 03 00 01 90 88 72 (01 90 = 400 = 40.0С) // ответ термостата : 01 06 03 00 01 90 88 72 (01 90 = 400 = 40.0С) // запрос уставки : 01 03 03 00 00 01 84 4E // ответ термостата : 01 03 02 01 90 B9 B8 (01 90 = 400 = 40.0С) // ... // и так далее ... // // byte TelLen; unsigned char SerialData[28]; void setup() { pinMode(13,OUTPUT); // пин, отвечающий за прием/передачу 485 модуля digitalWrite(13,LOW); // LOW = прием Serial.begin(9600); // мониторинг происходящего во время отладки Serial1.begin(9600); // к этому порту подключен преобразователь 485 интерфейса } void loop() { digitalWrite(13,HIGH); // включаем режим передачи по 485 интерфейсу // имитирую телеграмму запроса температуры. // Естественно в нормаьной программе эта часть кода будет выглядеть иначе, // например вычисление CRC ... Serial1.print(01,HEX); Serial1.print(03,HEX); Serial1.print(01,HEX); Serial1.print(00,HEX); Serial1.print(00,HEX); Serial1.print(01,HEX); Serial1.print(133,HEX); Serial1.print(246,HEX); // для наглядности повторяю в порт мониторинга Serial.print(01,HEX); delay(1); Serial.print(03,HEX); delay(1); Serial.print(01,HEX); delay(1); Serial.print(00,HEX); delay(1); Serial.print(00,HEX); delay(1); Serial.print(01,HEX); delay(1); Serial.print(133,HEX); delay(1); Serial.println(246,HEX); delay(1); Serial.println("---"); digitalWrite(13,LOW); delay(100); if (Serial1.available()) { TelLen = 0; while(Serial1.available() && TelLen<8) { SerialData[TelLen] = (char)Serial1.read(); TelLen++; } } // в порт мониторинга шлю ответ от контроллера for(byte i=0;i<TelLen;i++) { Serial.print(SerialData[i]); } Serial.println(" "); }
Заранее благодарен за дельные ответы.
Modbus RTU использует бинарный формат данных, т.е. "сырые" байты. А вы тут строки посылаете. Посмотрите в описаниях к классу Serial как записать в порт байт или буфер. Я бы использовал write(byte) вместо print(), а ещё лучше буфер передать, тогда не будет задержек между байтами.
Похоже Вы правы!
Вот экран осциллографа, когда я с компьютера посылал телеграммы и получал вразумительные ответы от термостата:
а вот что я получил, когда пытался отправить с Ардуино:
буду рыть в сторону формата отправляемых данных ...
Если у вас есть два конвертера, то осциллограф тут не нужен. Соедините оба конвертера со стороны RS-485, поставьте на компе программку Eltima Software Serial Port Monitor и какой-нибудь эмулятор modbus. Можно для начала и без эмулятора slave. Запустите ардуину и слушайте порт в мониторе (можно даже открыть его, если сервера нет). Там в окне дампа будут видны ваши посылки.
Нельзя просто так послать фрейм модбас. Возьмите библиотеку - например здесь - http://arduino.ru/forum/programmirovanie/modbus-rs485-gotovyi-shild и все проблемы уйдут.
Можно посылать.
прошу не считать меня троллем, но вот так:
не работает ...
Проще всего доискиваться причин, когда есть данные с линии. Я правда могу исходник в Proteus промоделировать и узнать в чём дело, но мне лень и лучше вам самому доработать испытательный стенд по моим рекомендациям выше. USB-RS-485 нужно соединить с RS-485 - TTL - Arduino (соединение A-A, B-B). Со стороны USB получается COM-порт, я надеюсь. Вот к этому порту нужно подключить программный монитор порта. Запустив скетч arduino вы увидите _что_ на линии есть непосредственно.
А код с виду правильный теперь, только можно использовать другую форму вызова функции write: write( SendData, 8 ); , т.е. без использования цикла. Он там уже реализован.
П.С. Нельзя мешать в кучу вывод байта в порт RS и на терминал. Сначала выводим в порт RS, а потом отображаем в терминале. Только вот в терминал как раз можно и нужно посылать строки, иначе вы ничего не увидите. Для Serial1 используем write( buffer, count ), а для Serial используем цикл print( byte, HEX ).
А как же тайминги, прописанные в стандарте? Их как выдерживать?
А какие там тайминги? Интервал 3,5 символа от символьной скорости? Его и рассчитать можно, тем более на скорости 9600. Нужно посылать запросы с интервалом не менее 3,5 символа на выбранной символьной скорости, иначе посылки будут склеиваться на приёмной стороне. Под символом здесь понимается "битовый символ", т.е. электрический эквивалент 0 или 1 на линии.
Что касается write( buffer, count ) нужно делать ещё Serial1.flush(), т.е. подождать, пока передача не будет завершена. Запись байта непосредственно в порт идёт только в случае, если внутренний буфер и буфер USART пустые.
А ответ принять и не замесить на 485? Но это так...
Готовую посылку можно отправить через Serial.write, лучше в виде Serial.write(buf, len) и сразу ждать ответ. Ответ может быть разный. Если ошибка короче, нормальный длиннее. Но по хорошему нужно получить второй байт, убедится что не равен 0х83, получить третий байт, если не 0х83 второй, то считать еще третий байт * 2. Посчитать контрольную сумму и сравнить с принятой. Если совпадает, то использовать полученное значение. Иначе повторить запрос. Как то так.
фигушки. ни Serial1.write(SendData,8);
ни Serial1.write(SendData[i]); в порт ничего не посылают ...
осциллограф на АВ телеграмм никаких не показывает. Вот.
Смешно в другом: если к порту Serial1 относиться как просто
к порту, то и телеграммы он принимает и отправляет запросы... красота в общем.
Но стоит на него нацепить RS485 интерфейс, как он становится глух и нем...
До принятия тут ещё далеко, с передачей бы разобраться. А принимаем в обратном порядке: очищаем буфер приёма, ждём до 3.5 символа, если во время ожидания в буфере появились данные, то прибавляем их к принятым ранее и опять ждём до 3.5 символа. Если по завершению ожидания приёмный буфер пустой, то приняли все данные. Далее разбираем что пришло.
3.5 символа это пауза между посылками. Если есть такая пауза, то нужно уже обрабатывать и слать ответ.
Глух и нем бывает только если не правильный конвертер TTL-RS485 прицепить. К каким пинам у вас TTL-RS485 подключён? В спокойном состоянии обе стороны должны слушать линию. (У вас именно так). Попробуйте просто между компьютером и ардуиной через компорт и 485 пообщаться.
вот я ГОБЛИН !!! стал разбираться - перепутал RX и TX когда думая что у меня трэбл с модулем, менял один на другой ... Да:
телеграммы шлет исправно. Рою землю дальше .....
Ура! Заработало !!! (с) Шарик из Простоквашино
Код (говнокод если быть точным) шлет 1 единственную телеграмму и получает от контроллера правильный ответ !!!
Но! Пока CRC я не допилил (не сложно, в инете полно примеров) мусор не отсеивается. Скриншот работы:
и позорище:
Пока так. Не ругайтесь на меня за аппаратную ошибку, виноват. В следующий раз буду внимательнее.
3,5 символа - это признак окончания передачи. Каждый байт посылки можно передавать с паузой до 3.5 символа, если вы превысите этот интервал, то приемная сторона будет считать это окончанием передачи. Отсюда и получается алгоритм приёма: ждём каждый байт 3.5 символа и не нужны ни какие лишние проверки, они делаются уже по факту приёма полного пакета. Насколько мне помнится такой алгоритм используется в freemodbus.
Так, похоже я ошибался. Решил ещё раз вернуться к стандарту (MODBUS over Serial Line Specification and Implementation Guide V1.0). Почему-то я раньше считал, что интервал 3.5 относится к битовой скорости. Судя по всему 1 символ = 11 битам (включая служебные), иначе не понять как детектировать межсимвольные ошибки (интервал t1.5).
Если у вас есть доступ к прерываниям по принятию байта, то алгоритм можно реализовать по описанию тут. Цитирую:
"Прошло t3.5 -> ждем старт-бита. Дождались старт-бита, принимаем символ. Если во время приема символа возникла ошибка t1.5 - отбрасываем и символ и фрейм, ждем t3.5 снова.
Если всё нормально -> принимаем следующий символ. Приняли фрейм без t1.5 ошибок -> ждем t3.5 и следующего фрейма."
Если у вас нет доступа к прерываниям, а работа идёт при помощи высокоуровневых функций чтения (fread(), к примеру, или приём реализован на скриптах), то низкоуровневый алгоритм вы реализовать не сможете, зато можно считать частью фрейма текущие данные в приёмном буфере и отсчитывать только 3,5 символьный интервал. В этом случае вы должны принимать данные до тех пор, пока не обнаружите 3,5 символьную "тишину" на линии. Это признак окончания передачи.
Конкретно в случае с Adruino, если не хотите писать свой обработчик прерывания, то нужно действовать по второму, описанному мной алгоритму. Сбрасываем приёмный буфер, ждём 3,5 символа, но мелкими интервалами, проверяя каждый раз есть ли данные в приёмном буфере, если есть, то добавляем их к ранее принятым и обнуляем счётчик ожидания, если нет, то продолжаем ждать все 3,5 символа. Если дождались до конца и приёмный буфер пуст - приняты все данные.
Описанный мною алгоритм приёма возникает из-за буферизации при приёме. Т.е. это чисто программная реализация. Если бы у нас была возможность точно знать момент приёма байта (используя прерывание), то мы могли бы что-нибудь придумать по поводу отслеживания 1,5 интервала. Поэтому мы используем 3,5 интервал для частей фрейма, которыми могут быть и отдельные байты.
Нужно ещё сделать оговорку. Ожидание в 3,5 символа при программной реализации работает только при реализации мастера или при единственном slave на линии. Мастер не ожидает других пакетов, поэтому может ждать сколько хочет. Единственный slave может спокойно ждать окончания текущего запроса от мастера на линии, т.к. он предназначен ему. В других ситуациях, описанных тут, такая реализация алгоритма для slave не подойдёт.
внимательно слежу за темой, так как у самого руки не доходят занятся передачей данных, вот у меня есть вопрос немного не в тему, как передать строку такого вида t1=24.2,t2=65,v1=5,v2=12.6
внимательно слежу за темой, так как у самого руки не доходят занятся передачей данных, вот у меня есть вопрос немного не в тему, как передать строку такого вида t1=24.2,t2=65,v1=5,v2=12.6
Создайте отдельную тему с полным описанием задачи. В modbus данные представляют из себя либо 16 разрядные регистры, либо биты. Для остальных случаев нужно придумывать функцию приёма/передачи строки и формат такой передачи, либо интерпретацию последовательности регистров, что не очень удобно. Это выходит за рамки типовых решений для mobus.
понял что со строкой можно наковыряться очень долго, а при помощи modbus как показания передать? если количество датчиков пока не известно (не больше 10-15) и тип у них будет int, float, bool ? возможно ли такое?
Ой, как хорошо! CRC чек отсеивает всякую ерунду и в монитор валятся только ПРАВИЛЬНЫЕ телеграммы !!!
Все жужжит как нужно, осталось:
а) оформить чтение модбас в виде функции (сейчас код в loop() )
б) написать функцию запись в регистры (думаю справлюсь, хотя черт его знает ... не серчайте, если что буду спрашивать)
в) подключить дисплей 1602 (вообще нет проблем)
г) даже не знаю что и писать. вроде все.
Вопрос: уважаемые Гуру! подскажите подавану, а зачем вообще нужны библиотеки работы с Modbus, если все действия (чтения/записи) осуществляются с помощью стандартных, нехитрых команд и алгоритмов? или я что то не понимаю?
Вопрос: уважаемые Гуру! подскажите подавану, а зачем вообще нужны библиотеки работы с Modbus, если все действия (чтения/записи) осуществляются с помощью стандартных, нехитрых команд и алгоритмов? или я что то не понимаю?
Ваш случай - частный. Master и один slave. Библиотеки содержат реализации для более сложных (общих) случаев. Там есть узкие места, которые требуют чётко придерживаться стандарта. Кроме того, в библиотеках реализован общий подход для разных функций Modbus, чтобы пользователь не думал о внутренней кухне, всех этих байтах, проверках, интервалах и пр.
Для простых нужд, как я писал выше, можно реализовать мастера и с помощью простого скрипта на bash в Linux.
понял что со строкой можно наковыряться очень долго, а при помощи modbus как показания передать? если количество датчиков пока не известно (не больше 10-15) и тип у них будет int, float, bool ? возможно ли такое?
Введение в протокол Modbus. Часть 1.
Введение в протокол Modbus. Часть 2.
понял что со строкой можно наковыряться очень долго, а при помощи modbus как показания передать? если количество датчиков пока не известно (не больше 10-15) и тип у них будет int, float, bool ? возможно ли такое?
Да. Возможно. Главное достоинство модбас очень простой, помехоустойчивый способ передачи информации между компьютером и множеством датчиков. Недостаток - только два типа данных - биты и 16 разрядные слова. Для передачи других типов их надо приводить к этим двум, а потом восстанавливать. Сделать это просто. Регистры MODBUS это просто массив uint16_t . Ничего не мешает заполнить этот массив вашей строкой t1=24.2,t2=65,v1=5,v2=12.6 и прочитать его. Или регистры заполнить данными, а строку сформировать после получения данных. Регистров более 65000. Пока не сталкивался с ситуацией чтобы их нехватало.
Или регистры заполнить данными, а строку сформировать после получения данных.
похоже самый удобный вариант подсказали, осталось разобраться как показания записать в регистр (причем несколько) а потом их считать, примерчик не подкинете случаем?
Выдалось полчасика, сделал набросок. Работает. Температуру читает и показывает. Приятно.
Код далек от совершенства, но работает и достаточно прост для понимания "чайниками", такими как я. Завтра буду писать функцию записи (CRC нужно будет вычислять до передачи в функцию записи, а в остальном думаю не сложно .......)
Или регистры заполнить данными, а строку сформировать после получения данных.
похоже самый удобный вариант подсказали, осталось разобраться как показания записать в регистр (причем несколько) а потом их считать, примерчик не подкинете случаем?
https://github.com/smarmengol/Modbus-Master-Slave-for-Arduino/blob/maste...
Посмотрите здесь. Хорошая библиотека. В примере выдаётся массив 16 элементов по запросу мастера. Если в цикле loop массиву присваивать значения датчиков, то мастер будет получать актуальные значения. По стандарту массив может иметь более 65000 элементов, но считывать за один раз можно не более 100. Если нужно больше то частями по 100.
Кому не спится в ночь глухую? ... таксисту!, жулику! и ....! я тоже между прочим не сплю!
Вот что получилось в итоге (пока мониторю через сериал порт):
вот что на рабочем столе:
ну и код соответственно (може кто чего для себя полезного узнает ... хотя и так все понятно, особенно теперь):
Здравствуйте. Не могли бы Вы, написать контакты, по которым можно с Вами связаться? Очень нужно у Вас уточнить кое-какие вопросы. Спасибо.
оживлю немного темку.
есть некое устройство, назовем его контроллер. мастер. его можно спросить... он должен ответить... все верно ?
тоже интересно, например как настроить com port
Протокол как бы стандартный? т.е. в ячейке 0х85 например всегда данные температуры.
битый час пытаюсь заставить устройство отвечать..
Дело не в том как настроить порт. Главное какие байты послать для того,что бы устройство ответило. Поставте на комп программу QMODBUS. Установите скорость порта, выберите команду читать, выберите ячейку 85 и отправте запрос. Еслиустройство ответит,то в логах на экране будут цифры посланные для запроса и цифры ответа. Ихбудет гораздо больше чем вы ожидаете. Это потому что протокол. Их и надо посылать,что бы устройство ответило.
Подниму старую тему, она очень схожа с моей проблемой. Так же пытаюсь управлять терморегулятором по RS485 через ModBus RTU. С компьютера через терминал устройства отлично работают. Но при попытке ими управлять через Arduino, что то идет не так, в терминале видно что передача исправно идет с верными данными, но несмотря на это термостат не отвечает. Байты отправляю на прямую с интервалом 10 мс, но пробовал разные значения
Подозреваю что проблема в таймингах, подскажите, пожалуйста, как будет правильнее отправлять запрос и получать ответ? Стоит использовать готовую библиотеку? Какую посоветуете?
Спасибо!
Делей лишний. Почитайте стандарт. Я не точно не помню, но чтотто типа 2.5 мс между символами это ошибка передачи.
Юморист :) А чтение у вас где ?
Кроме того, нога чтение-запись, вы где ее дергаете ? У вас шилд какой ? Если это нога 13, то как вы убеждаетесь, что у вас все данные ушли ?
Чтение от сюда убрал, разобраться бы сначала с отправкой, нога 13 все верно, убеждаюсь подключаясь через usb-rs485 конвертер вижу в терминале верную посылку, но термостат на это не реагирует. По всему видимо проблема в таймингах, терминалу то на них все равно, а термостату важно.. Какую библиотеку попробовать, чтобы иметь возможность отправлять просто произвольные 8 байт?
Вы что думаете я вам ваш код скопировал. Посмотрите внимательнее.
Немного отличается задержками, но результат тот же, в терминал приходит, но термостат такие команды игнорирует.
Прочитать 1 регистр первого слейва с адреса 1 будет последовательность байт ( без контрольной суммы) 01 03 00 01 00 01 . У Вас 01 03 01 00 00 01 - с адреса 4096. Точно по этому адресу у слейва есть информация?
У моего термостата своеобразный взгляд на ModBus RTU, и даже на подсчет контрольной суммы, или просто я не понимаю их.. https://ae01.alicdn.com/kf/HTB1.1GEKFXXXXXpXVXXq6xXFXXXl/220662474/HTB1.1GEKFXXXXXpXVXXq6xXFXXXl.jpg?size=392926&height=1852&width=1000&hash=0f7201225c6cd1c424e54a58e9d192b4
Удалось достучаться до него, и получить какой то ответ, не очень стабильно но что то получается, буду разбираться в этом протоколе, и пытаться извлечь температуру..
Это не rtu. Ни слова про тайминги и кодировку.
Да там об этом нет, но в названии термостата упоминалось именно ModBus RTU, так что предполагаю это такая вариация, с измененным порядком байт и другим способом подсчета контрольной суммы..
Вариации быть не может. Модбас это стандарт с точным описанием. А то как в анекдоте - ...и не преф а в очко.
У вас устройство работает на расширении мод баса. Иными словами, модбас допускает использование расширенного перечня команд, но должно поддерживать и стандартные. Ваше устройство стандартные не поддерживает.
Из вашего описания видно, что термостат принимает принимает команды от 0xA0 до 0хA7. Эти команды не описаны в стандарте модбас, но допускается , что такие команды могут быть активированы производителем. Далее контрольная сумма вычисляется по правилам модбас (не стал углубляться, но очень похоже). Далее, в руководстве же написано , что дефолтная скорость передачи 4800.
Теперь ответьте на вопросы:
ПОЧЕМУ ВЫ НАСТРАИВАЕТЕ ПОРТ НА СКОРОСТЬ 9600 ?
ПОЧЕМУ ВЫ ВЫДАЕТЕ ФУНКЦИЮ 1, тогда как судя по описанию устройство этих функций не обрабатывает ?
ПОЧЕМУ ВЫ СЧИТАЕТЕ, ЧТО АДРЕС ВАШЕГО ТЕРОМОСТАТА 1 ?
ПОЧЕМУ ВЫ СПРАШИВАЕТЕ, имея описание, но не читая его ?
Устройство перевёл на 9600, это допускается по описанию, и удобнее учитывая другие термостаты на 9600 на линии, но так же пробовал соединяться и на исходных 4800, оба варианта работают одинаково не стабильно.
Запросы посылал совершенно разные, отвечает на A0 01 01 00 00 00 00 07 и устанавливает температуру 25 градусов: A6 01 01 00 00 19 00 64, при этом, контрольные суммы тут не сходятся с теми что должны быть по стандарту ModBus.
Адрес моего термостата состоит из двух байт оба 01, адрес отправляется вторым и третьим байтом запроса.
Читал, но плохо понимаю, не могу понять как считать контрольную сумму, это какой то стандарт? Существует ли готовый калькулятор? Не понимаю как вытащить из ответа 50 01 01 71 00 19 17 56 температуру и режим работы.
Контрольная сумма, чего уж проще:
Читал, но плохо понимаю, не могу понять как считать контрольную сумму, это какой то стандарт? Существует ли готовый калькулятор? Не понимаю как вытащить из ответа 50 01 01 71 00 19 17 56 температуру и режим работы.
На какой запрос пришел этот ответ ?
Запросить все данные, судя по описанию, вы можете командой A0 01 01 00 00 00 00 07 , в ответ должен прийти пакет 50 01 01 ?? ?? ?? ?? CS
А вот расшифровка ответа именно на команду A0 в вашем руководстве отсутствует.
Да спасибо, 50 01 01 71 00 19 17 56 получаю в ответ на A0 01 01 00 00 00 00 07, соответственно 71 00 19 содержит всю информацию об устройстве..
Нет вся инфа будет в 4 байтах.
Не ну я не понял - угадал я или нет ?