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

Продолжаем трактат об отладке программ. На этот раз в бой идут одни старики.
 

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

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

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

В принципе все с виду работает пучком и никаких косяков. Но одолели меня сомнения, так ли это? Если двигатель вращается, то это еще не значит, что он работает хорошо и без ошибок. Так и у нас, надо проверить, все ли в порядке. Слушать мы будем осциллографом «механику» тиканья службы таймера. Почему его? Ну так вокруг него вся логика программы построена.
 

Переназначаем выводы PD4 и PD5 на выход и будем их использовать для своих грязных целей. Для отладки в смысле.
 

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

1
2
3
4
5
6
OutComp2Int:	SBI		BTA_P,BTB		; For Debug
 
		TimerService				; Служба таймера диспетчера 
 
		CBI		BTA_P,BTB		; For Debug
		RETI

 

Вначале мы ставим бит порта PD4, а перед выходом сбрасываем. Это даст нам время выполнения и частоту выполнения. И тыкаемся нашим осциллографом, глядим:

 

Обана, а в таймере то глюки есть! Тикать то он тикает и с первого взгляда все нормально. Но почему то у него возникла аритмия и если это сейчас не вылезло, то может вылезти потом, при добавлении новых фич, где от четкости таймера будет многое зависеть. Причем глюк по времени плавающий, возникающий раз в сколько то сработок. А это значит, мы его трассировкой не поймаем. Можно, конечно, и аналитически протупить в код и найти, но это еще надо знать, что глюк есть. Да и код может быть такой, что сам черт ногу сломит.
 

Попробуем вычислить кто это нам тут таймер сбивает. Первое что приходит на ум — другое прерывание. Оно во время своей обработки запрещает другие прерывания и может сбить нам таймер. Что там у нас еще есть? Да хотя бы прерывание от АЦП. Выведем как его на вторую отладочную ногу:
 

1
2
3
4
5
6
7
8
9
10
ADC_INT:	SBI		BTA_P,BTC		; For Debug
 
		PUSH		OSRG			; Сохраняем рабчий регистр
		IN		OSRG,ADCH		; Берем данные из АЦП
		STS		ADC_Data,OSRG		; Перекладываем их в ОЗУ
		POP		OSRG			; Восстанавливаем рабочий регистр
 
		CBI		BTA_P,BTC		; For Debug
 
		RETI					; Выход из прерывания

Это будет дрыганье ногой PD5.
 

Смотрим что получилось:

 

То же самое, но на аналоговом осциллографе. Там не так наглядно, поэтому не стал выносить в пост и перегружать страницу
 

Как видно из видео, у нас есть проблема — где то в коде есть процедура которая блокирует прерывания. Причем это не атомарный доступ, слишком уж большие задержки. Скорей что то похожее на цикл ожидания, в котором есть CLI/SEI конструкция. Где то что то я забыл удалить после отладки. Начинаем проглядывать код уже на предмет забытых прерываний. Мелкие процедурки видны сразу, а крупные, состоящие из нескольких файлов и макросов можно и прощупать.
 

Давайте как пощупаем задачу вывода на экран, переместим дрыгалку туда:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
;-----------------------------------------------------------------------------
; Задача обновления экрана дисплея. Дисплей обновляется 5 раз в секунду, каждые 200мс
; данные берутся из видеопамяти и фоном записываются в контроллер HD44780
Fill:		SBI	BTA_P,BTC		; ForDebug
 
		SetTimerTask	TS_Fill,200	; Самозацикливание задачи через диспетчер таймеров
 
		LCDCLR				; Очистка дисплея
 
		LDZ	TextLine1		; Взять в индекс Z адрес начала видеопамяти
 
		LDI	Counter,DWIDTH		; Загрузить счетчик числом символов в строке
 
Filling1:	LD	OSRG,Z+			; Брать последовательно байты из видеопамяти, увеличивая индекс Z
		RCALL	DATA_WR			; И передавать их дисплею
 
		DEC	Counter			; Уменьшить счетчик. 
		BRNE	Filling1		; Пока не будет 0 (строка не кончилась) - повторять
 
		LCD_COORD 0,1			; Как кончится строка - перевести строку в дисплее
 
		LDI	Counter,DWIDTH		; Опять загрузить длинной строки
 
Filling2:	LD	OSRG,Z+			; Адрес второй строки видеопамяти указывать не надо - они идут друг за другом 
		RCALL	DATA_WR			; И таким же макаром залить вторую строку из видеопамяти
 
		DEC	Counter			; Уменьшаем счетчик
		BRNE	Filling2		; Если строка не кончилась - продолжаем.
 
		CBI	BTA_P,BTC		; ForDebug	
		RET

 

И смотрим что получилось:
 

Аналоговый вариант данного действа.
 

Как видим, у нас в задаче Fill есть запрет прерываний, который на значительный период блокирует не только таймерную службу, но и прерывания от АЦП и все остальное. При этом у нас один тик таймерной службы откладывается до тех пор, пока прерывания не разрешат. ВЫполняется, а в этом время уже нащелкало на второй тик и прерывание выполняется еще раз. Пока катастрофы не случилось, но будь Fill подольше, то мы бы прозевали один тик. А какое то событие уплыло по времени.
 

Это черевато тем, что разные синхронизированные по времени задачи могут глючить, причем глючить не всегда, а только тогда, когда на них выпадает совпадение по времени с Fill. Попробуй такое отловить. Тем более по дефолту то считаем, что Fill работает и не глючит! Ведь раньше то ничего не мешало! Ну ну. Просто условия не создавались нужные.
 

Лезем в lcd4.asm и находим там запреты прерываний почти везде:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
;=========================================================================================
BusyWait:	CLI				; Ожидание флага занятости контроллера дисплея
		RCALL	PortIn			; Порты на вход
 
		CBI	CMD_PORT,RS		; Идет Команда!
		SBI	CMD_PORT,RW		; Чтение!
 
 
BusyLoop:	SBI	CMD_PORT,E		; Поднять строб	
		RCALL	LCD_Delay		; Подождать	
 
		IN	R16,DATA_PIN		; Считать байт
 
		PUSH	R16			; Сохранить его в стек. Дело в том, что у нас R16 убивается в LCD_Delay
 
		CBI	CMD_PORT,E		; Бросить строб - первый цикл (старший полубайт)	
		RCALL	LCD_Delay		; Подождем маленько	
 
		SBI	CMD_PORT,E		; Поднимем строб	
		RCALL	LCD_Delay		; Подождем 
		CBI	CMD_PORT,E		; Опустим строб- нужно для пропуска второго полубайта
 
		RCALL	LCD_Delay		; Задержка снова	
 
		POP	R16			; А теперь достаем сныканый байт - в нем наш флаг. Может быть.
 
		ANDI	R16,0x80		; Продавливаем по маске. Есть флаг?
		BRNE	BusyLoop		; Если нет, то переход
 
BusyNo:		SEI				; Разрешаем прерывания.
		RET

 

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

Е..й стыд! И я когда то это написал!? Нет, тогда запрет прерываний был оправдан. Я уже не помню почему, но без него в том случае было никак. Но вот переносить эти CLI/SEI в универсальную библиотеку это был полный маразм. Кстати, многие кто ее юзал отметили эту фишку с ненужным запретом прерываний. Многие, но далеко не все. Колитесь, кто не заглядывал в код и подключиле его к своей программе «as is» поленившись проверять и поверив на слово? ;) Все руки не доходят поправить ;)
 

Правим, заливаем в демоплату новую прошивку. Наблюдаем эффект:

 

Ничего не сломалось, все отлично работает. Done!
 

Ну и, напоследок, маленький трюк. Я тут вовсю юзаю многоканальный осцилл, а у меня тут еще и 16ти канальный анализатор есть… В общем, полный набор удовольствий. А кто то мучается с одноканальным осциллографом и как тут ему быть?
 

Не беда — можно применить одну хитрость. Обьединить несколько сигналов в один канал. Делается это вот по такой схеме:
 

 

Работает просто — когда на одном входе единичка, то напряжение с нее течет в землю. Этот ток создает падение напряжения на R3 которое видно на осциллографе. Когда на входе две единички, то через R3 уже идет ток с двух источников и падение напряжения становится суммой двух сигналов и это явно видно. Значения резисторов R1 и R2 лучше подбирать так, чтобы они были в пределах одного порядка, но различались раза в полтора-два. Это даст визуальное разделение сигналов и можно будет понять что к чему.
 

У меня же на демоплате я решил заюзать те резисторы, что уже стояли — 500омные на ограничение тока для светодиодов. А в качестве суммирующего применил один из переменников, выкрутив его на сопротивление около 800Ом. И с него отправил сигнал на осциллографы.
Резисторы там правда правда одинаковые и сигналы будут равные по высоте, но тут у нас по частоте уже понятно кто есть кто, так что не запутаемся :)
 

Вот что получилось:

 

Таким образом, можно на каждый канал загнать столько сигналов, что в экран не влезут. Правда анализировать их на глазок будет уже сложней. Но где наша не пропадала! :)
 

Игры с синхронизацией
Также не стоит забывать про такой канал как внешняя синхронизация (Ext Trig) Обычно на него забивают т.к. не находят ему применения. А зря! Например, можно на него вывести какой либо сигнал который тоже надо ловить. И настроить осцил в такой режим, что без этого сигнала триггер не срабатывает (Normal режим, не Auto). И тогда если сигнала нет, то мы ничего и не увидим. Отсутствие информации тоже информация — о том, что сигнала нет :) А если есть, то у нас останутся еще два свободных канала на отлов последующих событий.
 

Также вовсю стоит играться с разными видами синхры. Аналоговые и самые дешевые цифровые ограничены только синхрой по фронтам, да всяким телесигналам. А вот более продвинутые цифровые, вроде ATTEN или тот же RIGOL (про Лекрои и прочие Тектроники я даже не вспоминаю) умеют синхронизироваться и по длине импульса, по расстоянию между импульсами ,по скорости нарастания/спада сигнала и еще по полудесятку разных параметров. Ловить ими сбои в работе программ милое дело! Раз уж раззорились на цифроосцил, так юзайте его возможности на все сто! :)))
 

Исходник программы на которой я баловался

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

  1. > то аналитическое тупление в код

    Хочется обратить внимание на общепринятую терминалогию. «Аналитическое тупление» называется «Статический анализ кода».

    По теме тестирования программного обеспечения выпущено много книг, а сама тема уже очень стара :)

    1. * терминологию

      И вопрос не по теме: где можно посмотреть справку по правилам написания комментариев? Как вставлять ссылки, картинки, цитаты…

      1. Я в том смысле, что есть много литературы по этой теме и устоявшаяся терминология.

        То, что это будет актуально всегда — без сомнений :)

            1. Так, чтобы сразу было понятно о чем речь. Название «Регрессивное тестирование» смысла несет мало и без пояснений чо это такое непосвященный не поймет. Оно и нагугливается то не с первого раза.

              Ликвидация последствий прошлой отладки. Как то так.

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

    2. to abelankov

      Какие конкретно книги можете посоветовать по отладке программ?


      С Уважением Михаил Доронин

    1. Я себе этим летом собирал что-то типа велокомпьютера. Там стоял WH1602 с обычной подсветкой и чёрными символами. Вечером, когда было темно, разглядеть что-то на этом дисплейчике было сложно. Особенно, если едешь с большой скоростью.
      Поэтому светящиеся символы того стоят :)

  2. А что это там тот широкий импульс (от таймера кажись) имеет джитер заднего фронта. То есть прерывание его бывает разное по времени? Если я смог разглядеть там около неск. микросекунд.

    1. Там зависит от того обрабатывает таймер задачу или нет. Если вхолостую тикает то одно время, а если какая то задача дотикала до выполнения, то о нее ставит в очередь и тратит на это несколько больше времени.

  3. Ну как бы это, я использовал библиотеку для LSD. Я видел там сплошные CLI и SEI, даже спрашивал про них у DIHALTA в посте про LCD. Но в своем проекте их просто убрал и точка.

    1. Вот и получится LSD вместо ЖК-дисплея (LCD). Наверняка они там для атомарности операций, чтобы прерывания не порушили логику работы. Впрочем, если прерывания не используются, можно и убрать.

  4. Очень полезная статья!
    Век живи-век учись! Буду теперь всегда контролировать рывки от таймеров осциллографом на наличие «левых» импульсов.

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

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

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