РадиоКот :: Массивы. Бегущий огонек v2.1
Например TDA7294

РадиоКот >Обучалка >Микроконтроллеры и ПЛИС >Микроконтроллеры AVR - пишем, компилируем, прошиваем... >

Теги статьи: Добавить тег

Массивы. Бегущий огонек v2.1

Автор:
Опубликовано 18.10.2005

Сегодня мы заставим огонек не просто бегать слева направо, но и справа налево, из центра к краям и т.д. и т.п.

Можно конечно, поизвращавшись и просидев часик - другой за компом, написать прогу, которая будет выполнять наши самые извращенные желания по мегаизвращенному алгоритму.И они-таки побегут.
Но если нам понадобится, чтобы они бежали по-другому - придется тратить еще часа два на написание новой проги с другим гигаизвращенным алгоритмом. И так далее… Согласитесь - перспектива невеселая.

Что же делать? Да вот чего: задать все последовательные состояния светодиодов таблицей. Иначе говоря - массивом.
Ассемблер позволяет задавать линейные массивы как в программной памяти (ПЗУ), так и в оперативной (ОЗУ). Соответственно, массив на ПЗУ можно только читать, массив на ОЗУ можно и читать и писать. Сегодня мы рассмотрим первый вариант.

Итак, в любом месте программы мы можем написать что-то вроде:

MyArray:
.db 1,15,4,9,12,145,67,90

Это есть массив "MyArray" состоящий из 8 элементов. Массив размещается в ПЗУ начиная с адреса, на котором стоит метка (MyArray). То есть, адресу метки соответствует 0-й элемент массива.
Чтобы получить доступ к 1-му, 2-му, 3-му и т.д. элементу, нужно прочитать ячейку ПЗУ по адресу, который больше адреса метки соответственно на 1,2,3,…

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

Регистровые пары

Их всего три: X, Y и Z. Они принадлежат к РОН, и состоят, собственно, из шести последних регистров - R26…R31:

X - R26,R27
Y - R28,R29
Z - R30,R31

Для чего нужны регистровые пары? Для работы с 16-битными числами, коим, в частности, является адрес ПЗУ. Однако, мы можем не использовать данные пары, а распоряжаться выделенными для них регистрами так же, как и обычными. При работе с регистровыми парами, младшая и старшая часть обрабатываются по отдельности. Соответственно, зовут их L и H (от слов Low и High). Например, ZH и ZL.

Сегодня нам пригодятся следующие команды:

adc - Add with Carry - сложить два регистра с учетом переполнения предыдущей операции (если результат предыдущей операции > 255).

Пример:
add ZL,Temp
adc ZH,Temp1

lpm - Load Program Memory - загрузить данные из программной памяти (ПЗУ) по адресу из регистровой пары Z в регистр R0.

Пример:
ldi ZH,High(Label*2) ;загрузка адреса метки в
ldi ZL,Low(Label*2)  ;регистровую пару Z
lpm                   ;загрузка ячейки по адресу (Z)
mov Temp,R0          ;копирование загруженного
                      ;байта

Ну, собственно, теперь мы можем написать такую программульку:

          ldi Temp,0           ;инициализация регистра
                               ;внутренней адресации массива

ReadArray:
          ldi ZH,High(MyArray*2) ;загрузка адреса 0-го
          ldi ZL,Low(MyArray*2)  ;элемента в рег. пару Z

          ldi Temp1,0
          add ZL,Temp          ;прибавление
          adc ZH,Temp1         ;внутр. адреса

          lpm                  ;загрузка из ПЗУ

          mov Temp1,R0         ;копирование
          out PortB,Temp1      ;вывод в порт
          inc Temp             ;увелич. внутр. адреса
          rjmp ReadArray       ;в начало цикла

MyArray:
.db 12,16,3,4,10,17,255,37,158,14,13,98
.db 14,85,30,9,145,52,64,49,119,72,209,46

Эта программа читает последовательно элементы массива MyArray и выводит их в порт.

Разъясним некоторые непонятности.

Во первых, структура:


ldi ZH,High(MyAray*2)

Этой командой мы загружаем в старшую часть пары Z (ZH), старшую часть адреса по метке MyArray.
Что значит "*2"?
Дело в том, что каждая команда содержит два байта информации и занимает, таким образом, две ячейки ПЗУ. Поэтому, счетчик команд считает 2 адреса как один. Метка содержит именно данные для счетчика команд. Чтобы получить реальный адрес ПЗУ, необходимо увеличить адрес метки в 2 раза. Что мы и делаем.

Во-вторых: в нашем массиве две строки. Это не означает, что массив - двухмерный. Просто, мы разбиваем одну длинную строку на несколько более коротких - для удобства. Строк может быть сколько угодно.

ВНИМАНИЕ:
При разбивке, не допускайте нечетного количества элементов в строке, иначе после последнего элемента текущей строки перед первым элементом следующей, у вас прочитается один "несуществующий" элемент.
Это также связанно с особенностями адресации программной памяти. Нечетной может быть только последняя строка.

В-третьих: данные в массиве могут быть представлены в виде десятичных, шестнадцатеричных, двоичных чисел, а также, эквивалентных значений символов ASCII.

Примеры:

.db 3,4,5,75,32,12        ;десятичные числа
.db 0x2A,0x34,0x17,0xDF   ;шестнадцатеричные числа
.db 0b01101001,0b11011100 ;двоичные числа
.db "Здесь был Вася"      ;эквиваленты ASCII

Ну вот, теперь можно писать программу.
С вашего позволения, я опущу начало программы (вектора прерываний, инициализацию и пр), и начну прямо с обработчика прерывания компаратора. Опущенную часть можно взять из предыдущих статей.

Единственное что нужно прописать в инициализации:


ldi Temp1,0;   ;начальный внутренний адрес

Далее ищем обработчик и пишем следующее:


;****************************************************
; ОБРАБОТЧИК ПРЕРЫВАНИЯ КОМПАРАТОРА
;****************************************************

Timer1_comp1:


         ldi Temp,0            ;обнуление таймера
         out TCNT1H,Temp
         out TCNT1L,Temp


         cpi Temp1,30          ;сравнить с крайним знач.
         breq Init             ;если равно - загрузка нач. знач.


ReadArray:
         ldi ZH,High(Array*2)  ;загрузка начального адреса массива
         ldi ZL,Low(Array*2)

         ldi Temp,0            ;прибавление внутр. адреса
         add ZL,Temp1
         adc ZH,Temp

         lpm                   ;загрузка из ПЗУ

         mov Temp,R0           ;копирование в РОН
         inc Temp1             ;увеличение внутр. адреса

         rjmp Output           ;перейти на вывод в порт

Init:    ldi Temp1,0          ;загрузить нач. значение
         rjmp ReadArray

Output:  out PortB,Temp       ;вывод в порт

         reti                  ;выход из обработчика


Array:

.db   0b10000001,0b01000010
.db   0b00100100,0b00011000
.db   0b00011000,0b00111100
.db   0b01111110,0b11111111
.db   0b11100111,0b11000011
.db   0b10000001,0b11000001
.db   0b11100001,0b11110001
.db   0b11111001,0b11111101
.db   0b11111111,0b01111111
.db   0b00111111,0b00011111
.db   0b00001111,0b00000111
.db   0b00000011,0b00000001
.db   0b00000011,0b00000101
.db   0b00001001,0b00010001
.db   0b00100001,0b01000001

Ну вот, теперь разбирайтесь. Я все что мог - сказал :)

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

<<--Вспомним пройденное----Поехали дальше-->>




Как вам эта статья?

Заработало ли это устройство у вас?

27 0 0
8 3 2