Assembler для arm сканування бітів. Школа асемблера: мова асемблера для центральних процесорів архітектури ARM. Інші інструкції процесора ARM

GBA ASM - День 2: Небагато інформації про асемблера ARM - Архів WASM.RU

ARM – це компанія, яка робить процесор GBA. Процесори ARM є RISC-процесорами (на відміну процесорів INTEL). RISC розшифровується як Reduced Instruction Set Computers (CISC – Complex...). Хоча в цих процесорах не так багато інструкцій (що добре), інструкції ARM (а може й інших RISC-процесорів, я не знаю) мають багато різних призначень та комбінацій, що робить RISC-процесорами такими могутніми, якими вони є.

Реєстри

Я не знаю про інші процесори ARM, але у того, що використовується в GBA, 16 регістрів і на відміну від процесорів Intel (та інших), всі регістри можна спокійно використовувати (як правило). Реєстри такі:

r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15

Вау! Багато! Поясню по-порядку.

ro: Робіть що хочете!

r2 to r12: те саме

r13: На деяких ARM-системах r13 - це покажчик на стек (SP у процесорах INTEL). Я не впевнений, чи відіграє r13 ту ж роль у GBA, я можу тільки попередити, щоб ви були обережні з ним, коли працюєте зі стеком.

r14: Містить адресу повернення для процедур, що викликаються. Якщо ви їх не використовуєте, можете робити з ним все, що хочете.

r15: Program Counter і прапори, як і IP (Instruction Pointer в Intel). Він відрізняється від Intelівського регістру IP тим, що у вас є вільний доступ до нього, так само як і до будь-якого іншого регістру, але врахуйте, що його зміна призведе до того, що управління буде передано на іншу ділянку коду, а прапори зміняться .

Давайте проробимо невелику математичну вправу... 16 мінус 3 (зазвичай) дає нам 13 регістрів. Чи не так, це круто? Заспокойтесь.

Зараз ви можете запитати, чим насправді є регістри. Регістри – це спеціальні ділянки пам'яті, які є частиною процесора і не мають дійсної адреси, а відомі лише за своїми іменами. Регістри 32-х бітні. Майже все в будь-якій асемблерній мові використовує регістри, тому ви повинні знати їх так само добре, як своїх родичів.

Інструкції асемблера ARM

По-перше, я хочу почати з того, що на мою думку той, хто вигадав асемблер ARM, є генієм.

По-друге, я хочу уявити мого хорошого друга CMP. Скажіть йому привіт, і, можливо, тільки можливо, він стане і вашим другом. CMP розшифровується як CoMPare (порівняти). Ця інструкція може порівняти регістр та число, регістр та регістр або регістр та комірку пам'яті. Потім після порівняння CMP встановлює прапори статусу, які повідомляють вам результат порівняння. Як ви можете згадати, регістр r15 містить прапори. Вони повідомляють про результат порівняння. Інструкція CMP була спеціально створена для встановлення значення цих прапорів і більше.

Прапори можуть містити такі стани:

    EQ EQual / Рівне

    NE Not Equal / Не байдуже

    VS oVerflow Set / Встановлення переповнення

    VC oVerflow Clear / Очищення переповнення

    HI HIgher / Вище

    LS Lower or the Same / Нижче або те саме

    PL PLus / Плюс

    MI MINus / Мінус

    CS Carry Set / Встановлення перенесення

    CC Carry Clear / Очищення перенесення

    GE Greater than or Equal / Більше чи одно

    GT Greater Than / Більше

    LE Less than or Equal / Менше чи одно

    LT Less Than / Менше

    Z is Zero / Нуль

    NZ is not Zero / Не нуль

Ці стани відіграють важливу роль в асемблері ARM.

ПРИМІТКА: прапори лише зберігають умови (Рівне, Менше тощо). Більше вони нічим не важливі.

Суфікси умов

Ви вже побачили інструкцію B (Branch). Інструкція B робить те, що називається безумовним переходом (як GoTo у Basic або JMP в асемблері INTEL). Але вона може мати суфікс (один з перерахованих вище), тоді вона перевіряє, чи відповідає йому стан прапорів. Якщо ні, інструкція переходу просто не виконується. Тому, якщо ви хочете перевірити, чи регістр r0 дорівнює регістру r4, а потім перейти до мітки під назвою label34, то вам необхідно написати наступний код:

    CMP r0, r4; Коментарі в асемблері йдуть після крапки з комою (;)

    BEQ label34; B – інструкція переходу, а EQ – суфікс, що означає

    ; "Якщо Рівно"

ПРИМІТКА: У Goldroad Assembler мітки не потрібно супроводжувати (і крім імені мітки в рядку нічого не повинно бути.

ПРИМІТКА II: Писати CMP і BEQ великими літерами не обов'язково, це просто для того, щоб вам було зрозуміліше.

Тепер ви знаєте, як робити перехід в залежності від стану прапорів, але що ви не знаєте, так це те, що ви можете робити в залежності від стану прапорів все, що завгодно, просто додайте до будь-якої інструкції потрібний суфікс!

Також вам не потрібно використовувати CMP для встановлення стану прапорів. Якщо ви бажаєте, щоб, наприклад, інструкція SUB (Subtract) встановлювала прапори, додайте суфікс "S" до інструкції (розшифровується як "Set flags"). Це корисно, якщо ви не хочете встановлювати стан прапорів зайвою інструкцією CMP, тому зробити це і зробити перехід, якщо результат дорівнював нулю, можна наступним чином:

    SUBS r0, r1, 0x0FF; Встановлює прапори згідно з результатом виконання

    ; інструкції

    ldrZ r0, = 0x0FFFF; Завантажить в регістр r0 0x0FFFF тільки якщо стан

    прапорів і Нулю.

Огляд

Сьогодні ми вивчили (ще трохи) про регістри. Ми також дізналися про гнучкість інструкцій ARM, які можуть виконуватись (або не виконуватись) залежно від стану прапорів. Ми дізналися багато сьогодні.

Завтра ІІ використовуємо придбане сьогодні знання асемблера ARM для того, щоб вивести картинку на екран GBA.

Щось неможливе є таким лише до того часу, поки воно стає можливим /Jean-Luc Picard, Capt. , USS Enterprise/. Mike H, пров. Aquila

В даний час для програмування навіть досить простих мікроконтролерів використовуються мови високого рівня, як правило, є підмножина мови С або С++.

Однак при вивченні архітектури процесорів та її особливостей доцільно використовувати мови Ассемблера, оскільки тільки такий підхід може забезпечити виявлення особливостей архітектури, що вивчається. Тому подальший виклад ведеться з використанням мови Асемблера.

Перш ніж розпочати розгляд команд ARM7, необхідно відзначити наступні її особливості:

    Підтримка двох наборів команд: ARM із 32-бітовими командами та THUMB із 16-бітними командами. Далі розглядається 32-бітовий набір команд, слово ARM означатиме команди, що належать до цього формату, а слово ARM7 - ЦПУ.

    Підтримка двох форматів 32-розрядної адреси: зі зворотним порядком біт (big-endian processor і з прямим порядком біт (little-endian processor)). У першому випадку старший біт (Most Significant Bit - MSB) розташовується в молодшому биті слова, а в другому - у старшому. Це забезпечує сумісність з іншими сімействами 32-розрядних процесорів при використанні мов високого рівня. Однак у ряді сімейств процесорів з ядром ARM використовується тільки прямий порядок байтів (тобто MSB є найстаршим бітом адреси), що значно полегшує роботу з процесором. Оскільки компілятор, що використовується для ARМ7, працює з кодом в обох форматах, необхідно переконатися, що формат слів заданий правильно, в іншому випадку отриманий код буде «вивернуто навиворіт».

    Можливість виконання різних типів зсуву одного з операндів "на проході" перед використанням в АЛУ

    Підтримка умовного виконання будь-якої команди

    Можливість заборони зміни прапорів результатів виконання операції.

      1. Умовне виконання команд

Однією з важливих особливостей набору команд ARM є те, що підтримується умовне виконання будь-якої команди. У традиційних микроконтроллерах єдиними умовними командами є команди умовних переходів, і, можливо, ряд інших, як-от команди перевірки чи зміни стану окремих бітів. У наборі команд ARM старші 4 біти коду команди завжди порівнюються з прапорами умов у регістрі CPSR. Якщо значення не збігаються, команда на стадії дешифрації замінюється команда NOP (немає операції).

Це значно скорочує час виконання ділянок програми з «короткими» переходами. Так, наприклад, при вирішенні квадратних рівнянь з речовими коефіцієнтами і довільним корінням при негативному дискримінанті необхідно перед обчисленням квадратного кореня змінити знак дискримінанта, а результат привласнити уявної частини відповіді.

При традиційному вирішенні цього завдання необхідно запровадити команду умовного переходу. Виконання цієї команди займає щонайменше 2 такти – дешифрація та завантаження нового значення адреси в лічильник команд та додаткове число тактів на завантаження конвеєра команд. При використанні умовного виконання команди за позитивного дискримінанта команда зміни знака замінюється порожньою операцією. При цьому не відбувається очищення конвеєра команд і втрати становлять не більше одного такту. Поріг, у якому заміна умовних команд командою NOP виявляється ефективніше виконання традиційних команд умовного переходу пов'язаного з цим повторним заповненням конвеєра дорівнює його глибині, тобто. трьом.

Для реалізації цієї можливості до базових мнемонічним позначенням команд асемблера (і те саме), потрібно додати будь-який з шістнадцяти префіксів, визначальних тестовані стану прапорів умов. Ці префікси наведені у табл. 3. Відповідно, існує 16 варіантів кожної команди. Наприклад, наступна команда:

MOVEQ R1, #0x008

означає, що завантаження числа 0x00800000 в регістр R1 буде зроблено тільки в тому випадку, якщо результат виконання останньої команди обробки даних був «рівно» або отримано результат 0 і відповідно встановлений прапор (Z) регістра CPSR.

Таблиця 3

Префікси команд

Значення

Z встановлено

Z скинутий

З встановлений

Вище чи одно (беззнакове)

C скинутий

Нижче (беззнакове)

N встановлено

Негативний результат

N скинутий

Позитивний результат або 0

V встановлено

Переповнення

V скинутий

Немає переповнення

З встановлено,

Z скинутий

Вище (беззнакове)

З скинуто,

Z встановлено

Нижче чи одно (беззнакове)

Більше чи одно (знакове)

N не дорівнює V

Менше (знакове)

Z скинутий І

(N дорівнює V)

Більше (знакове)

Z встановлено АБО

(N не дорівнює V)

Менше чи одно (знакове)

(ігноруються)

Безумовне виконання

1. Лічильник годинника реального часу повинен бути включений (1); біт вибору джерела тактування скинутий (2), якщо тактування здійснюється від основного тактового генератора.

2. Один або обидва біти вибору поривної події (3) повинні бути встановлені. І вибрано, які саме події викликатимуть запит переривання (5).

3. Повинні бути задані маски подій, що переривають (4, 7).

2.5 Про програмування ARM7 на асемблері

Система команд ARM7 (розділ 1.4) включає лише 45 інструкцій, які досить складні через різноманітність методів адресації, умовних полів та модифікаторів. Програма на асемблері виходить громіздкою та

з працею читається. Тому асемблер рідко застосовується у програмуванні для архітектури ARM7.

Разом про те, мова високого рівня Сі приховує від програміста багато особливостей архітектури. Програміст практично не стосується таких процедур, як вибір режиму ядра, виділення пам'яті під стек та обробка переривань. Для вивчення цих процедур корисно скласти хоча б одну просту програму на асемблері.

Крім того, навіть при використанні Сі до мови асемблера все ж таки доводиться вдаватися.

1. Слід контролюватиСі-компілятор, відстежуючи, чи не виключив він у ході оптимізації важливі команди, вважаючи їх за непотрібні. Чи не генерує компілятор виключно неефективний код для порівняно простої операції через недостатню оптимізацію. Щоб переконатися, що компілятор справді задіює ті апаратні ресурси, які покликані підвищити ефективність конкретного алгоритму.

2. Під час пошуку помилок чи причин виникнення виняткових ситуацій (розділ 2.4.1).

3. Для отримання коду абсолютно оптимального за швидкодією або витратою пам'яті (розділи 2.2.20, 3.1.5).

Розглянемо основні прийоми складання програми на асемблері

з метою продемонструвати весь код виконуваний мікроконтролером, як є, і без посередництваСі-компілятора.

Порядок створення проекту на основі асемблера майже той самий, що й для Сі-програм (розділи 2.3.1–2.3.3). Винятки лише два:

а) файлу вихідного тексту надається розширення *.S;

б) тут передбачається, що файл STARTUP.S до програми не підключається.

2.5.1 Основні правила запису програм на асемблері

Текст програми на асемблері прийнято оформляти у чотири колонки. Можна сказати, що кожен рядок складається з чотирьох полів, а саме поля міток, операцій, операндів, коментарів. Поля відокремлюються один від одного символом табуляції або пробілами.

Основними є поля операцій та операндів. Допустимі операції та їх синтаксис наведені в таблиці (1.4.2)

Мітка – це символьне позначення адреси команди. Скрізь замість мітки виконуватиметься підстановка адреси команди, якій передує мітка. Найчастіше мітки використовуються у командах передачі управління. Кожна мітка має бути унікальною і при цьому є необов'язковою. На відміну від багатьох інших версій, в асемблері RealView мітки не закінчуються двокрапкою («:»).

Коментарі за бажанням розміщуються в кінці рядка і відокремлюються крапкою з комою («; »).

Наведемо найпростіший приклад.

2.5.2 Псевдокоманди

Асемблер RealView підтримує так звані псевдокоманди. Псевдокоманда - це мнемонічне позначення, яке насправді не відповідає системі команд процесора, а замінюється однією або (рідше) декількома командами. Псевдокоманди є свого роду макросами і служать спрощення синтаксису. Перелік підтримуваних псевдокоманд наведено у таблиці (2.5.1).

2.5.3 Директиви асемблера

На відміну від команд директиви не створюють код, що завантажується в пам'ять мікроконтролера. Директиви являють собою лише розпорядження асемблеру, керують формуванням здійснюваного коду.

Розглянемо директиви асемблера RealView 4, які часто використовуються.

Ім'я EQU Константа

Призначає Константі символьне позначення Ім'я , яке стає синонімом константи. Основне призначення - запровадження імен керуючих регістрів,

AREA Ім'я, Параметри

Визначає область пам'яті із заданим Ім'ям. За допомогою параметрів вказується призначення області пам'яті, наприклад DATA (дані) або CODE (код). Від обраного призначення залежать адреси визначеної області. Область CODE розміщується, починаючи з адреси 0x00000000, область DATA – з адреси 0x40000000. У програмі обов'язково має існувати область CODE з ім'ям RESET. Константи, розміщені у пам'яті програм, слід оголошувати у секції з кількома параметрами CODE, READONLY .

Позначає точку входу до програми, показує її "початок". Одна така директива завжди має бути у програмі. Зазвичай розміщується безпосередньо після директиви AREA RESET, CODE.

Таблиця 2.5.1 - Псевдокоманди, що підтримуються асемблером RealView 4

Мнемонічне позначення

Операція

Фактична реалізація

та синтаксис

ADR(Ум.)

у регістр

Додавання або віднімання константи з PC ко-

мандами ADD або SUB

ADRL(Ум.)

у регістр

Двічі ADD або SUB за участю PC

(Розширений діапазон адрес)

ASR(Ум.)(S)

Арифметичний зсув праворуч

ASR(Ум.)(S)

ням зсувного операнда

LDR(Ум.)

у регістр

адресацією (PC + безпосереднє усунення)

Розміщення константи

у пам'яті програм

LDR(з індексною адресою-

цією. Усуненням служить PC.

LSL(Ум.)(S)

Логічний зсув вліво

LSL(Ум.)(S)

ням зсувного операнда

LSR(Ум.)(S)

Логічний зсув праворуч

LSR(Ум.)(S)

ням зсувного операнда

POP(Ум.)

Відновити регістри зі стеку

Відновлення

регістрів

командою

LDMIA R13!, (...)

PUSH (Ум.)

Збереження

регістрів

командою

STMDB R13!, (...)

ROR(Ум.)(S)

Циклічне зрушення вправо

ROR(Ум.)(S)

ням зсувного операнда

RRX(Ум.)(S)

Циклічне зрушення вправо через

перенесення на 1 розряд

ням зсувного операнда

Ім'я SPACE Розмір

Резервує пам'ять для зберігання даних заданого Розміру. Ім'я стає синонімом адреси зарезервованого простору. Єдність адресного простору дозволяє застосовувати цю директиву як для постійної, так і для оперативної пам'яті. Основне призначення - створення глобальних змінних в оперативній пам'яті (DATA).

Мітка DCB/DCW/DCD Константа

«Прошивають» дані (числові Константи) у пам'яті програм. Мітка стає синонімом адреси, за якою будуть записані дані. Різні директиви (DCB, DCW і DCD) служать для даних різного розміру: байт, 16-розрядне слово, 32-розрядне слово (відповідно).

Служить ознакою кінця файлу. Весь текст після цієї директиви ігнорується асемблером.

2.5.4 Макроси

Макрос являє собою зумовлений фрагмент програми, що виконує будь-яку поширену операцію. На відміну від підпрограм, що викликаються за допомогою команд передачі управління, використання макросів не знижує швидкодії, але не знижує витрати пам'яті програм. Тому що при кожному виклик макросу асемблер впроваджує в програму повністю його текст.

Для оголошення макросу служить така конструкція

$ Параметр1, $ Параметр2, ...

Параметри дають змогу модифікувати текст макросу при кожному зверненні до нього. Усередині (у тілі) макросу параметри використовуються також із попереднім знаком «$». Замість параметрів у тілі макросу підставляються параметри, вказані під час виклику.

Виклик макросу здійснюється так:

Ім'я Параметр1, Параметр2, ...

Є можливість організувати перевірку умови та розгалуження.

IF "$ Параметр" == " Значення"

Звертаємо увагу, така конструкція не призводить до програмної перевірки умови мікроконтролером. Перевірку умови здійснює асемблер у ході формування коду, що здійснюється.

Спочатку ARMдосить незвичний асемблер (якщо переучуватися з x86, MCS51або AVR). Але в нього досить проста та логічна організація, тому засвоюється швидко.

Документації російською по асемблеру зовсім мало. Можу порадити зайти на 2 посилання (може, Ви знайдете більше, і підкажете мені? Буду вдячний):
Архітектура та система команд RISС-процесорів сімейства ARM - http://www.gaw.ru/html.cgi/txt/doc/micros/arm/index.htm
Осягаємо асемблер ARM, з циклу статей GBA ASM, автор Mike H, пров. Aquila - http://wasm.ru/series.php?sid=21.

Останнє посилання мені дуже допомогло, розвіяло туман =). Друге, що добре може допомогти - це, як не дивно, C-компілятор IAR Embedded Workbench for ARM(Далі просто IAR EW ARM). Справа в тому, що він з давніх часів вміє (як і всі компілятори, що поважають себе, втім) компілювати C-код в код асемблера, який, у свою чергу, так само легко компілюється асемблером IAR в об'єктний код. Тому немає нічого кращого написати найпростішу функцію на C, скомпілювати її в асемблер, і одразу стане зрозуміло, яка команда асемблера що робить, як передаються аргументи і як повертається результат. Вбиваєте відразу двох зайців - навчаєтеся асемблеру і заразом отримуєте інформацію, як інтергрувати асемблерний код у проект на C. Я тренувався на функції підрахунку CRC16, і в результаті отримав її повноцінну версію на асемблері.

Ось вихідна функція на C (u16 означає unsigned short, u32 - unsigned int, u8 - unsigned char):
// файл crc16.c
u16 CRC16 (void* databuf, u32 size)
{
u16 tmpWord, crc16, idx;
u8 bitCnt;
#define CRC_POLY 0x1021;

Crc16 = 0;
idx=0;
while (size! = 0)
{
/* складемо xor старший байт crc16 і вхідний байт */
tmpWord = (crc16>>8) ^ (*(((u8*)databuf)+idx));
/* результат запишемо в старший байт crc16 */
tmpWord<<= 8;
crc16 = tmpWord + (0x00FF & crc16);
for (bitCnt=8;bitCnt!=0;bitCnt--)
{
/* перевіримо старший розряд акумулятора CRC */
if (crc16 & 0x8000)
{
crc16<<= 1;
crc16 ^= CRC_POLY;
}
else
crc16<<= 1;
}
idx++;
size--;
}
return crc16;
}

Змусити генерувати код асемблера IAR EW ARM дуже просто. У опціях файлу crc16.c (доданого до проекту) поставив галку Override inherited settings, а потім на закладці List поставив 3 галки - Output assembler file, Include sourceі Include call frame information(хоча останню галку, напевно, можна не ставити – вона генерує купу непотрібних CFI-Директив). Після компіляції вийшов файл папка_проекту \ewp\at91sam7x256_sram\List\crc16.s. Цей файл також легко можна додати в проект, як і файл C (він буде нормально компілюватися).

Звичайно, коли я підсунув C-компілятор необрізаний варіант C-коду, то він мені видав такий лістинг асемблера, що я нічого в ньому не зрозумів. Але коли викинув із функції всі C-оператори, окрім одного, стало зрозуміліше. Потім крок за кроком додавав C-оператори, і ось у результаті вийшло:

; файл crc16.s
NAME crc16
PUBLIC CRC16

CRC_POLY EQU 0x1021

SECTION `.text`:CODE:NOROOT(2)
ARM

// u16 CRC16 (void* databuf, u32 size)
;R0 - результат повернення, CRC16
;R1 - параметр size
R2 - параметр databuf (він був при вході в R0)
;R3, R12 - тимчасові регістри

CRC16:
PUSH (R3, R12); методом тику з'ясував, що R3 і R13 зберігати
; не обов'язково. Але вирішив зберегти на всякий
; випадок.
MOVS R2,R0 ;тепер R2==databuf
MOV R3, #+0
MOVS R0, R3; crc16 = 0
CRC16_LOOP:
CMP R1, # + 0; всі байти обробили (size = = 0)?
BEQ CRC16_RETURN ;якщо так, то вихід
LSR R3, R0, # +8; R3 = crc16>>8
LDRB R12, ;R12 = *databuf
EOR R3, R3, R12; R3 = * databuf ^ HIGH (crc16)
LSL R3, R3, # +8; R3<<= 8 (tmpWord <<= 8)
AND R0, R0, #+255; crc16 &= 0x00FF
ADD R0, R0, R3; crc16 = tmpWord + (0x00FF & crc16)
MOV R12, # +8; bitCnt = 8
CRC16_BIT_LOOP:
BEQ CRC16_NEXT_BYTE ;bitCnt == 0?
TST R0,#0x8000 ;Ще в повному обсязі біти оброблені.
BEQ CRC16_BIT15ZERO ;Перевіряємо старший біт crc16.
LSL R0, R0, # +1; crc16<<= 1
MOV R3, # + (LOW (CRC_POLY)); crc16 ^ = CRC_POLY
ORR R3, R3, # + (HIGH (CRC_POLY)<< 8) ;
EOR R0, R3, R0;
B CRC16_NEXT_BIT

CRC16_BIT15ZERO:
LSL R0, R0, # +1; crc16<<= 1
CRC16_NEXT_BIT:
SUBS R12, R12, # +1; bitCnt--
B CRC16_BIT_LOOP;

CRC16_NEXT_BYTE:
ADD R2,R2,#+1;databuf++
SUBS R1,R1,#+1 ;size--
B CRC16_LOOP ;цикл по всіх байтах

CRC16_RETURN:
POP (R3, R12); відновлюємо регістри
BX LR ;вихід із підпрограми, R0==crc16

Компілятор C від IAR робить напрочуд хороший код. Мені зовсім мало вдалося його оптимізувати. Викинув лише зайвий тимчасовий регістр, який хотів використати компілятор (він чомусь взяв як зайвий тимчасовий регістр LR, хоча R3 і R12 було достатньо), а також прибрав пару зайвих команд, які перевіряють лічильники та виставляють прапори (просто додавши суфікс S до потрібних командам).

Сподобалася стаття? Поділіться з друзями!