При работе с AVR многие начинают с прекрасного учебника от DiHALT , но хотя на linux можно запустить AVRStudio, давайте попробуем моделировать поведение микросхемы с помощью свободного проекта simulavr. Также на linux есть программы для внутрисхемной отладки с помощью JTAG (openOCD) но для них нужны дополнительно адаптеры.
О линуксе часто говорят как о конструкторе в котором можно работать с железками, наверное это верно. Но все мои знакомые из университета в большинстве своем подсаживаются на какую нибудь закрытую удобную среду, где много народа уже делало примеры и вообще mainstream
А вот как залезаешь под капот embended тусовки linux, сразу начинается: рытьё форумных сказаний на AVRFreaks и кусков документации.
Да я знаю что есть плагин к Eclipse и Eclipse сборки, но я работаю в Emacs, а кто-то в Vim , а кто-то вообще ed использует…. вот и все что я хотел сказать. Только Makefile, только хардкор!
Через терни к звездам.
Table of Contents
http://www.cs.karelia.ru/~vadim/unix/emacs_gdb.php.ru
SimulAVR
Установка
Первое и главное это собрать simulavr и к сожалению по умолчанию в него много чего не включено ,причем не понятно зачем ведь тесты будут производится в основном на хост компьютере, в общем проще было бы ребятам все включить ,а конфигурационными опциями убирать то что не нравиться.
Требования
- gcc
- autoconf
- zlib-dev
- libtool
- swig (для python интерфейса)
- avr-gcc
- avr-gdb
- avr-libc
- tk-*-dev (для TCL интерфейса но я не видел примеры его использования)
- tk-dev
- xotcl-shells
- itcl
У меня не захотел компилироваться тарбол поэтому я стянул свежую и исправленную версию с git хранилища.
git clone git://git.savannah.nongnu.org/simulavr.git
cd simulavr
./bootstrap
Посмотрим список доступных переменных для конфигурации
./configure --help
Самое время озаботиться поддержкой Verilog ,Python и это весьма геморройный пунктик, например на Ubuntu 13.04 он не смог найти пути к python библиотекам, вот так можно их указать вручную.
./configure LDFLAGS="-L/usr/lib/python2.7" --enable-python --enable-tcl make make install
Установка Python интерфейса. Да его надо ставить отдельно даже если вы включили опциюю –enable-python при конфигурации
cd simulavr/src
sudo python setup.py install
Правим код!
«Детка это же опенсорс» (с), я отправил патч и замечания разработчикам, мне интересно как они на это отреагируют. В примере ниже я использовал Atmega16 ,а в ней PINB ,PIND для того чтобы они заработали пришлось следующим образом изменить строки с настройками
Файл /simulavr/src/atmega16_32.cpp
porta = new HWPort(this, "A"); - portb = new HWPort(this, "B"); + portb = new HWPort(this, "B",true); portc = new HWPort(this, "C"); - portd = new HWPort(this, "D"); + portd = new HWPort(this, "D",true);
Файл /simulavr/src/atmega16_32.cpp
void HWPort::SetPin(unsigned char val) { if(portToggleFeature) { - port ^= val; CalcOutputs(); - port_reg.hardwareChange(port); + pin = val; + port_reg.hardwareChange(pin); } else avr_warning("Writing of 'PORT%s.PIN' (with %d) is not supported.", myName.c_str(), val); }
Странно , почему у них там по умолчанию залочена возможность менять значение PIN-ов portToggleFeature=False , поэтому глобально менять ничего не стал.
Подготовка (компиляция)
Итак у нас есть некий файл с кодом на ассемблере и нам необходимо его отладить с помощью gdb.
avr-gcc -Wa -ggdb -mmcu=atmega16a example.S -o example.o
Смотрим справку и видим:
- -Wa или -Xassembler опция которая говорит сишному препроцессору что у нас в коде есть ассемблер… есть ещё конечно команда для компиляции ассемблера вроде avr-as но её вызов работает не корректно.
- -ggdb а иногда просто -g это добавление отладочной информации в бинарник, иначе не удастся связать ассемблерные команды и тест написаны например на Си
- -mmcu тип процессора, подробней в справке.
Запуск
simulavr -g -d atmega16
Что здесь написано:
- -g или –gdbserver ‘запускает gdb сервер которые ждет соединения по умолчанию через порт 1212
- -d или –device после этого указывается название микроконтроллера из поддерживаемых(список -L)
- Раньше ещё была опция -P simulavr-disp для отображения всех регистров, но её убрали.
Теперь воспользуемся gdb, полный код примера example.S приведен в конце статьи , пример был использован из книги В.Я. Хартова «Микроконтроллеры AVR. Практикум для начинающих», пример переработан и изменен под использование с GNU As
Запуск GDB
avr-gdb --annotate=3 example.o > target remote localhost:1212 > load > b waitstart > continue
Что мы сделали, подключились к удаленному серверу gdb , load загрузили туда нашу программу, поставили точку останова на метке waitstart и начали выполнение программы вплоть до точки останова continue. Пошаговое выполнение производиться командой next или n (где мы не входим в вызываемы функции) или step где мы переходим в вызываемые функции.
У меня в Emacs GUD отображаются изменение всех регистров, в консольном режиме info registers поможет рассмотреть состояние всех регистров. Но к сожалению там нет портов, поэтому смотрим даташит(спецификацию) на Atmega16 и узнаем что PORTD обладает адресом 0x32, DDRD=0x31,а PIND=0x30.
Как это посмотреть и поменять в пошаговом режиме.
> x/xb 0x32 0x800032: 0x03 > set {char}0x32=0xf0 > x/xb 0x32 0x800032: 0xf0 > set {char}0x30=0xf0 > x/xb 0x30 0x800030: 0xf0
Код примера, на нем сразу видно на что были заменены директивы Atmel Assemblera.
#include <avr/io.h> ;; #include <compat/deprecated.h> #define SFR(X) _SFR_IO_ADDR(X) #define reg_led r20 #define temp r16 #define START 0 #define STOP 1 ;; .includepath "/usr/share/avra/includes/" ;Папка с файлами заголовками ;; .include "m16def.inc" ; Используем ATMega16 ;; Переключение светодиодов при нажатии на кнопку START ;; .def temp =r16 ;; .def reg_led = r20 ;; .equ START = 0 ;; .equ STOP = 1 ;; .org $000 ;устанавливает начальный адрес первого сегмента .section .text .global main ;; rjmp init ;; Инициализация main: ldi reg_led, 0xfe ;сброс reg_led.0 для включения LED.0 sec ;C=1 set ;T=1 - флаг направления ser temp ;temp = 0xFF out SFR(DDRB),temp ;порта PB на вывод out SFR(PORTB), temp ;погасить LED clr temp ;temp = 0x00 out SFR(DDRD),temp ;??переключаем порт D на вход ldi temp,0x03 ;включение подтягивающих резисторов out SFR(PORTD), temp ;порта D waitstart: sbic SFR(PIND),START ;прыжок на +2 если PIND.START==0 rjmp waitstart loop: out SFR(PORTB),reg_led ;; Задержка в тактах ;; TIME=ldi+(ldi+(dec+brne)*B+dec+brne)*A ldi r17,2 d1: ldi r18,2 d2: dec r18 ;r18-- brne d2 ;перейти на d2 если r18 != 0 dec r17 ;r17-- brne d1 ;перейти на d1 если r17 != 0 sbic SFR(PIND),STOP ;прыжок +2 если PIND.STOP == 0 rjmp MM ;то переход rjmp waitstart ;для проверки кнопки START MM: ; ser temp ;temp=0xFF out SFR(PORTB),temp ; brts left ;brts - перейти если флаг Т==1 sbrs reg_led,0 ;прыгуть +2 если reg_led.0 == 1 set ;T=1 ror reg_led rjmp loop left: sbrs reg_led,7 ;прыгуть +2 если reg_led.7 == 1 clt ;T=0 rol reg_led ;сдвиг в <- в лево с переносом rjmp loop
Прерывания
Пример кода с прерыванием INT0, к сожалению оно автоматически не срабатывает при изменение значения указанного пина, через регистры, поэтому вызваем его напрямую с помощью команды
call __vector_1()
Номер вектора можно узнать из даташита к контроллеру(номер N-1 так как первое прерывание это reset, так для INT0 это 1), в программе же представлены стандартные для препроцессора C имена. (Смотри Note 7)
avr-objdump -d -m avr5 examplevec.o
Исходный код с кодом для тестирования. Вопросы разработчиком я отослал и вообще посоветовал им wiki завести,но ответа пока нет.
#include <avr/io.h> #include <avr/interrupt.h> ;; В примере используется обработка прерываний, нажатие INT0(stop) ;; #include <compat/deprecated.h> #define SFR(X) _SFR_IO_ADDR(X) ;Обращение к регистрам ввода вывода только через это ;; И оно не сработало пришлось вручную все заменить.... непонятно ;; Нужен ручной препроцессор здесь он не справляется т.е дополнительный .h файл #define reg_led r20 #define temp r16 #define START 0 .section .text .global main ;;###############Основная программа main: ldi reg_led,0xfe ldi temp,pm_lo8(RAMEND) ;RAMEnd константа окончания RAM памяти out _SFR_IO_ADDR(SPL),temp ldi temp,pm_hi8(RAMEND) ; out _SFR_IO_ADDR(SPH),temp sec set ser temp out _SFR_IO_ADDR(DDRB),temp out _SFR_IO_ADDR(PORTB),temp clr temp out _SFR_IO_ADDR(DDRD),temp ldi temp,0x05 out _SFR_IO_ADDR(PORTD),temp ldi temp,0x40 ;Установка битов INT1, INT0 или INT2 разрешает прерывания ;0100 0000 /* Условия генерации прерываний устанавливаются с помощью конфигурационных регистров. Для INT0, INT1 – это регистр MCUCR (MCU Control Register). Для INT2 – MCUCSR (MCU Control and Status Register) */ out _SFR_IO_ADDR(GICR),temp ;от процессора лучше это отдать макросам ldi temp,0x00 ;ISC01, ISC00 для INT out _SFR_IO_ADDR(MCUCR),temp sei waitstart: sbic _SFR_IO_ADDR(PIND),START rjmp waitstart loop: out _SFR_IO_ADDR(PORTB),reg_led rcall delay ser temp out _SFR_IO_ADDR(PORTB),temp brts left sbrs reg_led,0 set ror reg_led rjmp loop left: sbrs reg_led,7 clt rol reg_led rjmp loop ;;###############Функция задержки delay: ldi r17,250 d1: ldi r18,250 d2: dec r18 brne d2 dec r17 brne d1 ret .global INT0_vect ;Название взято отсюда ;; www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html ;;###############Обработка прерывания INT0 INT0_vect: waitstart2: sbic _SFR_IO_ADDR(PIND),START rjmp waitstart2 reti .end
Вспомогательные инструменты
Emacs GUD
Собственно это режим для отладки с помощью gdb в Emacs… по мимо регистров и локальных переменных используемых в программе, он позволяет просматривать участки памяти… и прочие вкусности в которых я пока не сильно разобрался.
Позже выложу видео о том как работать во всем этот великолепии.
Makefile и gdbinit
Красивый Makefile для embended проекта там создается инициализационный файл для gdb который убирает в себя рутинные операции.
GDBINITFILE=gdbinit-$(PROJECTNAME) gdbserver: gdbinit simulavr --device $(MCU_TARGET) --gdbserver gdbinit: $(GDBINITFILE) $(GDBINITFILE): $(PRG).hex @echo "file $(PRG).elf" > $(GDBINITFILE) @echo "target remote localhost:1212" >> $(GDBINITFILE) @echo "load" >> $(GDBINITFILE) @echo "break main" >> $(GDBINITFILE) @echo @echo "Use 'avr-gdb -x $(GDBINITFILE)'"
Т.е. теперь запуская GDB командой avr-gdb -x имя\_файла\_gdbinit
Это только первые и робкие примеры и шаги в этом направления, я ничего не сказал о DDD а также о том как можно раздуть конфиг к GDB до 4000 строк. Это постараемся добавить сюда по мере освоения.