Теория
Память программ микроконтроллеров AVR, помимо своего прямого предназначения может быть использована для хранения константных данных. Для этого в AVR ассемблере есть специальные директивы .db и .dw. Первая определяет константный байт или несколько байтов, вторая константное слово или несколько слов. (слово - это 2 байта). Для того чтобы указать, что заданные константы расположены во flash памяти микроконтроллера, используется директива .cseg
Пример:.cseg //эта директива определяет начало сегмента памяти программ
// данные следующие после нее будут размещены во flash //массив байтовkey1:
.db 12,66,7,19,26
//строкаtext:
.db “hi gringo”
//массив шестнадцатиразрядных слов key2:
.dw 0xff12, 0x0134, 0x3056, 0x01ff
//массив указателей на подпрограммыfunc:
.dw Led1On, Led1Off, Led2On, Led2Off
С помощью этих директив можно организовывать во flash памяти массивы данных, массивы указателей на подпрограммы, многомерные массивы, а также структуры и массивы структур. Массив - это набор данных одного типа последовательно размещенных в памяти, тогда как структура – это набор данных разного типа. Конечно, тип данных, массив, структура - термины языков высокого уровня, в частности Си. В ассемблере никаких типов данных нет. Просто эту терминологию удобно использовать, чтобы как-то разграничить обычные данные и указатели на подпрограммы.
Память программ микроконтроллеров AVR имеет 16-разрядную организацию. Доступ к данным в памяти программ осуществляется с помощью команды lpm и индексного регистра Z. При этом старшие 15 разрядов содержимого регистра определяют адрес 16-разрядного слова, а младший разряд определяет, какой из байтов слова будет прочитан: 0 – младший байт, 1 – старший байт.
Пример использования инструкции lpm:.cseg key1:
.db 12,66,7,19,26
…
ldi ZH, High(key1<<1)
//инициализируем индексный регистрldi ZL, Low(key1<<1)
//адресом метки key1lpm r16, Z+
//считываем в r16 первый байт массива – 12 //значение Z увеличивается на единицу lpm r16, Z
//считывем в r16 второй байт массива - 66…
Итак, мы можем хранить во flash памяти данные и имеем к ним доступ. Важно четко понимать, как это происходит.
Практика
Я долго думал как изложить этот материал. Привести сначала абстрактный пример, а потом рабочий, как в статье про switch или сразу разбирать что-то конкретное. В конце концов остановился на втором варианте. Сам по себе знаю как трудно пробираться через дебри чужого ассемблерного кода, но что поделать. Напрягитесь и не будете разочарованы.
Вообщем рассматривать меню на таблицах будем, используя предыдущий проект. Структура меню, функции кнопок и вся аппаратная часть осталась без изменений. В самом проекте изменились только 2 файла:
Menu1.asm – главный файл проекта
Menu.asm – файл содержащий обработчик кнопок и таблицы.
Новый проект можно сказать
здесь.
Программа, реализующая меню на switch`е, состояла из двух отдельных частей – диспетчера и четырех обработчиков. Диспетчер был построен с помощью инструкции косвенного перехода ijmp. А основу обработчиков составляли ветви switch`а построенные на макросе Case. Ему “передавались” три параметра – текущее состояние(где находимся сейчас), следующее состояние(куда перейти потом), имя подпрограммы. Ветви switch`а были зеркальным отражением таблицы, которую я приводил в предыдущей статье.
В новом проекте диспетчер и обработчик нажатия кнопки совмещены в одной подпрограмме под названием HandlerMenu, а логика переходов по меню расписана в таблицах во флэш памяти микроконтроллера.
Таблицы
У нас есть четыре кнопки – Up, Down, Enter, Cancel. Для каждой кнопки во flash памяти микроконтроллера существует своя таблица. Строки таблиц определены с помощью директивы .db и имеют следующую структуру.
1 байт
| 2 байт | 3 байт | 4 байт |
Текущее состояние (где находимся сейчас) | Следующее состояние (куда перейти потом) | Младший байт адреса подпрограммы | Старший байт адреса подпрограммы |
Изначально одна строка таблицы для кнопки, например Up, выглядит следующим образом:
.equ End = 255
HUpButton:
.db 1,1, Low(Empty), High(Empty)
.db End
где HUpButton – метка, название таблицы,
Low(Empty) – младший байт указателя на подпрограмму Empty,
High(Empty) – старший байт указателя на подпрограмму Empty,
End – маркер конца таблицы.
Запись вида Low(Empty), High(Empty) довольно неудобна, поэтому я изменил ее с помощью директивы препроцессора define. Благо AVR ассемблер поддерживает такие директивы
#define func(x) Low(x),High(x)
Теперь таблицу для кнопки Up можно записать в таком виде
#define func(x) Low(x),High(x)
.equ End = 255
HUpButton:
.db 1,1, func(Empty)
.db 2,1, func(lcdSelectLed1)
.db 3,3, func(Empty)
.db 4,3, func(lcdSelectLed1)
.db 5,5, func(Empty)
.db 6,5, func(lcdSelectLed1)
.db End
Довольно наглядная запись. (Кстати, это чистой воды массив Си структур).
Еще во flash памяти микроконтроллера есть массив, в котором содержатся указатели на таблицы кнопок. Он определен с помощью директивы .dw
Handlers:
.dw HUpButton, HDownButton, HEnterButton, HCancelButton
Обработчик
Обработчик имеет следующий вид.
HandlerMenu:
//вычисляем указатель на таблицу
dec r16
clr zl
clr zh
mov zl,R16
lsl zl
subi zl, LOW(-(Handlers<<1))
sbci zh, HIGH(-(Handlers<<1))
lpm R16, Z+
lpm R17, Z
movw ZH:ZL, R17:R16
lsl zl
rol zh
//ищем нужную строку таблицы
lds r19, pCurrentState
CheckState:
lpm r16, z
cp r16, r19
breq ChangeState
cpi r16, End
breq ExitHM
adiw Z, 4
rjmp CheckState
//если нашли – то меняем текущее состояние
//и вызываем подпрограмму
ChangeState:
adiw Z, 1
lpm r16, Z+
sts pCurrentState, r16
lpm R16, Z+
lpm R17, Z
movw ZH:ZL, R17:R16
icall
ExitHM:
ret
Разберем алгоритм его работы
Регистр r16 содержит считанный из ОЗУ номер кнопки. Он помещается туда перед вызовом обработчика HandlerMenu. Номер кнопки используется как смещение для доступа к соответствующему элементу массива Handlers. Кнопки имеют номера от 1 до 4. А смещение имеет диапазон от 0 до 3. (потому что элементы массива нумеруются начиная с нулевого) Итак, мы декрементируем значение r16, очищаем регистровую пару ZH:ZL, загружаем в нее значение r16 и сдвигаем ZL.
dec r16
clr zl
clr zh
mov zl,R16
lsl zl
Теперь нам нужно сложить адрес метки Handlers и смещение. В системе команд микроконтроллера AVR нет команды сложения регистра с константой. Инструкцию adiw не в счет, потому что она не может прибавить к регистру больше 63. Выход из положения следующий. Вычитаем отрицательное значение адреса метки из регистров ZH:ZL. Минус на минус дает плюс и в итоге получается, что мы на самом деле складываем адрес метки со смещением.
subi zl, LOW(-(Handlers<<1))
sbci zh, HIGH(-(Handlers<<1))
Считываем указатель на нужную таблицу в r16 и r17, копируем в регистровую пару ZH:ZL и сдвигаем влево.
lpm R16, Z+
lpm R17, Z
movw ZH:ZL, R17:R16
lsl zl
rol zh
Понятно? Мы взяли адрес адрес метки Handlers, добавили смещение и считали из памяти программ адрес метки таблицы.
Ок. Теперь переходим к поиску нужной строки таблицы.
Считываем из ОЗУ текущее состояние, а затем в цикле сравниваем его с первым байтом строки таблицы. Если они не равны – проверяем, не дошли ли мы до маркера конца таблицы. Если дошли - выходим из обработчика, если не дошли – прибавляем к регистровой паре ZH:ZL смещение. Здесь то нам и пригодилась команда adiw. Каждая строка таблицы состоит из четырех байтов, и вся таблица расположена во flash памяти непрерывно. Собственно, поэтому мы и имеем доступ к ее строкам, зная только начальный адрес и значение смещения.
lds r19, pCurrentState
CheckState:
lpm r16, z
cp r16, r19
breq ChangeState
cpi r16, End
breq ExitHM
adiw Z, 4
rjmp CheckState
На метку ChangeState микроконтроллер попадает, если нашел нужную строчку в таблице. Здесь мы считываем второй байт строки, который определяет следующее состояние, и записываем его в ОЗУ по адресу pCurrentState. Далее считываем указатель на подпрограмму (это 3 и 4 байты) и вызываем ее.
ChangeState:
adiw Z, 1
lpm r16, Z+
sts pCurrentState, r16
lpm R16, Z+
lpm R17, Z
movw ZH:ZL, R17:R16
icall Вот и все, обработчик закончил свое дело.
Comments
ret оставить, так как на него есть переходы из других мест.
Я новичок, но мне нужно освоить по-быстрее ассемблер, хотя бы азы...
Если по-точнее необходима работа с Массивами,думаю одномерный тоже подойдёт...
Это статья не по этому вопросу, но оч хотелось бы что бы автор изложил пару простеньких примеров работы с массивами...
(письмецо тебе на почту я отправил)
читай название статьи
RSS feed for comments to this post