Comments 24
MB_USART->CR1 = 0;
MB_USART->CR1 |= (USART_CR1_TE | USART_CR1_RE | USART_CR1_RTOIE);
Зачем тут чтение из CR1? Вы же его обнулили только что, можно просто присваивать.
Ну это я так, просто на глаза попалось :)
А все остальное… Скажите, а за что вы так не любите структуры? Вам самому не страшно от конструкций типа *((uint16_t*)&frame[len — 2])? И да, применительно к ModBus на ARM. Есть замечательная коллекция инструкций REV (к слову, накрытая CMSIS и приведенная к удобным макросам) позволяющая эффективно крутить последовательность байт на лету.
Интересно, заметил что не один раз уже прочёл спецификацию на протокол Modbus в текущей версии.
В ней есть Очень много чего интересного, о чем многие не подозревают.
Не только «Modbus Serial Line Protocol and Implementation Guide V1.02»
но и «MODBUS Security Protocol» само по себе — интересное чтиво, а уж сколько всего интересного таит в себе «MODBUS Protocol Specification» и «MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE V1.0b ». Как говориться "… О сколько нам открытий чудных. Готовят просвещенья дух..." (с) А.С.Пушкин
И беда с этой шиной всегда одна — на столе работает, «в поле» нет. Чаще всего первопричина в таймингах и методике разрешения коллизий. Многие производители, как и автор этой статьи, спецификацию читают очень выборочно. В итоге появляется некоторый «зоопарк», в который надо вклиниться своим изделием. Отчасти по этой причине все чаще приходиться наблюдать рядок конвертеров TCP/RTU с последующим подключением точка-точка. Это медленнее и дороже, но работает сразу после сборки чаще.
И беда с этой шиной всегда одна — на столе работает, «в поле» нет.
Про 485 Не совсем согласен.
Почему? Вот работаю сейчас в компании выпускающей ОПС. При этом в, пожалуй, самой большой по пожарной сигнализации. Ну то-есть вот 70% всех систем пожарной сигнализации (и какое то количество охранных) так или иначе на этом заводе сделаны. Все так или иначе завязаны на 485-й интерфейс.
Это Очень много. Это прямо Огромные цифры по оборудованию, даже в месяц.
Диспетчеризации 27/7. Вполне себе в основном стабильно может работать.
(А самом деле и автоматизации, ибо даже клапана дымоудаления в высотках многоквартирных и станции пожарные — тоже Автоматизация. а их Очень Много.)
Тут скорее вопрос к реализации протоколов на шине, и шириной трактовки протокола,
которая в многих протоколах сужена, с определенными целями.
А так то Modbus вполне себе современен, и TSL вроде даже может в нём быть.
P.S. смешал тёплое с зеленым, но смысл в том, что 485 вполне себе в больших объёмах применяют и в достаточно надежных применениях.
Промышленное оборудование — цена ошибки убыток. Сертификация проще, но спрос на стабильность шкалящий. Должно работать годами без сбоев. Ибо ремонт — это простой, а значит убытки.
А есть транспорт. При чем не транспорт-транспорт, а сервис. Типа табло прибытия или бегущей строки с остановками. Поломалось — и гори оно…
Вам повезло. В дикой природе вы сталкиваетесь в основном с первым. Я — в основном с третьим. Ваши реализации, которые «в поле» работали нестабильно были зарезаны на корню еще на выходе из лаборатории. А то, с чем я имею дело… Увы, но подход «х?? к, х?? к — и в продакшн (с)» просто повсеместен. И, если совсем честно, пусть так и остается. Не дай бог в РЕАЛЬНО ответственных сферах будет такой бардак.
Что до ModBus — так подавляющее большинство реализаций плавать хотела на правила нарезки пакетов в RTU. Даже libMODBUS (по крайней мере некоторое время назад — сейчас не знаю) резала их по формату, а не по таймингам. В итоге как только интенсивность обмена превышает некоторый предел все разваливается. Следующая засада — игнор ответов. Типа я послал выставить значения регистров и в домике. Получили их, не получили — мне пофиг. Ну и шлю, разумеется когда хочу, а не когда можно. Эта статья как раз такие вещи содержит. А посмотрите на первый же комментарий: «в закладки». А значит рано или поздно или конкретно эта, или на нее похожая реализация встретится в дикой природе.
Извините за некропостинг, но хотел бы задать несколько философский вопрос. Регистры содержат управляющие значения, т.е. запись в регистр должна вызывать изменение поведения. Как это делается наиболее фэншуйно? Я вижу 3 варианта:
Привязываем к регистру или группе регистров коллбек и вызываем его внутри той же MB_WriteRegHandler, т.е. сначала применили обновления, потом записали
Наоборот, сначала записали, потом применили. Например, крутится другой таск, и в MB_WriteRegHandler в нее через очередь или нотификацию передается пара (адрес, новое значение).
Примерно как 2: существует копия регистров, которая "на самом деле", и таск через определенные интервалы или по извещению сравнивает публичную и теневую таблицы регистров и применяет диффы.
Как поступать правильнее?
Мне кажется тут сильно зависит от контекста.
Если говорить об реализации в устройствах устройствах - тогда возможно более правильно работать по прерыванию, в прерывании проверяем что это не ошибка, кладём в отдельный буфер, и возвращаем управление обратно и потом в общем цикле программы уже смотрим что и как.
Если вообще философски мне кажется- то скажем запись в регистр, допустим с номером 7, положим какого то числа DA, допустим функцией 6 - совсем не означает что к регистру с номером 5 можно вообще обратиться за чтением функцией 3,
И совсем не означает что можно применить групповое чтение или запись, включающее этот регистр.
Это в свою очередь означает, что один адрес на чтение и запись может использоваться вообще с разным смыслом.
да, Идеально когда система отработает и потом выставляет результат работы в регистр (приём байтов получаем ответом), Но это ,строго говоря не всегда применимо.
как пример из реальности - запрашивается по модбасу в преобразователя протоколов (есть такой в опс-е у меня) состояние зоны извещателя (датчика) -записывается номер датчика из конфигурации, допустим 18. Модбас устройство, при очередном цикле опроса мастером Опс-ной системе (а это другой круг интерфейса и данных) - передаёт тому, другому мастеру - " алё, чего там с датчиком"?. он, этот мастер, когда придёт его время с его приоритетом - спросит конкретное устройство - " чо там, как , хоть параметр скинь?" а устройство к примеру со своей 1-wire подобной шиной, которую целиком опрашивает в зависимости от количества датчиков и их типов и вообще запроса статуса - либо сразу из таблицы, либо если идёт дотошно спрашивает - до 5-и секунд. и потом обратно.
То-есть реальный ответ может придти и через секунду и через 10.
И конечно откидываться ошибками 06(обработка команды) и 15(данные пока не получены) можно, но это же ещё у нас к примеру разделено на два регистра, в один выставляешь что надо, в другом читаешь.
Вот только не все системы и плк могут обработать ошибку и в соответствии с ней изменить порядок опроса...
ну это я так вижу, есть более каноничные видения, на самом деле.
Извините, я просто не очень глубоко в теме, так что лучше переспрошу.
Вот у нас есть асинхронный канал связи, например, rs485, и период опроса, пусть будет 1 секунда. Тогда, если мы используем один регистр и для отдачи команд, и для получения результата, мы должны успеть сделать все необходимое (применить настройки) за период опроса, или вернуть ошибку. Но мы можем использовать один регистр для отдачи команд, а другой для отслеживания их фактического применения, и тогда мы не ограничены периодом опроса, правильно я понял?
именно так.
Мне кажется не зачем ограничивать одним регистром.
Опять же можно даже выделить отдельный регистр Модбас для счётчика команд.
Более того, можно такой счётчик повесить на каждый регистр, распределив каким то логичным образом адресное пространство.
Регистров можно сделать много, и всё зависит от потребности.
Мне кажется не стоит впадать в крайность, когда одним регистром и на чтение и на запись делается вообще всё - можно прийти к другому примеру
)не помню название датчика газа) - у него на чтение и на запись был один 4-х байтный регистр.
первый Бит означал сработку-несработку с момента последнего сброса сработок, естественно тот же бит на запись сбрасывал,
потом со второго Бита по какой то другой - было число - задаваемый порог. после порога шёл ещё один бит флага доступности, потом шли сколько то бит которые означали порог принижения. Примерно так - и надо было разбирать это число по битам. то ещё занятие, если честно. Особенно учитывая то, что можно было хотя бы по разным регистрам разнести кучу данных. Огромный минус таких решений - они не работают на простых панелях визуализации, которые не умеют в логику .
Спасибо, тогда для конфигурации сделаю две таблицы с разным смещением, одна на запись (отдачу команд), другая на чтение (статус). Тогда можно будет в комбинации freertos-freemodbus в колбеке писать в очередь "изменение такого-то регистра" (в фримодбас колбеки исполняются в контексте прерывания), а в отдельной таске ртоса разбирать очередь и применять изменения.
Извиняйте, у меня отпуск. В это время я редко бываю за ПК и обычно не занимаюсь ничем, кроме отдыха.
В целом вам абсолютно верно ответили - все зависит от контекста. Спецификация говорит лишь о том, что необходим ответ. Когда именно будет произведено изменение - типичное UB (неопределенное поведение, зависящее от конкретной реализации). Соответственно любой не противоречащий спецификации вариант вполне допустим. Но это хорошо, что Вы на этот момент обратили внимание. В природе будут встречатся самы разные реализации. И к этому надо быть готовым.
А так, здесь это не нужно, можно считать crc всего frame, при корректной crc — результат будет 0.
Вам самому не страшно от конструкций типа *((uint16_t*)&frame[len — 2])?
Не страшно, если только этим не пренебрегать =) Но не очень понимаю, как тут «натянуть» структуру на фрейм переменной длины. Если поделитесь, то буду только рад.
typedef struct {
uint8_t addr;
uint8_t function;
} modbus_rtu_header;
typedef struct queryPresetMultipleegisters {
modbus_rtu_header header;
uint16_t regStart;
uint16_t regNumbers;
uint8_t ByteCount; /* ByteCount = query.RegNumbers * 2 */
uint16_t raw[]; /* raw[query.regNumbers] - CRC */
} qPresetMultipleRegisters;
И вуаля… q->raw[0], q->raw[[1]… q->raw[q->regNumbers] — все наше. И даже дальше, но мы ж дальше не полезем ;-) При упаковке все то же самое в обратном порядке. CRC заполняется последним. Магия языка С (наложении структуры на массив) собственной персоной. Настоятельно рекомендую. Создать переменную типа qPresetMultipleRegisters не получится. Компилятор совершенно резонно заметит, что не знает сколько памяти под нее выделять. А вот массив байт привести к типу этой переменной и потом с ней работать — это запросто.
P.S.
Про тайминги. 3,5 — межпакетный, 1,5 межсимвольный. Если межсимвольный превышен (но межпакетный не достигнут), то пакет считается неживым и подлежит утилизации.
Создать переменную типа qPresetMultipleRegisters не получится. Компилятор совершенно резонно заметит, что не знает сколько памяти под нее выделять.
Отчего же не создаст, еще как создаст и даже ругаться не будет. Ведь
uint16_t raw[];
эквиалентно uint16_t* raw;
. А размер указателя на uint16_t извествен. Про тайминги. 3,5 — межпакетный, 1,5 межсимвольный. Если межсимвольный превышен (но межпакетный не достигнут), то пакет считается неживым и подлежит утилизации.
Да подлежит, но только после того, как будет выждан интервал 3.5 тоже. Как может быть достигнут межпакетный интервал в (3.5) без достижения межсимвольного (1.5) ?)
Как может быть достигнут межпакетный интервал в (3.5) без достижения межсимвольного (1.5) ?)
Абсолютно верно! Но именно по этой причине стоит проверять тот факт, что между отсечками в 3,5 символа была только одна отсечка в 1,5 символа. На самом деле все еще интереснее. 1,5 и 3,5 в спецификации это он конца до начала. Начало не всякий контроллер фиксировать умеет (и STM32 вроде как раз не умеет прерывание по START-биту делать). Потому фиксация межу концами. А значит не 1,5 и 3,5 а 2,5 и 4,5. А вот при таком раскладе наложения уже не получается. Потому ровно один.
Отчего же не создаст, еще как создаст и даже ругаться не будет.
Попробуйте. Но уверяю вас,
struct {
char data[];
} abc;
struct {
char *data;
} bbc;
С точки зрения компилятора совершенно разные структуры. Размер второй он знает точно. А на счет размера первой у него нет никаких мыслей. И это абсолютно правильно.
q->raw[q->regNumbers]Выглядит словно выход за пределы массива. Хотя, там будет 1-й байт CRC, по идее.
И еще — вы абсолютно правы — ножик острый. И не выход за пределы массива целиком и полностью ответственность автора. А не компилятора, или еще каких-то средств контроля (которые не бесплатны в части производительности и расхода памяти). Привет низкоуровневые языки. Но именно за то их и любят.
У меня сейчас в «опекунстве» в том числе интересный прибор — С2000-ПП — преобразование из событийного протокола (охранно-пожарного) в Modbus. И в нём реализованы тоже интересные механизмы — запрос буфера событий (до 256 событий в кольцевом буфере) 6-ю регистрами к примеру.
Проблемы с такими устройствами у пользователей существенная — не всегда ПЛК или опросчик (OPC сервер или программа) может полностью удовлетворить условию обработки данных и ошибок. Нередко опрос пытаются свести к простому перебору регистров в неизвестном последовательном порядке.
Год назад видел датчик газа — у него один регистр. первый бит отвечает за флаг сработки, второй бит за флаг настройки, дальше из 8-и бит надо собрать число которое будет уставкой, следующие биты уже не помню, но в целом документацию читать было достаточно весело.
Эхх, я даже недавно статью сюда на эту тему написал в песочницу, но ещё не изучил форматирование статей тут и, естественно не оформил.
Год назад видел датчик газа… — Этих российских датчиков с проприетарными протоколами столько, что пора делать фестивали протоколов и присуждать премии по номинациям =/.
нужно сначала записать значение в один регистр, потом подождать и прочитать значении в другом регистре.
Ну или с событиями — сначала прочитать один регистр — самого последнего события, потом другой — первого события, в зависимости от того, что там получилось — записать в третий регистр номер события который хочется прочитать, потом читать от 4-го регистра некоторое количество байт. количество сильно зависит от того, что в храниться в предыдущем регистре события.
вполне себе modbus. у я как нибудь оформлю это в статью. ну с третьего раза в песочницу принять могут. наверно)
STM32F3xx + FreeRTOS. Modbus RTU с аппаратным RS485 и CRC без таймеров и семафоров