AVRASM: Библиотека базовых Макроопределений "MACROBASELIB.INC"

AVR
Библиотека «базовых Макроопределений», расширяющая стандартный набор ассемблерных инструкций микроконтроллеров Atmel AVR (8-bit AVR Instruction Set), и рекомендующая парадигму программирования: с хранением «модели прикладных данных» в ОЗУ и использованием нескольких «временных регистров»…


Назначение

Данная библиотека написана на языке ассемблера, для компилятора AVRASM. Соответственно, она предназначена для разработки программных прошивок (firmware) на языке ассемблер, для микроконтроллеров Atmel AVR (8-bit).

Рекомендую вам использовать данную библиотеку в каждой вашей программе на языке ассемблер (для прошивки для AVR), подключая её в самом начале главного ASM-файла, следующим образом:
.include "macrobaselib.inc"	; Библиотека базовых Макроопределений.
Это сделает мета-язык доступным глобально во всей программе. А также, упростит шаблон нового проекта, поскольку данная библиотека содержит также макросы с кодом базовой инициализации микроконтроллера.

Библиотека составлена из наиболее полезных и компактных кусочков кода, взятых из примеров и статей «Сообщества EasyElectronics.ru»; с других сайтов; из datasheet микроконтроллеров и Atmel Appnotes… собранных и переработанных мною в единый стиль; дописанных до функциональной полноты.

Код библиотеки сейчас размещается в одном файле «macrobaselib.inc» и разделён по функциональным разделам (Инициализация МК, Поддержка Ввода-Вывода, Арифметические операции).

Для улучшения читабельности, код отформатирован в едином стиле (инспирированном «Atmel appnotes» и авторским чувством целесообразности). На каждый макрос имеются комментарии (на диком суржике из русско-английского): по Назначению, Параметрам и Побочным Эффектам (какие регистры задействованы, и как изменяются в процессе).

Идеология

Основные положения:
В коде данной библиотеки, и при работе с ней, регистры R16,R17,R18,R19 рекомендуется использовать как «временные переменные» — поэтому, в вашем прикладном коде, не используйте их для хранения постоянных данных!
Как сказал DI HALT:
При вычислениях, регистры можно предварительно заталкивать в стек… но я дам тебе лучше другой совет: когда пишешь программу, продумывай алгоритм так, чтобы использовать регистры как сплошной TEMP, данные которого актуальны только здесь и сейчас. И что с ними будет в следующей процедуре — уже не важно. А все перманентные данные — следует сохранять в оперативке (SRAM).
В основу данной библиотеки положены идеи и опыт DI HALTа к методу использования Макроассемблера, и в частности директивы MACRO: для создания своего мета-языка, расширяющего стандартный набор директив ассемблера AVR, и базовых приёмов, облегчающих программирование:
MACRO — оператор макроподстановки. Вот уж реально чумовая вещь! Позволяет присваивать имена целым кускам кода, мало того, еще параметры задавать можно.
Макросы позволяют насоздавать себе удобных команд на все случаи жизни, по сути создать свой язык. Макроассемблер это мощнейшая штука. По ходу пьесы я буду вводить разные макросы и показывать примеры работы макроопределений...
Ассемблер AVR, хоть и довольно развит, но как и любой ассемблер — очень низкоуровневый, а потому, с ним, довольно сложно «выстрелить себе в ногу». Данная библиотека предоставляет вам некоторые детали для изобретения «пистолета» (в частности: «пулю» и «курок»). Теперь, вам остаётся лишь доизобрести «пистолет», и «вашу ногу»…

Код

Код библиотеки «macrobaselib.inc» опубликован на GitHub (это веб-сервис для хостинга IT-проектов и их совместной разработки), на условиях лицензии MIT (разрешительной opensource, т.е. практически без ограничений к использованию). Ответвляйтесь!

Обратите также внимание на «Шаблон нового проекта» — для старта разработки программной прошивки (firmware) на языке ассемблер, в среде «AVR Studio 4», для микроконтроллеров Atmel AVR. Этот код, также, опубликован на GitHub, на условиях лицензии MIT… Вы можете использоваться этот Шаблон для новых проектов прошивок. Код основан на идеях и рекомендациях DI HALT, создан и проверен «в бою»: при работе над реальным проектом. Это чистый шаблон, без прикладного кода. Код Шаблона также содержит единый стиль форматирования, и комментарии с рекомендациями и описанием секций кода...


Примечание: GitHub был выбран для распространения кода — как наиболее прогрессивный, удобный и функциональный метод взаимодействия opensource-разработчиков. Развивайте и дополняйте библиотеку — затем, сможете легко контрибутить...

Ликбез для неискушённых пользователей: те кто не используют системы управления версиями, могут просто скачать архив с кодом: нажав на кнопку «Download ZIP» на странице репозитория GitHub, по ссылкам выше.

  • +3
  • 05 декабря 2013, 06:59
  • Celeron

Комментарии (49)

RSS свернуть / развернуть
что уж макрос для собачьего таймера не допилил для всех камней?:) с мегой 48, например, он работать не будет.

ну и в целом — раз уж есть универсальные макросы outi, outr и inr, то зачем везде использовать in/out?
0
не допилил для всех камней
Для всех камней — пока не стояла задача, да и трудоёмко очень переворачивать все datasheet-ы. (Это моя первая серьёзная программа на ассемблере. И вообще, первая прошивка для микроконтроллера. Не сдюжил полную универсальность.)

зачем везде использовать in/out?
В пределах этой библиотеки (как программнорй прослойке) старался не использовать вложенных макросов — для прозрачности кода и упрощения его поддержки. Ведь, при вложенных макросах, даже и перекомпоновать порядок разделов будет уже нельзя, просто так…
0
Эх ассемблер… классная вещь. Сам когда то года три на нем писал прошивки для Avr. Сейчас уже пересел на СИ. Иногда хочется тряхнуть стариной и снова что нибудь сделать на асме. Но никак не могу. Как то в лом чуток. Видя что на СИ можно любое решение сделать в разы быстрее, как то на асм сложно обратно вернуться и что то делать. Хотя в свое время тоже накопил на асме много разных полезных подпрограмм. Да и прошивки сейчас пишу под Atmega1280. У нее 128 кило флеша. Тут только СИ рулит. На асме такие объемы писать, это чекнуться можно. По опыту асм еще рулит до скажем 16 кило флеша. Потом прошивки превращаются в многокилометровые портянки кода, в которых если надо что то подправить, приходится убить кучу времени, чтоб вспомнить логику кода. Да, все таки асм это круто… это вещь… но СИ рулит.
+2
Я, после 15 лет асма, пересел на С. Потому что быстрее и удобнее и т.д. и т.п. Но принять тезис про 100500 памяти, которую сложно заполнить асмом, я не могу. Особенно от того кто долго писал на асме.
Кто заставляет заполнять всю память? Зачем? Редкие задачи требуют в асме более 8к кода + 100500 данные(если есть).
А вернуться на асм сложно, согласен. Но мелкие задачки (на тиньках) по прежнему с удовольствием решаю на асме.
0
вобщем, и 8К флеша отлично годятся для Си. Даже для RTOS даже для аллах.
0
Ассемблер потому и ассемблер, позволяет очень гибко и наибольшей эффективностью использовать железо. Здесь же:
«В коде данной библиотеки, и при работе с ней, регистры R16,R17,R18,R19 рекомендуется использовать как «временные переменные» — поэтому, в вашем прикладном коде, не используйте их для хранения постоянных данных!».
Обычно я адаптирую макросы под программу, а не наоборот. Но использую их постоянно.
0
Регистры можно переопределить. В программном коде, по всей программе, используются только псевдонимы: temp, temp1,temp2,temp3,temp4… Главное, переопределить их одинаково, во всех модулях программы.
0
.if @0 < 0x40, а не 0х60?
0
Manual говорит, для инструкций:
IN Rd,A        0 ≤ d ≤ 31, 0 ≤ A ≤ 63
OUT A,Rr       0 ≤ r ≤ 31, 0 ≤ A ≤ 63
где адрес 63=0x3F <0x40

Нагляднее, я разобрал пределы адресации, для всех инструкций, в своей «Шпаргалке по стандартным инструкциям ассемблера AVRASM».
0
Т.е DS ВЫ не читали. И такой раздел как MEMORY MAP Вам не знаком. Кстати он стандартен для всех 8-бит AVR. И Вам видимо не ведомо, что область которую охватывают команды IN & OUT простирается значительно дальше чем 0Х40. Уберите все и не позорьтесь!
0
Уберите все и не позорьтесь!

а чего у него не так? ты хоть глянь ради интереса файлы def.inc:

.equ PRR0 = 0x64; MEMORY MAPPED
.equ CLKPR = 0x61; MEMORY MAPPED
.equ WDTCSR = 0x60; MEMORY MAPPED
.equ SREG = 0x3f
.equ SPL = 0x3d
.equ SPH = 0x3e
.equ EIND = 0x3c

всё, что перед memory mapped, заканчивается на 0x3F. а из 0х60, что под «младшие» регистры выделены, 0х20 занимают РОН. отсюда и 0х40. как-то странно ты DS читаешь:)

область которую охватывают команды IN & OUT простирается значительно дальше чем 0Х40

это куда она простирается? или в ассемблер-хелпе нагло врут?:

IN Rd,A 0 ≤ d ≤ 31, 0 ≤ A ≤ 63
0
Для совсем одаренных. Регистры 0-32 занимают область 0х00 — 0х1F, Область регистров ввода- вывода с 0х20-0x5F вот в этой области действуют команды IN OUT, хотя можно и к регистрам и к регистрам вода- вывода обращаться как к памяти SRAM т.е не использовать IN OUT.
Начтет файлов def.inc — каждый из этих файлов описывает только один проц., согласно распределению памяти из раздела DS Register summary.
По поводу макросов, было написано не мало статей и обсуждений. Интернет велик и всё что в этой статье лишь повторение давно известного.
0
Регистры 0-32 занимают область 0х00 — 0х1F

[согласно кивает]

Область регистров ввода- вывода с 0х20-0x5F

[опять согласно кивает]

вот в этой области действуют команды IN OUT

и чего это меняет? в команде in можно написАть адрес 0x49? или ты просто не понимаешь, зачем вообще нужна строка ".if @0 < 0x40"? если не понимаешь — так и скажи, тебе объяснят. если понимаешь — зачем тогда нести ахинею?

каждый из этих файлов описывает только один проц

ну и? да, только один проц. и для каждого проца область перед memory mapped заканчивается на 0x3F. уж не потому ли, что команды in/out позволяют работать только с адресами до 64?

всё что в этой статье лишь повторение давно известного

естественно. что-то действительно новое — это уникальные случаи. по-твоему, из-за этого не надо писАть статьи? чем больше материалу — тем проще его найти.
+1
".if @0 < 0x40"? если не понимаешь — так и скажи, тебе объяснят. если понимаешь — зачем тогда нести ахинею?
".if @0 < 0x60"? Далее читай твой текст

если совсем не добрёт, то на попробуй
;***************************************
;-
;***************************************
.macro inr
.if ( @1 < 0x60 )
in @0,@1
.else
lds @0,@1
.endif
.endm
;***************************************
;-
;***************************************
.macro outr
.if ( @0 < 0x60 )
out @0,@1
.else
sts @0,@1
.endif
.endm
;***************************************
;-
;***************************************
.macro outi
ldi CopySram,@1
.if ( @0 < 0x60 )
out @0,CopySram
.else
sts @0,CopySram
.endif
.endm
0
попробовал. закономерный результат — в скриншоте



то, что макрос никогда не глючил — не значит, что он правильный.
0
А теперь тоже самое без констант
inr R16,SREG
outr R18 ,R16
0
Кстати я взял Ваш пример изменил в своем макросе 60 на 40
Дизасм плиз

0000028A: 9100005F LDS R16,0x005F Load direct from data space
0
ну всё правильно пишет — при адресе, большем 0x3f, пользуемся оператором LDS
0
а при адресе меньшем 0x40 — пользуемся in, чтоб не отжирать флэш впустую
0
уважаемый, я прекрасно понимаю, что первая строчка пройдет без ошибок, тут даже пробовать нечего. более того, чтобы продемонстрировать границы действия команд in/out, я специально в программе написАл:

inr R16, 0x3F
outr 0x3F, R16,

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

.if ( @0 < 0x60 ),

которое работает с ошибками, а не

.if ( @0 < 0x40 ),

как у автора (да и не только у него — интернет действительно велик и материал статьи действительно не раз и не десять многие переписывали для себя заново)? это при том, что запись с 0х40 будет безотказно работать в 100% случаев?

что до второй строки — думаю, там всё встанет в месте сравнения с 0х60 — как может сам регистр R18 (сам по себе регистр, а не его значение) быть меньше 0х60? хотя тут могу и ошибаться — надо пробовать.

ну и главное — как это «без констант»? а что такое с точки зрения компилятора «SREG»? не константа? или ты даже этого не знаешь? я ж тебе не зря посоветовал в def.inc посмотреть — там сразу видно, что все «человеческие» названия аля timsk, portb и т.д. — не более чем константы, переназванные просто для удобства написания программ.
0
".if @0 < 0x60"? Далее читай твой текст

нет, именно 0х40. если 0х60 — смысл макроса пропадает.
0
Как то Вы забыли ответить на втору часть вопроса. Где с Вашим макросом отработал Ваше код. Куда же делся SREG?
Кстати R18 приведён для примера — можете ставить по своему усмотрению.
Пользуйтесь 0х40 пока не нарвётесь. Флаг в руки зведу на шею!
0
на втору часть вопроса

какого вопроса? про дизассемблер? ответил — если использовать 0х40, макрос при адресе большем 0х3F подставляет lds, а при меньшем 0х40 — in. т.е. работает правильно. это же и выше написано.

Где с Вашим макросом

с моим? я никаких макросов не приводил в пример.

нужен скриншот работы с sreg и r18? вот они:





всё согласуется с вышенаписанным — с SREG всё работает чудесно, т.к. срег равен 0х3f, а с r18 — херня какая-то получается:)
0
забыл в программе поменять 0х40 на 0х60. но в данном случае это абсолютно некритично.

правильные скриншоты:



0
Правильно, что херня. R18 не в области ввода вывода — не подчиняется OUT — моя ошибка. Подставьте тот же SREG
ну и главное — как это «без констант»? а что такое с точки зрения компилятора «SREG»? не константа? я ж тебе не зря посоветовал в def.inc посмотреть — там сразу видно, что все «человеческие» названия аля timsk, portb и т.д. — не более чем константы, переназванные просто для удобства написания программ.
и исходя из Вашей логики, что макрос подставит вместо SREG — константу
и все работает.
0
Во-общем бесполезный спор.
0
да как сказать. для тебя, может, и бесполезный, а кто-то, может какие-то правильные мысли почерпнет.
0
не макрос подставит, а компилятор. макрос же просто посмотрит на SREG (которая с его точки зрения, естественно, тоже константа) и подставит in, а не lds, поскольку SREG = 0x3F < 0x40.

суть этого макроса — например, не переписывать по сто раз программы, заточенные под «старые» камни (например, мега8) при переходе на «новые» (та же мега48). по пинам они совпадают, а вот по расположению РВВ — нет (не для всех РВВ, но есть такие). к примеру, строка

out OCR1AL, R16

прекрасно откомпилируется для меги8, поскольку для нее OCR1AL = 0x2a (меньше пресловутых 0х40), но дурканет при компиляции на меге48, т.к. там OCR1AL = 0x88 (больше 0х40) и надо использовать sts. вот чтобы постоянно не исправлять старые проекты, и вводят этот макрос.

по умолчанию и твой макрос будет работать нормально. но это только из-за того, что у АВР по адресам 0х40-0х5F нет никаких РВВ. и если атмелу взбредет в башку засунуть туда какой-нибудь регистр — твой макрос выдаст ошибку, а макрос автора подставит lds и компиляция продолжится дальше. я ж говорю — если макрос никогда не глючил — это не значит, что он правильный.
0
но это только из-за того, что у АВР по адресам 0х40-0х5F нет никаких РВВ. и если атмелу взбредет в башку засунуть туда какой-нибудь регистр
Ну, на самом деле, их там и быть не может — т.к., например, 0x5F — это тот же SREG, только в RAM-пространстве. Но если зачем-то подсунуть макросу RAM-адрес регистров из диапазона 0x20-0x3F (которые и отображаются в RAM на адреса 0x40-0x5F) — то вариант с @0<0x60 таки глюканет (а на RAM-адресах регистров R0-R31 и 0x00-0x1F любой вариант макроса выберет не ту команду, хотя все скомпилируется).
0
блин, опередил:)

Ну, на самом деле, их там и быть не может

да это понятно:) всё, что после «и если атмелу...» — полнейшая херня, написанная от безысходности. думал, может хоть так получится направить мысль в нужное русло — типа, а! я тебя подловил!:) ведь адреса RAM от 0х40 до 0х5ф даже дебаггер Студии не показывает. ну и может мысли нужные в голову придут:)
0
то вариант с @0<0x60 таки глюканет

ну, он и не может не глюкануть: In/out работают только до адреса 64.

думаю, с такой путанией атмеловцы решили смириться в пользу того, что добавляется команда, отжирающая меньше флэша (in/out взамен lds/sts)
0
Пользуйтесь 0х40 пока не нарвётесь

на что?:)
0
ДА ПРИЗНАЮ ЧТО 0х40 ПРАВИЛЬНО!!!. Я сам кое-то проверил. Извиняюсь перед автором статьи!!!
0
а говоришь — бесполезный:) вот если бы я такое прочитал лет 5-6 назад — не ломал бы башку полдня, почему все пишут 0х40, когда я, такой умный, знаю, что 0х60. таки да, даташыт несколько путает в этом плане.
0
У меня за ~ 10 лет с моими макросами ни одного сбоя. А тут специально в рабочем девайсе вызвал сбой.
0
так и должно быть:

по умолчанию и твой макрос будет работать нормально. но это только из-за того, что у АВР по адресам 0х40-0х5F нет никаких РВВ
0
т.е., с практической точки зрения и твой макрос, и макрос автора, в принципе, равнозначны (в принципе). но говорить, что правильно 0х60, а не 0х40 — это категорически неверно!:)
0
Я заглянул в самый древний проект, там 40 при этом сам же в комментах писал
Компилятор интерпретирует в IN OUT, если получатель записан как адрес ввода-вывода, и STS LDS, если указан адрес регистра в SRAM т.е +20
0
камменты из старых прожэктов убирать — себе дороже:) сам сталкивался одно время с таким — на хер эти комментарии, и так всё понятно. однако, довольно быстро понял, что понятно тогда, когда постоянно одни и те же камменты пишешь:) забывается всё очень быстро. и когда смотришь на «голый» код, написанный пару лет назад — обычно мысли такие: «еб@ть-колотить, как же оно работает-то?»:)

в один прекрасный момент пересилил себя и решил писАть комментарии даже для очевидных моментов — чтоб не расслабляться. в итоге мой макрос, который тут обсуждается, выглядит так:

;===========================================================================================
;
; Универсальный макрос загрузки содержимого какой-либо ячейки ОЗУ в любой регистр общего
; назначения. В качестве источника может выступать любой регистр ввода-вывода (РВВ) (как из
; стандартной, так и из расширенной области регистров ввода-вывода), переменная в ОЗУ или
; непосредственно адрес ячейки ОЗУ.
;
; В частности, при использовании данного макроса отпадает необходимость постоянно исправлять
; in на lds при переносе шаблонов подпрограмм со "старых" камней (Mega8, Mega16, ...),
; имеющих схему РОН+РВВ=32+64, на "новые" (MegaX8, MegaX4, ...), имеющих схему
; РОН+РВВ=32+64+160.
;
; Формат вызова:			_IN		@0,		@1
;
; @0 - РОН, в который переносится содержимое ячейки-источника,		@0 = R0...R31;
; @1 - адрес или символическое имя ячейки-источника в ОЗУ		,	@1 = 0...65535.
;
; Пример использования:		_IN		R0,		TIFR			; Считать в регистр R0 содержи-
;															; мое регистра флагов прерываний
;															; от таймеров/счетчиков
;
;							_IN		R16,	VAR0			; Считать в регистр R16 значение
;															; переменной VAR0 в ОЗУ
;
.macro						_IN								; Имя макроса
	;---------------------------------------------------------------------------------------
	_I_LOAD_VALUE_STS:										; Загрузка в РОН содержимого РВВ
							.if		@1	>	0x3F			; Если адрес РВВ больше 63
							lds		@0,		@1				; (0x3F), используем LDS
	;---------------------------------------------------------------------------------------
	_I_LOAD_VALUE_OUT:										; Если адрес РВВ меньше 64, то
							.else							; экономим 2 байта памяти, заг-
							in		@0,		@1				; ружая содержимое РВВ при помо-
															; щи IN
	;---------------------------------------------------------------------------------------
							.endif
	;---------------------------------------------------------------------------------------
.endmacro													; Конец макроса


так писАть, сцуко, очень долго:) но зато даже через 10 лет при первом же взгляде на макрос понятно, что к чему:)
0
Ну дык, ассемблер такой ассемблер. Я однажды столкнулся с тем, что без комментариев код даже не работает) Пока комментировал собой же написанную простыню — выловил с пяток ошибок, которые в асме аналитическим туплением вообще не видно, в отличие от ЯВУ. После этого заработало)
0
[поднимает указательный палец]

вот!!!

зы. как-то даже странно, что данный топик не превратился в срач «Си vs асм»:) я даже ужЕ волноваться начал:)
0
да… как-то хреново TAB'ы вставились:)
0
Инициализация РВВ:

Запись — Старший байт, младший байт.
Чтение — Младший байт, старший байт.

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

.macro INIT_STACK
.ifdef SPH
outi SPH,HIGH(RAMEND)
.endif
outi SPL,LOW(RAMEND)
.endmacro

.macro Clear_Registers
clr ZH
ldi ZL, 30
Clear_Register_Cycle:
dec ZL
st Z, ZH
brne Clear_Register_Cycle
.endmacro

.macro Clear_SRAM
ldx SRAM_START
clr r16
Clear_SRAM_Cycle:
st X+, r16
cpi XL, LOW(SRAM_START+SRAM_SIZE)
brne Clear_SRAM_Cycle
cpi XH, HIGH(SRAM_START+SRAM_SIZE)
brne Clear_SRAM_Cycle
.endmacro
0
меня вот давно интересует — а зачем регистры обнулять? интерес чисто академический, ибо сам я никогда не сталкивался с такой необходимостью. про ОЗУ — да, бывало, что мешал оставшийся хлам.
0
Я думаю, что если используется математика в проекте. И какой-то регистр получает значение отличное от 0. А при мягком рестарте, с выбором инициализации, может сохранить значение и посчитать не тот результат.
0
а зачем регистры обнулять? интерес чисто академический, ибо сам я никогда не сталкивался с такой необходимостью.
я вот столкнулся, когда у меня прерывание использует значение регистра, который еще не был инициализирован. Т.е. в части общей инициализации М.К. разрешаем некоторое прерывание, в обработчике которого используется значение энного регистра, обнуляемого (или «заряжаемого» стартовым значением, как было у меня) где-то после разрешения этого прерывания. А оно (событие -> прерывание) и имело место, при этом читая хлам из регистра. Пример кода привести не могу, уже давно исправлен. Надеюсь понятно изложил :)
Я после того завел себе правилом всякие прерывания разрешать лишь в конце общей инициализации.
0
Надеюсь понятно изложил

изложил понятно:) но напомнило:

— Простите, я Вас правильно понял?
— Не знаю.
0
Нашел в вашей библиотеке (ver.1.0 beta) в макросе SETBM ошибку:
.MACRO        SETBM
        .if        @0 < 0x20                ; Low IO
                SBI        @0,        @1
        .elif        @0 < 0x40                ; High IO
                PUSH        R17
                IN        R17,        @1         ;<---- ЗДЕСЬ !
                ORI        R17,        1<<@1
                OUT        @0,        R17
                POP        R17
        .else                                ; Memory
                PUSH        R17
                LDS        R17,        @1         ;<---- И ЗДЕСЬ !
                ORI        R17,        1<<@1
                STS        @0,        R17
                POP        R17
        .endif
        .ENDM
Может вы уже ее исправили, я тут не нашел об этом.
0
Благодарю, ценно! Я эту группу «регистро-безопасных» макросов не использовал — потому недотестировал и проглядел…
Исправлено: <macrobaselib.inc> (ver.1.0.3 beta)
0
Автор топика запретил добавлять комментарии