Макроассемблер

5, Июль, 2008

Перед изучением системы команд микроконтроллера надо бы разобраться в инструментарии. Плох тот плотник который не знает свой топор. Основным инструментом у нас будет компилятор. У компилятора есть свой язык — макроассемблер, с помощью которого жизнь программиста упрощается в разы. Ведь гораздо проще писать и оперировать в голове командами типа MOV Counter,Default_Count вместо MOV R17,R16 и помнить, что у нас R17 - значит Counter, а R16 - это Default_Count. Все подстановки с человеческого языка на машинный, а также многое другое делается средствами препроцессора компилятора. Его мы сейчас и рассмотрим.

Комментарии в тексте программы начинаются либо знаком «;», либо двойными слешами «//», а еще AVR Studio поддерживает CИшную нотацию комментариев, где коменты ограничены «колючей проволокой» «/* коммент */».

Оператор .include позволяет подключать в тело твоей программы кусок кода из другого текстового файла. Что позволяет разбить большой исходник на кучу мелких, чтобы не загромождать и не мотать туда сюда огромную портянку кода. Считай, куда ты воткнул .include, туда и вставился кусок кода из другого файла. Если надо подключать не весь файл, а только его часть, то тебе поможет директива .exit, дойдя до которой компилятор выйдет из файла.

Оператор .def позволяет привязать к любому слову любое значение из ресурсов контроллера — порт или регистр. Например, сделал я счетчик, а считаемое значение находится в регистре R0, а в качестве регистра-помойки для промежуточных данных я заюзал R16. Чтобы не запутаться и помнить, что в каком регистре у меня задумано, я присваиваю им через .def символические имена.

  .def schetchik = R0
  .def pomoika = R16

И теперь в коде могу смело использовать вместо официального имени R0 неофицальную кличку schetchik.
Одному и тому же регистру можно давать кучу имен одновременно и на все он будет честно откликаться.

Также есть оператор .undef, после которого компилятор напрочь забывает, что данной переменной что-либо соответствовало. Иногда бывает удобно. Когда одно и то же символическое имя хочется присвоить разным ресурсам.

  .undef pomoika

Оператор .equ - это присвоение выражения или константы какой-либо символической метке.
Например, у меня есть константа, которая часто используется. Можно, конечно, каждый раз писать ее в коде, но вдруг окажется, что константа выбрана неверно, а значит придется весь код шерстить и везде править, а если где-нибудь забудешь, то получишь такую махровую багу, что задолбаешься потом ее вылавливать. Так что нафиг, все константы писать надо через .equ! Кроме того, можно же присвоить не константу, а целое выражение. Которое при компиляции посчитается препроцессором, а в код пойдет уже исходное значение. Надо только учитывать, что деление тут исключительно целочисленное. С отбрасыванием дробной части, без какого-либо округления, а значит 1/2 = 0, а 5/2 = 2

  .equ Time = 5
  .equ Acсelerate = 4
  .equ Half_Speed = (Accelerate*Time)/2

Директивы сегментации. Как я уже рассказывал в посте про архитектуру контроллера AVR, память контроллера разбита на независимые сегменты — данные (ОЗУ), код (FLASH), EEPROM

Чтобы указать компилятору, что где находится, применяют директивы сегментации и адресации.

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

В сегменте кода уместны директивы:
Адресная метка. Любое слово, не содержащее пробелов и не начинающееся с цифры, главное, чтобы после него стояло двоеточие.

    .CSEG
label:
  LDI  R16,'A'
  RJMP label

В итоге, после компиляции вместо label в код подставится адрес команды, перед которой стоит эта самая метка, в данном случае адрес команды LDI R16,’A’
Адресными метками можно адресовать не только код, но и данные, записанные в любом сегменте памяти. Об этом чуть ниже.

.ORG address - означает примерно следующее: «копать отсюда и до обеда», т.е. до конца памяти. Данный оператор указывает, с какого адреса пойдет собственно программа. Обычно используется для создания таблицы прерываний.

  .CSEG
  .ORG 0x0000
  RJMP Start      ;перепрыгиваем таблицу векторов.

  .ORG INT0addr   ; External Interrupt0 Vector Address
  RJMP INT0_expection

  .ORG INT1addr   ; External Interrupt1 Vector Address
  RETI

  .ORG OC2addr    ; Output Compare2 Interrupt Vector Address
  RJMP PWM_1

  .ORG OVF2addr   ; Overflow2 Interrupt Vector Address
  RETI

  .ORG ICP1addr   ;Input Capture1 Interrupt Vector Address
  RETI

  .ORG 0х0032     ; Начало основной программы

Start:
  LDI R16,0x54    ; и понеслась...

Статичные данные пихаются в флеш посредством операторов:

.db массив байтов.
.dw массив слов — 2 байта.
.dd массив двойных слов — 4 байта
.dq массив четверных слов — 8 байт.

  Constant: .db 10   ; или 0хAh в шестнадцатеричном коде
  Message: .db "Привет лунатикам"
  Words: .dw 10, 11, 12

В итоге, во флеше вначале будет лежать число 0А, затем побайтно будут хекс-коды символов фразы «привет лунатикам», а дальше 000A, 000B, 000С.
Последние числа, хоть сами и невелики, но занимают по два байта каждое, так как обьявлены как .dw.

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

Тут действует оператор .BYTE, позволяющий указать на расположение данных в памяти.

  var1: .BYTE 1
  table: .BYTE 10

В первом случае мы указали переменную var1, состоящую из одного байта.
Во втором случае у нас есть цепочка из 10 байт и переменная table, указывающая на первый байт из цепочки. Адрес остальных вычисляется смещением.
Указывать размеры перменных нужно для того, чтобы компилятор их правильно адресовал и они не налезали друг на друга.

.EESEG - сегмент EEPROM, энергонезависимая память. Можно писать, можно считывать, а при пропаже питания данные не повреждаются.
Тут действуют те же директивы, что и в flash — db, dw, dd, dq.

MACRO — оператор макроподстановки. Вот уж реально чумовая вещь. Позволяет присваивать имена целым кускам кода, мало того, еще параметры задавать можно.

.MACRO SUBI16      ; Start macro definition
  subi @1,low(@0)  ; Subtract low byte
  sbci @2,high(@0) ; Subtract high byte
.ENDM              ; End macro definition

@0, @1, @2 - это параметры макроса, они нумеруются тупо по порядку. А при вызове подставляются в код.

Вызов выглядит как обычная команда:

  SUBI16 0x1234,r16,r17  ; SUBI16 @0,@1,@2

После имени через запятую передаются параметры, которые подставятся в код.
Макросы позволяют насоздавать себе удобных команд на все случаи жизни, по сути создать свой язык. Но надо помнить, что каждый макрос - это тупо кусок кода, поэтому если макрос получается большой, то его лучше оформить в виде процедуры или функции — будет резкая экономия места в памяти, но выполняться будет чуток медленней.

Макроассемблер - это мощнейшая штука. По ходу пьесы я буду вводить разные макросы и показывать примеры работы макроопределений.


AVR Studio. Ликбез Содержание Простейшая программа


ист-к: http://easyelectronics.ru/avr-uchebnyj-kurs-makroassembler.html