gdb_emacs_assembler.png

При работе с AVR многие начинают с прекрасного учебника от DiHALT , но хотя на linux можно запустить AVRStudio, давайте попробуем моделировать поведение микросхемы с помощью свободного проекта simulavr. Также на linux есть программы для внутрисхемной отладки с помощью JTAG (openOCD) но для них нужны дополнительно адаптеры.

О линуксе часто говорят как о конструкторе в котором можно работать с железками, наверное это верно. Но все мои знакомые из университета в большинстве своем подсаживаются на какую нибудь закрытую удобную среду, где много народа уже делало примеры и вообще mainstream

А вот как залезаешь под капот embended тусовки linux, сразу начинается: рытьё форумных сказаний на AVRFreaks и кусков документации.

Да я знаю что есть плагин к Eclipse и Eclipse сборки, но я работаю в Emacs, а кто-то в Vim , а кто-то вообще ed использует…. вот и все что я хотел сказать. Только Makefile, только хардкор!

Через терни к звездам.

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 строк. Это постараемся добавить сюда по мере освоения.