В первой части мы познакомились с аппаратным обеспечением китайского клона ПЛК FX3U-14MR (одного из его вариантов). В это части мы научимся подключаться к его MCU по доступным коммуникационным интерфейсам, останавливать, запускать программу, заливать прошивку. И в конце рассмотрим примеры программ на базе библиотеки STM32duino, задействующие периферию ПЛК. Кстати нам придется еще немного поговорить о железе.
Подключения к MCU по SWD
Для подключения по интерфейсу SWD понадобится внутрисхемный программатор ST‑Link V2. В моем распоряжении был самый дешевый китайский ST‑Link V2 «за 100 рублей». В общем на этом его преимущества и заканчиваются. На нем не были выведены линии SWO и NRST для чипов STM32. SWO (Serial Wire Output) для ПЛК FX не нужен, его тут некуда подключать, вместо него выведены USART/UART порты. А вот наличие NRST, т. е. выхода программатора, сбрасывающего аппаратно MCU, было бы очень кстати, т.к. рассматриваемый ПЛК FX3U имеет на разъеме SWD вход сброса RST. Без NRST программатора придется замыкать RST на 0 вручную в определенные моменты при заливке прошивки или подключении в STM32CubeProgrammer. При наличии в программаторе действующего и подключенного выхода NRST прошивка будет происходить в автоматическом режиме.
Я для своих нужд доработал этот ST‑Link по следующим статьям:
Доработка китайского ST‑Link v2: добавляем интерфейс вывода отладочной информации SWO и ногу Reset
Делаем ST‑Link V2.1 из китайского ST‑Link V2
Минимально тут для наших нужд я бы рекомендовал припаять NRST, вместо RST, который сделан только для STM8.
На самых дешевых контроллерах FX линия сброса NRST не выведена на внешний разъем. В этом случае придется либо подпаиваться к RC‑цепи, которая подключена к NRTS, либо читать статью дальше про альтернативные способы подключения/прошивки.
В общем случае не нужно сбрасывать STM32 при подключении к нему по SWD. Если SWD линии не задействуются в программе MCU в качестве GPIO или других функций, то скорее всего (по крайней мере для core STM32duino это так) к SWD можно подсоединиться «горячим подключением» во время выполнения программы MCU. Если вспомнить, что я писал в части 1 этой статьи, а именно, что выходы GPIO и программный I2C делят линии интерфейса SWD, видно, что реализован худший сценарий. Тут SWD перестает работать сразу при запуске программы MCU (если в программе используются I2C или линия DE RS-485), и без принудительного сброса не обойтись. SWDIO и SWCLK подключаются к одноименным линиям программатора.
В качестве альтернативы NRST можно вызывать встроенный загрузчик STM32 замыканием входа BOOT0 на +3.3В при включении общего питания. На моем FX3U-14MR вывод BOOT0 не распаян на разъем, но я легко замыкал проводом типа Dupont +3.3В прямо на резистор, подключеный к этому выводу. При этом программа MCU не стартует, и можно легко подключиться по SWD без NRST. Правда, тут есть неудобство, которое связано с наличием буферных конденсаторов питания (а также ионисторов на других ПЛК), которые десятки секунд поддерживают жизнь MCU до полной потери энергии! Таким образом вход в режим загрузчика чаще не быстрый процесс.
На других контроллерах, как я уже писал, бывают контактные площадки для замыкания BOOT0 на +3.3В. BOOT1 должен быть притянут к 0, не забываем о нижнем положении переключателя Start/Stop на рассматриваемом FX3U-14MR.
Есть еще третий способ «достучаться» до MCU по SWD при отсутсвии NSRT. Это программная остановка программы MCU, точнее прыжок (jump) в программу встроенного загрузчика из вашей программы MCU. Конечно это можно будет сделать только после успешной заливки в MCU своей программы. Как это сделать, я расскажу ниже в примерах.
Итак, идеальная связка для подключения по SWD к FX3U-14MR это ST‑Link2 со сбросом NRST.
Подключения к MCU по Serial (USART1)
Если у вас на контроллере отсутствует что‑либо похожее на разъем SWD, не отчаивайтесь. Можно подключаться и заливать прошивку через UART (USART1) в режиме встроенного загрузчика STM32. Если NSRT не выведен на внешний разъем: отключаем питание ПЛК, ждем пока разрядятся все конденсаторы и ионисторы, подключаем BOOT0 на +3.3В, BOOT1 должен быть притянут к 0, подаем питание на ПЛК. Можем подключаться в STM32CubeProgrammer или заливать прошивку STM32duino через последовательный порт ПЛК.
Третий способ из предыдущего раздела тут тоже работает. И он может стать самым удобным в итоге.
STM32CubeProgrammer и STM32 ST-LINK Utility
Для первого подключения нам понадобится одна из этих программ. Надеюсь, вам удастся раздобыть их самостоятельно. STM32CubeProgrammer — поновее, STM32 ST‑LINK Utility более старая. Это бесплатные утилиты для работы с прошивками и режимами MCU STM32.
STM32CubeProgrammer также нужна для прошивки вашей программы STM32duino из среды Arduino IDE, если вы доберетесь до этой части. Точнее Arduino IDE использует консольный клиент STM32_Programmer_CLI.exe, который ставится вместе с STM32CubeProgrammer.
Кстати в одном из ПЛК FX у меня вместо STM32 установлен APM32F030C8T6 от Geehy, и STM32CubeProgrammer чудесным образом тоже видит его при подключении через UART, а STM32duino библиотека компилируется и работает. SWD не проверял, на том ПЛК его просто нет.
Итак давайте сначала попробуем подключиться по UART. Убедимся, что BOOT1 притянут к 0. BOOT0 замыкаем на +3.3В. Включаем питание ПЛК. Выбираем тип соединения UART в STM32CubeProgrammer и жмем Connect.
Внимание! При первом подключении в STM32CubeProgrammer или STM32 ST‑LINK Utility вы скорее всего увидите сообщение: Can not read memory! Disable Read Out Protection and retry.
Оно означает, что текущая программа MCU (т. е. китайская прошивка клона FX) защищена от чтения. Вы можете установить галочку Read Unprotect (MCU) и заново подключиться, но это автоматически обнулит текущую прошивку MCU, т. е. из ПЛК FX вы получите «железку» без какого либо софта. На следующем изображение очищенный флеш MCU.
После этой процедуры вы не сможете вернуться к программирование в GX Developer, GX Works2, т.к. оригинальная прошивка утрачена. Можно ли скопировать оригинальную прошивку, я не знаю, этот вопрос выходит за рамки статьи.
Зато теперь мы можем попробовать запрограммировать ПЛК с помощью библиотеки STM32duino в Arduino IDE.
А что насчет подключения по SWD. При подключенном к программатору вывода NRST MCU выбираем тип подключения ST-LINK в STM32CubeProgrammer, Mode=Normal, Reset mode=Hardware reset.
Таким образом перед подключением к MCU программатор кратковременно сбросит NRST и успеет установить соединение до старта программы MCU. Тут все происходит автоматически.
Если NRST не подключен к программатору и вы собираетесь замыкать его вручную. В STM32CubeProgrammer выбираете Mode=Under reset (при сбросе), Reset mode не имеет значения. NRST вручную замыкаете на 0 и не отпускаете до тех пор, пока не нажмете Connect в STM32CubeProgrammer. После размыкания NRST с 0 STM32CubeProgrammer установит соединение.
И последний вариант с SWD. Если загрузились при BOOT0 на +3.3В и BOOT1 на 0, т.е. в режиме встроенного бутлодера STM32, то в STM32CubeProgrammer выбираете Mode=Normal, Reset mode не имеет значения.
При первом успешном подключении по SWD скорее всего увидите ошибку чтения данных:
Нужно отключить флаг защиты прошивки, применить изменения. Получите чистый флеш, как и выше с подключением по UART.
Если вы освоили подключение к ПЛК (точнее к MCU) в STM32CubeProgrammer, можете переходить к написанию hello world программы в Arduino IDE. На самом деле нет, там несколько сложнее.
STM32duino. Настраиваем Arduino IDE
Писать код для STM32 мы будем в Arduino IDE 1.8.19 с использованием программной библиотеки (ядра) STM32duino. Про недостатки говорить не буду, конечно вы можете выбрать любые инструменты программирование MCU в перспективе. А вот из достоинств: низкий порог вхождения и наличие готовых библиотек, мы же хотим писать программы для автоматизации, а не для микроконтроллеров.
Процесс установки библиотеки STM32duino в Arduino IDE расписан тут. На выходе получаем Arduino IDE с поддержкой множества Generic STM32 MCU. Для нашего FX3U-14MR с STM32F103VCT6 делаем настройки, как на рисунке ниже.
Board part number: Generic F103VCTx. Upload method: SWD или Serial. Порт - это то, что подключено к USART1 MCU, может быть использован для заливки (если метод заливки Serial), и далее для отладки, например.
Hello world примеры
Я подготовил пару примеров, которые помогут раскрыть потенциал платформы:
https://github.com/Chupakabra303/FX_PLC_STM32duino/tree/main/examples
Базовый пример FX3U-14MR_STM32duino_basic.ino
В нем выполняется инициализация всех дискретных и аналоговых входов‑выходов, инициализация USART1, UART4, сделан мониторинг питания, программный переход в загрузчик, логирование в USART1.
На что отдельно можно обратить внимание.
Считывается положения переключателя START SWITCH для программного перехода в bootloader (не забываем, что он же делает аппаратный BOOT1=0 в нижем положении). Для этого я специально написал заголовочный файл STM32duinoBootloaderInit.h с функцией bootloaderInit(), который лежит рядом с примерами. Таким образом, переводя переключатель в нижнее положение, программа MCU «прыгает» в загрузчик (вызывается bootloaderInit()). Это как раз и есть тот третий способ, который позволяет подключится в STM32CubeProgrammer или прошить контроллер в Arduino IDE. Вы можете использовать любой незадействованный дискретный вход или комбинацию, или даже «магическую» последовательность символов, принятую по UART для переключения ПЛК в режим загрузчика.
Чтобы выйти из загрузчика, нужно вернуть переключатель в верхнее положение, и либо отключить питание от ПЛК, либо перезалить программу (после перезаливки она автоматически запускается). Также можно воспользоваться консольным STM32_Programmer_CLI.exe для переключения состояний MCU. Например, если MCU в режиме загрузчика, можно запустить прошитую программу в окне CMD.
cd C:\Program Files\STMicroelectronics\STM32Cube\
Если подключен USART1 на COM17:
STM32_Programmer_CLI.exe -c port=COM17 -g
Если использовать SWD:
STM32_Programmer_CLI.exe -c port=SWD mode=UR -g
С помощью SWD можно даже "прыгнуть" в загрузчик во время выполнения программы. (Не забываем про ограничения использования SWD при работающей программе MCU, о которых было выше). Тут адрес 0x1FFFF000 - специфичен для конкретных типов STM32, читайте документацию AN2606 (STM32 microcontroller system memory boot mode), там для разных MCU указаны адреса загрузчиков (куда "прыгать").
STM32_Programmer_CLI.exe -c port=SWD mode=UR -g 0x1FFFF000
Про инициализацию дискретных выходов (DO).
В примере можно обнаружить такие строки:
pinMode(RUN_LED_PIN, INPUT); // Сначала инициализируем как вход в высокоимпедансном состоянии
digitalWrite(RUN_LED_PIN, HIGH); // Инвертированная логика LED, инициализация с HIGH
pinMode(RUN_LED_PIN, OUTPUT); // Потом переключаем в Output
Почему так сложно? Многие ПЛК имеют инвертированную логику DO. На рассматриваемом контроллере таких выходов мало, но на других могут быть все DO. При старте программы MCU при простой инициализации pinMode(RUN_LED_PIN, OUTPUT) физический выход оказывается в состоянии Вкл. Мы можем, конечно, потом сбросить выход в Выкл.: digitalWrite(RUN_LED_PIN, HIGH), но доли секунды выход будет включен, и реле «щелкнет» на старте. Может быть ударное воздействие на какой‑то подключенный к DO механизм. Поэтому я делаю вот такую хитрую инициализацию с начальным состоянием HIGH, которая не приводит к «удару» на старте. Думаю, тут нужно копать в сторону корректной инициализации GPIO для STM32, но я пока остановился на таком «шаманстве».
Расширенный пример FX3U-14MR_STM32duino_advanced.ino
К возможностями базового примера тут добавлена работа с программным I2C EEPROM, часами реального времени (RTC), а также «заведен» Modbus RTU Slave на RS-485 (UART4).
Используются следующие дополнительные библиотеки, которые устанавливаются в Arduino IDE.
Для программного I2C (напомню, для I2C EEPROM в рассматриваемом ПЛК не используется аппаратный I2C MCU) используется библиотека SlowSoftI2CMaster.
Для Modbus RTU бибиотека ModbusRTUServer.
Для работы с RTC используется STM32RTC.
Я реализовал в примере примитивную логику записи сохраняемых переменных в EEPROM при пропаже питания и загрузку на старте.
readRetainEEPROM(); // Чтение из EEPROM Retain переменных
/* ------- Мониторинг питания + запись Retain в EEPROM ------- */
if (!digitalRead(POWER_MONITORING_PIN) && !poweroff) {
Serial.println("--------------- Power off ---------------");
poweroff = true;
// ...
writeRetainEEPROM(); // Запиcь Retain в последнии секунды при отключении питания
}
Также можете посмотреть видео, на котором показана компиляция, заливка и работа примеров.
Заключение
По результатам проделанной работы могу утверждать: все что задумывалось, удалось реализовать в полной мере. На мой взгляд, прямое программирование на C/C++ данных китайских ПЛК возможно и позволит в некоторых случаях расширить возможности этих ПЛК. Сам же я получил удовольствие и ценный опыт от процесса. Надеюсь, и вам будет что-то полезно из написанного. Удачных экспериментов.
YDR
а вот вообще, что лучше - пробовать сделать программу на софте от Мицубиши, или стереть прошивку и сделать все на Ардуино? Понятно, от задачи зависит.. Допустим, и там и там решается.
Глобальный подход - делать "индустриально стандартно", или "по-ардуиновски"?
slog2
Конечно "индустриально стандартно" , тут и обсуждать нечего.
Chupakabra303 Автор
Лучше попробовать сделать на софте от Митсубиси. Описываемое в статье скорее для решения нестандартных задач, либо решения стандартных задач нестандартным способом ). Если упретесь в ограничения стандартной системы программирования и будете уверены в своих силах написать на си под embedded фактически уже, то... Из нестандартных, например, использовать свой коммуникационный протокол, либо можно взять сишный ПИД регулятор или modbus rtu и запихнуть их в младшие FX1N, где такового, кажется, нет. Также в младших ПЛК можно запустить RTC. Там нет батарейки, но RTC "тикает" пока есть основное питание, и при кратковременном отключении от ионистора ещё какое-то время работает.