Как я осуществляю отладку микроконтроллерных программ

Задающего этот вопрос товарища интересовало как вообще осуществляется отладка микроконтроллеров STM32 из среды Linux.

В Винде, там — понятно, там сама среда разработки (IAR, KEIL) для этого имеет соответствующие режимы и средства. А вот в Линуксе — как? Тут ведь нет единой среды. Текстовый редактор — отдельно, компилятор — отдельно, программа для заливки кода — отдельно. Всё это хозяйство логически объединяется и управляется с помощью Make-файла. И это у меня в блоге было неоднократно расписано на примерах разного типа микроконтроллеров (AVR, MSP430, STM32). А вот про отладку — я скромно умолчал.

Вообще под процессом отладки я понимаю несколько уровней (отладки).

Первый уровень — самый низкий уровень. Это уровень портов. Тут самое главное получить от микроконтроллера адекватное «шевеление ножками». На этом этапе отладки микроконтроллер должен уметь однозначно управлять битами порта. На специально выделенные для этого дела выводы порта я обычно навешиваю светодиоды. Таким образом я могу наблюдать «неспешные» процессы.

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

Шевеление ножками удобно так же для наблюдения за быстро протекающими процессами. В этом случае к портам я подключаю осциллограф. Осциллограф позволяет оценить не только сам факт наличия изменения состояния порта, но и длительность импульсов. Осциллограф позволяет так же оценить время изменения какого-то конкретного вывода порта относительно изменения сигналов на других выводах порта.

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

На этом этапе обычно отлаживается система тактирования микроконтроллера и подключение тех или иных периферийных модулей.

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

Второй уровень отладки зависит от типа микроконтроллера и типа подключения к нему. Например для микроконтроллеров AVR, подключенных по SPI этот этап отладки физически невозможен. Поэтому для этих микроконтроллеров, мы вынуждены перейти сразу к следующему — третьему уровню. А для всех остальных это очень познавательный этап. Давайте немного погрузимся в него.

Второй уровень отладки возможен только при подключении к микроконтроллеру по JTAG. Не все типы микроконтроллеров имеют JTAG. Например, у семейства ATTINY интерфейс JTAG отсутствует как класс.

С другой стороны, некоторые виды микроконтроллеров имеют JTAG, «завёрнутый» в другой интерфейс, у которого задействует меньше выводов микроконтроллера. Думаю, не надо объяснять чем это полезно.

У семейства микроконтроллеров MSP430x2xx и более поздних семейств этот интерфейс называется SBW (Spy By Wire). В этом интерфейсе используются два сигнала: SPWTCK и SBWTDIO.

У микроконтроллеров STM32 тоже имеется малоножечный отладочный интерфейс, он называется SWD (Serial Wire Debug). Названия его контактов: SWCLK и SWDIO.

Дальше всех пошли микроконтроллеры AVR. У некоторых из них реализован вообще однопроводный отладочный интерфейс dW (Debug Wire). Честно говоря, у меня нет опыта работы с ним. Если при работе с MSP430 и STM32 я постоянно использую SBW и SWD, то относительно AVR-овского dW — ни разу! Точнее так, был у меня печальный опыт «окирпичивания» камня, после чего я стал сторониться пользоваться этой технологией. Да и оборудования (то бишь — программатора) у меня нет для dW.

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

Достижение такого уровня отладки осуществляется не только с помощью подключения соответствующих отладочных средств (по простому — программаторов), но так же запуском соответствующих программ.

Если речь идет об отладке MSP430, то здесь несколько проще. Запускается одна программа — mspdebug, и в ней осуществляется отладка. Поскольку работа программы осуществляется в терминале, а сама программа работает в среде Линукс, то «пробросить» терминал на другой компьютер не составляет особого труда. У меня даже есть небольшой опыт работы с MSP430, который находился от меня за несколько тысяч километров. Я не ощутил каких-либо различий в работе по отношению где лежит этот микроконтроллер — у меня на столе или где-то в Испании или Канаде. Скорость человеческой реакции намного меньше, чем скорость передачи информации по интернету. Поэтому работа с микроконтроллером выглядит так, как будто он лежит рядом. Максимум за стеной в соседней комнате.

Чтобы работать с mspdebug, нужно знать команды этой среды. Я не думаю, что было бы сейчас уместно останавливаться на этом и углублять свои знания. Поэтому, если кому-то интересно, то я могу рассказать об этом как-нибудь в другой раз.

В отношении отладки STM32 под Линуксом по JTAG-у или урезанному интерфейсу SWD дела обстоят несколько сложнее. Для полноценной отладки нужно запускать не одну, а две программы.

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

На первый взгляд кажется странным способ связи этих программ — по сетевому протоколу TCP/IP. Но стоит вспомнить, что в среде Линукса межпроцессоное взаимодействие в основном так и осуществляется. Для Линукса такое межпроцессное взаимодействие естественно и очень эффектвно. Конечно, если у вас отладка идет не на двух компах, разнесённых в пространстве на тысячи километров, то вы в качестве сетевого IP-адреса можете указать loop-интерфейс — localhost (или 127.0.0.1). Операционной системе ведь по барабану на какой адрес слать пакеты. зато в случае двух-комповой отладки не надо напрягаться — программы будут взаимодействовать между собой точно так же.

Та, программа, которая решает интерфейсные вопросы, по своей сути является серверной программой. Соответственно, он должна запускаться первой. До недавнего времени это была единственная (по моему) программа OpenOCD. Но совсем недавно набор утилит STLink обзавёлся подобным функционалом. Во всяком случае, последние свои разработки я проводил с использованием именно st-util, не openocd.

debugging-1
После того, как сервер будет запущен, он сообщит на каком порту он будет «слушать» поступающие к нему команды. Для st-util этот порт — 4242. Но это ничего не значит! Выбрать можно любой другой не занятый. Поскольку каждый компьютер (разумеется исключения не рассматриваем!) имеет свой уникальный IP-адрес, а программа — свой уникальный сетевой порт, то где-бы ни находился отлаживаемый микроконтроллер, мы всегда сможем к нему «достучаться». Конечно, для случая, когда микроконтроллер будет трудится где-нибудь на Марсе, время отклика от него займёт очень много часов. Но в пределах планеты Земля, время ответа практически никак не сказывается.

Далее нам нужно запустить дебаггер. Если отладка происходит на одном и том же компе (а не на разных, разнесённых в пространстве), то лучше открыть ещё одно окно псевдо-терминала.

Запустить дебаггер можно просто тупо «толкнув» его:

debugging-2

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

$ arm-none-eabi-gdb myproga.elf

Особой разницы нет. Всё будет снивелировано в процессе дальнейшей работы.

Обратите внимание, что в окне с программой-сервером тишина, ничего не произошло.

Да. На данный момент мы имеем две запущенные программы, которые ничего друг о друге не знают. Иначе говоря, соединения программ ещё нет. Давайте сделаем это.

Программа сервера нам не нужна, мы её не трогаем. И в самом деле — а вдруг она запущена не на нашем компе, а где-нибудь в жарком Мадриде? И что тогда?

В общем, сейчас задаем команды в окне программы-отладчика. Первая наша команда — target. Она предназначена для подсоединения к серверу.

(gdb) target remote :4242

Здесь remote — сообщает о том, что мы будем работать по удалённому соединению. (Это не важно, что это может быть на том же самом компе, где запущен сервер. Важно то, что программы взаимодействуют на основе (по принципу) удалённого соединения.)

Если сервер находится на этом же компе, то можно не указывать его имя или IP-адрес. А вот порт :4242 указывать обязательно. Ведь сервер может быть «поднят» и на каком-то другом порту, как знать?

Если сервак находится где-то там далеко, то указываем полный путь к нему. Например, я могу сделать так:

(gdb) target remote madrid:4242

Естественно, что имя madrid должно быть известно моему компу. Как правило оно «прописано» в файле /etc/hosts.

Итак, в окне отладчика мы имеем:

debugging-4

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

debugging-3

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

(gdb) file read45db.elf

Это — Линукс! Здесь вам необязательно набирать (как это обычно происходит при работе в терминале у пользователей Виндовс) всё ручками-ручками! Здесь действуют приёмы работы в терминале. Например, нажатие на клавишу Tab позволит «закончить» имя файла.

Вам достаточно набрать первые несколько символов и нажать на Tab.

Если же имя файла завершить не удалось с первого раза, то ещё одно нажатие на Tab выведет список файлов, которые подходят под начатый шаблон. Оболочка останавливает «завершение» имени файла, когда она не может сделать однозначный выбор. В этом случае ей нужна подсказка от человека. Вот так в содружестве машина и человек помогают друг другу выполнять обычную работу. (В Виндовсе работа с именами файлов происходит вообще по другому. Но это не значит, что Виндовс плохой. Он —другой! Да. С моей точки зрения он какой-то странный. Но ведь он кому-то нравится, и кто-то не мыслит ничего другого кроме Виндовса.)

Во время написания текста этой статьи я пользовался наработками из своего предыдущего проекта. Я обычно чищу проекты после их завершения от производных файлов, оставляю только исходники и некоторые конфигурационные файлы. Вот так получилось, что в момент написания статьи конечного файла read45db.elf в директории проекта у меня не было.

На следующем скриншоте видно, что я даже не пытался набрать имя файла. Я тупо дважды нажал на Tab. Система напечатала список доступных файлов. Среди них нет нужного мне .elf-файла:

debugging-5

Но это не плохо. Это даже очень хорошо! Хорошо потому, что у меня появился прецедент рассказать людям ещё про одну фичу, о которой, честно говоря, я не очень-то и планировал.

Вопрос к залу — что бы вы сделали в описанном выше случае, когда вы зашли в дебаггер, а откомпилированной программы для отладки нет и в помине?

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

Так вот, ничего этого делать не надо! Держитесь за край стола!

Достаточно в отладчике (не выходя из него!) набрать команду для компиляции проекта:

(gdb) make

и всё само срастётся волшебным образом! Вуаля:

debugging-6

Теперь вводим команду для загрузки символов (не забывайте про волшебную клвишу Tab!):

debugging-7

При необходимости мы можем заново залить код в микроконтроллер:

(gdb) load

, но я это делать сейчас не буду.

Честно говоря, то, о чём я рассказал — это только организационная или подготовительная часть работы по отладке программ в STM32. По большому счёту саму отладку нужно описывать в отдельной статье, причём обучение отладке основывается на знании команд отладчика. Этих команд достаточно много, чтобы «залепить» их в эту статью. Возможно, как-нибудь я и напишу такую статью, но не могу обещать. А «раздувать» эту статью я не хочу. Перебор информации — это ведь не есть хорошо.

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

К сожалению, я тут должен извиниться. Извиниться за то, что я разрушаю ваши надежды.

Я что-то не припомню специфики отладки AVR-ок по JTAG-у. Не, конечно, мне что-то такое приходилось делать, но я что-то не особо помню что там и как конкретно происходит. Соответственно, я не могу поделиться опытом по отладке AVR-ок по JTAG-интерфесу. Звиняйте!

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

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

Зачастую это даже не столько отладочный уровень, сколько уровень наблюдения за работой программы — уровень, где осуществляется логирование работы программы. Потом, когда-нибудь, логи программы будут подняты и рассмотрены. А пока это монотонное сливание отладочных сообщений на компьютер в какой-нибудь текстовый файл.

Это очень индивидуальный (зависящий от проекта) подход к отладке. Поэтому тут нужно рассматривать специфику работы на конкретных примерах. У меня многие такие проекты обладают коммерческой тайной. (Не в том смысле, что я на них делаю деньги! Не-ет! Мои личные проекты практически бесполезны и бессмысленны, и денег на них не поднимешь. Но ведь я так же работаю с другими людьми… Нужно уважать чужой бизнес!) Поэтому, я рассчитываю на ваше понимание проблемы.

Ну, наверно, на этом я и завершу свою скучную лекцию.

3 responses to “Как я осуществляю отладку микроконтроллерных программ

  1. Хорошая статья, а то все эти фишки со временем забываются, если есть пауза в использовании.

  2. Полезная статья. У меня тоже часто люди спрашивают как отлаживать МК под Linux. Теперь буду их сюда отправлять. Правда за всё время работы с микроконтроллерами GDB мне так и ни разу не пригодился. Большинство проблем у меня было во взаимодействии со внешними устройствами. Поэтому здесь лучший помощник — осциллограф. Также ещё полезно иметь отладочный интерфейс UART, куда я обычно вывожу какую-либо телеметрию. Это позволяет отлаживать устройство без остановки выполнения программы.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s