В предыдущей статье мы узнали какого это компилировать simulavr. Попробовали работать с ним в пошаговом режиме через gdb.

Вызывали прерывания, вручную меняли регистры, вызывали прерывания. Теперь давайте протестируем всё в автоматическом режиме. Вот и задача подвернулась есть датчик DHT11 он работает на однопроводном интерфейсе задача симулировать работу с ним.

Также немного поработаем с декодировщиком sigrok в offline режиме(т.е. без самого логического анализатора).

simulavr_pro_intro_final.jpg

Собираем наш simulavr

Здесь все просто, на самом деле нам нужен только verilog, а это потребует установки iverilog и gtkwave, остальные примеры с python и tcl, напишу как до них руки дойдут.

sudo apt-get build-dep simulavr
git clone git://git.savannah.nongnu.org/simulavr.git
cd simulavr
./bootstrap
./configure --enable-python --enable-verilog  --enable-tcl --prefix=/usr
make
sudo checkinstall

Вообще нужно будет опакетить simulavr по полной для поддержки tcl например понадобился itcl3 itcl3-dev

configure выдаст что то вроде

Summary:
build system = Linux
AVR_GCC=avr-gcc
PYTHON=/usr/bin/python
have sphinx python module? yes
have rst2pdf python module? no
TCL_WISH=/usr/bin/wish8.6
tcl has package Itcl? yes
build verilog modul avr.vpi? yes

Прошу пишите если какие то проблемы у вас появились

Вот например:

make[2]: Entering directory '/home/user/source/simulavr/examples/verilog'
avr-gcc -c -Wa,-gstabs -x assembler-with-cpp -o right-unit.o right-unit.s
right-unit.s: Assembler messages:
right-unit.s:17: Fatal error: redefinition of mcu type `avr2' to `attiny25'
Makefile:602: recipe for target 'right-unit.o' failed
make[2]: *** [right-unit.o] Error 1
make[2]: Leaving directory '/home/user/source/simulavr/examples/verilog'
Makefile:485: recipe for target 'all-recursive' failed
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory '/home/user/source/simulavr/examples'
Makefile:507: recipe for target 'all-recursive' failed
make: *** [all-recursive] Error 1

Тут за время обновления с Ubuntu 15.04 до 16.04 поменялись директивы в GCC поэтому надо в двух файлах:

  • simulavr/examples/verilog/right-unit.s
  • simulavr/examples/verilog/singlepincomm.s

Заменить .arch ATTiny25 на .arch AVR2

Пример работы с DHT11

С помощью напильника из двух примеров пишем программу которая принимает на один из выводов данные с датчика dht11,а затем передает эти данные на компьютер по UART

Все необходимые для работы примеры находятся в архиве

../files/dht_to_uart.zip

Внимание!!

К сожалению необходимые verilog модели и привязки в simulavr не устанавливаются с помощью checkinstall, поэтому вам необходимо будет в Makefile поправить, это путь туда где вы скачали и распаковали simulavr

VERILOG_SRC = /home/user/source/simulavr/src

Программа кстати плохая так как в AVR-ку пихается полноценная функция printf так как мы используем в программе подстановки вида %s. Итого программа получает +4.5k при то что их всего 16k

вот головной файл с раскомментированными printf

#include "main.h"

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>

#include "DHT.c"
#include "uart.c"

char h[7];
char t[7];

FILE uart_str = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);

//Функция чтения DHT11
char Read_dht(char *_h, char *_t) {
  char temp[2]={0,0};
  unsigned char i;
  char dht11_dat[5];


  stdout = stderr = stdin = &uart_str;
  for(i=0;i<7;i++) {
        _h[i]=0;
        _t[i]=0;
  }  
  switch (GetDhtData(dht11_dat))
    {
    case DHT_OK:
      itoa(dht11_dat[0],temp,10);
      strcpy(_h,temp);
      itoa(dht11_dat[1],temp,10);
      strcat(_h,".");
      strcat(_h,temp);


      itoa(dht11_dat[2],temp,10);
      strcpy(_t,temp);
      itoa(dht11_dat[3],temp,10);
      strcat(_t,".");
      strcat(_t,temp);
      return 1;

    case DHT_ERROR_START_FAILED_1:
      printf("Err:startfail1");
      break;

    case DHT_ERROR_START_FAILED_2:
      printf("Err:startfail2");
      break;

    case DHT_ERROR_READ_TIMEOUT:
      printf("Err:timeout");
      break;

    case DHT_ERROR_CHECKSUM_FAILURE:
      printf("Err:checksum");
      break;
    }
  return 0;
}

int main (void)
{
  uart_init();
  stdout = stderr = stdin = &uart_str;
  _delay_ms(2000); // Время входа в строй датчика DHT11 после включения  
  for (;;){
    if (Read_dht(h,t)) {
      //UART
      printf("Hum: %s  Temp: %s\n",h,t);
      _delay_ms(600);
    }
  }
}

Simulavr

Tracepath — следим за сигналами

Итак давайте рассмотрим что будет происходить если к контролеру ничего не подсоединить. Микроконтроллер будет слать в пустоту 20 мс сигналы и ждать ответа с периодичностью 280 мс.

Ещё немалая проблема это то что ваш микроконтроллер может отличаться от тех что поддерживаются simulavr , а ещё он может отличаться от тех микроконтроллеров которые поддерживаются в verilog симуляции, а их совсем мало. Посмотреть можно в папке simulavr/src/verilog вот все богатство моделей поддерживающих verilog:

  • avr_ATmega32.v
  • avr_ATmega8.v
  • avr_ATtiny2313.v
  • avr_ATtiny25.v

Сразу скажу, как добавлять новые модели не знаю.

Перед тем как мы что нибудь будем трассировать давайте посмотрим, а какие значения можно смотреть для этого введем команду

simulavr -d atmega32 -o avr.trace

в появившемся файле avr.trace находится список всех параметров, удалите дублирующиеся и ненужные вам параметры.

Теперь запустим симуляцию, на конечное время.

simulavr -c vcd:avr.tracet:trace.vcd -f simul.elf -d atmega16 -F 8000000 -m 6000000

Здесь частота указана в герцах, а время в микросекундах. Частоту указывать обязательно! Теперь можно открыть файл trace.vcd в gtkwave и посмотреть на периодичные пики.

Дописываем в конец Makefile

VCD вывод simulavr на данный момент, не поддерживается Sigrok поэтому пересохраняйте ваш vcd через GTKwave

Ради интереса можете попробовать наш пример с передачей буквы A также, симулировать.

simul_avr_firsttrace.png

На снимке мы видим то что должно получится, а именно наш вывод. 2 секунды, пока просыпается контроллер, находится в неопределенном состоянии, что можно объяснить строкой

_delay_ms(2000); // Время входа в строй датчика DHT11 после включения

возможно строчка будет закомментирована так как при симуляции 2-секунды превращаются в 30 минут полноценной симуляции.

Затем он начинает посылать с периодичностью ~280 мс, сигналы длинной 20 мс

_delay_ms(250); //Файл DHT.c
_delay_ms(20); //Файл DHT.c

Дальше не получив ответа, происходит выход из функции и сообщение об ошибке по UART.

Verilog

Как вы понимаете, на linux есть один GNU-тый verilog — iverilog или icarus verilog

Учим Verilog

На самом деле быстро здесь не получится. Вкратце Verilog это язык описания и работы цифровых устройств. На нем описывают микроконтроллеры и процессоры, затем на заводе преобразуют с помощью библиотек физических элементов, код Verilog в транзисторы и их соединения на кремнии.

Вот пример софта по разводке кремния и вообще куча ссылок вам от увлекающихся людей:

это мы отвлеклись в файлах .v располагаются модули это черные ящики наружу которых торчат ножки входов и выводов внутри модулей могут быть ещё модули и все это соединяется вместе. В конечном итоге на верху этой иерархии находится файл тестирования, тоже написанный на верилоге, вот так его например могут обозначить _tb.v

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

Собираем тестовый пример для проверки работы simulavr-verilog

Все необходимые файлы для этого теста находятся в dht_to_uart/buttons_example

Протестируем работу verilog привязки к simulavr. Для этого создадим простой проект. У нас есть atmega32 к PIND которой подключены идеальные кнопки(без дребезга), порт D работает на прием, в это же время PORTB работает на выход и показывает нам инвертированное значение с PIND

#include <avr/io.h>

int main (void)
{
DDRD = 0x00; //порт D - вход
//PORTD = 0xFF; //подключаем нагрузочный резистор pullup
DDRB = 0xFF; //порт B - выход
PORTB = 0x00; //устанавливаем 0 на выходе

while(1)
{
PORTB = ~PIND; //~ знак поразрядного инвертирования
}
}

Собираем наш elf файл командами

PROGRAM = example
MCU = atmega32
MCU_SIM = atmega32
MCU_VER = atmega32
CC = avr-gcc
OBJCOPY = avr-objcopy
CFLAGS += -Wall -g -Os -mmcu=$(MCU)
LDFLAGS +=
OBJS = $(PROGRAM).o
SIMULVERILOG=/home/user/source/simulavr/src/verilog

avr-gcc -Wall -g -Os -mmcu=atmega32 -o example.elf example.c

Анализируем verilog тест

На основе примеров из файлов simulavr/example/verilog пишем файл тестирования. Надеюсь что Вы прочли вводную в верилог по ссылкам в предыдущем параграфе.

/* SPI slave Verilog example code. */
`timescale 1ns / 1ns

module test;
   reg clk;
   reg [7:0] buttons;

   wire [7:0] pa;
   wire [7:0] pb;
   wire [7:0] pc;
   wire [7:0] pd;

   assign    pd=buttons;

   defparam  avr.progfile="example.elf";
   ATmega32 avr(clk, pa, pb, pc, pd);
   //avr_clock clock(clk);

   initial begin
      $avr_trace("avr.trace");
      $dumpfile("example.vcd");
      $dumpvars(0, test);
      buttons = 8'b00000000;
      #30000;
      #4000
          begin
             buttons=8'b01010101;
          end // UNMATCHED !!
      #4000
          begin
             buttons=8'b10101010;
          end
      #4000
          begin
             buttons=8'b01010101;
          end // UNMATCHED !!      
      #4000
          begin
             buttons=8'b10101010;
          end
   end

   initial begin
      # 100000 $finish;
   end // initial begin

   always begin
      #500 clk<=0; 
      #500 clk<=1;
   end
endmodule

Разберем чуть подробней.

/* SPI slave Verilog example code. */
`timescale 1ns / 1ns

module test;
   reg clk;
   reg [7:0] buttons;

   wire [7:0] pa;
   wire [7:0] pb;
   wire [7:0] pc;
   wire [7:0] pd;

   assign    pd=buttons;

timescale — это указание размерности всех единиц находящихся в данном примере. Т.е. цифра вида #500 на самом деле 500 нс.

Далее задаются некие переменные. Это регистр(reg) тактовой частоты clk а также кнопки которые тоже представлены здесь в виде регистра, т.е. того что может хранить значения.

Wire это проводники соединители, они значения не хранят а лишь соединяют между собой в данном случае они нужны будут чтобы подключится к портам микроконтроллера. Да в нашем случае это не просто проводники это шины по 8 проводов в пучке.

assign это объявление о соединения, в данном случае мы соединили регистр кнопок и порт B через проводники pd

defparam  avr.progfile="example.elf";
ATmega32 avr(clk, pa, pb, pc, pd);

Здесь мы через параметр указывающий программный файл. А также загруженную модель, инициируем наш микроконтроллер.

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

simul_avr_example_buttons.png

Figure 3: Здесь два теста с pullup и без pullup

И видим непонятные красные полосы — это неопределенное значение xx, а zz — это высокоомное состояние, или желтая полоса по центру.

initial begin
   $avr_trace("avr.trace");
   $dumpfile("example.vcd");
   $dumpvars(0, test);
   buttons = 8'b00000000;
   #30000;
   #4000
       begin
          buttons=8'b01010101;
       end // UNMATCHED !!
   #4000
       begin
          buttons=8'b10101010;
       end
   #4000
       begin
          buttons=8'b01010101;
       end // UNMATCHED !!      
   #4000
       begin
          buttons=8'b10101010;
       end
end

Здесь всё просто avr_trace — это необязательная функция в которой будут отслеживаться все команды выполняемые процессором

example.elf 0x006c: main                           OUT 0x11, R1 
example.elf 0x006e: main+0x1                       LDI R24, 0xff 
example.elf 0x0070: main+0x2                       OUT 0x12, R24 
example.elf 0x0072: main+0x3                       OUT 0x17, R24 
example.elf 0x0074: main+0x4                       OUT 0x18, R1 
example.elf 0x0076: main+0x5                       IN R24, 0x10 
example.elf 0x0078: main+0x6                       COM R24 SREG=[---S-N-C] 
example.elf 0x007a: main+0x7                       OUT 0x18, R24 
example.elf 0x007c: main+0x8                       RJMP 76 
example.elf 0x007c: main+0x8                       CPU-waitstate
example.elf 0x0076: main+0x5                       IN R24, 0x10

вплоть до остановки нашей симуляции.

dumpfile — это собственно файл куда будут скидываться значения verilog переменных.

dumpvars(0, test); test -это верхний модуль он так и называется в файле example.v , а цифра 0 — указывает на то что нужно, записывать не только переменные верхнего модуля test но и всех переменных подмодулей использованных в головном модуле. 1 — переменные только в топ модуле.

Далее задаем начальное значение для регистра кнопок(buttons) — равное 0 для наглядности пользуюсь бинарной записью.

Ждем 30 мкс или 30 000 нс , затем в течении 4 мкс включаем состояние x55, переключаем xAA опять 4 мкс, повторяем x55 и переключаемся в конце на xAA — это состояние в регистре и останется.

initial begin
   # 100000 $finish;
end // initial begin

always begin
   #500 clk<=0; 
   #500 clk<=1;
end

Первая конструкция ждет с начала теста и завершает его по истечении 100 мкс

Вторая конструкция запускает цикл для нашего тактового генератора. в течении 0,5 мкс сигнал равен 1 , в течении оставшихся 0,5 мкс — 0. И это повторяется постоянно пока не закончится тест. Период равен 1 мкс а значит контроллер работает с частотой 1МГц. Кстати если приглядеться то первые 0,5 мкс регистр clk имеет неопределенное состояние, что говорит о том что присваивание происходит в конце временного периода а не в начале.

На примере картинки выше мы видим что контроллер не сразу инициализируется, а также видим задержки в 4-6 тактов при изменении значений в регистре pd что можно перепроверить посмотрев дизассемблер нашего кода и оценив за сколько тактов выполняется один цикл.

Используем Verilog модель датчика DHT11

Все необходимые файлы находятся в папке dht_to_uart/dht11_example

Cпециально для этого занятия была написана verilog модель DHT11.

https://github.com/spacemonkeydelivers/dht11 за что спасибо SMD

Она находиться в файле проекта dht11_to_uart/dht11.v исправленная и откомментированная мной.

Немного справки по двунаправленному выводу — inout . Так как датчик использует один провод для приема и передачи сигнала.

Inout — буфферный элемент, использующийся для двунаправленных сигналов. Этот элемент пропускает через себя входной сигнал, только если на входе CTRL есть управляющая единица. Если на входе CTRL ноль, то элемент отключается от выходного провода переходя в высокоомное состояние. Такие элементы вообще-то используются только для выводов цифровых микросхем. Иными словами, использовать двунаправленные сигналы ( inout ) правильнее всего только в модуле самого верхнего уровня.

Для тестирования нашего модуля с двунаправленным пином. Есть тестирующий файл — dht11_tb.v его и dht11.v -я максимально возможно прокомментировал.

Для быстрого понимания представим все это картинкой

madskillz_inout_dht.png

Наш черный ящик DHT11 с одним единственным двунаправленным выводом мы соединяем с двунаправленным выводом data который в случае если out=1 выдает нам значение регистра data_reg. В случае если out=0, провод отрубается от регистра и переходит в высокоомное состояние.

Но так как у нас data соединен с выводом DHT11, то если из DHT11 выходит сигнал то линия будет подтянута(pullup) до его значения.

Как посмотреть вывод датчика

iverilog dht11_tb.v
vvp a.out

На выходе будет привычный vcd файл.

Немного магии Sigrok

Некоторые долго смотрят на матрицу и не видят символы, а видят брюнетку, блондинку… ну в общем если вы не можете декодировать и читать протоколы на лету в gtkwave, а там встроенного декодировщика нет. Поэтому давайте вырежем всю нужную нам информацию из файла трасировки(vcd) с помощью Cut/Paste,сохраним(Экспорт -> VCD). Потом немного поколдуем и откроем в Sigrok.

Собираем свежий Sigrok

Увы придется поставить свежий, там есть декодеры DHT11

Совсем свежий 0.4.0 PulseView сейчас крашится при чтение VCD ,ставте 0.3.0

http://sigrok.org/wiki/Building

Последовательность установки а также необходимые пакеты написаны на официальном сайте http://sigrok.org/wiki/Linux опишу лишь проблемы которые у меня возникали.

libsigrok

Попросил python-gi-dev

sigrok-cli

Здесь аккуратней с checkinstall он мне имя пакета как sigrok записал

pulseview

У меня возникала ошибка

pulseview: error while loading shared libraries: libsigrokcxx.so.3: cannot open shared object file: No such file or directory

Все потому что libsigrok в пакетах назывался libsigrok2 а так как я не удалил sigrok и ставил пакеты поверх то линк шел на libsigrok2

Grand Final

А теперь мы зайдем в наш пример dht_to_uart и запустим

make verisim

симулироваться будет долго и это удручающий минус.

Никакой магии, просто выполняются команды по компиляции и исполнению verilog кода

iverilog -o test.vvp dht11_atmega32_tb.v $(VERILOG_SRC)/verilog/avr_ATmega32.v $(VERILOG_SRC)/verilog/avr.v
vvp -M $(VERILOG_SRC) -mavr test.vvp

После чего Вам останется пересохранить файл example.vcd в Gtkwave, нам нужны проводники uart_tx и dht_data , заодно проверьте все ли в порядке. Попытка вырезать отдельный пин из шины и экспортировать его увы не удалась ,возможно я просто плохо знаю Gtkwave.

Откройте файл vcd в Pulseview укажите количество каналов как 2 а параметр Downsample как 10000, иначе PulseView съест всю вашу память.

Примените декодеры DHT к нужной линии и декодер UART(bd 9600) к другой

simulavr_pro_intro_final.jpg

Выводы, мы можем автоматизированно создавать тесты и проверять работу, каждый раз не собирая стенд. За это мы платим скоростью работы.

Если вы смогли повторить этот эксперимент из желудей и палок, это круто.

Все же большим достижением из этой работы была бы налаженная коммуникация между GTKwave и Sigrok это поможет всем. Ну хотя бы что VCD чуть по умней читалось а не съедало всю память.