Как стать автором
Обновить

ModBus Slave RTU/ASCII без смс и регистрации

Время на прочтение31 мин
Количество просмотров10K
Всего голосов 7: ↑5 и ↓2+6
Комментарии49

Комментарии 49

… и показала свою 142% работоспособность
Не стоит так — Вы же техническую статью пишите, а не протокол результатов голосования. Тем более для MODBUS, спецификации которого с тех пор, как он вышел за пределы контроллеров MODICON, стали трактоваться очень вольно. Сами же довольно вольно пишите о паузе, которая в спецификации MODBUS RTU оговорена строго в 3.5 символа, а на практике отнюдь не любой слейв откликается через такую задержку после чужой посылки, иногда и десятки миллисекунд приходится вводить. Так что я бы и 100% постеснялся писать.
Пожалуйста, не судите строго… это литературный прием, гипербола.
Да я это понял, но уж больно глаз резануло. Извините, что не сдержался.
И это если не иметь ввиду различные USB конверторы, у которых задержки вообще никак не нормируются.
Очень сильно режет глаза стиль форматирования кода.
Ни за что не буду использовать такие исходники, тем более, что сложностей с поддержкой Modbus никогда не возникает.

Прогоните через стилизатор.

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
int24
А разве так бывает???
Понятно, что через #define или typedef можно сделать все, что угодно, интересен практический смысл.
НЛО прилетело и опубликовало эту надпись здесь
Я сам сейчас настраиваю кодек для звука (микросхема CS43L22).
И, например, для I2S протокола независимо от формата кодирования, данные по DMA все равно передаются по 16 или 32 бита (даже при семпле 24 бита).
Поэтому и интересен практический смысл такого типа данных.
Могу рассказать свой пример. Я использовал тип uint24_t один раз в жизни. Делали очень простой контроллер для СКУД. Важна была цена, поэтому заложили очень простой микроконтроллер с минимальным объёмом памяти. При этом, чтобы обеспечить поддержку большего числа пользователей, для хранения кодов карт доступа использовали поле размеров в 24 бита. Карты были Em-Marine. В результате на 1000 пользователях экономия аж целый килобайт оказалась, но для контроллера это много :-)
Еще один пример UINT24 — это UEFI Firmware File System v2, где Intel не хватило одного байта в заголовке размером 24 байта, и чтобы не поплыло все выравнивание его решили откусить от размера файла, потому что 16 мегабайт (0xFFFFFF) должно хватить всем. В результате, понятно, не хватило, и пришлось выдумывать FFSv3, править все заголовки, обновлять все парсеры, и до сих пор еще встречаются прошивки, которые тома с v3 толком прожевать не могут и зависают в случайных местах.
Короче, если у вас там не прямо жесть-жесть, то экономить один байт, чтобы потом иметь геморрой и новый заголовок на восемь — это плохая негодная стратегия, не рекомендую.
НЛО прилетело и опубликовало эту надпись здесь
А вот я памятью все интереснее. Во-первых, процессоры обычно оперируют не единичными байтами, а машинными словами, поэтому за раз именно 24 бита из памяти вы не скопируете, придется копировать все 32. Но это, в принципе, не проблема, достаточно сделать сдвиг или битовую маску (в зависимости от того какая у нас endianness), тут ничего сложного.
При чтении памяти понятно, но как быль при записи в память?
НЛО прилетело и опубликовало эту надпись здесь
Конечно, в отрыве от конкретной задачи это обсуждение бессмысленно, но Вам не кажется странной оптимизацией заменять один цикл записи в память на чтение-модификация-запись ради экономии 25% объема памяти за счет более плотной упаковки? Да, объем памяти — ограниченный ресурс, но ведь и быстродействие тоже.
НЛО прилетело и опубликовало эту надпись здесь
Поэтому я сразу и оговорился про конкретную задачу, и речь шла именно о формате хранения данных в памяти, а не о вводе/выводе.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
На счёт int8_t, int16_t, int32_t абсолютно верно. Всё таки заявляется, что код может использоваться и на встраиваемых системах. Тут чёткость должна быть.
А вот на счёт табличного расчёта CRC не соглашусь. Скорость в таких вещах не так важна. Тут же всё упирается в канал связи, а там обычно не более 115200 бит/сек.
Зато обычный метод расчёта сильно экономит память, а для микроконтроллеров это важно, её там не особо много.
Почему вы не формируете ответ с кодом ошибки, если мастер неверно запросил данные?

//если неправильный адрес и количество
if((AdresBit+KolvoBit)>(ModBusMaxOutBit) || KolvoBit>ModBusMaxOutBitTX || KolvoBit==0)
{
       //неправильный адрес и количество
        CRCmodbus=0xFFFF; //установить начальное значение CRC
        return;//повторный запрос не требуется
}


Ведь по спецификации Modbus должен быть ответ в таком случае, а ваш slave молчит как партизан. Поди догадайся, то ли сетевой номер не совпадает, то ли с командой что то не то.
При неправильном адресе slave не должен ничего отвечать. Ведь таких устройств на шине может быть много, для каждого из них чужой адрес будет «неправильным». В этом случае получится коллизия на шине.
НЛО прилетело и опубликовало эту надпись здесь
А, тогда я неправильно понял. В этом случае да, слэйв обязан выдать ответ с ошибкой.
Вы неправильно понимаете.
Да, если адрес slave не совпадает с тем что запрашивает master надо молчать.
Но, если slave понимает что пакет адресуется ему и при этом он не может его обработать, он должен ответить коротким сообщением, в котором передаётся код ошибки.

Да, это верно. Спецификация Modbus подразумевает передачу слэйвом кода ошибки.
Моя практика показывает, что это не так уж и надо. А код раздувается.
В следующей версии сделаю как опцию.
Это надо, потому что это описано в спецификации, по которой производители программного обеспечения по всему миру пишут свой софт. Если вы ей не следуете, то не можете называть то что вы делаете modbus протоколом.
Как разработчик OPC сервера, использующего в своей работе Modbus в том числе, я часто сталкиваюсь с подобными поделками. Мне жалко конечного пользователя, который обычно остаётся с таким прибором один на один, стараемся вместе выкрутиться, приходится вносить изменения в собственное ПО, но к сожалению это не всегда получается. И всё только из за того что кто то решил
А, мне это не надо...

Вспомнил почему я не делал обработку ошибок!
Все дело в неоднозначности трактовок функций ошибок 2 (ILLEGAL_DATA_ADDRESS) и 3 (ILLEGAL_DATA_VALUE).
Каждый трактует их как хочет.
По большому счёту без разницы кто как трактует. Самое главное показать ошибку. Мастер от этого не сломается, зато человек сразу поймёт в каком месте проблема.
В натуре! сейчас проверил на Kepware,
задал мастеру блок чтения дискретных входов 16, а в слейву 8

что возвращаешь слайвом ILLEGAL_DATA_ADDRESS, что ILLEGAL_DATA_VALUE
Kepware пишет
Date Time Level User Name Source Event
05.11.2020 19:39:58 2 Default User Modbus Serial Bad address in block [000002 to 000012] on device 'c1.d1'
НЛО прилетело и опубликовало эту надпись здесь
В качестве «калибра» использовал OPCсервер Kepware. Методику тестирования описывать долго. Но покрытие 100%!
Ну что тут скажешь. Все уже сказали до меня. Мне остается только сказать так — вернитесь к этой статье лет через пять. Не плохо. Но и не хорошо. ModBus точно не из тех протоколов, для которых надо (и стоит) из главного цикла запускать ModBusRTU/ASCII(). Так как ModBus (почти?) всегда не единственная функция устройства, то времена начинают плавать. А это не здорово. Да, я понимаю — большинство кода в сети написано так. Но попробуйте однажды написать код так, чтоб в главном цикле остался только сброс watchdog'а. Первый раз это всегда сложно. Но поверьте — результат того стоит и вам точно понравится.
ModBus точно не из тех протоколов, для которых надо (и стоит) из главного цикла запускать ModBusRTU/ASCII(). Так как ModBus (почти?) всегда не единственная функция устройства, то времена начинают плавать. А это не здорово.

Я тоже так когда-то думал… Но лет 15 как отпустило…
Хорошо. Значит у каждого свой путь.
Но 15 лет практики и такой код… Кто-то из нас куда-то не туда свернул…
Без обид. Абсолютно ничего личного.
Некоторые изучают стили форматирования, правила именования переменных итд. итп. А некоторые просто пишут.
Нельзя же утверждать что Достоевский не писатель, на основании почерка.
Нет конечно. Хотя, полагаю, графологи могут с этим не согласиться. Да и помимо подчерка есть построение предложений, обороты речи, манера использования слогов, созвучных описываемым явлениям. Впрочем, оставим это литераторам, лингвистам, и прочим специалистам-криминалистам. Я к ним «примазываться» не собираюсь. Не мое.

Мне понравились битовые поля в коде. Не часто их используют, хотя инструмент очень мощный. У меня даже нет особых претензий к длинным функциям. Я, конечно, побил бы. Но с другой стороны… Слегка ужать стек заinline'ив все руками — разве кто-то может запретить такую микрооптимизацию? Да, я вижу даже те места, которые написаны в угоду переносимости и выглядят не шибко оптимально. Хорошо, если это все сделано осознано и с пониманием.

Но знаете что настораживает? Несопоставимость задачи и решения. Уж больно несерьезная задача для серьезного разработчика. Без откровенно новаторских идей. Собственно отсюда и мои «лет через пять». Потому я позволю себе оставить эту рекомендацию. По себе знаю. Тот код, который писался 5 лет назад, сегодня был бы написан похоже… но немного иначе. А за тот, который успешно отработал для гарантийных срока по 8 лет откровенно стыдно. И это не смотря на то, что и 20 лет назад код писался на том же голом С.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Знаете, я сам железячник. И в низкоуровневые программисты пришел из схемотехников. И с улыбкой встречаю рассказы веб-программистов о сложностях очередного PHP/JAVA/нужное-подчеркнуть фреймворка. И да, для меня тоже главный критерий это бессбойная работа 24x7x365xМНОГО. Если устройство работает не стабильно, периодически сбоит или неадекватно реагирует на «шум на входе» — значит устройство не работает. Пожалуй, это наша «профессиональная деформация». Главное ее признать и не выпячивать лишний раз. Тем более, что строго по Михалкову «мамы всякие нужны, мамы всякое важны». Мир из одних только железячников был бы откровенно страшен.

Потому я уже сожалею что запустил эту ветку. Давайте закругляться. Абсолютно ничего личного.
НЛО прилетело и опубликовало эту надпись здесь
Есть ещё одна, не сказать ошибка, скорее тонкость.
      if(CRCmodbus==0) 
        {//проверка на длинные пакеты
        if(PaketRX[1]==15 || PaketRX[1]==16)
          {//если длинные команды (15,16) , проверяем "Счетчик байт"
          if((PaketRX[6]+9)!=UkPaket) continue;
          }
        break; //Ура! Пакет принят!!!
        }


Вы окончание пакета определяете по совпадению контрольной суммы. Это не совсем корректно, на моей практике случалось, что у больших пакетов был такой набор данных что контрольная сумма совпадала в теле пакета, в результате поступали данные поступали некорректные.
По хорошему, необходимо анализировать служебную информацию, количество регистров, вычислять сколько должно быть байт в пакете, сличать что бы количество принятых байт было не меньше чем ожидается и только потом рассчитывать контрольную сумму.
Прошу прощения, оказывается есть такая проверка.
if((PaketRX[6]+9)!=UkPaket) continue;

Просто я дополнительно ещё и с количеством регистров на всякий случай сверяю.
дык это оно и есть.

там еще выше защита для коротких пакетов
if(UkPaket<8) continue;

Я ввожу дополнительную проверку ещё и потому, что в Modbus минимальный элемент имеет размер в 2 байта и запросто может оказаться так, что в результате ошибки мастер выдаст нечетное количество байт.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории