среда, 6 июля 2016 г.

Программируем микроконтроллеры используя Qt+mbed

Речь пойдет о микрокнроллерах STM32 с ядром ARM Cortex-M3/M4. Конкретно - о дешевых платах с микроконтроллерами stm32f103c8t6 и *cBt6. Настройка показана на примере ОС Mac OS X El Capitan, в Linux практически так же всё, а Windowz не операционка, поэтому не пользуюсь более 10 лет.


Собственно, инструкция подключения будет мало отличаться от более старших камней, надо будет подсунуть другие пути к описаниям портов и прочих аппаратных ништяков. Плюс включить компилятору аппаратные вкусности. К примеру, для stm32f407vg надо переключить в библиотеке mbed другой cmsis и в проекте включить аппаратную поддержку вычислений с плавающей запятой.

Но всё по-порядку....


Credits

Собственно, идея использовать эту связку совсем не моя, а обнаружена в интернете. Описания этого процесса были обнаружены по следующим ссылкам:

Аппаратная часть

Упомянутые чипы отличаются объемом набортной флэш-памяти: 64 и 128кБайт. Выбор именно их обусловлен крайне низкой ценой за платку с микроконтроллером, кварцем, стабилизатором питания, светодиодом питания, пользовательским светодиодом и разъемами SWD для отладки, USB - можно использовать для реализации всяких подключений и гребенками, на которые выведены питание, сброс и ... выводов микроконтроллера. Разъем для программирования выведен отдельно. Также на плате присутствуют два джемпера, которые управляют точкой входа (для организации нескольких режимов запуска).

Кратко о возможностях самого микроконтроллера stm32f103c8(B):
  • Тактовая частота ядра - 72 МГц (получается умножением частоты 8 МГц кварца).
  • Объем оперативной памяти - 
  • Объем флэш-памяти - 
  • Порты ввода-вывода - 
  • Таймеры - 
  • Дополнительные интерфейсы:

Для работы нам также потребуется "программатор" для прошивки, он же обычно позволяет отлаживать микроконтроллеры. Бывает 2 типов - JTAG и SWD. Отличаются разъемом и составом сигналов вообще. Для меня выбор однозначен - SWD, так как подключение осуществляется по 3 проводам - земля (GND), такт (SWCLK) и данные (SWDIO). Обычно тем же кабелем можно от программатора кинуть питание.

Я использую клон программатора STLink/V2, китайский:

Вполне себе работает. До того использовал клон Segger JLINK:

Но там прошивка уже устарела, а новая на нём не работает (такой вот клон).

Подключение выглядит так. Цепляем к "свистку" плату 4 проводами (и у программатора, и на плате они подписаны) и втыкаем свисток в USB порт:


Далее, надо установить софт.

Компилятор

Исползуется кросс-компилятор для ARM, сборка GCC, отсюда: https://launchpad.net/gcc-arm-embedded

Я качал последнюю версию. Что приятно, по сравнению с инструкцией на хабре ничего не надо перевыбирать, отладчик с поддержкой python уже в комплекте.

Разворачиваем архив, я для средств разработке завёл в корне диска директорию Development, установив себя владельцем (чтобы были права на чтение и запись):

$ sudo mkdir /Development
$ sudo chown пользователь:группа /Development

Скачиваем нужный вариант (у меня это файл https://launchpad.net/gcc-arm-embedded/5.0/5-2016-q1-update/+download/gcc-arm-none-eabi-5_3-2016q1-20160330-mac.tar.bz2), на Mac OS он сохранится в директории Downloads (по русски он называется Загрузки) пользователя.

Вытаскиваем содержимое файла в /Development:

$ cd /Development
$ tar xvjf ~/Downloads/gcc-arm-none-eabi-5_3-2016q1-20160330-mac.tar.bz2

получается директория gcc-arm-none-eabi-5_3-2016q1.

После этого я (для сокращения путей) сделал символическую ссылку gcc-rm как короткое имя:

$ ln -s gcc-arm-none-eabi-5_3-2016q1 gcc-arm

Далее, прописываем пути компилятора в свою переменную PATH. Я пользуюсь Midnight Commander, поэтому открываем в mcedit файлик .profile в директории пользователя:

$ mcedit ~/.profile

Собственно, если что, то можно это и с помощью встроенного nano отредактировать:



$ nano ~/.profile

В любом случае, добавляем в PATH путь до компилятора /Developer/gcc-arm/bin:

export PATH=~/bin:$PATH:/Development/gcc-arm/bin

Да, у меня перед PATH прописан bin в домашней директории, для собственных скриптов. Сохраняем нажав F2 и выходим, нажав Esc.

Далее, для проверки, открываем новую консоль (чтобы в ней применились изменения) и проверяем запуск компилятора:

$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 5.3.1 20160307 (release) [ARM/embedded-5-branch revision 234589]
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Работает. Следующий шаг - отладчик. Для отладки будем пользоваться openocd.

Openocd

Это OPEN On-Chip Debugger. C ним я не стал ломать голову - поставил через brew:

$ brew install openocd

Если кто-то не знает, что такое brew, то это порты приложений для Mac OS X. Установить их очень просто - дать команду в терминале. Подробности тут: http://brew.sh.

После установки openocd пробуем "достучаться" им до платы:

$ openocd -f /usr/local/share/openocd/scripts/interface/stlink-v2.cfg 
-f /usr/local/share/openocd/scripts/target/stm32f1x.cfg 

Здесь мы в командной строке указали путь к 2 файлам: описателю работы с программатором STLink/V2  и к описателю микробы stm32f103. Эта строчка нам еще дальше понадобится.

После запуска в консоли будет наблюдаться нечто такое:
Open On-Chip Debugger 0.9.0 (2015-11-15-13:10)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v17 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.243329

Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

Желающие могут установить набор утилит st:

$ brew install stlink

Хотя это совершенно не обязательно, openocd вполне хватит и самого по себе. Далее, устанавливаем Qt.

Qt Creator

Данная штукенция нам нужна как крайне удобная IDE. С последней версией (Qt 5.6) под Mac OS я поимел аццкий геморрой (именно такой, красочный и радостный, с двумя "це" и двумя "эр") с отладкой: отладка не работала как класс под El Capitan, а под Mavericks работала с обычными приложениями, но не работала с микроконтроллерами. Поэтому Кьюти ставим старую (у меня хорошо заработала версия 5.4.1) через оффлайн-инсталлер, скачать его можно тут: http://download.qt.io/archive/qt/5.4/5.4.1/

После установки надо "прицепить" к ней установленный компилятор. Запускаем Qt Creator и Первым делом включаем поддержку плагина BareMetal (именно она нужна для разработки под микроконтроллеры). Жмакаем в меню Qt Creator -> О модулях и отмечаем в списке Device Support -> Bare Metal:



После чего перезапускаем Qt Creator.

Далее, надо создать toolchain для наших микроконтроллеров.
Идём в настройки для устройства Qt Creator -> Настройка ->  Голое железо и добавлем новый вариант через OpenOCD. Для начала жмём добавить и в появившемся меню выбираем Голое устройство:


Далее появится диалог следующего вида:



Название пишем по желанию, я написал STM32F103openocd, чтобы сразу было видно что это чисто под сотую серию STM32 (параметры запуска openocd будут чисто под эту штукенцию).
Режим запуска - pipe, чтобы он сам запускал нам  openocd при запуске отладки. Для этого режима команда запуска  (строка Хост GDB) начинается со знака конвейера и далее путь к openocd с параметрами:


| /usr/local/bin/openocd -c "gdb_port pipe" -c "log_output openocd.log;" 
-f /usr/local/share/openocd/scripts/interface/stlink-v2.cfg
-f /usr/local/share/openocd/scripts/target/stm32f1x.cfg 

Далее жмём сохранить, потом ОК и вываливаемся из настроек. Но не надолго.

Идём Qt Creator -> Настройка -> Сборка и запуск, надо прописать компилятор и отладчик.

Жмем вкладку Компиляторы, в ней выбираем Добавить и в выпавшем меню выбираем GCC. В появившемся окне указываем путь к компилятору - файлу и имя. В качестве имени я прописал GCC ARM 5.3, чтобы было сразу видно версию (у меня параллельно стояла еще 4.9, и чтобы не путаться), хотя вполне хватит GCC ARM.

Еще момент - флаги. Внизу выбираем выпадающие списки ABI, и устанавливаем в первом выпадающем пункте arm, во втором linux, в третьем undefined или generic. В общем, как на скрине:



Сохраняем, нажав ОК, потом возвращаемся сюда же, жмем вкладку Отладчики. Выбираем путь к файлу arm-none-eabi-gdb-py (это сборка gdb с поддержкой python, которая нужна Qt Creator). Пишем имя GCC GDB 7.10.1 (для моего варианта оно так, можно ограничиться и GDB ARM):



Сохраняем, возвращаемся. Теперь можно создать toolchain. Жмем на вкладку Комплекты и создаём новый набор с настройками:
  • Название - по вкусу (у меня stm32f1xx).
  • Тип устройства - голое устройство.
  • Устройство - stm32f1xx (это тот самый доступ через openocd).
  • Компилятор - GCC ARM 5.3.
  • Отладчик - GDB ARM 7.10.1.
  • Профиль Qt - отсутствует.


Жмём OK и всё сохраняется. Итак, Qt  настроили. Берём заготовку проекта...

Проект

Проект лежит в архиве на диске гуглей, откуда его можно скачать по ссылке:

https://drive.google.com/open?id=0B0xYYXcjQzHVS1ZkRkJsNkpPVjQ

Это архив .tar.bz2, разворачивать можно руками, можно скормить какой-нибудь утилите. В результате получаем директорию mbed-baremetal-stm32f103c8t6, которую можно переименовать по вкусу и переложить куда хочется.

Вкратце о структуре проекта:
  • mbed.qbs - файл проекта.
  • mbed-src - директория с исходниками mbed, часть файлов не относящихся к stm32f1xx удалена.
  • srd.
  • mbed-devices - директория для всяких сторонних классов, расширяющих mbed (драйвера всяких устройств).
  • info - скрипт для получения размеров прошивок. О нём будет чуть далее.


Для сборки проекта используется Qbs, как наиболее продвинутая и гибкая система, поддерживаемая и разрабатываемая авторами Qt.

Описание сборки (файл mbed.qbs) имеет кучу настроек и вариантов, но не имеет описания проекта как настроек IDE. Это важно, так как обязывает нас ткнуть Qt Creator носом в используемый для сборки комплект. Поэтому перед первым открытием qbs файла надо установить интересующий нас toolchain по умолчанию.

Идём Qt Creator -> Настройки -> Сборка и запуск, вкладка Комплекты и отмечаем наш комплект stm32f1xx как комплект по умолчанию выбрав его в списке и нажав кнопку Сделать по умолчанию. Скорее всего он и так им будет.



Далее можно открывать qbs файл. Файл -> Открыть файл или проект..., далее идём куда разархивировали проект и выбираем mbed.qbs. После открытия IDE чуток потупит, потом покажет содержимое проекта. Там можно выбрать папочку mbed и откроется дерево файлов проекта:



Далее, чтобы работал приложенный скрипт оценки объемов прошивки (info), надо настроить проект на сохранение файлов компиляции в директориях Debug и Release внутри папки проекта.

Итак, перед нами интерфейс (см. выше), слева у нас тублар с пиктограммами и подписями Начало, Редактор, Дизайн и т.д.

Жмём в тулбаре кнопочку Проекты (5я сверху "кнопка", если вдруг кто читать не умеет), и в каталог сборки прописываем ./Debug, после чего нажимаем слева Редактор (2й сверху), переключаем режим сборки в "Выпуск" (слева, 4й снизу):



После этого идем в Проекты и в каталог сборки указываем ./Releasе:



Далее возвращаемся в редактор. Обнаруживаем, что Qt "насрало" нам в папку проекта директориями с именами по умолчанию:


Можно (или на мой взгляд нужно) их грохнуть обычным образом. Не нужны они.

После всех этих настроек, если мы закроем проект и откроем его заново, то Qt Creator обнаружит файлик mbed.qbs.user в директории проекта и будет знать о настройках компиляции и куда класть откомпилированное.

Разбираемся дальше. Итак, мы вернулись в режим редактирования. Ознакомимся "ху из ху" в файле проекта. Жмем в проекте на mbed.qbs и смотрим туда.



Большая часть всего там интуитивно понятна (программисту, знакомому с принципами компиляции C/C++). Кучка строк в начале определена для гибкости при адаптации проекта. model определяет модель платы, cortex - тип ядра, cpp.defines - символы компилятора. Все эти ништяки нужны для библиотеки mbed. О ней чуть далее.

Далее, общие флаги сборки, определяющие возможности аппаратной части:

cpp.commonCompilerFlags: [
        "-mthumb","-mcpu=cortex-m3","-msoft-float","-mfpu=vfp","-Os",

      "-fdata-sections","-ffunction-sections","-fno-inline","-std=c++11","-flto"]

Описывают для какого ядра (Cortex-M3), с какими возможностями (вещественная арифметика через эмуляцию) и т.п. В том числе здесь включена поддержка стандарта C++11. Эта строчка ответственна за дикую кучу варнингов при компиляции. Так уж получилось.

Так как памяти кот наплакал (по современным меркам), то включена оптимизация по размеру "-Os".

Аналогично - флаги сборки:

cpp.linkerFlags:[
        "-flto","-mthumb","-mcpu=cortex-m3","-msoft-float","-mfpu=vfp","--specs=nano.specs","-Wl,--start-group",
        "-Wl,--gc-sections","-T",path+"/"+mbed+"targets/cmsis/TARGET_"+vendor+"/TARGET_"+model+"/TARGET_NUCLEO_F103RB/TOOLCHAIN_"+toolchain+"/STM32F103XB.ld",

        "-lnosys","-lgcc","-lc","-lstdc++", "-lm"]

Обратите внмание, явно прописаны линковка с библиотеками stdc++ и math, за что ответствены два последних флага.

Еще один важный момент - описание разметки памяти:
"-T",path+"/"+mbed+"targets/cmsis/TARGET_"+vendor+"/TARGET_"+model+"/TARGET_NUCLEO_F103RB/TOOLCHAIN_"+toolchain+"/STM32F103XB.ld"

Это для STM32F103C8T6, для CBT6 надо поменять имя файла разметки памяти с STM32F103XB.ld на STM32F103XB.ld.

Внутри файлы практически идентичны, за исключением описания разметки объемов памяти:

  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K        

Для C8T6 это 64К, для CBT6 - 128К.

Но вернемся к QBS файлу. Далее там указаны пути где искать заголовочные файлы: cpp.includePath и пути к исходным файлам: files.

Последняя полезная штукенция - отдельная запись символа DEBUG=1 в режиме отладки:

    Properties {
        condition: qbs.buildVariant === "debug"
        cpp.defines: outer.concat(["DEBUG=1"])
    }


Проверяем сборку

Подключив платку к программатору и воткнув его в USB жмакаем "кнопку плей" (в левом тулбаре, 3я снизу) или под ней "плей с жуком". Для разработки под микроконтроллеры особой разницы нет.

Сначала нас ждёт тупление и ругань - процесс можно наблюдать в окне "4 Консоль сбрки":


188 - это количество предупрежденийй при сборке. В основном из-за того, что стоит опция C++11, не подходящая для чистого С.

По завершении сборки будет запущен openocd, залита новая прошивка и запущена.
Отображается сей процесс в окне "3 Вывод приложения":

Вывод повторен 2 раза, так как в первый раз заливается прошивка, во второй запускается сама отладка, насколько я понял.

Программа проста как мычание:

    #include "mbed.h"
    #define LED PC_13
    int main()
    {
        DigitalOut led(LED);
        while(1)
        {
            led = 1;
            wait(0.25);
            led = 0;
            wait(0.25);
        }
    }

Создается объект доступа к светодиоду (на моих мини-платках он подцеплен к выводу PC13) и в цикле включается-выключается с задержкой в 1/4 секунды.

По факту запуска программы на плате должен замигать оный светодиод:



Оценка занятой памяти

После сборки (в режимах отладки и выпуска) можно оценить "насколько всё плохо". Для этого в папке проекта лежит скрипт info. Собираем проект в режимах "отладка" и "выпуск, далее переходим в файл проекта и запускаем info:

$ ./info
DEBUG
   text   data     bss     dec     hex filename
  20960     128     500   21588   5454 Debug/qtc_stm32f1xx-debug/mbed.qtc_stm32f1xx/mbed
RELEASE
   text   data     bss     dec     hex filename
  20276     128     500   20904   51a8 Release/qtc_stm32f1xx-release/mbed.qtc_stm32f1xx/mbed

Как видите, прошивка занимает около 20 килобайт (чуточку больше в отладочном виде). Стоит отметить, что на самом деле пошаговая отладка возможна и в варианте выпуска. Возможно, в каких-то случаях и понадобится отладочная информация, но пока что не натыкался на такое.

Пошаговая отладка

Прелесть выбранного IDE в том, что оно умеет нормально управлять gdb, обеспечивая полноценную отладку с пошаговой трассировкой, анализом значений переменных, точками останова, возвратом из подпрограмм и т.д.

Проверяем её работу. Прерываем отладку выбрав явно Отладка -> Прервать отладку. При этом будет закрыт отладчик. Эта опция нужна будет и для перезапуска отладки, её надо делать перед пересборкой проекта кнопкой "плэй" в левом тулбаре.

Далее, выбираем файл main.cpp, скроллим в функцию main и жмём мышкой на какой-нибудь wait в основном цикле. Жать надо слева от строки на свободном поле, на этом месте и появится  кружочек с часиками (часики символизируют, что данный breakpoint сейчас не активен):



Далее, стартуем отладку. После компиляции и заливки прошивки выполнение тормознется на указанной строке и нажимая кнопочки в появившемся тулбаре отладки:



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

Не забудьте, что для корректного запуска новой программы после редактирования может потребоваться предварительно остановить отладку, нажав Отладка -> Прервать отладку.

Зачем нужен этот страшный mbed?

Пара слов о том, почему именно ARMmbed (https://www.mbed.com/en/).

В оригинале это нечто Arduino-подобное, с онлайн-IDE. Но так как написано на чистом C/C++, то просто взяв исходники, положив их в mbed-src и настроив проект на сборку и исходников mbed, и нашей программы совместно, можно получить себе счастье в виде высокоуровневой работы с железяками и, самое главное, доступ к куче классов, написанных для этой mbed.

Подробно можно ознакомиться тут: https://developer.mbed.org.

Потребуется регистрация, зато потом можно легко и просто скачивать нужные классы и подключать их. Именно для сторонних частей mbed (как правило это драйвера каких-нибудь штукенций) в проекте есть директория mbed-drivers.

Даже в "родной поставке" (исходниках mbed) есть куча вкусностей. Например, подключение LCD экранчика делается как нельзя проще: создаём экземпляр класса TextLCD (или его варианта для нужного интерфейса) и далее пользуемся выводом в стиле printf. Пример helloworld для экрана с I2C  интерфейсом:

    I2C i2clcd(PC_9, PA_8);
    TextLCD_I2C lcd(i2c, address, type);
    lcd.setBacklight(TextLCD_Base::LCDBacklight::LightOn);
    lcd.printf("Hello, %s!", "World").

Первая строчка - инициализация i2c шины, второй создаётся объект доступа к ЖК экрану. Далее включаем подсветку и вывод. Обратите внимание, что метод printf аналогичен printf из стандартной библиотеки С.

Поддержка C++

Это самый главный ништяк всей этой связки: полноценный C++. Со всеми шаблонами, с динамической памятью, полиморфизмами и прочей нечистью.

Разумеется, я не агитирую ипользовать все его возможности на 200%, но все-таки классы, наследование и виртуальные методы очень упрощают программирование для микроконтроллеров. А уж если использовать STL, тогда всякие хитрые буферы можно создавать.

В общем, простор для творчества.


Итоги

Итак, чего же мы получили?

Плюсы:
  • удобное IDE;
  • наличие отладки;
  • полноценный C++, до стандарта C++11 проверял, всё нормально;
  • удобную и простую работу с оборудованием (через mbed);
  • "доступ" к куче классов, расширяющих возможности mbed.
Минусы:
  • заточено под stm32f103c8 и *cB, переход на другие МК требует перекапывания файла проекта;
  • библиотека достаточно прожорлива в плане памяти и даже такой малюсенький проект жрёт аж 20 кБайт памяти (для сравнения, написанный на чистом С проект считывателя touch button с эмулятором на 4 "таблетки" и виртуальной USB консолью занимает 13 кБайт).