USB консоль для управления радиолюбительскими приборами

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

USB-is-the-king.png 

Лет 7..10 назад такими интерфейсами были RS-232 (COM-порт) и LPT, организовывать обмен данными с помощью этих интерфейсов было просто и удобно. Сегодня ни в одном ноутбуке таких интерфейсов уже нет, постепенно COM и LPT порты исчезают даже с десктопов. Теперь фаворит среди интерфейсов подключения внешних устройств - только USB.

На рынке появилось довольно много программно-аппаратных решений, которые могут облегчить радиолюбителю подключить свой прибор через USB к компьютеру. Одно из популярных решений - применение специальной дополнительной микросхемы - FTDI, которая позволяет сделать мостик между интерфейсом USB компьютера и последовательным портом микроконтроллера. Есть также микроконтроллеры с интерфейсом USB (например AT90USB162, см. ссылку [1]), а также USB-устройства на основе библиотеки V-USB (см. ссылку [2]). Два последних способа довольно удобны, так как не нуждаются в дополнительной аппаратуре (микросхеме), однако они имеют некоторый недостаток - не все обладают достаточной квалификацией, чтобы изменить исходники примеров библиотеки LUFA или avr-usb-russian (см. ссылки [3] и [4]). В этой статье я сделаю попытку максимально упростить применение в радиолюбительском устройстве подключения по USB с помощью виртуального COM-порта, основанного на программной поддержке протокола USB.

[Что описано в этой статье]

Дан пример на основе библиотеки V-USB и класса USB CDC, т. е. виртуального COM-порта (проект AVR USB CONSOLE, ссылка [5]), как подключить обычный микроконтроллер AVR (которые радиолюбители часто применяют в своих разработках) к компьютеру через интерфейс USB. Работа примера описывается на основе использования в макетной плате AVR-USB-MEGA16, см. [2]. Проект легко портируется на другие микроконтроллеры серии AVR ATmega, его можно взять за основу и просто добавить в него нужные команды. В примере реализованы:
- прием и выполнение текстовых команд от пользователя.
- обработка кнопок управления курсором (на примере кнопок "стрелка вверх", "стрелка вниз").
- сохранение настроек прибора в EEPROM с защитой их целостности контрольной суммой - т. е. нужные параметры прибора можно сделать энергонезависимыми, сохраняющимися при выключении питания. В демонстрационном примере запоминается состояние светодиода, подключенного к ножке порта PB0.
- глобальный таймер с точностью 1 мс - для отсчета задержек времени в программе;

В примере с целью демонстрации реализованы следующие команды:
"стрелка вверх" - выставляет лог. 1 на ножке порта PB0 (зажигается светодиод)
"стрелка вниз"  - выставляет лог. 0 на ножке порта PB0 (светодиод гаснет)
LED             - меняется состояние ножки PB0 на противоположное
I               - выдается информация о состоянии светодиода
T               - показывается время с момента включения питания, в секундах.
?               - вывод подсказки по всем командам
Если команда не распознана (введена неверно), то выводится сообщение об ошибке. Если ввести команду и за ней знак вопроса, то выведется подсказка именно по этой команде.

[Чего это стоит]

В деньгах - 0 рублей 0 копеек. В потраченном времени - довольно немного, поскольку есть простой пример проекта, в котором весь непонятный код (реализация протокола USB и класса USB CDC - поддержка виртуального COM-порта) убран подальше с глаз долой, и оставлен простейший интерфейс обмена - запись и чтение двух кольцевых буферов (rx_usb и tx_usb). В ресурсах микроконтроллера это стоит две ножки портов (под сигналы USB D+ и D-), 6 килобайт кода и 440 байт ОЗУ (эти требования можно уменьшить и увеличить в зависимости от необходимого для Вас функционала консоли). Ножка, на которой D+, должна быть подключена к прерыванию INT0 (аппаратное прерывание по изменению логического уровня на ножке). Ножка D- может быть подключена к любому порту, но при этом возможно потребуется изменение заголовочных файлов кода (usbconfig.h).

[Самое трудное]

Если не имели дела с AVRStudio (что, конечно вряд ли, если Вы разрабатывали раньше что-то на микроконтроллерах AVR), то придется потратить некоторое время на установку этой IDE и на её освоение - чтобы Вы смогли скомпилировать пример консоли (см. ссылку [5]). Кроме того, Вам необходимо скачать и установить последнюю версию пакета WinAVR (подробнее, как это сделать, см. ссылку [6]).

[Из чего состоит пример AVR USB CONSOLE]

Скачайте пример по ссылке [5] и распакуйте. Проект имеет две конфигурации - debug и release. Конфигурация debug удобна для отладки, так в ней включена оптимизация, а конфигурация release предназначена для рабочей версии устройства. Получающиеся двоичные файлы прошивок соответственно debug\usb-console.hex и release\usb-console.hex. Обе конфигурации основаны на командных файлах Makefile (debug\Makefile.debug и release\Makefile.release соответственно). Эти файлы очень важны, т. к. в них задаются тип процессора, его тактовая частота, опции для компиляции библиотек, состав компилируемого проекта и другие параметры (для новичка эти файлы могут показаться китайской грамотой, но постепенно туман рассеется, так как там все довольно просто и логично). Назначение папок и файлов архива:

debug\   папка, в котором хранится Makefile.debug и результаты компиляции debug
doc\     некоторая документация
release\ папка, в котором хранится Makefile.release и результаты компиляции release
usbdrv\  папка, которую даже не открывайте - там библиотека V-USB
cmd.c    код, поддерживающих обработку команд от пользователя
cmd.h    заголовочный файл для функций, переменных и констант модуля cmd.c
crc16.c  подпрограмма подсчета контрольной суммы
crc16.h  заголовочный файл для функций, переменных и констант модуля crc16.c
eepromutil.c   подпрограммы, работающие с энергонезависимой памятью (сохранение и считывание параметров)
eepromutil.h   заголовочный файл для функций, переменных и констант модуля eepromutil.c
main.c   модуль, где находится главная функция программы (основной алгоритм)
pins.c   подпрограммы для работы с портами ввода/вывода
pins.h   заголовочный файл для функций, переменных и констант модуля pins.c
readme.txt  файл, с общими комментариями
strval.c строковые константы, содержащиеся во flash, и код для вывода текста в консоль
strval.h заголовочный файл для функций, переменных и констант модуля strval.c
timer.c  код для работы с аппаратным таймером (нужен для отсчета глобального времени)
timer.h  заголовочный файл для функций, переменных и констант модуля timer.c
timerint.S  код обработчика прерывания таймера (на ассемблере)
types.h  определения типов
usbcode.c   код поддержки протокола USB и класса CDC. Его изменять и понимать не нужно.
usbconfig.h важные настройки, касающиеся интерфейса USB и библиотеки V-USB (об этом далее)
usb-console.aps   настройки проекта AVRStudio
usb-console.aws   настройки рабочего окружения AVRStudio
vars.c   некоторые определения глобальных переменных
vars.h   заголовочный файл для глобальных переменных

[Как встроить консоль в свое радиолюбительское устройство на микроконтроллере ATmega]

Я предполагаю, что у Вас уже установлена среда AVRStudio и пакет WinAVR, проект по ссылке [5] Вы скачали и он успешно компилируется. Если это так, то считайте что 50% работы уже проделано. Теперь что делать дальше, процесс по шагам (ИМХО, конечно):

1. Определитесь с ресурсами. Убедитесь, что Вы сможете встроить консоль в свой проект - для консоли потребуется минимум 6 килобайт кода и 440 байт ОЗУ. Проверьте, что кварц в устройстве выбран на одну из частот 12, 15, 16, 16.5 и 20 МГц (другие частоты не подойдут!), проверьте, соответствует ли в Makefile.debug и Makefile.release переменная F_CPU частоте выбранного кварца. Проверьте также переменную MCU - она должна соответствовать вашему микроконтроллеру.

2. Выделите ножки для интерфейса USB. Одна нога (сигнал D+) должна без вариантов сидеть на прерывании INT0, поэтому если она у Вас где-то используется, то освободите. Вторая ножка (D-) не предъявляет под себя никаких дополнительных требований (т. е. в принципе, это может быть любой порт - но только в том случае, если Вы понимаете, что делаете). Проверьте, что Выбор ножек совпадает с установками макросов USB_CFG_IOPORTNAME, USB_CFG_DMINUS_BIT, USB_CFG_DPLUS_BIT (см. файл usbconfig.h). Если не хотите разбираться и заморачиваться - тупо освободите те ножки, которые уже сконфигурированы в usbconfig.h и подключите их по одной из схем включения (примеры см. в папке doc).

3. Задумайтесь над тем, как Вы хотели бы, чтобы Ваш COM-порт назывался в компьютере. Помните, как Вы назовете корабль, так он и поплывет (шутка). На самом деле эти параметры менять необязательно, но если хотите, то см. макросы USB_CFG_VENDOR_NAME, USB_CFG_DEVICE_NAME, USB_CFG_SERIAL_NUMBER (внимание, с ними связаны макросы USB_CFG_VENDOR_NAME_LEN, USB_CFG_DEVICE_NAME_LEN USB_CFG_SERIAL_NUMBER_LEN соответственно!) все того же файла настроек usbconfig.h.

Макрос USB_CFG_SERIAL_NUMBER довольно важен, так как от него зависит поведение определения наличия USB-устройства в системе Windows. Так, например, если Вы оставите макрос USB_CFG_SERIAL_NUMBER без изменений, то в какой бы USB-порт Вы свое устройство не воткнули, к нему всегда будет привязан один и тот же номер COM-порта. Если же Вы этот макрос поменяете, то при подключении устройства к компьютеру у Вас обнаружится новое устройство, которое привяжется к другому номеру COM-порта. Если же Вы этот макрос Вы удалите совсем (закомментируете), то для каждого USB-порта в компьютере для Вашего устройства будет назначен индивидуальный номер COM-порта. Какую конфигурацию выбрать - решайте сами (выбор может быть полезен, если надо подключить несколько устройств одновременно).

Если по какой-то причине необходимо поменять VID и PID устройства, то см. макросы USB_CFG_VENDOR_ID и USB_CFG_DEVICE_ID соответственно, в том же самом файле usbconfig.h.

4. Определитесь с функционалом Вашей консоли, т. е. подумайте над набором команд, которые должна консоль выполнять. Как только составите список, начните добавлять команды по одной в проект AVR USB CONSOLE. После того, как все команды добавите, то можно перенести в проект и остальной код прибора. Можно, конечно, действовать и по другой схеме - например, перенести модули проекта AVR USB CONSOLE в Ваш проект. Все зависит от Вашего опыта и предпочтений.

При встраивании программной поддержки USB необходимо учитывать, что обработка протокола USB занимает много ресурсов микроконтроллера. Поэтому Ваш код должен быть написан таким образом, чтобы он был неблокирующим, т. е. не блокировал надолго прокрутку основного кода main. Кроме того, все обработчики прерываний (если они у Вас есть) должны либо отрабатывать очень быстро, либо должны сразу же разрешать внутри себя прерывания - для того, чтобы прерывание INT0 от сигнала D+ могло выполниться с приоритетом.

Для изменения алгоритма работы и функционала консоли достаточно исправить только файлы usbconfig.h, cmd.c, strval.c, types.h, main.c:
usbconfig.h - настройка интерфейса USB (об этом файле я уже написал)
cmd.c       - добавление и обработка новых команд
strval.c    - добавление подсказок
types.h     - добавление полей сохраняемых параметров в структуру Teeparam eeprm
main.c      - добавление вызовов неблокирующих процедур в основной цикл main

Теперь подробнее о назначении модулей и об изменениях, которые потребуется туда внести при "заточке" консоли под ваши нужды.

[cmd.c]

В модуле cmd.c реализован обмен данными (т. е. прием и обработка команд) с текстовой консолью на компьютере. Приятно то, что никакую программу управления для компьютера писать не надо. Можно взять готовую текстовую консоль, работающую с COM-портом (SecureCRT, Hyperterminal, TerraTerm, putty и т. д.). Все, что вводите в консоль, попадает в кольцевой буфер rx_usb микроконтроллера. Все, что микроконтроллер поместит в буфер tx_usb, будет моментально выведено на экран консоли. Осталось только обработать этот обмен нужным образом - простые примеры, как это делается, есть в файле cmd.c. Если нужны более сложные примеры, скачайте проекты по ссылке [7].
Теперь немного подробнее о вводе и выводе через кольцевые буферы.

Как я уже упоминал - все, что вводит пользователь с клавиатуры консоли, попадает в кольцевой буфер rx_usb - запись в буфер происходит по индексу записи inUsbRX (о записи Вам беспокоиться не надо, это делают процедура обработки протокола USB CDC), а чтение из буфера по индексу чтения outUsbRX (подробнее про принцип работы с кольцевым буфером см. по ссылке [8]). Обработка данных, которые туда попадают, производится периодическими вызовами процедуры cmd.c -> DecodeCommands. Процедура DecodeCommands ждет появления в буфере символов перевода строки или каретки, и после этого анализирует принятую команду (с помощью вызовов GetCmd и CmdOK). Если введенная пользователем команда совпала с ожидаемой, то выполняются соответствующие действия, иначе выводится сообщение об ошибке. Список обрабатываемых команд определен в начале модуля cmd.c. Все тупо и просто.

Вывод в консоль символов происходит через другой кольцевой буфер tx_usb - все по аналогии, пишем в консоль по индексу inUsbTX, а о чтении беспокоиться не надо, это делает процедура virtualComPoll, вызываемая в основном цикле main.

Размеры кольцевых буферов rx_usb (размер определен константой RX_BUF_SIZE) и tx_usb (размер определен константой TX_BUF_SIZE) можно увеличить или уменьшить в зависимости от требований к программе. Если нужно за один раз вывести в консоль больше текста, то надо увеличить TX_BUF_SIZE, а если нужно принять от пользователя команду подлиннее, то надо увеличить RX_BUF_SIZE. И наоборот, если ввод и вывод происходит более короткими порциями, то размер буферов можно уменьшить.

[strval.c]

Вывод в консоль текстовых сообщений делают процедуры msgn, msg, pmsg из модуля strval.c. В этом же модуле размещены тексты подсказок.

[types.h]

В этом файле определена структура Teeparam. В неё вы можете добавить поля любого типа - bool, char, u8, u16, u32 и даже float, double или long double. Все поля экземпляра этой структуры eeprm будут исправно сохраняться в EEPROM, если Вы вызовете процедуру Save.

[main.c]

Функционал программы, как обычно, определен поведением кода функции main. Все по стандарту - до основного цикла делаем нужную инициализацию, а в тело основного цикла добавляем вызовы неблокирующих процедур, которые выполняют нужные для нас действия. В демонстрационном примере AVR USB CONSOLE в основной цикл добавлены следующие процедуры:

wdt_reset      - сброс сторожевого таймера (туда лезть не надо)
usbPoll        - процедура, поддерживающая работу USB     (туда лезть не надо)
virtualComPoll - процедура, поддерживающая работу USB CDC (туда лезть не надо)
timePoll       - процедура, поддерживающая отсчет реального времени (туда лезть не надо)
DecodeCommands - прием и обработка команд пользователя, которые он отдает через текстовую консоль

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

[eepromutil.c]

Модуль eepromutil.c предназначен для сохранения параметров в энергонезависимой памяти через структуру Teeparam eeprm. В эту структуру можно добавлять дополнительные поля для сохранения нужных параметров. Пока в структуре определено только одно полезное поле - ledstate, для сохранения состояния светодиода. Значения параметров в структуре eeprm защищены контрольной суммой. Если, например, при включении питания выяснилось, что контрольная сумма не совпадает (т. е. параметры в eeprm ошибочны), то происходит автоматическая реинициализация параметров значениями по умолчанию (это делается вызовом eepromutil.c -> RestoreEEPROM).

[timer.c]

В модуле timer.c реализована настройка таймера 1 и поддержка обработки часов реального времени с точностью до 1 мс. Милисекундный счетчик находится в 32-битной глобальной переменной timestamp. Эту переменную удобно использовать во всех модулях для отсчета интервалов времени (например, для отсчета таймаутов, реализации мигания светодиодом, временнЫх задержек). Вот так, например, просто реализуются вспышки светодиода с частотой 1 Гц:

static u32 ledtimestamp = 0;
if (ledtimestamp < timestamp)
{
  if (RED())
  {
     //погасим светодиод на 900 мс
     ledtimestamp = timestamp+900;
     OFF_RED();
  }
  else
  {
     //зажжем светодиод на 100 мс
     ledtimestamp = timestamp+100;
     ON_RED();
  }
}

[Процедура первого подключения Вашего устройства к USB]

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

[Makefile.debug]
AVR Memory Usage
----------------
Device: atmega32
Program:   10876 bytes (33.2% Full)
(.text + .data + .bootloader)
Data:        318 bytes (15.5% Full)
(.data + .bss + .noinit)

[Makefile.release]
AVR Memory Usage
----------------
Device: atmega32
Program:    5774 bytes (17.6% Full)
(.text + .data + .bootloader)
Data:        310 bytes (15.1% Full)
(.data + .bss + .noinit)

Если ничего не напутали (не ошиблись с выбором типа микроконтроллера, его тактовой частотой, подключением ножек D+ и D-, прошили правильно код, фьюзы), то все заработает сразу. Если у Вас макетная плата AVR-USB-MEGA16, то ошибиться очень трудно - схема подключения к USB уже готовая, фьюзы прошиты, все настройки Makefile (тип процессора и частота) и usbconfig.h совпадают, осталось только залить во flash код с помощью программатора (или bootloader-а, если Вы его используете).

avr-usb-mega16-IMG_8082.JPG

После первого подключения компьютер запросит драйвер. Драйвер можно скачать по ссылке [10] (класс USB CDC, виртуальный COM-порт), при установке тупо жмем "Да", "Далее" и  "ОК". Если запросит подтверждение на установку "неподписанного" драйвера, соглашаемся.

setup-cdc-driver01.PNG setup-cdc-driver02.PNG setup-cdc-driver03.PNG setup-cdc-driver04.PNG setup-cdc-driver05.PNG setup-cdc-driver06.PNG

После этого драйвер установится в Вашей системе, и можно подключится к получившемуся COM-порту Вашей любимой программой. В этом примере используется замечательная программа SecureCRT.

Все параметры подключения - стандартные (скорость подключения роли не играет) - 8 бит данных, без четности, 1 стоп-бит, аппаратное управление потоком отключено.

usb-console01.PNG

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

usb-console02.PNG

В примере используется вывод только англоязычных сообщений, но можно также легко выводить сообщения и на русском языке - если кодировку Win-1251 поддерживает Ваша программа консоли. В SecureCRT русификация включается через настройку свойств подключения - Appearance -> Fonts -> Character encoding: Default -> кнопка Font... -> в выпадающем списке Script выбираем Cyrillic.

[Ссылки]

1. Макетная плата AVR-USB162.
2. Макетная плата AVR-USB-MEGA16.
3. LUFA - библиотека примеров программ для микроконтроллеров AVR с аппаратным интерфейсом USB.
4. avr-usb-russian - библиотека V-USB с переведенными на русский язык комментариями и документацией (программная реализация протокола USB на микроконтроллерах AVR, не имеющих аппаратного интерфейса USB).
5. Максимально упрощенный демонстрационный проект рабиолюбительской консоли USB - AVR USB CONSOLE (проект AVRStudio со всеми исходниками). Проект может быть скомпилирован для микроконтроллеров ATmega8, ATmega16, ATmega32, ATmega128 и других микроконтроллеров AVR, имеющих досточно ресурсов (минимум 6 килобайт flash и 440 байт RAM). Значения фьюзов (совпадают со значениями фьюзов AVR-USB-MEGA16) - LOW FUSE BYTE: 0xCF, HIGH FUSE BYTE: 0x98, LOCKOPT BYTE: 0xEF.
6. Разработка устройства USB - как начать работу с библиотеками AVR USB (V-USB) и libusb.
7. Проекты с использованием текстовой консоли - hardctrl, VA-meter, radio-synt-LC72131, LED8X8.
8. Работа с кольцевым буфером.
9. AVR-CDC: виртуальный COM-порт через Low-Speed USB (используется библиотека V-USB).
10. Драйвер для устройства AVR USB CONSOLE.