Работа с регистрами
Регистры, байты, биты
В этом уроке мы научимся работать напрямую с регистрами микроконтроллера. Зачем? Я думаю в первую очередь это нужно для того, чтобы понимать чужой код и переделывать его под себя, потому что прямая работа с регистрами в скетчах из Интернета встречается довольно часто. Начнём с того, что вспомним, где мы пишем код: Arduino IDE. Несмотря на большое количество не всегда оправданного негатива в сторону этой программы, она очень крутая. Помимо кучи встроенных инструментов и поддержки “репозиториев” от сторонних разработчиков, Arduino IDE позволяет нам программировать плату на разных языках программирования. Это некая условность, но по сути получается три языка:
Что такое регистр? Тут всё весьма просто: это сверхбыстрые блоки оперативной памяти объёмом 1 байт, находящиеся рядом с ядром МК и периферией. На стороне программы это обычные глобальные переменные, которые можно читать и изменять. В регистрах микроконтроллера хранятся “настройки” для различной его периферии: таймеры-счётчики, порты с пинами, АЦП, шина UART, I2C, SPI и прочее железо, встроенное в МК. Меняя регистр, мы даём практически прямую команду микроконтроллеру, что и как нужно сделать. Запись в регистр занимает 1 такт, то есть 0.0625 микросекунды (при частоте тактирования 16 МГц) – это ОЧЕНЬ быстро. Имена регистров фиксированные, полный перечень с подробным описанием можно найти в даташите на микроконтроллер (официальный даташит на ATmega328 – Arduino Nano/UNO/Mini). Работа с регистрами очень непростая, если вы не выучили их все наизусть, потому что названия у них обычно нечитаемые, аббревиатуры. Так называемые “Ардуиновские” функции собственно и занимаются тем, что работают с регистрами, оставляя нам удобную, понятную и читаемую функцию. Ничего сверхъестественного в этом нет. Зачем вообще работать с регистрами напрямую? Есть несколько больших преимуществ:
Но считать значение бита по его названию можно! Причём значение будет равно его номеру в регистре, считая справа. CS11 равен 1, WGM13 равен 4, ICNC1 равен 7 (см. таблицу выше). Думаю здесь всё понятно: есть регистр (байт), имеющий уникальное имя и состоящий из 8 бит, каждый бит также имеет уникальное имя, по которому можно получить номер этого бита в байте его регистра. Осталось понять, как этим всем пользоваться.
Запись/чтение регистра
Существует несколько способов установки битов в регистрах. Мы рассмотрим их все, чтобы столкнувшись с одним из них вы знали, что это вообще такое и как работает данная строчка кода. Абсолютно вся работа с регистрами заключается в установке нужных битов в нужном байте (в регистре) в состояние 0 или 1. Рекомендую прочитать урок по битовым операциям, в котором максимально подробно разобрано всё, что касается манипуляций с битами. Давайте вернёмся к регистру таймера, который я показывал выше, и попробуем его сконфигурировать. Первый способ, это явное задание всего байта сразу, со всеми единицами и нулями. Сделать это можно так:
Только на первый можно посмотреть и сразу понять, что где стоит. Чего не скажешь про остальные два. Очень часто в чужих скетчах встречается такая запись, и это не очень комфортно. Гораздо чаще бывает нужно “прицельно” изменить один бит в байте, и тут на помощь приходят логические (битовые) функции и макросы. Рассмотрим все варианты, во всех из них BYTE это байт-регистр, и BIT это номер бита, считая с правого края. То есть BIT это цифра от 0 до 7, либо название бита из даташита.
| Установка бита в 1 | Установка бита в 0 | Описание | ||
| BYTE |= (1 | BYTE &= (1 | Использование битового сдвига BYTE |= (2^BIT); | BYTE &= (2^BIT); | Используем 2 в степени (пример не рабочий!) |
| BYTE |= bit(BIT); | BYTE &= bit(BIT); | Используем ардуиновский макрос bit(), заменяющий сдвиг | ||
| BYTE |= _BV(BIT); | BYTE &= _BV(BIT); | Используем встроенную функцию _BV(), опять же аналог сдвига | ||
| sbi(BYTE, BIT); | cbi(BYTE, BIT); | Используем общепринятые макросы sbi и cbi | ||
| bitSet(BYTE, BIT); | bitClear(BYTE, BIT); | Используем ардуиновские функции bitSet() и bitClear() |
Можно ещё добавить вариант, где в одной строчке можно “прицельно” установить несколько битов:
Я думаю тут всё понятно, давайте теперь попробуем “прицельно” прочитать бит из регистра:
| Чтение бита | Описание |
| (BYTE >> BIT) & 1 | Вручную через сдвиг |
| bitRead(BYTE, BIT) | Ардуиновская макро-функция |
Два рассмотренных способа возвращают 0 или 1 в зависимости от состояния бита. Пример:
Ещё больше примеров работы с битами смотри в предыдущем уроке по битовым операциям.
16-бит регистры
Такую запись почему-то используют редко, возможно для совместимости с другими компиляторами. Чаще всего вы встретите вот такой вариант, в котором значения “склеиваются” через сдвиг:
Записывать нужно со старшего байта. Как только мы записываем младший байт (последний) – МК “защелкивает” оба регистра в память, соответственно если сначала записать младший – в старшем будет 0, и последующая запись старшего будет проигнорирована.
Ассемблер. Сегменты памяти и регистры
Обновл. 27 Сен 2021 |
Результат выполнения программы:
Сегменты памяти
Модель сегментированной памяти разбивает системную память на группы независимых сегментов, на которые указывают указатели, расположенные в регистрах сегментов. Каждый сегмент используется для хранения данных определенного типа. Первый сегмент используется для хранения кода инструкций, второй — для хранения элементов данных, а третий — для программного стека.
Сегменты памяти:
Сегмент данных (data segment) — представлен секциями .data и .bss. Секция .data используется для объявления области памяти, где хранятся элементы данных для программы. Эта секция не может быть расширена после объявления элементов данных, и она остается статической во всей программе. Секция .bss также является секцией статической памяти, содержащей буферы для данных, которые будут объявлены в программе позже. Эта буферная память заполнена нулями.
Сегмент кода (code segment) — представлен секцией .text. Он определяет область в памяти, в которой хранятся коды инструкций. Это также фиксированная область.
Стек (stack) — это сегмент, который содержит значения данных, передаваемые в функции и процедуры в программе.
Регистры
Обычно операции с процессором включают в себя обработку данных. Эти данные могут быть как сохранены в памяти, так и извлечены оттуда. Однако процесс чтения данных из памяти и хранения данных в памяти замедляет работу процессора, так как это предполагает сложный процесс отправки запроса данных в блок памяти и получение данных обратно из блока по одному и тому же каналу — через шину управления.
Чтобы ускорить свою работу, процессор подключает определенные внутренние места хранения памяти, которые называются регистрами. Регистры хранят элементы данных для обработки без необходимости получать доступ к памяти. Ограниченное количество регистров встроено в чип процессора.
Регистры процессора
В архитектуре IA-32 есть десять 32-битных и шесть 16-битных процессорных регистров. Регистры делятся на три категории:
Общие регистры (General Registers);
Регистры управления (Control Registers);
Сегментные регистры (Segment Registers).
В свою очередь, общие регистры делятся на следующие:
Регистры данных (Data Registers);
Регистры-указатели (Pointer Registers);
Индексные регистры (Index Registers).
Регистры данных
Регистры данных — это четыре 32-битных регистра, которые используются для арифметических, логических и других операций. Эти 32-битные регистры могут быть использованы следующими тремя способами:
как полные 32-битные регистры данных: EAX, EBX, ECX, EDX;
нижние половины 32-битных регистров могут использоваться как четыре 16-битных регистра данных: AX, BX, CX и DX;
нижняя и верхняя половины вышеупомянутых четырех 16-битных регистров могут использоваться как восемь 8-битных регистров данных: AH, AL, BH, BL, CH, CL, DH и DL.
Некоторые из этих регистров данных имеют специфическое применение в арифметических операциях:
AX (primary accumulator) — используется для ввода/вывода и в большинстве арифметических операций. Например, в операции умножения один операнд сохраняется в регистре EAX/AX/AL в соответствии с размером операнда.
BX (base register) — используется при индексированной адресации.
CX (count register) — хранит количество циклов в повторяющихся операциях (также, как и регистры ECX и CX).
DX (data register) — используется в операциях ввода/вывода, а также с регистрами AX и DX для выполнения операций умножения и деления, связанных с большими значениями.
Регистры-указатели
Регистрами-указателями являются 32-битные регистры EIP, ESP и EBP и соответствующие им 16-битные регистры IP, SP и BP. Есть три категории регистров-указателей:
Указатель на инструкцию или команду (Instruction Pointer или IP) — 16-битный регистр IP хранит смещение адреса следующей команды, которая должна быть выполнена. IP в сочетании с регистром CS (как CS:IP) предоставляет полный адрес текущей инструкции в сегменте кода.
Указатель на стек (Stack Pointer или SP) — 16-битный регистр SP обеспечивает значение смещения в программном стеке. SP в сочетании с регистром SS (SS:SP) означает текущее положение данных или адреса в программном стеке.
Базовый указатель (Base Pointer или BP) — 16-битный регистр BP используется в основном при передаче параметров в подпрограммы. Адрес в регистре SS объединяется со смещением в BP, чтобы получить местоположение параметра. BP также можно комбинировать с DI и SI в качестве базового регистра для специальной адресации.

Индексные регистры
В процессоре существуют 32-битные индексные регистры ESI и EDI и их 16-битные версии: SI и DI. Все они используются в индексированной адресации, и, иногда, в операциях сложения/вычитания. Есть два типа индексных указателей:
Исходный индекс (Source Index или SI) — используется в качестве исходного индекса в строковых операциях.
Индекс назначения (Destination Index или DI) — используется в качестве индекса назначения в строковых операциях.

Регистры управления
Регистром управления является объединенный 32-битный регистр инструкций и 32-битный регистр флагов (регистр процессора, отражающий его текущее состояние). Многие инструкции включают в себя операции сравнения и математические вычисления, которые способны изменить состояния флагов, а некоторые другие условные инструкции проверяют значения флагов состояния, чтобы перенести поток управления в другое место.
Распространенные битовые флаги:
Флаг переполнения (Overflow Flag или OF) — указывает на переполнение старшего бита данных (крайнего левого бита) после signed арифметической операции.
Флаг ловушка (Trap Flag или TF) — позволяет настроить работу процессора в одношаговом режиме.
Вспомогательный флаг переноса (Auxiliary Carry Flag или AF) — после выполнения арифметической операции содержит перенос от бита 3 до бита 4. Используется для специализированной арифметики. AF устанавливается, когда 1-байтовая арифметическая операция вызывает перенос из бита 3 в бит 4.
Флаг переноса (Carry Flag или CF) — после выполнения арифметической операции содержит перенос 0 или 1 из старшего бита (крайнего слева). Кроме того, хранит содержимое последнего бита операции сдвига или поворота.
В следующей таблице указано положение битовых флагов в 16-битном регистре флагов:
| Флаг: | O | D | I | T | S | Z | A | P | C | |||||||
| Бит №: | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Сегментные регистры
Сегменты — это специфические части программы, которые содержат данные, код и стек. Есть три основных сегмента:
Сегмент кода (Code Segment или CS) — содержит все команды и инструкции, которые должны быть выполнены. 16-битный регистр сегмента кода или регистр CS хранит начальный адрес сегмента кода.
Сегмент данных (Data Segment или DS) — содержит данные, константы и рабочие области. 16-битный регистр сегмента данных или регистр DS хранит начальный адрес сегмента данных.
Сегмент стека (Stack Segment или SS) — содержит данные и возвращаемые адреса процедур или подпрограмм. Он представлен в виде структуры данных «Стек». Регистр сегмента стека или регистр SS хранит начальный адрес стека.
Кроме регистров CS, DS и SS существуют и другие регистры дополнительных сегментов — ES (Extra Segment), FS и GS, которые предоставляют дополнительные сегменты для хранения данных.
Сегментные регистры хранят начальные адреса сегмента. Чтобы получить точное местоположение данных или команды в сегменте, требуется значение смещения. Чтобы сослаться на любую ячейку памяти в сегменте, процессор объединяет адрес сегмента в сегментном регистре со значением смещения местоположения.
Пример на практике
Посмотрите на следующую простую программу, чтобы понять, как используются регистры в программировании на ассемблере. Эта программа выводит 9 звёздочек с простым сообщением:
Уроки FASM. Регистры. Window x32
Что это такое и зачем нужны?
Это самая быстрая память в ПК по сравнению с ОЗУ. Но при этом размер регистров намного меньше чем ОЗУ, по этой причине используют ОЗУ.
Регистр RAX это дополнение EAX, EAX это дополнение AX, AX это объединение 2 регистров AH и AL.
wikipedia
OllyDbg
Ollydbg
Зачем так много разновидностей 1 регистра?
Для поддержке более старых версий процессоров x86 (обратная совместимость, например: на 64 битном ЦП запустить 32 битную программу)
Какие бывают регистры?
Их очень много и каждый под свои задачи, но есть регистры общего доступа, с которыми мы будем работать.
Это 32 битные регистры (16, 8):
ESI (SI, это 16 битный регистр, меньше нет)
EDI (DI, это 16 битный регистр, меньше нет)
Как работать с регистрами?
В них можно хранить любую информацию: числа со знаком (int), числа без знака (unsigned int), числа с плавающей запятой (float, в x64 можно хранить double в регистре), адрес, символы (не превышая размер регистра), и другую информацию.
Попробуем записать значение в регистры:
Что за регистр FLAGS?
Это регистр состояния ЦП, он нужен для проверки и сравнению чисел, проверки регистров.
Регистры процессора
Начиная с модели 80386 процессоры Intel предоставляют 16 основных регистров для пользовательских программ и ещё 11 регистров для работы с мультимедийными приложениями (MMX) и числами с плавающей точкой (FPU/NPX). Все команды так или иначе изменяют содержимое регистров. Как уже говорилось, обращаться к регистрам быстрее и удобнее, чем к памяти. Поэтому при программировании на языке Ассемблера регистры используются очень широко.
В этом разделе мы рассмотрим основные регистры процессоров Intel. Названия и состав/количество регистров для других процессоров могут отличаться. Итак, основные регистры процессоров Intel.
Таблица 2.1. Основные регистры процессора.
| Название | Разрядность | Основное назначение |
| EAX | 32 | Аккумулятор |
| EBX | 32 | База |
| ECX | 32 | Счётчик |
| EDX | 32 | Регистр данных |
| EBP | 32 | Указатель базы |
| ESP | 32 | Указатель стека |
| ESI | 32 | Индекс источника |
| EDI | 32 | Индекс приёмника |
| EFLAGS | 32 | Регистр флагов |
| EIP | 32 | Указатель инструкции (команды) |
| CS | 16 | Сегментный регистр |
| DS | 16 | Сегментный регистр |
| ES | 16 | Сегментный регистр |
| FS | 16 | Сегментный регистр |
| GS | 16 | Сегментный регистр |
| SS | 16 | Сегментный регистр |
Регистры EAX, EBX, ECX, EDX – это регистры общего назначения. Они имеют определённое назначение (так уж сложилось исторически), однако в них можно хранить любую информацию.
Регистры EBP, ESP, ESI, EDI – это также регистры общего назначения. Они имеют уже более конкретное назначение. В них также можно хранить пользовательские данные, но делать это нужно уже более осторожно, чтобы не получить «неожиданный» результат.
Регистр флагов и сегментные регистры требуют отдельного описания и будут более подробно рассмотрены далее.
Пока для вас здесь слишком много непонятных слов, но со временем всё прояснится)))
Когда-то процессоры были 16-разрядными, и, соответственно, все их регистры были также 16-разрядными. Для совместимости со старыми программами, а также для удобства программирования некоторые регистры разделены на 2 или 4 «маленьких» регистра, у каждого из которых есть свои имена. В таблице 2.2 перечислены такие регистры.
Вот пример такого регистра.
Из этого следует, что вы можете написать в своей программе, например, такие команды: Обе команды поместят в регистр AX число 1. Разница будет заключаться только в том, что вторая команда обнулит старшие разряды регистра EAX, то есть после выполнения второй команды в регистре EAX будет число 1. А первая команда оставит в старших разрядах регистра EAX старые данные. И если там были данные, отличные от нуля, то после выполнения первой команды в регистре EAX будет какое-то число, но не 1. А вот в регистре AX будет число 1. Сложно? Ну это пока… Со временем вы к таким вещам привыкните.
Мы пока не говорили о разрядах (битах). Эту тему мы обсудим в разделах, посвящённых системам счисления. А сейчас пока вам достаточно знать, что нулевой разряд (бит) – это младший бит. Он крайний справа. Старший бит – крайний слева. Номер старшего бита зависит от разрядности числа/регистра. Например, в 32-разрядном регистре старшим битом является 31-й бит (потому что отсчёт начинается с 0, а не с 1).
Ниже приведён список регистров общего назначения, которые можно поделить описанным выше способом и при этом к «половинкам» и «четвертинкам» этих регистров можно обращаться в программе как к отдельному регистру.
Таблица 2.2. «Делимые» регистры..
| Регистр | Старшие разряды | Имена 16-ти и 8-ми битных регистров | |
| 31…16 | 15…8 | 7…0 | |
| EAX | . | AX | |
| AH | AL | ||
| EBX | . | BX | |
| BH | BL | ||
| ECX | . | CX | |
| CH | CL | ||
| EDX | . | DX | |
| DH | DL | ||
| ESI | . | SI | |
| EDI | . | DI | |
| EBP | . | BP | |
| ESP | . | SP | |
| EIP | . | IP | |
На этом мы закончим наше краткое знакомство с регистрами. Если вам пока не всё понятно – просто прочитайте этот раздел, чтобы более-менее представлять себе, что такое регистры. По мере приобретения новых знаний вы можете вернуться к этому разделу и уже на новом уровне воспринять эту информацию. А в следующем разделе мы коротко опишем процесс выполнения команды.
MS-DOS и TASM 2.0. Часть 8. Регистры.
Регистры процессора.
Процессор работает с данными. Обрабатываемые данные содержаться в оперативной памяти, куда считываются с жёсткого диска. Для того, чтобы ими манипулировать, а также верно организовывать свою работу по их обработке — выполнению программного кода, у процессора имеется система, построенная на небольших, но очень быстрых блоках памяти — регистров. Регистры процессора — это входящие непосредственно в процессор блоки памяти.
Изучать регистры процессора мы будем с использованием отладчика TD (Turbo Debugger) и нашей первой программы «Hello, world!» (prg.com). Так что запускаем предустановленный DOSBox и поехали (всё необходимое имеется в архиве DOS-1.rar, который можно скачать с нашего сайта).

Для удобства работы с Turbo Debugger целесообразно увеличить рабочую площадь программы на всё окно.

Регистры процессора Intel и их аналоги.
Количество регистров и их размер зависит от разрядности и сложности процессора.
Практически при написании кода регистры нужно понимать как постоянно имеющиеся под рукой программиста переменные.
Начиная с 80386, процессоры Intel и их аналоги имеют 16 основных регистров и 11 регистров для работы с числами с плавающей запятой (FPU/NPX) и мультимедийными приложениями (MMX). Для машины всегда быстрее и удобнее обращаться к регистру процессора, чем к памяти.
Помимо основных регистров существуют регистры управления памятью (GDTR, IDTR, TR, LDTR), регистры управления (CR0, CR1 – CR4), отладочные регистры (DR0 – DR7) и машинно-зависимые регистры, которые практически не используются в прикладном программировании.
Регистры процессора 8086.
Так как мы имеем дело с 16 битной операционной системой MS-DOS, то вернёмся во времена процессоров 8086 и 80286. В процессорах следующих поколений указанные регистры являются частями соответствующих 32 и 64 битных регистров с сохранением названия и функционала.
Например, регистр ax (16 бит) является составляющей частью регистра eax (32 бита). Благодаря указанному подходу достигается совместимость работы старых программ на новых компьютерах. Также сохраняются команды и директивы, переходя с 16 битного в 32 битный код.
Итак, процессор (8086 и 80286) содержит 12 16-ти разрядных программно-адресуемых регистров. Регистры процессора принято объединять в три группы:
Кроме этого, в состав процессора входят:
16-ти разрядность обозначает, что в каждом регистре может содержаться 0FFFFh бит информации, то есть два байта: 0FFh-0FFh информации.
Регистры данных (регистры общего назначения).
Регистры данных допускают независимое обращение к старшим (High — обозначается литерой H) и младшим (Lough — обозначается литерой L) половинам.




