- STM32MP1 — ядра + Linux = идеальный микроконтроллер
- Идеальный контроллер
- Первое знакомство
- Память
- Организация рабочего места
- Engineering & Production mode
- Ядро М4. Загрузка и запуск программ
- Ядро М4. SW4STM32
- Ядро А7. Кросс-компилятор
- Ядро А7. Загрузка и запуск программ
- Обмен данными между ядрами
- Дальнейшие планы
- STM32 + linux
- Светодиод, кнопка
- 1-wire
- Датчик Холла
- Шаговый двигатель
STM32MP1 — ядра + Linux = идеальный микроконтроллер
По роду своей деятельности занимаюсь разработкой различных приборов: измерительных, управляющих, контролирующих и т.п. В подавляющем большинстве устройство делится на 2 части:
- Требующий больших ресурсов микроконтроллера и не требующий жесткого реального времени графический интерфейс пользователя (GUI).
- Потребляющая немного ресурсов и работающая в жестком реальном времени аппаратная часть прибора.
И как правило, переход на новую серию микроконтроллеров определялся улучшением GUI: больше цветов, выше разрешение, более сложные элементы дизайна. Одно время была мысль использовать в качестве графического интерфейса какую-нибудь плату Raspberry.
Но с появлением у STM новой линейки микроконтроллеров STM32MP1 мои терзания закончились и вот что пока получилось.
Идеальный контроллер
Идеальный контроллер для меня должен обладать следующими характеристиками:
- иметь приличные графические возможности, такие как простая поддержка различных графических форматов, поддержка векторных шрифтов
- большой объем памяти для графики
- поддержка USB клавиатуры и мышей
- поддержка Ethernet
- большое количество аппаратных таймеров
- генерация ШИМ
- большое количество GPIO
- 16 разрядный АЦП
Именно такие требования нужны для построения различных приборов с приличным графическим интерфейсом пользователя.
И поэтому я очень обрадовался увидев новый серию от STM: STM32MP1.
Первое знакомство
Для ознакомления с новым чипом была приобретена плата STM32MP157C-DK2. Вот такая (фото из инета):
Демоплата построена на кристалле STM32MP157CAC, который имеет в своем составе:
- 2 ядра А7, по 650 МГц
- 1 ядро М4, 208 МГц
Память
Самое первое отличие, которое бросается в глаза — это отсутствие флеш памяти на кристалле. Для ядра А7, которое по сути микропроцессор, это нормальная ситуация и код всегда выполняется из DRAM. Микроконтроллеры на основе М4, обычно имеют в своем составе флеш память, в которой хранится исполняемый код.
В этом кристалле у ядра М4 есть только оперативная память и код выполняется из нее (из даташита):
708 Kbytes of internal SRAM: 256 Kbytes of
AXI SYSRAM + 384 Kbytes of AHB SRAM +
64 Kbytes of AHB SRAM in Backup domain
and 4 Kbytes of SRAM in Backup domain
The STM32MP157C/F devices embed a controller for external SDRAM which support the
following devices:
- LPDDR2 or LPDDR3, 16- or 32-bit data, up to 1 Gbyte, up to 533 MHz clock.
- DDR3 or DDR3L, 16- or 32-bit data, up to 1 Gbyte, up to 533 MHz clock.
С одной стороны памяти у ядра M4 вроде бы меньше, чем у версии в флеш памятью, но с другой стороны RAM память работает на частоте ядра. Флеш память так не может, и там приходится использовать делитель частоты.
Организация рабочего места
Поскольку для работы с платой необходим Линукс, то пришлось установить дополнительный жесткий диск в свой комп и развернуть на нем Ubuntu 16.04. Эту ОС рекомендует STM.
Engineering & Production mode
Плата может стартовать в одном из двух режимов (определяется дип-свитчем). В режиме Engineering, ядро М4 работает обособленно. В Production mode ядро М4 работает под управлением ядра А7. Я не использовал режим Engineering, работал только в режиме Production. В этом режиме можно не заботиться о настройке тактирования ядер. Тактирование настраивается в процессе загрузки по максимальным величинам. Я проверил несколько регистров в отладчике, вывел часть частот на MCO и посмотрел осциллографом. A7 работает на частоте 650 МГц, М4 на частоте 208 МГц.
Ядро М4. Загрузка и запуск программ
Поскольку ядро М4 работает под управлением А7, значит под управлением OpenSTLinux. Для управления ядром М4 используется Linux remoteproc framework (RPROC). Этот фреймворк позволяет включать и отключать ядра, загружать и отлаживать программное обеспечение(ELF файлы).
Для загрузки программы в ядро M4 служит следующая команда:
echo -n > /sys/class/remoteproc/remoteproc0/firmware
Для старта программы:
echo start >/sys/class/remoteproc/remoteproc0/state
echo stop >/sys/class/remoteproc/remoteprocX/state
Ядро М4. SW4STM32
With System Workbench for Linux, Embedded Linux on the STM32MP1 family of MPUs from ST was never as simple to build and maintain, even for newcomers in the Linux world.
And, if you install System Workbench for Linux in System Workbench for STM32 you can seamlessly develop and debug asymmetric applications running partly on Linux, partly on the Cortex-M4.
То есть можно писать код как для ядра м4, так и для А7. Но это платная версия. А бесплатная, которая доступна для скачивания, позволяет писать и отлаживать код только для ядра М4.
IDE SW4STM32 была скачана, установлена и попробована. Все работает, процесс написания и компиляции не отличается от версии в Windows.
Отличается только загрузка кода и отладка, поскольку для записи софта используется Ethenet и соединение по SSH. Для отладки и остановки по точкам используется встроенный на плате ST LINK.
Ядро А7. Кросс-компилятор
Для компиляции программ на ПК STM предлагает кросс-компилятор. Процесс загрузки и запуска скрипта кросс-компилятора подробно описан в Вики STM32MP1, ссылка ниже. Все было сделано согласно инструкции, все работает как надо. 🙂
Запуск кросс-компилятора для экосистемы v1.1.0:
source SDK/environment-setup-cortexa7t2hf-neon-vfpv4-openstlinux_weston-linux-gnueabi
ну далее команда
make
и исходный код, в соответствии с makefile, скомпилируется в коды ARM 🙂
Ядро А7. Загрузка и запуск программ
Чтобы загрузить программу в демо плату используется команда:
scp gtk_hello_world root@192.168.1.18:/usr/local
Эта команда загружает файл gtk_hello_world в раздел /usr/local, который находится на плате с IP адресом: 192.168.1.18. Root — пароль, который стоит на плате по умолчанию.
Обмен данными между ядрами
Больше всего времени ушло на понимание работы механизма обмена данными между ядрами. В примерах, которые поставляются STM, есть несколько вариантов реализации подобного обмена. Можно работать напрямик с драйвером из OpenSTLinux или использовать механизм виртуальных UARTов. Я решил сделать обмен через виртуальные порты, как более простой.
Со стороны Линукс это выглядит так:
fd = open(«/dev/ttyRPMSG0», O_RDWR);
Потом пишем в него или читаем:
write(fd, «LED_Toggle\r\0», 10);
len = read( fd, buf, sizeof(buf));
Все достаточно просто, но есть маленький нюанс. Чтобы появилось устройство ttyRPMSG0, нужно чтобы ядро М4 обратилось к ядру А7 в таком виде:
это строки кода ядра М4:
Прием данных из виртуального порта ядром М4:
Запись данных в виртуальный порт:
Все работает, но есть небольшой нюанс, который пока не могу понять. Прием данных ядром А7, которые передает ядро М4, не начинается, если предварительно ядро А7 не запишет в порт какие-либо данные.
Весь этот механизм реализуется набором сторонних библиотек OpenAMP. Эта библиотека должна быть включена в проект ядра М4 и соответственно проинициализирована.
Дальнейшие планы
Установить и настроить Eclipse для написания и отладки кода для А7.
Написать нормальное графическое демо, типа осцилоскопа из АЦП М4.
И наконец, сделать свою плату контроллера на основе этого чипа. 🙂
Источник
STM32 + linux
Для разработки системы управления одной железякой после длительных поисков мною был выбран ARM-микроконтроллер семейства STM32 — STM32F103 (в «стоножечном» исполнении). А в качестве макетки для разработки и отладки — STM32P103 (там ножек хоть и меньше, но ядро то же самое). «Истории успеха» я понемногу выкладывал в своей ЖЖшке, но вот решил собрать все воедино и рассказать о том, каково же оно — программировать микроконтроллеры в линуксе. Сам проект лежит на sourceforge.
Прежде всего коснусь общего, а потом уже перейду к деталям.
Итак, помимо макетки (или же готового устройства — когда оно будет готово) потребуется JTAG-адаптер. В моем случае это — ST-LINK/V2. Одного железа, естественно, недостаточно: надо еще как-то код компилировать, а потом еще и заливать на контроллер. Для этого были установлены компилятор gcc для ARM (arm-none-eabi) и утилита для работы с ST-LINK (она так и называется — stlink).
В качестве образца я взял этот проект. Отсюда я скачал простенькие демонстрационные проекты и попробовал скомпилировать простейший. А самым первым оказался стандартный Helloworld для МК: мигание светодиодом.
Сразу скажу, на какие грабли я напоролся с самого начала: я забыл про objcopy, без которого получить работающий код будет сложновато. После компиляции проекта обязательно надо создать бинарник при помощи этой утилиты. И не стоит выкидывать из Makefile’ов непонятных (и вроде бы даже ненужных на первый взгляд) целей.
В качестве удобного IDE я использую Geany. Так как на работе у меня два монитора, довольно удобно работать: на одном мониторе у меня открыт Geany с кодом, а на втором — терминал, где я запускаю make и com (терминал из tinyserial).
Весь Makefile я рассматривать не буду, обращу лишь внимание на то, что в нем следует менять:
- BIN — имя получающегося после компиляции бинарника
- STM32_LIBSRC и OBJ содержат подключаемые файлы библиотеки STDPeriphLib, неиспользуемые надо закомментировать
- SRC сдоержит перечень пользовательских исходников
После того, как код написан, запускаем make. Если все в порядке, в текущей директории появится файл $(BIN).bin, который и нужно записать во флеш-память МКшки. Запись выполняется при помощи make load: эта цель сборки просто вызывает st-flash для прошивки микроконтроллера.
Итак, прежде всего необходимо наладить связь компьютера и МКшки. Учитывая то, что в современных компьютерах RS-232 отсутствует, отладочную связь организую посредством USB. Однако, в «боевых условиях» команды МКшка будет получать по RS-232 от другого контроллера, поэтому я решил сразу же смотреть в сторону организации эмулятора переходника USB RS-232. Этот подход удобен еще тем, что не надо заморачиваться с лишним кодом для взаимодействия с устройством по USB (хоть это и элементарно, но лень же!). Да и отлаживать просто: открываем устройство /dev/ttyACM0 как последовательный порт при помощи любого эмулятора последовательного терминала и «общаемся». Да, в качестве эмулятора терминала на первых порах (пока нет никакого ПО со стороны компьютера) я использовал tinyserial.
Отсюда я скачал код эмулятора переходника USB RS-232. Так как проверить работоспособность второй стороны (RS-232) я сразу не мог (некуда подключить), неиспользуемый код работы с USART временно закомментировал.
Для работы с USB используется библиотека от STMicroelectronics. Если не вникать в коды самой библиотеки, все довольно-таки просто: нам нужно переопределить дескрипторы для своей железяки (файлы usb_desc.[ch]), чтобы компьютер опознал ее как переходник USB RS-232, а также изменить обработчики прерываний на события USB (как минимум — обработать принятые данные, а для прозрачной работы в качестве переходника, надо будет еще добавить обработку прерываний USART для передачи полученных оттуда данных по USB).
Для передачи сообщений используем что-то вроде кольцевого буфера, который постепенно будет наполняться, а по необходимости — передаваться по USB. Считывать данные будем в «обычный буфер». Так как пока что я использую только короткие команды, обработкой длинных посылок я не заморачивался. Если же они будут, нужно будет немного усложнить обработчик прерывания по приему данных с USB.
Так как некоторые команды (например, чтение температуры с 1-wire датчиков) выполняются довольно-таки долго, обработчик пришедших по USB команд только модифицирует флаги для подобных операций, а уж основной цикл в main() эти флаги обрабатывает. Операции, выполняющиеся быстро (работа со светодиодом), вызываются непосредственно из этой функции. В отладочных целях я добавил «эхо» на команды в виде краткой ее расшифровки:
Все, теперь при подключении макетки к компьютеру по USB (а она, вообще-то, у меня всегда подключена, т.к. питается через USB) появляется устройство /dev/ttyACM0, с которым можно работать, как с обычным последовательным портом. Например, открыть его при помощи последовательного терминала (как я уже выше сказал, на первых порах пользуюсь tinyserial).
Светодиод, кнопка
Наверное, традиционным является «помигать диодом» в начале изучения какой-нибудь новой железяки, поэтому и я сделаю так же. А заодно повешу на «user button» прерывание, которое будет менять режимы работы светодиода.
Просто мигать не интересно: интересно менять яркость. Для этого достаточно простого «софтового» ШИМа. Настроим таймер SysTick на период в 10мкс. Заведем два счетчика: один для количества «тиков», в течение которых светодиод горит, а второй — для количества «тиков», в течение которых светодиод не горит. Для изменения яркости свечения светодиода я сделал простейшую восьмиуровневую схему изменения скважности ШИМа.
Получилось вот что:
На «пользовательскую кнопку» я повесил внешнее прерывание:
1-wire
Код для работы с 1-wire я стащил откуда-то с сайта easyelectronics.ru. Изменил я его совсем немного. Прежде всего, изменил функцию поиска устройств, висящих на шине (она в оригинале почему-то не работала, хотя логика вроде-бы вполне четкая и правильная была).
В сворованном мною примере 1-wire работала через USART, а для чтения/записи использовался DMA. Мне эта идея очень понравилась, поэтому воспользовался именно этим способом (хотя можно было организовать и программный протокол 1-wire).
Стандартная схема подключения 1-wire шины к последовательному порту подразумевает наличие диода Шоттки:
Однако, у меня такого диода не было. Но я углядел, что помимо push-pull режима можно отвечающую за USART_TX ногу перевести в режим с открытым стоком — в этом случае короткого замыкания не будет. Для работы с 1-wire я использовал USART3 (пока я балуюсь, ног мне хватает — поэтому remap делать не надо). На схеме я увидел, что ноги USART3 (PB10 и PB11) уже подтянуты к земле через резисторы по 10кОм, так что мне даже резистор припаивать не пришлось: только подпаял на макетку небольшую платку с гнездами, чтобы удобно было подключать термодатчики.
Подробно расписывать содержимое файла onewire.c не буду: это сделано уже до меня неоднократно, а коснусь лишь непосредственно работы с термометрами.
Для мониторинга температуры теплых (выше -50°C) частей устройства я решил воспользоваться простыми датчиками DS18S20 (заявленная точность измерения — не хуже 0.5°C). Подпаянную на макетку панельку я подключил к нужным выводам, чтобы можно было одновременно подключить к МКшке пару термометров.
Вот, например, что я получаю при работе с термометрами:
Для начала я промаркировал все термометры, чтобы знать, какой идентификатор у кого. А после я решил посмотреть, сильно ли различаются их показания. Температура — первые два байта ответа датчиков. В седьмом байте хранится «остаток» от преобразования температуры встроенным в термометр АЦП. По документации на датчики, этот остаток помогает уточнить значение температуры. Однако, выяснилось, что толку от него — как от козла молока.
В процессе работы датчик сам нагревается, что сказывается на результатах измерения. Поэтому не стоит слишком часто их опрашивать. Кроме того, показания датчиков отличались друг от друга вплоть до полутора градусов! Это нужно иметь в виду: если планируется использовать несколько датчиков так, чтобы мониторить разницу температур между участками чего-то с точностью не хуже 0.5°C, предварительно надо все датчики откалибровать. И показания брать по калибровочным формулам, а не ответу датчиков.
Реальная погрешность датчика иной раз превышает 0.5°C, поэтому все-таки лушче считать, что датчик имеет точность в 1°C.
Датчик Холла
Датчики Холла у меня аналоговые — SS495A. Спецификации на датчик можно найти в интернете. Скажу лишь, что в нормальном состоянии на его выходной ноге напряжение составляет около 2.5В (логическая единица STM32), в зависимости от полярности и величины внешнего магнитного поля он будет изменять свои показания в пределах 0..5В. Учитывая то, что напряжение на выходе может достигать пяти вольт, надо использовать не обычные, а «пятивольтовые» (обозначены как FR в спецификации) входы контроллера.
Имеющиеся у меня магниты (специально для этого датчика) при помещении их рабочей поверхности в пределах 1мм от «морды» датчика (маркированная сторона) приводили к появлению на его выходе нулевого напряжения. Причем уровень логического нуля появляется в довольно-таки небольшой зоне по координатам в параллельной маркированной стороне датчика плоскости, т.е. точность позиционирования получается довольно приличной.
Для опытов я распаял на макетке один датчик. Питание его подключил к 5В, а сигнальный выход вывел на порт PC10, который не сгорит, если на него подать 5В. Для того, чтобы не дергать постоянно порт, я повесил на него прерывание (по аналогии с кнопкой). Обработчик прерывания просто выставляет соответствующий флаг, а уж в основном цикле, если этот флаг выставлен (т.е. магнит либо появился, либо покинул «поле зрения» датчика) проверяем, что у нас на PC10. Если там ноль (есть МП), пишем в терминал «Magnet», иначе пишем «clear». Еще можно принудительно проверить, есть датчик или нет его, нажав «h» в терминале.
Помимо «теплых зон» мне еще надо будет измерять температуру в холодных (вплоть до 75К сверху). Для этого будут использоваться платиновые термосопротивления, подключенные к аналоговому коммутатору ADG506A. Ну и, естественно, мне стало интересно, насколько плох «родной» АЦП МКшки: нельзя ли его использовать для измерения температуры?
Примеров работы STM32 с АЦП полным-полно, я взял пример из STDPeriphLib. Будем запускать АЦП в режиме непрерывного преобразования, а результат заносить в память при помощи DMA. Время преобразования устанавливаю в самое большое (чтобы поточнее было), а сам вход АЦП пока что повешу на ногу PB0 (ADC8):
Для работы с коммутатором нужно сконфигурировать пять бит управляющего порта. Чтобы не париться с преобразованием бит, я просто взял первые четыре бита порта C в качестве адреса, а пятый бит — в качестве ключа, включающего коммутатор:
Прерываний здесь никаких не надо, а в файле interrupts.c надо дописать установку нового флага при поступлении команды (скажем, команды ‘a’) отображения напряжения на датчиках. В main() добавим обработку этого флага:
При поступлении команды в цикле запускаем выставление нужного адреса на коммутаторе, ждем пару миллисекунд, чтобы АЦП отработало, а затем выводим полученное значение. Потом отключаем коммутатор и ждем (на всякий случай) еще пару миллисекунд.
На отдельной макетке я собрал простой резистивный делитель напряжения, соединив все аналоговые входы коммутатора мелкоомными (200..900 Ом) резисторами. К S1 подключил «землю», а к S16 — +3.3В с макетки STM32. Запитал микросхему я старым БП от внешнего HDD (12В).
В макетке STM32P103 эталонное напряжение для АЦП берется от общего питания, поэтому точность получилась низкой: значения плавают иной раз аж на 20 единиц!
Вот, например, что получилось при двух опросах:
В общем, надо будет либо попробовать добавить стабильный источник опорного напряжения (да и измеряемую цепь питать оттуда же), либо вообще использовать внешний АЦП. Учитывая низкое сопротивление датчиков, которые будут использоваться, все равно придется паять еще и усилитель.
Шаговый двигатель
С шаговиком я еще не закончил возиться, т.к. подозреваю, что при монтаже элементов на макетке у меня ничего «не взлетит». Надо паять. А пайкой займусь, скорее всего, только в следующем году (надо еще радиодеталей прикупить). Пока лишь вкратце расскажу, как планирую управлять шаговыми двигателями.
Шаговики у меня будут — VSS42 на 1.2 ампера. Управлять такими удобней всего при помощи драйвера ШД — L6208. При работе на эту микросхему надо подавать лишь сигналы управления направлением движения, сигнал разрешения работы да тактовые импульсы. Контроллер сам регулирует ШИМ и устанавливает нужные напряжения на обмотках двигателя.
Укажу пока основное, на что следует обратить внимание:
- Регулировка ШИМа выполняется посредством сравнения падения напряжения на Sense-резисторах с опорным напряжением Vref. Поэтому для тока Imax и сопротивления резисторов RSense это падение можно рассчитать довольно просто:
Uref = Imax · RSense
Т.е. для выставления предельного тока в 1.2А при RSense=0.33Ом нужно задать Uref=0.4 В. Ни в коем случае нельзя оставлять ноги Vref висящими в воздухе или прижатыми к земле! - Режим Slow/Fast decay имеет значение для обычных коллекторных двигателей, шаговикам же Fast decay нужен лишь в режиме microstepping. В общем, если не извращаться, достаточно просто подать +5В на ножку CONTROL. На HALF/FULL тоже просто подаем +5В и работаем в полушаговом режиме. Аналогично поступаем с ногой RESET, если не хотим сбрасывать счетчик фаз (а его сбрасывать и не нужно, если честно отдавать на каждый шаг по 8 синхроимпульсов).
- Вход ENABLE является еще и выходом: если с драйвером L6208 случается неприятность (перегрев, скачок тока), он самостоятельно отключает напряжение на нагрузке, а ENABLE подтягивает к земле. Это значит, что можно проверять, не случилась ли аварийная ситуация, если ногу контроллера, управляющую портом ENABLE, активировать в режиме выхода с открытым коллектором.
По спецификации STM32, в режиме открытого коллектора при подаче единицы на выход порта просто запирается транзистор, подтягивающий ногу к земле. Если же на выход подать нуль, то нога опять подтягивается.
Таким образом, подтянув ногу контроллера к +5В (ногу нужно выбирать FT) через, скажем, пятикилоомный резистор, а между ней и ENABLE воткнув, скажем, килоомный резистор (и обязательно не забыть шунтировать ногу ENABLE конденсатором на землю, иначе можно сжечь управляющий контроллер), можно и включать/выключать нужный двигатель, и проверять, не было ли аварий (а для этого можно повесить на соответствующие ноги контроллера прерывание периферии по спадающему фронту). - Проводники, помеченные в спецификации жирным, должны быть как можно короче и шире. Но при этом надо следить и за тем, чтобы уменьшить паразитные емкости и индуктивности.
RSense должны располагаться как можно ближе к драйверу. Недалеко от них должны быть и конденсаторы C₁ и C₂ (по спецификации): через эту цепь течет не только ток сравнения, но и обратный ток индукции (поэтому, кстати, диоды в эту цепочку включать нельзя).
Сигнальную землю соединять с силовой землей только далее точки подключения C₁ к земле, иначе ток индукции может попортить электронику просто падением напряжения на проводниках печатки (до меня, правда, не дошло, как это возможно, но лучше прислушаться к требованиям безопасности). - Еще я прочел интересную штуку: силовое питание нельзя подключать без сигнального! В документации рекомендуется даже брать напряжение 5 В от силового источника при помощи стабилизирующего блока.
Я, честно говоря, ничего не понял: собственно +5 В подается лишь на управляющие ноги драйвера. Если мы не пользуемся двигателем, то, по идее, у него на всех сигнальных входах может быть 0. Но включать силовое питание таки буду после включения питания контроллера. И выключать в обратной последовательности. И следить, чтобы USB-шнурок, питающий контроллер, не выскочил.
Источник