русс | укр

Мови програмуванняВідео уроки php mysqlПаскальСіАсемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование


Linux Unix Алгоритмічні мови Архітектура мікроконтролерів Введення в розробку розподілених інформаційних систем Дискретна математика Інформаційне обслуговування користувачів Інформація та моделювання в управлінні виробництвом Комп'ютерна графіка Лекції


Флеш память


Дата додавання: 2014-11-27; переглядів: 865.


МЕТОДИЧНІ ВКАЗІВКИ

Укладачі:

Вінничук Ігор Станіславович

Зюков.Сергій Володимирович

Відповідальний за випуск Григорків В.С.

Літературний редактор Лупул О.В.

Друкарня видавництва Чернівецького національного університету

 

58012, Чернівці, вул. Коцюбинського, 2

Флеш память

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

Записать то мы запишем, а как достать? Для этого сначала надо туда что-либо положить.
Поэтому добавляй в конце программы, в пределах сегмента .CSEG метку, например, data и после нее, используя оператор .db, вписывай свои данные.

Оператор DB означает что мы на каждую константу используем по байту. Есть еще операторы задающий двубайтные константы DW (а также DD и DQ).

data: .db 12,34,45,23

Теперь, метка data указывает на адрес первого байта массива, остальные байты находятся смещением, просто добавляя к адресу единичку.

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

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

Для загрузки данных из памяти программ используется команда из группы Load Program Memory

Например, LPM Rn,Z

Она заносит в регистр Rn число из ячейки на которую указывает регистровая пара Z. Напомню, что Z это два регистра, R30 (ZL) и R31 (ZH). В R30 заносится младший байт адреса, а в R31 старший.

В коде выглядит это так:

LDI ZL,low(data*2) ; заносим младший байт адреса, в регистровую пару Z LDI ZH,high(data*2) ; заносим старший байт адреса, в регистровую пару Z ; умножение на два тут из-за того, что адрес указан в ; в двубайтных словах, а нам надо в байтах. ; Поэтому и умножаем на два ; После загрузки адреса можно загружать число из памяти   LPM R16, Z ; в регистре R16 после этой команды будет число 12, ; взятое из памяти программ.     ; где то в конце программы, но в сегменте .CSEG data: .db 12,34,45,23

 


 

AVR. Учебный курс. Подпрограммы и прерывания

Автор DI HALT
Опубликовано 07 июля 2008
Рубрики: AVR. Учебный курс
Метки: Assembler, AVR, Программирование

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

Вот, например, кусок кода, передающий в регистр UDR байты с некоторой выдержкой, выдержка делается за счет вращения бесконечного цикла:

.CSEG LDI R16,Low(RAMEND) ; Инициализация стека OUT SPL,R16 ; Обязательно!!!   LDI R16,High(RAMEND) OUT SPH,R16   .equ Byte = 50 .equ Delay = 20   LDI R16,Byte ; Загрузили значение Start: OUT UDR,R16 ; Выдали его в порт   LDI R17,Delay ; Загрузили длительность задержки M1: DEC R17 ; Уменьшили на 1 NOP ; Пустая операция BRNE M1 ; Длительность не равна 0? Переход если не 0   OUT UDR,R16 ; Выдали значение в порт   LDI R17,Delay ; Аналогично M2: DEC R17 NOP BRNE M2   OUT UDR,R16   LDI R17,Delay M3: DEC R17 NOP BRNE M3   RJMP Start ; Зациклим программу

Сразу напрашивается повторяющийся участок кода вынести за скобки.

LDI R17,Delay M2: DEC R17 NOP BRNE M2

Для этих целей есть группа команд перехода к подпрограмме CALL (ICALL, RCALL, CALL)
И команда возврата из подпрограммы RET

В результате получается такой код:

.CSEG LDI R16,Low(RAMEND) ; Инициализация стека OUT SPL,R16 ; Обязательно!!!   LDI R16,High(RAMEND) OUT SPH,R16   .equ Byte = 50 .equ Delay = 20   LDI R16,Byte ; Загрузили значение Start: OUT UDR,R16 ; Выдали его в порт   RCALL Wait   OUT UDR,R16 RCALL Wait OUT UDR,R16 RCALL Wait OUT UDR,R16 RCALL Wait RJMP Start ; Зациклим программу.     Wait: LDI R17,Delay M1: DEC R17 NOP BRNE M1 RET

Как видишь, программа резко сократилась в размерах. Теперь скопируй это в студию, скомпилируй и запусти на трассировку. Я хочу показать как работает команда RCALL и RET и при чем тут стек.

Вначале программа, как обычно, инициализирует стек. Потом загружает наши данные в регистры R16 и выдает первый байт в UDR… А потом по команде RCALL перейдет по адресу который мы присвоили нашей процедуре, поставив метку Wait в ее начале. Это понятно и логично, гораздо интересней то, что произойдет в этот момент со стеком.

До выполнения RCALL

Увеличить
Адрес команды RCALL в памяти, по данным PC = 0×000006, адрес следующей команды (OUT UDR,R16), очевидно, будет 0×000007. Указатель стека SP = 0×045F - конец памяти, где ему и положено быть в этот момент.

После RCALL

Увеличить
Смотри, в стек пихнулось число 0×000007, указатель сместился на два байта и стал 0×045D, а контроллер сделал прыжок на адрес Wait.

Наша процедура спокойно выполняется, как ей и положено, а по команде RET процессор достанет из стека наш заныченный адрес 0×000007 и прыгнет сразу же на команду OUT UDR,R16

Таким образом, где бы мы не вызвали нашу процедуру Wait - мы всегда вернемся к тому же месту откуда вызвали, точнее на шаг вперед. Так как при переходах в стеке сохраняется адрес возврата. А если испортить стек? Взять и засунуть туда еще что нибудь? Подправь процедуру Wait и добавь туда немного бреда, например, такого

Wait: LDI R17,Delay M1: DEC R17 NOP BRNE M1   PUSH R17 ; Ой, я не специально!   RET

Перекомпиль и посмотри что будет =) Заметь, компилятор тебе даже слова не скажет. Мол все путем, дерзай :)

До команды PUSH R17 в стеке будет адрес возврата 00 07, так как в регистре R17 ,в данный момент, ноль, и этот ноль попадет в стек, то там будет уже 00 00 07.

А потом идет команда RET… Она глупая, ей все равно! RET тупо возьмет два первых верхних байта из стека и запихает их в Programm Counter.

И куда мы перейдем? Правильно — по адресу 00 00, в самое начало проги, а не туда откуда мы ушли по RCALL. А будь в R17 не 00, а что нибудь другое и попади это что-то в стек, то мы бы перешли вообще черт знает куда с непредсказуемыми последствиями. Это и называется срыв стека.

Но это не значит, что в подпрограммах нельзя пользоваться стеком в своих грязных целях. Можно!!! Но делать это надо с умом. Класть туда данные и доставать их перед выходом. Следуя железному правилу “Сколько положил в стек - столько и достань!”, чтобы на выходе из процедуры для команды RET лежал адрес возврата, а не черти что.

Мозговзрывной кодинг
Да, а еще тут возможны стековые извраты. Кто сказал, что мы должны вернуться именно туда откуда были вызываны? =))) А если условия изменились и по итогам вычислений в процедуре нам ВНЕЗАПНО туда стало не надо? Никто не запрещает тебе нужным образом подправить данные в стеке, а потом сделать RET и процессор, как миленький, забросит тебя туда куда надо. Легко!

Более того, я когда учился в универе и сдавал лабы по ассемблеру, то лихо взрывал мозги нашему преподу такими конструкциями (там, правда, был 8080, но разница не велика, привожу пример для AVR):

LDI R17,low(M1) PUSH R17 LDI R17,High(M1) PUSH R17   ; потом дофига дофига другого кода... для отвлечения ; внимания, а затем, в нужном месте, ВНЕЗАПНО   RET

И происходил переход на метку M1, своего рода извратский аналог RJMP M1. А точнее IJMP, только вместо Z пары мы используем данные адреса загруженные в стек из любого другого регистра, иногда пригождается. Но без особой нужды таким извратом заниматься не рекомендую — запутывает программу будь здоров.

Но побалуйся обязательно, чтобы во всей красе прочувствовать стековые переходы.

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

Иногда подпрограммы ошибочно называют функциями. Отличие подпрограммы от функции в том, что функция всегда имеет какое то значение на входе и выдает ответ на выходе, как в математике. Ассемблерная подпрограмма же не имеет таких механизмов и их приходится изобретать самому. Например, передавая в РОН или в ячейках ОЗУ.

Подпрограммы vs Макросы
Но не стоит маникально все повторяющиеся участки заворачивать в подпрограммы. Дело в том, что переход и возврат добавляют две команды, а еще у нас идет прогрузка стека на 2 байта. Что тоже не есть гуд. И если заменяется три-четыре команды, то овчинка с CALL-RET не стоит выделки и лучше запихать все в макрос.


<== попередня лекція | наступна лекція ==>
Створення та знищення папок | Рекурсія – це такий спосіб організації обчислювального процесу, при якому підпрограма в ході виконання звертається сама до себе.


Онлайн система числення Калькулятор онлайн звичайний Науковий калькулятор онлайн