Отладка микроконтроллеров AVR в Linux


В продолжении статьи "Программирование для AVR в Linux", мы расскажем, как осуществлять отладку микроконтроллеров семейства Atmel AVR в Linux, используя симулятор и интерфейс JTAG. Будем исходить из того, что мы используем дистрибутив, основанный на Debian (Ubuntu) и у нас есть все необходимые программные пакеты, которые мы установили, руководствуясь вышеупомянутой статьей (gcc-avr binutils-avr avr-libc avrdude gdb-avr avarice simulavr). Отладку будем производить в интегрированной среде разработке Eclipse с установленным AVR Eclipse Plugin. Эта статья, так же как и предыдущая адресуется начинающим пользователям Linux и начинающим разработчикам ПО для микроконтроллеров.

Для отладки мы будем использовать GNU Debugger (gdb), а точнее его модифицированную версию avr-gdb (бинарный файл avr-gdb мы получили, установив пакет gdb-avr). Этот отладчик умеет работать с удаленным сервером по TCP-соединению, именно так осуществляется удаленная отладка на платформах, отличных от платформы разработчика (например, так отлаживают программы для ARM-устройств). Клиентская часть отладчика общается с сервером, отсылая ему команды на чтение данных, установку точек остановки и т.п.

Мы, конечно не сможем установить gdb-server на микроконтроллер, но инструменты отладки, которые мы будем использовать, как раз умеют притворяться удаленным gdb-сервером. Именно так работает симулятор simulavr и утилита avarice, которая общается с микроконтроллером через интерфейс JTAG и реализует gdb-сервер для общения со стандартным отладчиком gdb. Так как эти утилиты реализуют удаленный сервер, то мы можем запускать их не только на локальной машине, но и на любой другой машине в сети. Полагаем, пока нам это не особо нужно и мы запустим все на своей машине. Нам нужно будет знать два параметра после запуска gdb-сервера - сетевой адрес (в нашем случае - localhost) и номер порта (выберем любой незанятый адрес из диапазона 1024-65535 и зададим этот номер порта в качестве параметра при запуске simulavr или avarice).

Для отладки нам понадобиться скомпилировать исходники с опцией -g, чтобы добавить в elf-файл отладочную информацию. Настраивая Eclipse (см. предыдущую статью), мы указали такую опцию для компилятора GCC.

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

Отладка с помощью симулятора

Симулятор - это программа, которая притворяется микроконтроллером. Таким образом, мы можем отлаживать нашу программу, загрузив ее в симулятор, не имея при этом макетной платы, микроконтроллера и программатора. Кроме того, для микроконтроллеров, не поддерживающих JTAG, о котором будет рассказано ниже, использование симулятора - это единственный способ, который дает возможность, не прибегая к модификации кода, выполнить программу с точками останова, пошагово, копаясь по пути в содержимом регистров и переменных.

Мы рассмотроим отладку с помощью симулятора на примере simulavr. Чтобы почитать справку об использовании simulavr, достаточно набрать в терминале man simulavr (клавиша 'h' - список клавиш для перемещения и поиска по справке). Краткую справку можно вызвать, набрав simulavr --help (это стандартный ключ, поддерживающийся практически всеми программами в Linux). Посмотреть список устройств, которые программа может симулировать можно так: simulavr -L.

Запустим simulavr в качестве gdb-сервера (опция -g), слушающего порт 1200 (-p 1200), в режиме микроконтроллера ATMega8 (-d atmega8), работающего на частоте 8 МГц (-c 8000000) и заставим его отображать содержимое регистров внешней программой simulavr-disp, которая установилась вместе с пакетоми simulavr (опция -P simulavr-disp):

    simulavr -g -p 1200 -d atmega8 -c 8000000 -P simulavr-disp

    Waiting on port 1200 for gdb client to connect...

Мы видим, что программа ожидает соединение клиентской части отладчика на порту 1200 и еще мы видим, что открылось окно, отображающее значение регистров общего назначения и регистров ввода-вывода.

Мы могли бы теперь открыть еще одно окно виртуального терминала, запустить в нем отладчик avr-gdb, ввести команды чтения elf-файла, адрес и порт gdb-сервера (localhost:1200), команду load и отлаживаться, задавая вручную точки остановок и выводя на печать значения переменных, но нам будет намного удобнее отлаживаться в IDE. Настроим Eclipse для работы с симулятором.

Настройка IDE Eclipse для отладки с simulavr

Сконфигурируем инструмент отладки:
 
    Run -> Debug configurations...
    
В левой части окна кликнем правой кнопкой мыши на пункте "GDB Hardware Debugging" и создадим новую конфигурацию (New...). Первым делом укажем имя для новой конфигурации (поле "Name"). Придумаем что-то, понятное для нас (например, simulavr-test).

На вкладке Main введем в поле "Project" нужный проект, в поле "C/C++ Application" укажем путь elf-файлу, проще всего воспольтзоваться кнопкой Search Project...

На вкладке Debugger в поле "GDB Command" впишем полный путь к клиенту отладчика: /usr/bin/avr-gdb. Отметим чекбокс "Use remote target", установим пункт "JTAG Device" в "Generic TCP/IP", укажем ниже адрес и порт сервера отладки (localhost, 1200).

Перейдем ко вкладке Startup и найдем там секцию "Runtime Options". Установим чекбокс напротив "Set breakpoint at" и введем значении функции: main. Установим чекбокс напротив "Resume". При отладке программа остановится при входе в функции main и мы сможем пройти ее пошагово или ввести дополнительные точки остановки и запустить программу до первой из них.

На этом конфигурирование отладки можно закончить, закроем окно Debug Configurations и нажмем на стрелочке рядом с кнопкой отладки (изображение жука), выбрав "Organize Favorites...". Отметим галочкой только что созданную конфигурацию simulavr-test.

В отдельном окне терминала у нас уже запущен simulavr на нужном нам порту, поэтому теперь мы выделяем в Eclipse наш проект и запускаем отладку, выбрав из списка "simulavr-test" под кнопкой отладки.

Eclipse запустит avr-gdb, загрузит elf-файл и предложит открыть набор инструментов ("перспективу") для отладки. После этого у нас окажутся под рукой вкладки с исходным кодом, значениями локальных переменных, списком точек остановки, значениями регистров общего назначения, вкладку Expressions, где мы сможем добавлять Си-выражения и отслеживать их значение в разных точках программы. На вкладке с исходным кодом мы сможем устанавливать и удалять точки остановки, а в верхней панели найдем кнопки для управления ходом выполнения программы, в том числе для пошагового выполнения программы.

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

Обратите внимание на то, что на вкладке регистров мы видим только содержимое регистров общего назначения, но, к сожалению, не видим регистров ввода-вывода. Кроме того, мы не сможем указать в выражении что-то типа "PORTB". Поэтому для того, чтобы просмотреть содержимое регистра ввода-вывода нам придется создать переменную и присвоить ей в нужном месте содержимое регистра. Но, если вы еще не забыли, у нас есть запущенное симулятором окно с программой simulavr-disp, в котором мы видим все регистры - можно пользоваться им.

Чтобы не запускать каждый раз симулятор из командной строки, сконфигурируем Eclipse для запуска simulavr:

    Run -> External Tools -> External Tools Configuration...

В левой части окна жмем правой клавишей мыши на пункте "Program", "New...". Придумаем имя, например "ATMega8 8MHz localhost:1200".

На вкладке Main, в поле "Location" укажем полный путь к симулятору: /usr/bin/simulavr. В поле "Arguments" - параметры вызова: -g -p 1200 -d atmega8 -c 8000000 -P simulavr-disp.

На вкладке Build снимем галочку с пункта "Build before launch", иначе симулятор не запустится, если при компиляции будут выданы предупреждения.

На вкладке Common, в разделе "Display in favorites menu" установим галочку напротив "External Tools".

Теперь у нас все готово для комфортнорй работы. Перед первым запуском сеанса отладки запускаем симулятор, выбрав в под кнопкой внешних инструментов, в выпадающем списке только что созданную конфигурацию "ATMega8 8MHz localhost:1200". Потом под кнопкой отладки выбираем конфигурацию отладки ("simulavr-test") и переходим к отладке.

По окончании отладки процесс симулятора будет оставаться в фоне, снова запускать его не нужно. Если нам нужно отлаживать несколько проектов для разных устройств, создаем несколько конфигураций в External Tools и соответствующих им конфигураций отладки, развесив каждый вызов симулятора на разные порты.

Отладка с помощью JTAG

Интерфейс JTAG позволяет осуществлять отладку непосредственно внутри микроконтроллера, без модификации кода. Здесь тоже есть некоторые ограничения: потребуется занять некоторые выводы микроконтроллера, кроме того, не все микроконтроллеры поддерживают JTAG.

У нас в офисе нашелся AVR-JTAG-USB от Olimex. Мы скачали с сайта производителя даташит к этому устройству, посмотрели его распиновку и без труда соединили с микроконтроллером на макетной плате.

Нам нужно определить, как будет представлено это устройство в каталоге /dev после подключения его к разъему USB. Сравним листинг каталога до и после включения устройства:

    ls /dev > /tmp/dev1 # сохраним список устройств в /dev во временный файл /tmp/dev1
    # присоединим JTAG
    ls /dev > /tmp/dev2 # сохраним список устройств в /dev во временный файл /tmp/dev2
    comm -3 /tmp/dev1 /tmp/dev2 # найдем различия в списках устройств

Допустим, мы обнаружили, что появилось устройство ttyUSB1. Теперь нужно запустить в терминале утилиту avarice с нужными параметрами, чтобы получить gdb-сервер, который будет общаться с микроконтроллером чере JTAG (заставим его слушать порт 1201):

    avarice -j /dev/ttyUSB1 -B 1000 -P atmega16 -d localhost:1201

Здесь мы указали устройство (-j /dev/ttyUSB1), скорость обмена данными (-B 1000), которая не должна превышать ¼ от тактовой частоты микропроцессора (подробнее можно прочитать в мануале, man avaraice), тип микропроцессора (-P atmega16), режим отладки (-d), адрес и порт (localhost:1201). К слову сказать, вызвав avarice с соответствующими параметрами, мы можем и залить прошивку в микроконтроллер, при условии, что установлен соответствующий Fuse-бит (см. avarice --help или man avarice).

Если JTAG подключен и утилита успешно загрузилась, мы увидим сообщение "Waiting for connection on port 1201", avarice ждет соединения gdb-клиента. Если этого не произошло, то читаем сообщения avarice и устраняем неполадки. Проверяем соединение, скорость соединения, тип микроконтроллера, права доступа на устройство /dev/ttyUSB1 (о том, как включить пользователя в группу для работы с устройством, мы рассказывали в предыдущей статье).

Теперь настроим Eclipse:

    Run -> Debug configurations...
    
Создаем новую конфигурацию в разделе "GDB Hardware Debugging" и задаем ей имя, к примеру, "JTAG-test". Выполним все оставшиеся шаги по настройке конфигурации точно так же, как мы это делали для конфигурации отладки "simulavr-test", у нас изменится только номер порта gdb-сервера. В заключении перейдем на вкладку Debugger и выберем "Standard GDB Hardware Debugging Launcher" по ссылке в самом низу окна. После этого в разделе "GDB Setup" на этой же вкладке появится два дополнительных полях, установим значения в них: "Command Set" -> "Standard", "Protocol Version" -> "mi2".

Пробуем запустить отладку в Eclipse. Если что-то идет не так, смотрим на сообщения, которые выводит avarice. После каждого сеанса отладки avarice, возможно, придется запускать заново. Если все работает хорошо, добавляем вызов avarice в External Tools таким же способом, как мы это делали для simulavr. Полный путь к avarice можно узнать командой

    whereis avarice

    /usr/bin/avaraice

Мы рассмотрели вкратце настройку инструментов отладки на примере IDE Eclipse. В Linux есть и другие визуальные среды отладки, которые могут работать с исходным кодом и отладчиком GDB, можно попробовать avr-gdbtui (утилита в составе пакета gdb-avr), утилита ddd (ставится отдельным пакетом), и др.

Owlet 

Обсуждение на форуме