AVR. Учебный Курс. Отладка программ. Часть 1

У каждого случалась такая ситуация — программа вроде бы написана, даже компилится, но не работает. Почему не работает? Дак все же просто — в ней есть лажа!
 

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

В очередном цикле статей я постараюсь описать как можно более подробно методы, применяемые при отладке.
 

Метод 0. Тупление в код (Аналитический)
К моему великому удивлению, данный метод является наиболее популярным в народе и, а у начинающих порой единственным.
Видимо сказывается засилье всяких высокоуровневых языков вроде ПОХАПЭ или Си, где такое вполне может и проканать. Там да, можно пофтыкать несколько минут в исходник, глядишь да найдешь где накосячил.
 

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

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

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

Хотя я, в свое время, изучал ассемблер вообще без компа — не было его у меня. Тетрадка в клеточку, команды i8008 в столбик. А потом и Z80 с его божественным ассемблером. И опять без отладчиков, аналитически. Ляпота! Но вот когда я сел за ассемблер 80С51, в первую же очередь я нашел нормальную IDE с эмуляцией — Keil uVision. А эра х86 прошла под знаменем Borland Turbo Debugger и TASM. Когда моя первая 128 байтная интруха полыхнула по экрану пиксельным пламенем клеточного автомата… да ощущения были еще те. Где то ее сорцы валяются, надо поискать.

 

В написании может быть, но вот в отладке нифига подобного. Так как нечего тут думать. Ассемблер это как лопата — берешь и копаешь, а не думаешь как там поршни и трансмиссия в экскаваторе крутится.
 

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

Под каждый стиль написания кода свои инструменты отладки. Поэтому переходим к другому методу.
 

Метод 1. Лопата — бери и копай (Трассировка)
Не можешь понять как будет вести код? Как сработают условия? Как пойдет ветвление? Что будет в регистрах?
 

Что тут думать? Выполни в эмуляторе и все! Какая-нибудь AVR Studio идеально для этого сгодится.
 

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

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

Трассировка
Активно используй не только пошаговую трассировку (F11), но и такие удобные фичи как трассировка до курсора (Ctrl+F10), позволяющая сразу же выполнить весь код до курсора. Правда может застрять в каком нибудь цикле по пути. Но можно нажать на паузу и вытащить трассировщик из цикла вручную (выставив флаги так, чтобы произошел нужный нам переход).
 

Обход ненужных условий
Условия, встречающиеся на пути, довольно легко обходятся путем установки вручную нужных флагов. А если логика отлаживаемого участка от этого не пострадает, то можно и временно закомментить мешающий код. Если до нужного участка кода предстоит продраться через несколько десятков условий, то можно облегчить себе задачу, воткнув сразу же после всех нужных инициализаций секцию DEBUG
 

1
2
3
;-----DEBUG
	JMP KUDA_NADO
;----------

 

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

Генерация прерываний
Если нужно отлаживать прерывание, то необязательно этого прерывания дожидаться. Помни, что вызов прерывания делается установкой флага в соответствующем регистре. Т.е. хочешь ты сэмулировать прием байта по UART — тыкай на бит RXC регистра UCSRA и вот у тебя на следующем же шаге произошло прерывание. Разумеется если оно разрешено локально и глобально.
 

Если хочешь запустить прерывание в случайный момент. То нет ничего проще — запусти программу на исполнение (F5) через пару секунд тупления в монитор нажми паузу (Ctrl+F5), тычком по нужному биту сгенерь прерывание, а дальше пошагово влезь в обработчик и позырь что там происходит. После чего проверь, что выход из прерывания произошел корректно и пусти прогу пастись дальше.
 

Ускоряй процессы
Многие процессы на трассировке идут весьма длительно. Например, в режиме скоростного прогона (F5) эмуляция секунды на моем компе (AThlonXP 1300+/512DDRPC3200) идет около минуты, если не больше. Разумеется ждать столько западло.
 

Но зачем ждать? если алгоритм задержки верен, то что нам мешает взять и уменьшить время с секунд до десятков миллисекунд?
Если задержка сделана на таймере, то можно временно уменьшить предделители таймеров в 1. Или даже выставить другие значения уставок. Главное, не забыть вернуть все как было.
 

Остановка по требованию
Это в маршрутках остановок «Здеся» и «тута» не существует. В нашей же симуляции мы можем выгрузиться в пошаговый режим где угодно, хоть посреди МКАД в час пик. Достаточно применить волшебный щелчок по почкам.
 

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

Причем брейкпоинты можно ставить не только на участки кода, но и на события происходящие в регистрах. Это, так называемые, Data Breakpoint. Ставятся они из Debug-NewBreakPoint-DataBreakPoint
 

А там на выбор событий тьма:

Например, хочешь ты поймать событие когда кто то стукнулся в ОЗУ и нагадил, запоров значение. Кто такой поганец? Стек? Где то в коде опечатался и не заметил? Индексный регистр не туда залез?

Чем гадать и тупить в код лучше поставить бряк. Например, загадилась переменная CCNT объявленная в RAM:

1
2
3
4
5
; RAM ========================================================
	.DSEG
CCNT:	.byte	4
TCNT:	.byte	4
; FLASH ======================================================

 

Выбираешь тип события, Location is Acessed (доступ к обьекту) в поле location выбираешь объект за которым будем следить В выпавшем списке будут ВСЕ твои символические имена используемые в программе. В том числе и наши CCNT и TCNT. Надо только внимательно поискать — они там не в алфавитном порядке, а черт знает в каком.
 

Дальше выбираешь тип доступа к переменной (Access Type) — чтение или запись, а может и то и другое. Также можно указать тип данной переменной или ее байтовый размер.
 

А еще можно в памяти разметить колышками Start Addr — End Addr (в графе Custom Scope) делянку, где наша конопля растет. И как только туда кто сунется — алярм, ловить и пинать по почкам.
 

А через контекстное меню, можно по быстрому, на любой регистр, ячейку ОЗУ/ПЗУ/EEPROM повесить Data breakpoint. При этом он сразу же появляется в окошке Breakpoints&TracePoints и там уже можно подправить ему конфигурацию как нужно.

 

Осталось только заполнить графу хитов Break Execution after… Т.е. после скольких событий останавливать трассировку. По умолчанию стоит 1. Но если, например, нам надо пропустить несколько сотен итераций цикла, а на сто первом посмотреть что происходит, то указываем число хитов в 100 и жмем запуск, не страдая фигней на промотке этих итераций.
 

Есть там еще одна интересная галочка — Continue executions after views have been updated. Она превращает бряк в информер. Думаю ты уже замечал, что когда студия гонит код в режиме выполнения (F5), то данные в окошках периферии и регистрах в реальном времени не меняются. Чтобы увидеть изменения надо поставить программу на паузу.
Так вот, брейкпоинт-информер нужен для принудительного обновления этих значений. Т.е. программа на нем не останавливается, а только лишь обвновляет данные в окошках. Что позволяет динамически наблюдать как данные ползают по памяти, как щелкают таймеры, как тикают регистры.
 

Своего рода автоматическое пошаговое выполнение (Alt+F5), но обновление не по каждой команде, а по условию. Да, превратить обычный путевой бряк в информер можно, но для этого надо открыть окно управления брейкпоинтами View-Toolbars-Breakpoint&TracePoint. И там уже, найдя наш путевой брейк, даблкликом по нему залезть в свойства.
 

Там же бряки можно оперативно включать-выключать, что очень удобно. Выключать и включать их также можно по F9.
 

Пускай на самотек
Если программа в железе ведет себя как то не так, то может у ней крышу сорвало? Когда пишешь на ассемблере (да и на Си тоже, но там поймать сложней) легко ошибиться со стеком или индексным переходом. А сразу не поймаешь. Далеко не факт, что срыв найдется при первом или втором пошаговом прогоне. В этом случае я просто жму запуск симуляции и ухожу нарезать колбаски и чаю налить. Прихожу — жму паузу. Если прога идет по своему циклу, значит все в порядке. Если же произошел срыв, то это будет сразу же видно — трассировщик сойдет с ума и выбросит тебя из исходного кода в дизассемблер, где будет видно, что происходит что то явно не из нашей оперы. Например, выполнение кода там где его нет — за пределами программы. Если такое случилось надо внимательно проверить возможные пути срыва. Наставив брейков на входы-выходы из прерываний, перед и после индексных переходов и так далее.
 

Еще тут может помочь режим автошага (Alt+F5) нажал его и студия сама начала шустро тикать тактами, сразу же показывая что происходит. Тебе же остается сидеть и тупить в этот телевизор, глядишь найдешь глюк. Пару раз я таким способом отлавливал ошибки атомарного доступа, вылезавшие только в полнолуние и исключительно по спец мантре.
 

Глядим внимательно
Несмотря на то, что можно тупить в регистры, порты или напрямую в память, делать это все равно неудобно — глаз замыливается разглядывать в этой каше наши значения. Даже несмотря на то, что при изменении они подкрашиваются красным. Тут нам поможет очередной инструмент студии Watch (вызывается по Alt+1). Ватч это гляделка. С ее помощю можно наши выделенные переменные пихать в отдельные окошки, где за ними удобно наблюдать.
 

Навести гляделку можно на что угодно — на регистр, на ячейку памяти, на порт. При этом мы можем выбирать тип отображения (DEC,HEX,ASCII), что бывает очень удобно.
 

А если мы отлаживаем Сишный код, то watch умеет показывать структуры и массивы не в виде кучи барахла в памяти (какими они на самом деле и являются), а красиво раскладывая все по полочкам.
 

Эмуляция периферии
Внутреннюю периферию отлаживать проще простого — все прерывания вот они, свисают в IO регистрах, дергай не хочу. Регистры там же. А вот внешняя…
 

Тут все плохо. Дело все в том, что Студия не имеет понятия о том, что есть за ее пределами. Поэтому либо отлаживать периферию ручным тыком по битам в портах (что ужасно муторно), либо юзать примочки вроде HAPSIM или StiGen от ARV. Да, пользуясь моментом, рекомендую прошуршать по сайту arv.radioliga.com — много интересных штуковин. Проблем они всех не решают, но лучше чем ничего.
 

Трассировка по Сишному коду
А тут все весело. Дело в том, что оптимизатор может так залихватски перелопатить код, что на исходный код он будет походить весьма и весьма отдаленно. Т.е. ты грузишь переменную в одном месте, а компилятор решил это сделать в самом начале программы. Работать то будет так как ты сказал, но вот отлаживать это… Некоторые участки кода вообще будут перепрыгиваться.

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

В общем, я понимаю почему трассировка среди тех кто пытается писать на Си не прижилась. В том виде в каком ее видишь в первый раз пользоваться ей не хочется совершенно. В самом деле, кому охота связываться с дебильным симулятором? Проще уж в код тупить, там хоть какая то логика есть.
Но не стоить хоронить трассировку по высоким языкам раньше времени. Если отбросить приколы с выполнением некоторых участков кода, то все еще вполне ничего. А если выключить на время оптимизацию (параметр -O0), то вообще самое то. Правда отлаживать совсем без оптимизации я бы не советовал.
 

Т.к. с оптимизатором там есть свои приколы и грабли. И при несоблюдении ряда правил (volatile, пустые циклы и прочие фишечки), код который прекрасно работает без оптимизации на -Os с треском разваливается на куски.
 

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

Но это далеко не все методы отладки. А так, самая малость. Однако трассировкой можно выловить 90% проблем связанных с внутренней периферией и алгоритмом работы кода на уровне ядра.
 

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

32 thoughts on “AVR. Учебный Курс. Отладка программ. Часть 1”

  1. а еще навешивание контрольных светодиодов, подмена функций на врапперы с системой логгирования, например, или счетчиком входа/выхода и пр.

          1. А еще очень часто не получается смотреть переменные после компиляции с оптимизацией. Вот и приходится идти в асм и смотреть содержимое изменяемого регистра…

  2. К нулевому методу еще можно добавить тщательную проверку скопипастенного кода из другого проекта, своего или чужого. Так и норовит там поменяться буква в названии переменной, или счетчики i/j не все переименуются. Благо, что после трассировки это все находится и исправляется.

  3. «Ассемблер это как лопата — берешь и копаешь, а не думаешь как там поршни и трансмиссия в экскаваторе крутится.» — ггг )), точно подмечено…

  4. Я для того чтобы видеть, что происходит и попадаю ли я туда куда надо и когда надо и делается ли там то, что мне надо, делал вывод на пин контроллера — вошли в прерывание — вывести «1», сделать что надо, обратно «0». И на другие аналогично. И смотрел это потом в Proteus’е на фоне реального процесса в окне Analogue analysis. :) Но это только при отладке (хотя пока до железа еще не дошло).

  5. Аналитический метод рулит. Особенно на асме. Да и вообще, на любом языке. Берешь и втыкаешь в код. Если не понятна причина глюка — рисуешь блок-схемы именно участка кода, а не того что хочешь получить. Там реально наглядно вылазит глюк. Ну, а если уже совсем тяжко, — тогда уже делаешь «моргание контрольным выводом со светодиодом на конце»… Можно даже чередовать моргание с блок-схемами.

    1. Совершенно согласен. Если для отладки неприменим аналитический метод — значит программа криво написана. Исключение, пожалуй, составляют случаи, когда кривизна является вынужденной из-за требований оптимизации.

    2. Когда имеет место логическая ошибка, аналитический метод «рулит», но для таких ошибок, как неправильный адрес, или подставлен не тот псевдоним порта, или еще что-нибудь в таком же роде, лучший метод поиска — прогон и контроль регистров

  6. Я когда писал прошивку для дешифровки сигналов с ИК пульта (есть готовые библиотеки, но мне захотелось самому написать), использовал вход звуковухи, один канал на датчик ик (который был присоединен к МК), другой на одну из ног контроллера. При помощи этой ноги и проводил отладку — записывал сигналы звуковым редактором, получалось две временных диаграммы, одна сигнал с датчика, другая отладочный сигнал с ноги (можно просматривать дошла ли программа до нужного места и в какой момент она это сделала). Единственный минус этого способа — нельзя определить где 0, а где 1(только аналитически), ну и частота конечно не как у осциллографа. Тут можно сказать ставишь засечки, изменением уровня напряжения на ноге.
    Тем у кого есть цифровой осциллограф этот способ конечно бесполезен (вернее просто вместо звуковой карты нужно использовать осциллограф), но у меня его нема т.ч если бы не этот способ я бы наверное пару недель потратил на отладку=)

  7. Подскажите как в АВР студио в редакторе кода выставить слева циферки — номера строк(Line Numbers)??
    Обычно это делается в View, но я как ни бился, к сожалению не смог найти, где же спрятан этот не хитрый пункт меню:)

    1. Видимо, история с частотой контроллера заставляет людей верить — в студии есть все, стоит только хорошенько поискать. :)

    1. Сейчас уже и микроконтроллеры можно программировать «по-взрослому», с использованием современных технологий. Правда, информации маловато и мало кто в курсе, но при должном старании можно найти.

      Вот что мне пока удалось найти:
      Фреймворк для юнит-тестирования программ на C, ориентированный на встроенные системы: CUnit;
      Генератор mock-объектов для юнит-тестирования программ на C: CMock;
      Обработка исключений на языке C: CException.

      Все эти продукты с открытым кодом, их можно загрузить с SourceForge.

      Нашел также несколько очень толковых статей по этой теме. Пару из них успел перевести: http://club.shelek.ru/viewart.php?id=335 и http://club.shelek.ru/viewart.php?id=337 , на очереди еще десяток-полтора статей.

      И главное: скоро выходит книга Джеймса Греннинга «Test Driven Development for Embedded C» ( http://pragprog.com/titles/jgade/test-driven-development-for-embedded-c ). Я не утерпел и купил ее в бета-версии, благодаря чему прочитал почти целиком еще до выхода официального релиза. Как и практически все, что выпускается издательством The Pragmatic Programmers, книга изумительная и здорово ставит мозги на место, настоятельно рекомендую. Кроме того, при желании есть возможность пообщаться на форуме с ее автором.

  8. Проблема есть, может кто знает как разрешить.
    Создал проект в avr studio с winavr’овским компилятором. В нём 2 файла — один с кодом на Си, другой — на ассемблере. При отладке в эмуляторе по сишному коду трассируется без проблем, а ассемблер пропускает. Как сделать так, чтобы и по ассемблерным файлам создавалась дебажная информация?

  9. Трассировка по си коду отлично выполняется в avrстудии или VMLAB.
    Есть ещё одна проблема. Если отлаживать программу с кучей библиотек в h файлах то трассировщик бегает только по основному c файлу и пропускает все процедуры, которые находятся внутри библиотек или включаемых файлах…
    Вопрос — как заставить дебаггер подгружать исходный код и остальных библиотек? А то вытаскивать подозрительные процедуры из библиотек в основной модуль для отладки — замотался…

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

Ваш e-mail не будет опубликован.

Перед отправкой формы:
Human test by Not Captcha