Метод 3. USART (Работа с последовательными интерфейсами)
Пожалуй самым популярным отладочным интерфейсом является все же USART. Во-первых, он поддерживается аппаратно почти всеми микроконтроллерами. Во-вторых, он прост в использовании и требует всего один/два сигнальных провода, а в третьих, для связи с компом не надо городить никаких специфичных девайсов. В худшем случае UART-USB или UART-RS232 конвертер на FT232RL или MAX232.
Пользоваться им проще простого — в любой момент, когда нам это захочется, мы берем и отправляем нужный байт по этому интерфейсу. При этом достаточно заранее его настроить — стандартная инициализация UART:
1 2 3 4 5 6 7 8 9 10 11 12 | ; Usart INIT .equ XTAL = 8000000 .equ baudrate = 9600 .equ bauddivider = XTAL/(16*baudrate)-1 uart_init: LDI R16, low(bauddivider) OUT UBRRL,R16 LDI R16, high(bauddivider) OUT UBRRH,R16 LDI R16,0 OUT UCSRA, R16 |
1 2 3 | ; Прерывания запрещены, прием-передача разрешен. LDI R16, (1< <RXEN)|(1<<TXEN)|(0<<RXCIE)|(0<<TXCIE)|(0<<UDRIE) OUT UCSRB, R16 |
Маркеры
Всегда полезно знать где наша программа шляется в данный момент. Конечно, можно зажигать светодиодики, как в прошлом методе. Но когда точек много, то выводов не напасешься на эту затею. Или затрахаешься перекомпилировать. Проще всего натыкать в код маркеров, наступая на которые наш МК будет отрыгивать в терминал свое местоположение. Терминальный клиент будет писать лог и ничего не проморгаешь.
Для отправки надо записать наше маркерное число в регистр UDR, пусть это будет код буквы «А».
1 2 | LDI R16,'A' OUT UDR,R16 |
Я даже проверку на занятость UDR не делаю. Для одиночного байта, если у нас отправки по UART нет и участок не зациклен, этого вполне достаточно — регистр будет свободен. Разумеется в реальной программе такого делать не следует, но для отладочной затычки вполне сойдет.
Можно нафаршировать весь код такими вот затычками, но с разными буквами и смотреть в терминалке в каком порядке программа выполняется по выдаваемому слову. Правда учитывайте тот момент, что если данные будут сыпаться в терминалку быстрей чем она ее может прожевать, то регистр UDR захлебнется и у нас будет полная лажа. В этом случае можно сделать затычку чуть сложней, с ожиданием освобождения UDR
1 2 3 4 | LDI R16,'A' SBIS UCSRA,UDRE ; Пропуск если нет флага готовности RJMP PC-1 ; ждем готовности - флага UDRE OUT UDR, R16 ; шлем байт |
Можно в макрос это запихать:
1 2 3 4 5 6 7 8 | .MACRO DEB_SEND PUSH R16 LDI R16,@0 SBIS UCSRA,UDRE RJMP PC-1 OUT UDR,R16 POP R16 .ENDM |
Заодно R16 в стеке спрячем, так что макрос можно юзать где угодно, словно команду:
1 | DEB_SEND 'A' |
Но это затормозит выполнение программы, могут полететь тайминги. Так что если у вас есть какие то участки жестко зависимые от времени (скажем тайминги протокола 1-wire), то данная затычка даст сбой.
Выводить можно любую инфу. Например содержание нужных регистров, содержание переменных, ячеек памяти, состояния битов регистров периферии. Что хотим поглядеть — то и тащим. Надо только загрузить это в UDR. А поскольку там могут быть произвольные значения, то глядеть их лучше через терминалку способную показывать в хексах. Например, моя любимая Terminal v1.9b.
Также полезно выбрасывать в терминал маркеры прохождения кода. Чтобы оценить правильность переходов и логики работы.
Еще настоятельно рекомендую заиметь спец литеру отправляемую в терминал сразу же после инициализации USART, еще до перехода к основной программе. У меня это обычно буква «R». От «Reset». Это позволяет поймать момент перезагрузки контроллера.
Например, глючила у меня программа. Все работало нормально, но при попытке принять байт все начинало странно глючить. Все облазил, код весь до дыр проглядел. По коду все должно работать… Наконец воткнул в код OUTI UDR,’R’ еще до вхождения в main. Опаньки — а контроллер то сбрасывается!
А почему контроллер может сбрасываться? Сбой по питанию я исключил сразу — не было там ничего такого, что бы могло дергаться. RESET был подтянут через 10кОм и особо тоже не вихлялся. Что еще?
Срыв стека, если произошел переход за конец кода, тоже похож на сброс — программа уходит за конец памяти и возвращается к нулевому адресу. Прошарил в отладчике и этот момент — нет срыва. Но может срыв не сразу, а спустя несколько итераций? Поставил .ORG на предпоследний адрес флеша и прописал туда маркер — не вылазит, значит срыва стека нет.
Да и код был прост как мычание, нечему там глючить. Накидал отладочных маркеров по коду, засылающих байты в строго определенном порядке — в терминалке высветилась фраза «УБЕЙ СИБЯ АПСТОЛ» — значит прога работает четко по алгоритму.
Заглянул под панельку… Вот в чем засада — медная ворсинка от провода МГТФ попала между RXD и RST (На меге8 они совсем рядом) и при приходе байта в порт дрыганья на RXD сбрасывали контроллер. Соблюдайте чистоту на рабочем месте! Не допетри я тогда поставить на перезагрузку маркер, так я бы до утра ковырялся с кодом. А так я резко сузил круг возможных косяков и ушло не более десяти минут на отлов этого бага.
Управление кодом, добыча произвольных данных
Но не едиными маркерами сыт боец USART’овой отладки.
Мы же можем не только слать, но и принимать! А принятое обрабатывать! Так что нам мешает делать полноценные брейкпоинты?
Скажем, такие:
1 2 3 4 5 | PUSH R16 SBIS UCSRA,RXC RJMP PC-1 IN R16,UDR ; Читаем UDR, чтобы сбросить RXC флаг POP R16 |
Данный код является банальным бряком, т.е. программа на ней встанет как вкопанная до тех пор, пока мы не зашлем ей байт в терминалку. Удобно, особенно когда осциллографа нет под рукой, можно, например, спокойно мультиметром уровни замерить на выводах, а потом пустить прогу до следующего чекпоинта.
В сочетании с маркерами дает еще и информацию о том, где мы встали. Но можно же и большее заиметь!
Что нам мешает сделать небольшой обработчик команд? Скажем, если мы послали R — выдаст значение нужного регистра, если М — ячейки памяти, I — байта из IO, а G — пойдет дальше:
Пример макроса может быть таким:
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 32 33 34 35 36 37 38 39 | .MACRO DEB_CMD PUSH R16 ; Сохраняем регистр и флаги в стек IN R16,SREG PUSH R16 SBIS UCSRA,RXC RJMP PC-1 IN R16,UDR ; Читаем UDR, чтобы сбросить RXC флаг ; Определяем что там пришло CPI R16,'R' BREQ PC+0x07 ; BREQ REGISTER CPI R16,'M' BREQ PC+0x07 ; BREQ MEMORY CPI R16,'I' BREQ PC+0x09 ; BREQ IO CPI R16,'G' BREQ PC+0x0A ; BREQ GONEXT OUT UDR,@0 ; REGISTER - отправляем в UDR значение регистра RJMP PC+0x0008 ; Проверка на пустоту UDR тут скорей всего не нужна. ; Т.к. пока впечатаешь ручкам в терминалку байт, наверняка ; все что хотело уйти уже уйдет и UDR опустошится. LDS R16,@1 ; MEMORY - шлем значение из памяти OUT UDR,R16 RJMP PC+0x0004 IN R16,@2 ; IO - шлем значение из порта ввода-вывода. OUT UDR,R16 POP R16 ; GONEXT - достаем все сохраненное из стека и идем дальше OUT SREG,R16 POP R16 .ENDM |
Как видишь, макрос массово использует относительные переходы (иначе при вставке двух макросов метки будут дублироваться). Так что модифицировать его нужно очень осторожно. Высчитывая команды. Но есть спрособ проще — написать его вначале с обычными метками, скомпилить, запустить отладку, потом открыть дизассемблер (view->disassembler) и поглядеть там смещения.
Используется просто — вставляешь в код такую штуку:
1 | DEB_CMD R15,0x0000,PORTD |
И при попадании в это место МК остановится и будет ждать приказа. По команде G — пойдет дальше. По команде R — даст содержание регистра R15, по команде M — содержание ячейки ОЗУ с адресом 0х0000, а по команде I — значение из порта PORTD. Порты, регистры, ячейки памяти вписываешь те которые хочешь посмотреть. Если нужно что то одно, то пишешь в ненужные поля любые подходящие значения. Ну или делаешь узкоспециализированные макросы под память, регистр и порт ВВ.
Можно еще прерывания запретить, чтобы стопор был полный, но не забывай, что всякие таймеры продолжают тикать самостоятельно вне зависимость от того работает МК или зациклен в ожидании команды. А еще, если включен WATCHDOG, то он может и за задницу укусить. Поэтому в макрос можно и команду WDR добавить, в цикл ожидания байта.
Более того, никто не запрещает написать простейший (или сложный) командный обработчик с шахматами и поэтессами, позволяющий указывать непосредственно в передаваемой команде какой ресурс надо забрать. Или что и в какой регистр записать, куда перейти… В общем, этакий микро SoftICE (если кто еще помнит этот легендарный отладчик).
А если дружишь с какими-нибудь Дельфями, то можешь снаружи и отладчик написать, который через этот супервизор будет выковыривать все нужные данные по USART и раскладывать их красивыми рядами на панельках, дабы было любо-дорого глядеть. Например Николев в своем отладочном модуле так и сделал, правда он через SPI работал, но не суть важно.
Оставляю это на самостоятельное изучение :)
На Си все пишется примерно в том же ключе. Можешь функции забабахать под это дело. Можешь макросы, тут по желанию.
Блин! Как же я раньше-то не додумался отладчик сделать? Привык в VB, привык что там много_чего есть, теперь мучаюсь, а НАДО =) DI HALT, спасибо за идею, и за статью.
Кидайте заявки на программу внутрь этого комментария: что там должно быть, что хочется, чтобы было, как лучше это сделать. Будет время — напишу, выложу с исходниками.
Если таких контр. точек с пол сотни, то задолбаешься расшифровывать буквы в терменале. Если есть место на кристале и запас по времени между метками, то лучше отправлять короткое сообщение на прерваниях. Возможно наложение , но мир же не идеален.
Возможно стоит собрать себе небольшой набор полезных отладочных функций типа: вывод стека на терминал, условный бряк, минимальный аналог printf, hex/dec/bin2char* и т.п. При отсутствии jtag и невозможности подключить светодиод, может помочь.
Я последнее время собрал себе http://www.mikrocontroller.net/topic/92704 вот такое устройство на ЖКИ 320х240. Подключаю его вместо терминала прямо к железке.. Есть у меня библиотечка которая умеет послать туда текст или число… Есть вариант который умеет посылать текст или число на определённые координаты на мониторчику. Думал о том чтобы приделать кнопки для обратной связи — но как то руки не дошли :) Часто возникает проблема с синхронизацией скорости когда мега работает на внутриннем генераторе. Приходится доля начала находить подходящую константу для скорости. http://wowa.cz/alex/100_5294.JPG и http://wowa.cz/alex/100_5293.JPG — это фото первого прототипа. щас это уже в коробочке :)
а как работает
LDI R16, (1<<RXEN)|(1<<TXEN)|(0<<RXCIE)|(0<<TXCIE)|(0<<UDRIE)
Ясно что меняет задает биты, но не ясно как работает, последовательность написания RXEN-UDRIE важна? В описании регистра UCSRB другая. И как меняются биты этой командой тоже не ясно. Или это макрос для компилятора?
Команда вида 1<<RXEN сдвигает единичку влево на столько, какое значение прописано в RXEN. Соответственно 0<<RXCIE сдвигает ноль на значение RXCIE. То есть, по датащиту RXEN = 4 (это значение сохранено в инклуде с определениями выводами), то 1<<RXEN в итоге даст 00010000. Знак | — это побитное OR. В общем в результате мы будем иметь (1<<RXEN) = 00010000, (1<<TXEN) = 00001000 все остальное это 00000000 (не важно, насколько мы нулик будем двигать), а 00010000|00001000 = 00011000 (побитно складываем оба числа). Получается LDI R16, (1<<RXEN)|(1<<TXEN)|(0<<RXCIE)|(0<<TXCIE)|(0<<UDRIE) == LDI R16, 00011000 — всё как по даташиту!
уууу, спасибо большое. Понял наконец-то )
Последовательность не важна.
Это не макрос.
Если бы ты сделал так: ldi r16,0x55 или ldi r16,0b01010101, то потом сам бы не разобрался, что ты засунул в регистр. А так, когда потом сам читаешь и даже если другой человек читает твой код, понятно:
1<<RXEN — Прием UART разрешен.
1<<TXEN — Передача UART разрешена.
0<<RXCIE — Прерывание по окончании приема запрещено
0<<TXCIE — Прерывание по окончании передачи запрещено.
0<<UDRIE — не помню. Лень в datasheet лезть. Сам не маленький.
То есть LDI R16, (1<<RXEN)|(1<<TXEN)|(0<<RXCIE)|(0<<TXCIE)|(0<<UDRIE) — это запись в регистр битов: 1<<RXEN — установка бита RXEN, 0<<RXCIE — сброс бита RXCIE.
А кто может подтолкнуть к алгоритму приема строки с ПК и сравнением онной в контроллере. Управление командами, а не одиночными символами. Переходы на подобие ниже совсем не улыбаются — тяжело контролируются. Когда команд много, то переходы задоблаешься пересчитывать.
…
Ring:
rcall GetByte
cpi t2, ‘R’
brne Ring ;Переход на следующую строку для сравнения
rcall GetByte ;Принимаем символ с ПК
cpi t2, ‘I’
brne Ring ;Переход на следующую строку для сравнения
rcall GetByte ;Принимаем символ с ПК
cpi t2, ‘N’
brne Ring ;Переход на следующую строку для сравнения
rcall GetByte ;Принимаем символ с ПК
cpi t2, ‘G’
brne Ring ;Переход на следующую строку для сравнения
rcall GetByte ;Принимаем символ с ПК
cpi t2, 13
brne Ring
;######################################################
;Выполнение команды после правильного приема «RING»
;######################################################
…
GetByte: ;Подпрограмма приема символа по USART
sbis UCSRA, RXC ;Проверка на заполнение буфера передачи
rjmp GetByte ;Ожидаем заполение буфера
in t2, UDR ;Сохраняем символ в t2
ret
Во первых, всосвать целую терминальну службу это тебе никакой памяти не хватит. Во вторых пока она обрабатывается у тебя куча прерываний периферии успеет отработать (или просраться, если прерывания запрещены). А в третьих твой код, что ты предложил выше, работать не будет. Сам подумай почему :)
Приведенный код не коректен, согласен, он отрабатывает только одну команду «RING» и конечно же по тупому, остальные остаются не у дел. Мне всего-то надо принимать 5-10 команд. В ветке http://easyelectronics.ru/avr-uchebnyj-kurs-peredacha-dannyx-cherez-uart.html в каментах нашел
Цитата «Думаю проще чем XOR тут ничего не придумать. Зажираешь строку в буффер. Дальше берешь и начинаешь этот буффер циклично XORить на ту строку с которой сравниваешь. Прям побайтно. Если в результате всей операции у тебя получился 0 значит строка та самаяи в бит в бит совпадает со сравниваемой.»
Только еще добавить окончание команды например вводом Enter (0x0D.По приему Enter переходим в процедуру сравнения и в обратном порядке (начиная от 0х0D) вынимаем из буфера по одному символу и сравнениваем с зарание внесенными во flash. Как организовать цикличный буфер на асме пока не знаю. Ограничился 10 символами, если приходят дальше, то заполняться начинают опять с начального адреса буфера.
Как идея — можно все принятые сигналы по прерыванию засовывать во что-нибудь типа массива, а в качестве последнего символа использовать спец-символ, (/0, например) — чтобы увидя его пойти в процедурку выяснение, чего же там напринимали. Очень понятно можно решить такую проблему на С, но DI HALT, конечно же прав, гораздо разумнее использовать однобайтные команды — плюсов гораздо больше. А чтобы было ясно, что какая команда выполняет, в Flash можно записать описания символов (прямо строками), и выдавать их в УАРТ, например, по команде «?».
Я пытаюсь работать с GSM телефоном, так он при включении выдает несколько символов (разобрать не смог надо подбирать скорость). И один из символов может случайным образом совпасть с командой, что приведет к выполнению. Управление по одному принятому символу не так интересно.
Надо изначально ставить планку цели повыше, чтобы по ходу движения учиться.
Пока научился только включать, выключать и перезагружать программно, поднимать трубку на входящий звонок и принимать звонок в одного забитого номера (правда совсем не красиво, зато работает)
Таак, стоп. Ты пытаешься чтоль вирутальную машину вкорячить? Тема вообщет не об этом, а об отладке кода что в МК зашит.
А отладку связи с телефоном можно и на терминале проверить. Не обязательно вживую.
У меня была подобная проблема. Сделал так: сначала идёт заголовок (2 байта), потом команда (1 байт), потом параметр (1 байт), потом контрольная сумма (команда XOR параметр).
На стороне МК ждётся первый символ заголовка, если потом и второй совпал — начинаем считывать команду, параметр, и поле контрольной суммы. Потом расчитываем (команда XOR параметр) и сравниваем с ним. Если совпало — значит всё пучком, и выполняем команду.
А в основном цикле — CASE.
Спасибо. Хорошая идея.
В usbasp ещё uart был выведен на разъём. Только не работал вроде… Если в атмегу в usbasp добавить USB->UART(скорее всего, придётся не одновременно). Или добавить USB хаб и, например FT232RL. И на своих девайсах в ISP добавлять UART.. Было-бы, имхо, удобно.
Ди, никак с тобой не получаетсо связатсо.
стукни пожалуста на rodonanto(гав)gmail.com
Заранее извиняюсь. Вполне возможно данная тема уже есть но я мог её просто не найти в силу разных причин.
Делаю устройство с которого нужно передавать данные через UART.
Раньше всё камутировалось от самого программатора (аналог USBBit) что упрощало мою жизнь.
но теперь по расчётам USB питания просто не хватит да и давно уже нужно переходить на внешнее питание, и материнскую плату насиловать не очень хочется.
Питание устройства будет также 5В(от старого компьютерного БП).
Вопрос: как лучше всего развязать эти два питания программатора и самой схемы (заранее спасибо)
Да не обязательно их обьединять. Питаешь программатор с одного источника, плату с другого. Главное, чтобы земли были обьединены.
большое спасибо