28 января 2014 г.

Работа с Simulavr

При работе с AVR многие начинают запустка AVRStudio под WINE, но мы попробуем моделировать поведение микроконтроллеров с помощью свободного проекта simulavr


На linux есть также и программы для внутрисхемной отладки с помощью JTAG (например openOCD), но для них нужны дополнительно адаптеры.
../pics/gdb_emacs_assembler.png

Table of Contents

1 SimulAVR

1.1 Установка

Первое и главное это собрать 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

1.2 Правим код!

Я отправил патч и замечания разработчикам. В примере ниже я  спользовал Atmega16, а в ней PINB, PIND для того чтобы они заработали пришлось следующим образом изменить строки с настройками
Файл /simulavr/src/atmega1632.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/atmega1632.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 , поэтому глобально менять ничего не стал.

1.3 Подготовка (компиляция)

Итак у нас есть некий файл с кодом на ассемблере и нам необходимо его отладить с помощью gdb.
avr-gcc -Wa -ggdb -mmcu=atmega16a example.S -o example.o
Смотрим справку и видим:
  • -Wa или -Xassembler опция которая говорит сишному препроцессору что у нас в коде есть ассемблер… есть ещё конечно команда для компиляции ассемблера вроде avr-as но её вызов работает не корректно.
  • -ggdb а иногда просто -g это добавление отладочной информации в бинарник, иначе не удастся связать ассемблерные команды и тест написаны например на Си
  • -mmcu тип процессора, подробней в справке.

1.4 Запуск

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 обладает адресом 0×32, DDRD=0×31,а PIND=0×30.
Как это посмотреть и поменять в пошаговом режиме.
> 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

1.5 Прерывания

Пример кода с прерыванием 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

2 Вспомогательные инструменты

2.1 Emacs GUD

Собственно это режим для отладки с помощью gdb в Emacs… по мимо регистров и локальных переменных используемых в программе, он позволяет просматривать участки памяти… и прочие вкусности в которых я пока не сильно разобрался.
Позже выложу видео о том как работать во всем этот великолепии.

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

____
http://crafting.be/2013/09/simulavr-start/

1 комментарий: