понедельник, 12 февраля 2018 г.

Проблемы совместимости S7-200 и китайских PLC: глюк с оператором JMP

Читай также:
Проблемы совместимости S7-200 и китайских PLC: глюк с оператором FOR
Китайские поделки на тему Siemens S7-200

Итак, я хочу рассказать об интересном дефекте китайских контроллеров, который я на днях случайно обнаружил. Как уже известно, если вам нужен ПЛК Siemens S7-200 (снятый с производства в Германии в 2012 году), то есть два способа его купить: это выкупить остатки со склада каких-нибудь торгашей, которые берегут это добро уже больше пяти лет, чтобы продать по цене в 2 - 4 раза превышающей его былую стоимость, либо (самый простой способ) - купить S7-200CN на Aliexpress.

Контроллеры с маркировкой CN производятся в КНР официально, т.е. это оригинальный Siemens, но сделанный в Китае. Его отличие от немецкого Siemens's только в том, что Step-7 Micro/Win отказывается с ним работать при активированном английском интерфейсе, по крайней мере так было раньше (после переключения интерфейса на иероглифы проблема решается).
 Update. Аллелуя! Похоже, Siemens таки убрал необходимость переключения на китайский язык при заливки контроллеров S7-200CN (покупал тут, залился в английском MicroWIN без проблем, на вид, вроде, настоящий Siemens, label присутствует, цвет корпуса с зеленоватым оттенком как у немецких S7-200)

Однако, на Aliexpress есть и другие ПЛК, которые повторяют функционал S7-200. О них я уже как-то рассказывал в этой статье. Все они деляться на три группы:
1. Имеют полную совместимость с S7-200. Т.е. программируются в среде Step-7 Micro/Win и совместимы с модулями Siemens (GIPENG, AMSAMOTION, SHENJUYE (продаются под лэйблом LOLLETTE)).
2. Имеют совместимость с модулями S7-200, но имеют свою среду разработки (CO-TRUST, среда - MagicWorks PLC)
3. Не имеют совместимости с модулями S7-200, но программируются в среде Step-7 Micro/Win (FONIN)

Итак, я никогда не приобретал не полностью совместимые с Siemens контроллеры, такие как Co-Trust или Fonin, ничего не могу о них пока сказать.
Но у меня в распоряжении имеются контроллеры Gipeng, Amsamotion и Shenjuye (Lollette). И вот, добавив в программу для Amsamotion ранее написанную функцию ПИД-регулирования (я использую только свои собственные функции регуляторов), я внезапно обнаруживаю, что она не работает. Удивительное дело, ведь функция уже проверена на контроллерах Siemens S7-200, как же так? Оказывается, в контроллере Amsamotion наблюдается непонятный глюк: неправильно работает оператор JMP. Я проверил выполнение этого оператора на том же примере на контроллерах Siemens, Gipeng и Shenjuye (Lollette) и вот оказалось, что глюк не проявляется на оригинальном Siemens S7-200 и на Shenjuye (Lollette) LE-200, а на Gipeng GF-200 проявляется в том же виде, что и на Amsamotion AMX-200CN.

Описание проблемы оператора JMP

В общем, напомню, что оператор JMP (jump) из набора команд S7-200 является оператором условного перехода, что является косвенным аналогом оператора JE в классическом ассемблере и оператора JC в языке STL для S7-300. В языках программирования высокого уровня для ЭВМ обычно называется GOTO (go to).

Суть работы оператора JMP # в том, что при логической 1 в вершине стека он осуществляет переход на указанную метку, которая задаётся командой LBL #, где # - это номер метки.
При компиляции этот номер заменяется адресом строки кода, куда осуществляется переход. Т.е. весь код программы, который находится между операторами JMP и LBL чисто физически не должен выполняться.
Соответственно, после выполнения перехода стек сохраняется в том виде, в котором был на момент перехода. А поскольку переход условный, то если он выполнен, то после перехода в вершине стека может быть только логическая 1, т.к. это и есть условие для перехода. Инструкция от Siemens для особо одарённых даже содержит специальное уточнение:

The Jump To Label (JMP) instruction performs a branch to the specified label (n) withon program. When a jump is taken, the top of stake value is always is logical 1.

И вот представьте моё удивление, когда на моём ПЛК Amsamotion AMX-200CN переход выполняется, а в вершине стека при этом логический ноль. Взаимоисключающие параграфы сие называется. Я начал разбираться и выяснил, что, оказывается, не всем операторам одинаково везёт, как говориться. Т.е. большинство операторов спокойно игнорируются, когда оказываются между JMP и LBL при переходе, но есть операторы, которые таки выполняются! Невероятно, но факт. Смотрим.
 

Для удобства восприятия я выбрал адреса переменных похожими на адреса системных битов, т.е. в моих примерах бит M10.0 должен всегда быть равен 1 (т.е. равен SM0.0), а бит M10.1 всегда быть равен 0 (т.е. равен SM0.1, который равен нулю во всех циклах программы, кроме первого).
Я немного добавил линий, чтобы было понятно, какие команды выполняются правильно, а какие - нет (зелёные - это правильно, красные - нет).
В синей рамке находится то, что по правильному алгоритму работы программы должны быть пропущено.


Как видим на представленном выше скриншоте перед командой JMP находится команда LD SM0.0, результат которой всегда = 1, что подтверждается тут же в самой правой колонке. На скриншоте видно, что команда NOT расположена между JMP и LBL, т.е. выполняться не должна, но она таки выполняется и первое же присваивание M10.0 заканчивается записью туда не 1, а 0, поскольку произошло инвертирование, которого быть не должно.

Таким же точно глючным образом выполняются и некоторые другие команды, работающие только со стеком, а именно команды: OLD, LDS, LPS, LRD, LPP. Глючно выполняются команды LB=, LB<>, LB<, LB>, LB<= и LB>=, а также аналогичные команды для WORD (LW= и т.д.) и DWORD (LD= и т.д.). Вот пара примеров:





Команды LD, A и O, а также команды фронта EU и ED не выполняются между JMP и LBL, т.е. работают корректно:





Ну, и напоследок, есть команда, которая работает вообще непонятным образом в такой ситуации - это команда ALD. Она не просто выполнилась между JMP и LBL, хотя не должна была, но и дала результат равный логическому нулю, несмотря на то, что в обоих первых уровнях стека были единицы. Вот гляньте, картина маслом:


Контроллер команду ALD выполняет нормально в обычной ситуации, чему также даю подтверждение:



 В общем и целом, хочу заметить, что для написания новых программ тут нет проблемы: обойти эти препятствия нет никакой сложности. Совсем другое дело, если требуется использовать контроллер вместо уже существующего оригинального Siemens как резерв. Да, можно сказать, что вероятность, что результат работы будет иным, не так и велика. Но у меня, лично, в программе нашлось аж две функции, а всего три конкретных места кода, которые пришлось исправить. Т.е. если кто-то на предприятиях, где находятся мои программы с этими функциями в ПЛК Siemens S7-200 решит заменить эти контроллеры на Amsamotion или Gipeng, то, в лучшем случае, алгоритм не будет работать, а в худшем даже и не знаю.

Более-менее застрахованы от проявления глюка те, кто использует более примитивные языки LAD и FBD, поскольку интерпретатор, автоматически переводящий в STL, не осуществляет особо сильной оптимизации, т.е. не заботиться о более рациональном использовании стека, поэтому вероятность проблем меньше, хотя они и не исключены.
Кроме того, не застрахованы также и те, кто использует функции от Siemens, поскольку гипотетически и они могут содержать небезопасный код. Я бы рекомендовал, на всякий случай, распароливать функции от Siemens (смотри здесь) и проверять код.


Какой код можно считать небезопасным и как это проверить.

Всё очень просто. Нужно осуществить поиск по всему коду программы, искать надо оператор LBL. Если после него не следует команд, продолжающих логическую цепочку с использованием значений стека из того фрагмента кода, который шёл до LBL, то всё в порядке. Если же таковые команды имеются, то надо просто внести коррективы в код.

Условно говоря, если до оператора JMP в стеке были какие-то значения, которые будут использоваться после команды LBL путём вызова команд A, O, LPP, LRD, LDS, NOT, EU, ED и др., то надо до оператора JMP выполнить присвоение значений стека определённым битам (желательно тем, которые больше в программе нигде не участвуют), а после команды LBL изменить код так, чтобы использовать эти самые биты, а не стек. Да, это будет дольше выполняться (хоть этого никто и не заметит), будет менее рационально, но зато обезопасит всех от потенциальных проблем с китайскими ПЛК. Операторы, использующие стек и меняющие его значение, расположенные между JMP и LBL, имеют право на существование, поскольку, если JMP не выполнится, то их выполнение не будет иметь проблем, однако, важно понимать, что использования этих значений после LBL не должно быть, или же эти значения еще до оператора LBL нужно присвоить каким-то битам памяти.
Вот примитивный пример:

 
Как видим, выполнялась команда LPP, которая не должна была выполняться, и результат, записанный в M10.2, оказался рассчитан неправильным образом. Исправить это можно так:


Ну, я думаю, что все понимают, что речь идёт об абстрактном примере, поэтому в нём значение M10.0 присваивается биту M20.0, естественно, что можно это упростить, просто это не делается именно потому что предполагаются гораздо более сложные алгоритмы, где подобное упрощение не будет возможно.