пятница, 29 июля 2016 г.

STM32F103+ MBED + USB последовательная консоль

Итак, в рамках освоения платформы и написания базы для создания всяких страйкбольных штуковин (игровых таймеров и прочих устройств с блэкджеком и шлюхами) пришлось "прикрутить" модуль USBSerial к stm32f103cXt6.


В резульатате удалось заставить работать виртуальный ком-порт (последовательную консоль) на платах с микроконтроллерами stm32F103c8t6 и cBt6. Это самые дешевые платы с 64 и 128 килобайтами флэша соответственно.

Это расширенная версия описания, с деталями и ссылкой на архив с готовым проектом.


Структура данного пасквиля

Сочинение сие не претендует никоим образом на идеальность, каковую достичь невозможно, посему прошу простить мне некоторую вольность изложения, особливо касательно последовательности повествования.

Начну я с благ всякостных, коие можно приобрести, коли кому удастся повторить и применить таковую консоль в разработках своих.

Далее коснусь немного утилит вспомогательных, ибо оные имеют свойство помогать в трудный час. Посмотрим как их установить на UNIX-образных системах (Linux/Mac OS X), а также по мере сил потружусь описать, как же пользоваться оными утилитами.

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

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

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

Итак, приступаю...

Нафига вообще нужна такая консоль

Последовательная консоль позволяет резко упростить множество аспектов написания и управления устройством, а также здорово разнообразить игровой процесс.

В частности, с его помощью можно реализовать:
  • отладку, т.к. можно в процессе работы выводить через консоль отладочные данные и сообщения и сразу видеть это дело на компьютере;
  • конфигурирование устройства, т.к. можно сделать разбор команд, передаваемых через консоль. Это даёт возможность легко, просто, интерактивно настраивать устройства. Причём, если нужен пользовательский интерфейс, то программы для визуальной настройки также пишутся элементарно - поддержка последовательного порта есть везде, например в том же Qt это легче лёгкого. Задача сводится к написанию frontend, который получает состояние железяки (задав ей через консоль вопросы и проанализировав ответы), отображает пользователю чего и как и все изменения сообщает через ту же консоль устройству;
  • сбор игровой статистики и её обработка, т.к. можно "сливать" через это соединение статистику, накопленную устройством. Например, если на игровом таймере контрольной точки стоит считыватель RFID или TouchButton, плюс "мертвяк" завязан на эти самые карты/ключи, то можно отмечать кто конкретно и когда захватил точку и после игры, подсоединив таймер к компьютеру, через консоль получить эти данные, по базе данных посмотреть кто это был, какая команда  и т.п. Ну и графики красивые построить по данным со всех точек. А если прицепить на точку передатчик, то на компе оргов можно и в реальном времени состояние точек смотреть.... но это как-нибудь потом, хотя наработки есть;
  • управляющая связь с ПК, то есть можно давать через этот интерфейс кодированные команды для выполнения прямо сейчас. Например задача захватить точку, присоединить ноутбук и подать команду.

Что заработало и что нужно для общения с консолью

Заработала связка mbed + форк драйверов USBSerial с сайта mbed'а. Теперь можно подключать USB шнурок к разъему на плате и ОСь видит устройство, можно подключаться к нему с помощью screen в UNIX-образных системах или через hyperterminal в вениках. С точки зрения этих устройств мы подключили некий COM порт, через который можно посылать и получать данные.

Драйвера для этого подсоединения в UNIX-образных системах не нужны, для Windows надо найти STM Virtual Com Port Drivers. Но так как не люблю эту гадость, то да поможет вам святой Гугль.

В Linux/Mac OS X нужна утилита screen, т.к. с ней проще простого общаться с этой самой консолью.  Также, рекомендую установить утилиты для работы с usb, в них есть очень полезная утилита lsusb, отображающая подключенные USB устройства.

Для Centos:
# yum -y install screen usbutils

Для Debian/Ubuntu:
# apt-get install screen usbutils

Для Mac OS X ставим через homebrew (macports мне как-то меньше нравятся в последнее время):
$ brew update
$ brew tap jlhonora/lsusb
$ brew install lsusb


Утилита screen в Mac OS X, если я правильно помню, установлена по умолчанию.

После подключения платы к компьютеру можно в терминале проверить появилась ли она, запустив утилиту lsusb.

Если используются расширенные настройки как в примере (см. далее), то одна из строк вывода будет вроде такой:
Bus 250 Device 005: ID 0483:7540 STMicroelectronics CDC DEVICE  Serial: 0123456789

Если настройки по умолчанию, то:
Bus 250 Device 005: ID 1f00:2012 1f00 CDC DEVICE  Serial: 0123456789

Как видите, обязательно будет что-то про CDC DEVICE и некий странный серийник 0123456789.

Если такой строчки нет, то что-то пошло не так и компьютер не видит устройство через USB. Тут может быть виноват шнур, компьютер и т.д. Частенько при частом отключении подключении бывало, что видеть перестаёт. Перетыкаешь в соседний USB - и вуаля, всё работает. Как заглючит - возвращаешь в первый... В общем, для начала утилита очень полезная.

Далее, как добраться до консоли, если уж устройство появилось. Для начала надо определить, как его зовут. Обычно это tty.usbчего-то и посмотреть можно с помощью ls:
$ ls -1 /dev/tty.*
/dev/tty.Bluetooth-Incoming-Port

/dev/tty.usbmodemFA1231

Как видим, зовут его tty.usbmodemFA1231. Запускаем screen:
$ screen /dev/tty.usbmodemFA1231

По идее, с этими usb последовательными портами нет необходимости указывать скорость работы порта (она совпадает со скоростью и настройками по умолчанию - 9600 бод, 8 бит, 1 стоп, без четности). Да и вообще, оказалось, что можно любую указать. Но в любом случае, утилите можно сообщить требуемую скорость соединения, указав её вторым параметром:
$ screen /dev/tty.usbmodemFA1231 9600

Далее, получим черный экран и все наши нажатия кнопок будут улетать "на другую сторону", а вывод "с той стороны" будет тут же отображаться на экране.

Единственная "команда", которую нельзя просто так передать через screen это Ctrl+A, т.к. это сочетание используется для управления самим screen. Поэтому для посылки Ctrl+A надо нажать контрол, нажать кнопку а, отпустить контрол и опять нажать а. Тогда screen поймет, что мы хотели просто Ctrl+A отослать и сделает это.

Вроде по утилитам всё, переходим к проекту.

Проект

Хотя на сайте mbed и заявлена поддержка USB Serial виртуального порта для платы NUCLEO-F103RB (на основе софта которой я собирал комплект для работы с этими дешевенькими китайскими платами, о чём я уже детально писал), но на деле поддержки этого дела в библиотеке USBDevice,  которая является драйвером для USB устройств, нет. Пришлось собирать свой проект, подбирая состав файлов и прочее...

Архив с полным проектом можно скачать в виде zip архива (примерно 1 мегабайт):
https://drive.google.com/open?id=0B0xYYXcjQzHVYmlvV2dTTzBOSjg

На этот раз в архив включен и файл настроек для QtCreator, с "полуавтоматическим" размещением папок Debug/Release внутри папки с проектом. Они описаны как "./Debug" и "./Release" и при открытии будут автоматом переписаны.

В общем, работать по идее должно, но если не заработает, всегда можно стереть файл пользовательских настроек проекта (mbed.qbs.user) и прописать все папки вручную.

Как водится, код для работы цельностянут: умельцами уже допилена библиотека USBSerial для работы в том числе и с  NUCLEO-F103RB. Живёт библиотека (одна из версий) тут: https://developer.mbed.org/users/va009039/code/L152RE_USBDevice/

Прекрасно, должен сказать, живёт. Только вот не работает. По крайней мере с моими платами. Сам автор, впрочем, признаётся, что не пробовал на F103 её запускать.  Пришлось разбираться где там чего глючит.

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

Первая проблема - в общей логике работы USB для L103/F103. В файле USBDevice/USBHAL_STM32L1.cpp, строка 119 содержит проверку правильности выделения каких-то служебных буферов:

117: bool USBHAL::realiseEndpoint(uint8_t endpoint, uint32_t maxPacket, uint32_t flags) {
118:     int pmaadress = PktBufArea.allocBuf(maxPacket);
119: //   MBED_ASSERT(pmaadress != 0);
120:    if (pmaadress == 0) {
121:        return false;
122:    }


Так вот, если проверка не проходит, то служебная "ругалка" (MBED_ASSERT) плющит и таращит прошивку (UPDATE: сейчас у меня какие-то подозрения, что делать это она будет в варианте для отладки).

После комментирования этой строки оно просто выходит по следующей же (обычной) проверке, вернув false и системе сразу лучшеет.

Вторая проблема связана с изменениями в логике реализации именно последовательной консоли. На форумах обнаружилось, что автор советует откатить содержимое директории USBSerial на содержимое от версии 61 (сейчас версия 64), что и было сделано.

Таким образом, в проекте лежит набор необходимых файлов, скомбинированный по этим рекомендациям. В результате нормально удалось завести USBSerial на дешевых платках с STM32F103c8t6 (те, которые с 64 килобайтами флэш-памяти).

Единственно, понадобилось добавить "инициализатор" тактовых частот - STM32_USB48MHz.

Оба этих "драйвера" находятся в папке mbed-drivers в папках USBDevice_F103 и STM32_USB48MHz. Из первой удалены лишние файлы, оставлена только поддержка камней STM32L103/F103 и F4 (на всякий).

Изменения в файле проекта mbed.qbs (указано только то, что добавилось или изменилось, с секциями):
...
    Product {
...
    cpp.defines: ["TOOLCHAIN_GCC_CW=1", "TARGET_STM32F1"]
...
    cpp.includePaths: [
...
        devices+"STM32_USB48MHz/",
        devices+"USBDevice_F103/USBDevice/",
        devices+"USBDevice_F103/USBSerial/"
    ]
    files: [
...
        devices+"STM32_USB48MHz/*",
        devices+"USBDevice_F103/USBDevice/*",
        devices+"USBDevice_F103/USBSerial/*",
...
    ]
...

То есть добавился описатель платформы (TARGET_STM32F1) в определениях компилятора, плюс добавлены пути к нужным папкам с файлами.

Maple Mini 

Описнный проект прекрасно заработал на дешевенькой платке, а вот на более дорогой плате - китайском клоне платы Maple Mini с МК stm32f103cBt6 (ядро то же, но со 128 килобайтами флэш-памяти, на фото справа) отказалась работать:


Казалось бы, изменений в коде программы минимум - подключение другого описателя разметки памяти (в qbs файле) и другого адреса для "моргалки" светодиода (в main.cpp). А не работает. Даже не просто не работает - не видится через lsusb.

Анализ платы и даташитов показал, что в наших чипах для работы USB нужна подтяжка одной из ноги данных USB (DATA+) к плюсу питания, а на платке Maple Mini это включалось как-то извращенно, с пинками от микроконтроллера. Решил не разбираться с особенностями этого изврата, да и просто выдрал транзистор, подтягивавший через резистор в 1.5ком этот самый вывод к питанию, и подключил питание "напрямую", как на дешевенькой плате сделано:


Слева модифицированная плата, справа - не ковыряная. Удалён транзистор в левом верхнем углу. В таком виде заработало. Потом буду думать, как заставить обычный вариант заработать.

main.cpp

Что реализовано в основной программе:

#include "mbed.h"
#include "USBSerial.h"
#include "STM32_USB48MHz.h"

// F103c8t6
#define LED PC_13
// F103cBt6 - maple mini
//#define LED PB_1

DigitalOut led(LED);

int main()
{
    STM32_HSI_USB48MHz();
    USBSerial serial;// (0x0483, 0x7540, 0x0002, false);
    while(1)
    {
        led = 1;
        wait(0.5);
        led = 0;
        wait(0.5);
        while(serial.available())
        {
            char c = serial.getc();
            serial.putc(c);
            serial.printf("Hello, world!\r\n");
        }

    }
}


То есть всё крайне тупенько: подключены заголовочные файлы, создан экземпляр объекта доступа (led) к светодиоду на выводе PC_13 (для дешевенькой платы) или PB_1 (для Maple Mini) и в основной программе устанавливается тактирование и создаётся объект доступа к USB консоли (serial).

Далее идёт бесконечный цикл с миганием светодиодом (1 раз в секунду) и опросом консоли. Если есть символ на входе (serial.available()), то получаем символ и кидаем его обратно. Дополнительно выводим Hello, World! с последовательностью перевода строки (\r\n).
При инициализации объекта serial можно самому задать параметры USB устройства. В частности производителя, модель и версию. В комментариях как раз "спрятано" то, что нужно прописать для вывода STMicroelectronics в lsusb. 

Полезной штукой является также последний (четвёртый) параметр инициализации объекта serial. Если передаём true (по умолчанию как раз оно), то объект создаётся в блокирующем режиме: пока мы не подключим USB кабель к разъему, выполнение программы не продолжится.

Если же указать false, то это означает, что USB  подключение нам типа не совсем обязательно, и программа пойдёт дальше не ожидая подключения и не блокируя работу. При этом serial.available() будет работать как положено, без глюков и ругани, и сообщать false при отключенном кабеле (ну, символов-то и вправду не получено). 

Если же мы подключим USB кабель, то оно само инициализируется, и где-то через пять секунд можно будет запускать screen. То есть возможно использование usb "по желанию" или "по необходимости". Удобненько, однако.







Печальное

Когда была написана и опробована эта программка, перевел в режим генерации release, перекомпилировал прошивку и приложенным скриптиком info оценил объем прошивки.

Получилось 39,7 килобайта. Как-то весьма неприлично выходит, учитывая, что на c8t6 камне всего 64 килобайта флэша. А это, на минуточку, 62% памяти...

Как альтернатива, был переписан код под работу с обычным последовательным портом (USART):

#include "mbed.h"

// F103c8t6
#define LED PC_13

// F103cBt6 - maple mini
//#define LED PB_1

DigitalOut led(LED);

int main()
{
    Serial serial(PA_2, PA_3);
    while(1)
    {
        led = 1;
        wait(0.5);
        led = 0;
        wait(0.5);
        while(serial.readable())
        {
            char c = serial.getc();
            serial.putc(c);
            serial.printf("Hello, world!\r\n");
        }

    }
}


То есть отличия в самом объекте serial, плюс проверка наличия символа делается через serial.readable(). В остальном то же самое.

После сборки объем программы составил 30,1 килобайта, то есть на 9,6 килобайт меньше, чем в случае USBSerial. И то хлеб.

Последнее, что было сделано - удалён и Serial:

#include "mbed.h"

// F103c8t6
#define LED PC_13

// F103cBt6 - maple mini
//#define LED PB_1

DigitalOut led(LED);

int main()
{
    while(1)
    {
        led = 1;
        wait(0.5);
        led = 0;
        wait(0.5);
    }
}


В таком кастрированном варианте программа занимала 22,4 килобайта.

Таким образом, подключение обычного последовательного (USART) порта Serial отожрёт 7,7 килобайт, а USBSerial - 17,3 килобайта. Подозреваю, что больше всего памяти в Serial/USBSerial жрут всякие puts, printf и прочие удобства. Ну и в USB еще логика работы с USB добавляется.

Кстати, под такой же чип (STM32F103) программа-эмулятор TouchButton в Keil на чистом C  занимала 13 килобайт. При этом в программе было:
  • считывание TouchButton;
  • эмуляция TouchButton (то есть МК прикидывался таблеткой);
  • хранение и переключение нескольких TouchButton (до 4 шт, данные сохраняются перезаписью участка флэш-памяти);
  • режим замка (то есть подавался сигнал при считывании известного TouchButton);
  • часы реального времени;
  • текстовая USB консоль, поддерживающая 11 команд: вывод содержимого памяти TouchButton, редактирование и внесение кодов, помощь, работа с часами и т.д. 

Повторюсь, объем прошивки - ТРИНАДЦАТЬ килобайт. А тут 22 килобайта тупая моргалка светодиодом. Вот она, плата за "высокие технологии", ООП и простоту программирования.

Альтернативы и радость для кружевного жабо

Текстовая консоль возможна не только напрямую через USB.  В микроконтроллере есть такая классная штука, как последовательный порт, который уже опробован выше. Это USART, причем в наших МК - аж 3 штуки, и этот порт можно подключить к компьютеру.

Для этого надо либо подключить преобразователь уровней в обычный COM/RS-232  (к примеру, на микросхеме max232).

Либо использовать конвертер COMTTL->USB (на микросхеме FTDI-2232 или CP-2102).

Получим абсолютно такой же функционал через USB, но через объект Serial, без использования USB порта микроконтроллера. Это позволит нам сэкономить почти 10 килобайт памяти, либо применить USB для чего-нибудь другого.

Однако, это сразу же требует наличия этого самого конвертера, а он денег стоит - порядка 250р, если учесть стоимость пересылки регистрируемой почтой (China Post Air Mail). Плюс время на пересылку из Китая. Можно попробовать купить его здесь, но это будет еще дороже.

В общем, применение этого USBSerial может весьма порадовать жабу.