Отладка, бывает двух видов:
Синтаксическая отладка . Синтаксические ошибки выявляет компилятор, поэтому исправлять их достаточно легко.
Семантическая (смысловая) отладка . Ее время наступает тогда, когда синтаксических ошибок не осталось, но результаты программа выдает неверные. Здесь компилятор сам ничего выявить не сможет, хотя в среде программирования обычно существуют вспомогательные средства отладки, о которых мы еще поговорим.
Отладка — это процесс локализации и исправления ошибок в программе.
Как бы тщательно мы ни писали, отладка почти всегда занимает больше времени, чем программирование.
2. Локализация ошибок
Локализация — это нахождение места ошибки в программе.
В процессе поиска ошибки мы обычно выполняем одни и те же действия:
прогоняем программу и получаем результаты;
сверяем результаты с эталонными и анализируем несоответствие;
выявляем наличие ошибки, выдвигаем гипотезу о ее характере и месте в программе;
Debug или пошаговая отладка программ
проверяем текст программы, исправляем ошибку, если мы нашли ее правильно.
Способы обнаружения ошибки:
Аналитический — имея достаточное представление о структуре программы, просматриваем ее текст вручную, без прогона.
Экспериментальны й — прогоняем программу, используя отладочную печать и средства трассировки, и анализируем результаты ее работы.
Оба способа по-своему удобны и обычно используются совместно.
3. Принципы отладки
Принципы локализации ошибок:
Большинство ошибок обнаруживается вообще без запуска программы — просто внимательным просматриванием текста.
Если отладка зашла в тупик и обнаружить ошибку не удается, лучше отложить программу. Когда глаз «замылен», эффективность работы упорно стремится к нулю.
Чрезвычайно удобные вспомогательные средства — это отладочные механизмы среды разработки: трассировка, промежуточный контроль значений. Можно использовать даже дамп памяти, но такие радикальные действия нужны крайне редко.
Экспериментирования типа «а что будет, если изменить плюс на минус» — нужно избегать всеми силами. Обычно это не дает результатов, а только больше запутывает процесс отладки, да еще и добавляет новые ошибки.
Принципы исправления ошибок еще больше похожи на законы Мерфи:
Там, где найдена одна ошибка, возможно, есть и другие.
Вероятность, что ошибка найдена правильно, никогда не равна ста процентам.
Наша задача — найти саму ошибку, а не ее симптом.
Это утверждение хочется пояснить. Если программа упорно выдает результат 0,1 вместо эталонного нуля, простым округлением вопрос не решить. Если результат получается отрицательным вместо эталонного положительного, бесполезно брать его по модулю — мы получим вместо решения задачи ерунду с подгонкой.
Исправляя одну ошибку, очень легко внести в программу еще парочку. «Наведенные» ошибки — настоящий бич отладки.
Дебажим код Отладка программы
Исправление ошибок зачастую вынуждает возвращаться на этап составления программы. Это неприятно, но порой неизбежно.
4. Методы отладки
Использование дампа (распечатки) памяти.
Это интересно с познавательной точки зрения: можно досконально разобраться в машинных процессах. Иногда такой подход даже необходим — например, когда речь идет о выделении и высвобождении памяти под динамические переменные с использованием недокументированных возможностей языка. Однако, в большинстве случаев мы получаем огромное количество низкоуровневой информации, разбираться с которой — не пожелаешь и врагу, а результативность поиска — исчезающе низка.
Использование отладочной печати в тексте программы — произвольно и в большом количестве.
Получать информацию о выполнении каждого оператора тоже небезынтересно. Но здесь мы снова сталкиваемся со слишком большими объемами информации. Кроме того, мы здорово захламляем программу добавочными операторами, получая малочитабельный текст, да еще рискуем внести десяток новых ошибок.
Использование автоматических средств отладки — трассировки с отслеживанием промежуточных значений переменых.
Пожалуй, это самый распространенный способ отладки. Не нужно только забывать, что это только один из способов, и применять всегда и везде только его — часто невыгодно.
Сложности возникают, когда приходится отслеживать слишком большие структуры данных или огромное их число. Еще проблематичнее трассировать проект, где выполнение каждой подпрограммы приводит к вызову пары десятков других. Но для небольших программ трассировки вполне достаточно.
С точки зрения «правильного» программирования силовые методы плохи тем, что не поощряют анализ задачи.
Суммируя свойства силовых методов, получаем практические советы :
использовать трассировку и отслеживание значений переменных для небольших проектов, отдельных подпрограмм;
использовать отладочную печать в небольших количества и «по делу»;
оставить дамп памяти на самый крайний случай.
Метод индукции — анализ программы от частного к общему.
Просматриваем симптомы ошибки и определяем данные, которые имеют к ней хоть какое-то отношение. Затем, используя тесты, исключаем маловероятные гипотезы, пока не остается одна, которую мы пытаемся уточнить и доказать.
Метод дедукции — от общего к частному.
Выдвигаем гипотезу, которая может объяснить ошибку, пусть и не полностью. Затем при помощи тестов эта гипотеза проверяется и доказывается.
Обратное движение по алгоритму.
Отладка начинается там, где впервые встретился неправильный результат. Затем работа программы прослеживается (мысленно или при помощи тестов) в обратном порядке, пока не будет обнаружено место возможной ошибки.
Источник: primat.org
Отладка программ
Если ошибка очевидна, то можно исправить ее, прекратив выполнение программы. Происходит переход в режим конструктора , в котором вносятся изменения в программу, и программа запускается заново. При перезапуске программа вновь возвращается к начальному состоянию, восстанавливаются исходные значения всех переменных, и из памяти удаляются все приостановленные процедуры.
В режиме отладки программы можно продолжить или прервать выполнение программы, используя кнопки и
или соответствующие команды меню Run. Можно выполнять программу по шагам. При корректировке текста программы в режиме прерывания редактор VB иногда выдает сообщение о невозможности продолжения программы.
Время отладки программ можно существенно сократить, если пользоваться различными средствами VBA , предназначенными для обнаружения ошибок в программах.
17.1. Ошибки и их обнаружение
Ошибки делятся на три категории: ошибки разработки (синтаксические ошибки), ошибки компиляции, ошибки выполнения.
Синтаксическая ошибка – нарушение правил языка VBA. Это может быть некорректный оператор, неверно введенное имя переменной, если объявление переменных обязательно, отсутствие разделителей между аргументами, несоответствие открывающих и закрывающих скобок, отсутствие закрывающих операторных скобок ( End If , Next и др.), неуникальное название процедуры и т.п.
При разработке программ рекомендуется включать опцию автоматической проверки синтаксиса – Auto Syntax Check ; пользоваться контекстно-зависимой подсказкой, которая возникает при наборе имен объектов, их свойств или методов и устанавливается опцией Auto Quick info (см. рис. 15.4).
Для уточнения правил синтаксиса при записи функций и конструкций языка в окне программы можно выделить ключевое слово и нажать клавишу F1. Появится окно справочной системы, которое соответствует выделенному фрагменту кода и показывает допустимую форму записи.
При наборе текста процедур редактор VB немедленно реагирует на синтаксическую ошибку: некорректный оператор выделяется цветом (по умолчанию красным) и причина возникновения ошибки поясняется сообщением. Невозможно запустить процедуру, если в какой-нибудь процедуре любого открытого проекта обнаружена синтаксическая ошибка.
При запуске процедуры происходит процесс проверки уже не отдельных строк, а программы в целом. Вначале выявляются ошибки компиляции, например, повторное объявление переменной в одной процедуре, несоответствие типов переменных присваиваемым значениям.
На этапе выполнения программы выявляются ошибки выполнения, которые не могли быть обнаружены редактором Visual Basic, например, логические ошибки или ошибки вычислений. Примерами логических ошибок могут служить неверные имена или типы переменных, бесконечные циклы, ошибочные условия или неверные размеры массивов. Ошибки вычислений возникают при попытке выполнить недопустимую операцию, например, деление на нуль.
При выявлении ошибки происходит прерывание программы и возникает диалог (рис.17.1), если на вкладке General диалогового окна Options (рис. 15.4) установлена опция Break on All Errors (останов при любой ошибке). Нажатие кнопки Debug переводит программу в режим отладки . Оператор, на котором произошло прерывание, т. е. возникла ошибка, подсвечивается.
Рис. 17.1. Прерывание программы и сообщение об ошибке
Окна отладчика
При отладке программ рекомендуется активно использовать окна Visual Basic.
Окно проверки Immediate Window (иначе «оперативная панель») выводится на экран одноименной командой из меню View или клавишами Ctrl+G и используется для целей:
- отображения информации, получаемой в результате выполнения операторов ( отладочная печать );
- тестирования команд, вводимых непосредственно в этом окне;
- получения или изменения значений переменных или свойств объектов, доступных в выполняемой процедуре.
Окно Immediate полезно при проверке работы однострочных команд. В этом окне доступны те же переменные, которые были доступны выполняемой процедуре в момент прерывания программы, а операторы, вводимые в окне проверки, выполняются в контексте, т. е. так, как если бы они вводились в выполняемую процедуру. Любой оператор, записанный в строке окна или скопированный в это окно из текста процедуры, будет выполнен после нажатия клавиши Enter.
Оператор Debug.Print используется для организации вывода в окно Immediate в режиме выполнения программы ( отладочная печать ). Синтаксис оператора:
- outputlist – список распечатываемых переменных или выражений (указывать необязательно). Если аргумент опущен, выводится пустая строка.
Ссылка на объект Debug является обязательной. В результате выполнения оператора данные отображаются в окне Immediate с учетом национальной настройки, т. е. используется соответствующее форматирование. Значения типа Date выводятся в стандартном кратком формате дат, установленном в системе. Логические значения распечатываются как True или False.
Например, оператор ? 355/113 рассчитает приблизительное значение числа pi, а оператор Debug.Print «value of a», a, расположенный в теле процедуры, распечатает текст «value of a» и значение переменной a на одной строке.
Если во время прерывания программы подвести курсор к идентификатору переменной внутри выполняемой процедуры, рядом с текстовым курсором появится текущее значение переменной в виде контекстной подсказки . Это окно значения переменной. На рис.17.2 показано значение переменной Var_A, равное 2.
Рис. 17.2. Окно значения переменной
На рис.17.3 показано окно локальных переменных (Locals Window), которое отображает все описанные в текущей процедуре Value_A переменные и их значения. Окно высвечивается командой Locals Window из меню View.
В окне локальных переменных можно:
- просмотреть значения любой переменной выполняемых процедур, локальных или определенных на уровне модуля;
- изменить значения переменных.
Когда окно локальных переменных открыто, его обновление происходит автоматически при каждом переключении из режима выполнения в режим прерывания, при пошаговом выполнении процедуры, а также при перемещении по стеку вызова процедур (кнопка Call Stack на рис.17.3).
Рис. 17.3. Окно локальных переменных
Окно контрольных значений (Watches) (рис.17.4) применяется для отслеживания значений переменных или выражений. Можно одновременно задавать несколько контрольных выражений. Можно использовать контрольные значения для задания моментов остановки программы.
Определенное пользователем контрольное выражение позволяет следить в процессе пошагового выполнения программы за изменением значения этого выражения. Контрольные выражения не сохраняются вместе с программой. Для задания и модификации контрольных выражений используются команды Add Watch, Edit Watch и Quick Watch из меню Debug. В команде Add Watch можно задать контекст контрольного выражения (Context), т. е. имя процедуры, имя модуля, имя текущего проекта, для которых будет вычисляться введенное выражение. Таким образом, контекст выводимого значения выражения определяется процедурой и модулем.
Рис. 17.4. Окно контрольных значений
Значения автоматически обновляются в окне Watches при прерывании программы, при переходе к следующему шагу, а также после выполнения каждой инструкции в окне Immediate.
Контрольная точка или точка останова ( Breakpoint ) представляет собой строку процедуры, на которой разработчик во время отладки планирует остановить выполнение программы. Обычно точки останова используются в больших программах с целью определения их поведения. Имеет смысл устанавливать контрольные точки там, где предположительно может возникнуть ошибка, или в местах, которые отмечают прохождение логических частей программы, например, окончание ввода данных, расчет промежуточных результатов и т. п.
При достижении точки останова программа не прерывается, а переходит в режим отладки , который позволяет посмотреть возможное место ошибки и исправить ее.
Поскольку последовательность выполнения операторов программы не всегда очевидна, можно выполнять программу по шагам – команду за командой или процедуру за процедурой. Такой способ выполнения программы носит название трассировка программы. Пошаговое выполнение программы позволяет отследить порядок выполнения операторов и наблюдать реакцию программы на выполнение каждого оператора.
Для запуска пошагового режима можно нажать одну из кнопок панели инструментов Debug (рис. 15.8) или выполнить соответствующие команды из меню Debug. Трассировку можно выполнять с начала программы или с точки останова после прерывания программы. При трассировке на вертикальной полосе слева от выполняемого оператора расположена стрелка – указатель выполняемого оператора.
Источник: intuit.ru
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. При этом он сразу же появляется в окошке BreakpointsTracePoint. И там уже, найдя наш путевой брейк, даблкликом по нему залезть в свойства.
Там же бряки можно оперативно включать-выключать, что очень удобно. Выключать и включать их также можно по F9.
Пускай на самотек
Если программа в железе ведет себя как то не так, то может у ней крышу сорвало? Когда пишешь на ассемблере (да и на Си тоже, но там поймать сложней) легко ошибиться со стеком или индексным переходом. А сразу не поймаешь. Далеко не факт, что срыв найдется при первом или втором пошаговом прогоне. В этом случае я просто жму запуск симуляции и ухожу нарезать колбаски и чаю налить.Прихожу — жму паузу. Если прога идет по своему циклу, значит все в порядке. Если же произошел срыв, то это будет сразу же видно — трассировщик сойдет с ума и выбросит тебя из исходного кода в дизассемблер, где будет видно, что происходит что то явно не из нашей оперы. Например, выполнение кода там где его нет — за пределами программы.
Если такое случилось надо внимательно проверить возможные пути срыва. Наставив брейков на входы-выходы из прерываний, перед и после индексных переходов и так далее.
Еще тут может помочь режим автошага (Alt+F5) нажал его и студия сама начала шустро тикать тактами, сразу же показывая что происходит. Тебе же остается сидеть и тупить в этот телевизор, глядишь найдешь глюк. Пару раз я таким способом отлавливал ошибки атомарного доступа, вылезавшие только в полнолуние и исключительно по спец мантре.
Глядим внимательно
Несмотря на то, что можно тупить в регистры, порты или напрямую в память, делать это все равно неудобно — глаз замыливается разглядывать в этой каше наши значения. Даже несмотря на то, что при изменении они подкрашиваются красным. Тут нам поможет очередной инструмент студии Watch (вызывается по Alt+1). Ватч это гляделка. С ее помощю можно наши выделенные переменные пихать в отдельные окошки, где за ними удобно наблюдать.Навести гляделку можно на что угодно — на регистр, на ячейку памяти, на порт. При этом мы можем выбирать тип отображения (DEC,HEX,ASCII), что бывает очень удобно.
А если мы отлаживаем Сишный код, то watch умеет показывать структуры и массивы не в виде кучи барахла в памяти (какими они на самом деле и являются), а красиво раскладывая все по полочкам.
Эмуляция периферии
Внутреннюю периферию отлаживать проще простого — все прерывания вот они, свисают в IO регистрах, дергай не хочу. Регистры там же. А вот внешняя…Тут все плохо. Дело все в том, что Студия не имеет понятия о том, что есть за ее пределами. Поэтому либо отлаживать периферию ручным тыком по битам в портах (что ужасно муторно), либо юзать примочки вроде HAPSIM или StiGen от ARV. Да, пользуясь моментом, рекомендую прошуршать по сайту arv.radioliga.com — много интересных штуковин. Проблем они всех не решают, но лучше чем ничего.
Трассировка по Сишному коду
А тут все весело. Дело в том, что оптимизатор может так залихватски перелопатить код, что на исходный код он будет походить весьма и весьма отдаленно. Т.е. ты грузишь переменную в одном месте, а компилятор решил это сделать в самом начале программы. Работать то будет так как ты сказал, но вот отлаживать это… Некоторые участки кода вообще будут перепрыгиваться.Т.к. оптимизатор все заоптимизировал за счет других строк, а промежуточные варианты тебе в виде отчета забыл предоставить.
В общем, я понимаю почему трассировка среди тех кто пытается писать на Си не прижилась. В том виде в каком ее видишь в первый раз пользоваться ей не хочется совершенно. В самом деле, кому охота связываться с дебильным симулятором? Проще уж в код тупить, там хоть какая то логика есть.
Но не стоить хоронить трассировку по высоким языкам раньше времени. Если отбросить приколы с выполнением некоторых участков кода, то все еще вполне ничего. А если выключить на время оптимизацию (параметр -O0), то вообще самое то. Правда отлаживать совсем без оптимизации я бы не советовал.Т.к. с оптимизатором там есть свои приколы и грабли. И при несоблюдении ряда правил (volatile, пустые циклы и прочие фишечки), код который прекрасно работает без оптимизации на -Os с треском разваливается на куски.
Но общую логику работы программы отследить можно. А учитывая умные гляделки, бряки, информеры… так вообще сказка!
Но это далеко не все методы отладки. А так, самая малость. Однако трассировкой можно выловить 90% проблем связанных с внутренней периферией и алгоритмом работы кода на уровне ядра.
Впереди же будут описаны еще реалтаймовые способы на реальном железе — дебаг выводы, развлекухи с осциллографом, облизывание на логический анализатор и JTAG мониторы.
Не обойду вниманием и симуляторы вроде PROTEUSа, хотя я с недавних пор предпочитаю ими не пользоваться — мне своих глюков хватает.Спасибо. Вы потрясающие! Всего за месяц мы собрали нужную сумму в 500000 на хоккейную коробку для детского дома Аистенок. Из которых 125000+ было от вас, читателей EasyElectronics. Были даже переводы на 25000+ и просто поток платежей на 251 рубль.
Это невероятно круто. Сейчас идет заключение договора и подготовка к строительству!
А я встрял на три года, как минимум, ежемесячной пахоты над статьями :)))))))))))) Спасибо вам за такой мощный пинок.
35 thoughts on “AVR. Учебный Курс. Отладка программ. Часть 1”
а еще навешивание контрольных светодиодов, подмена функций на врапперы с системой логгирования, например, или счетчиком входа/выхода и пр.
Источник: easyelectronics.ru