Как работает регистр памяти

Обновлено: 08.07.2024

Регистры памяти (параллельные регистры) - простейший вид регистров. Их назначение - хранить двоичную информацию небольшого объема в течение короткого промежутка времени. Эти регистры представляют собой набор синхронных триггеров, каждый из которых хранит один разряд двоичного числа. Ввод (запись) и вывод (считывание) информации производится одновременно во всех разрядах параллельным кодом. Ввод обеспечивается тактовым командным импульсом. С приходом очередного тактового импульса происходит обновление записанной информации.

При построении параллельных регистров обычно используются простейшие асинхронные RS-триггеры и синхронные RS- и D-триггеры. На рис. 6.9 представлен один из вариант параллельных регистров. Регистр на основе асинхронных RS-триггеров перед занесением очередного n-разрядного числа со входов x1, . , хп требует предварительного сброса всех триг­геров в нулевое состояние. Сброс производится сигналом R = 0. Ввод информации в регистр происходит по сигналу А = 1, R = 1. Если на некотором i-м входе Xi = 1, то Si = = 0 и данный триггер (с инверсным управлением) переключается в состояние «1». Если на входе Хj = 0, то Sj = = 1 и j-й триггер сохраняет состояние «0». Вывод информации из регистра производится по сигналу В = 1, определяющему со­стояния выходов Yк = .Если В = 0, то на всех выходах устанавливаются уровни «логической 1», а при В = 1 имеем Yк =Qк.Основной недостаток данного регистра - необходи­мость предварительной очистки регистра, из-за чего обновление информации осуществляется за два такта.

Регистр хранения типа К155ИР15 может служить примером устройства с тремя выходными состояниями. Он состоит из четырех D-триггеров с независимыми информационными входами (D1-D4) и общим синхронизирующим входом С. Выходы триггеров Q1-Q4 - прямые. Имеются также два равноценных разрешающих входа Е1 и Е2, вход установки нулей R и два EZ1 и EZ2 для перевода микросхемы в третье состояние.

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


Память и регистры

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

В целом, регистр — это особый вид памяти внутри микроконтроллера, который используется для управления процессором и периферийными устройствами. Каждый регистр в архитектуре ARM представляет собой ячейку памяти и имеет длину в 32 бита, где каждый бит можно представить в виде крошечного выключателя с помощью которого осуществляется управление тем или иным параметром микроконтроллера.

Каждый из регистров имеет свой порядковый номер – адрес. Адрес регистра обозначается 32-битным числом представленным в шестнадцатеричной системе счисления. Путём записи по адресу регистра определённой комбинации единиц и нулей, которые обычно представлены в шестнадцатеричном виде, осуществляется настройка и управление тем или иным узлом в МК. Вспомним, что в программе для работы с битовыми операциями, мы могли представить в виде шестнадцатеричного числа произвольный набор единиц и нулей. В целом стоит отметить, что существует два вида регистров: регистры общего назначения и специальные регистры. Первые расположены внутри ядра МК, а вторые являются частью RAM-памяти.

Так же стоит отметить, что Reference Manual, который мы скачивали в первом уроке, это один большой справочник по регистрам, содержащимся в целевом микроконтроллере, а библиотека CMSIS позволяет нам оперировать символьными именами регистров вместо числовых адресов. Например, к регистру 0x40011018 мы можем обратиться просто, используя символьное имя GPIOC_BSSR. Конкретные примеры конфигурирования мы рассмотрим в ходе разбора нашей программы из первого занятия.

Итак, обычно структура регистра описывается в виде небольшой таблицы с указанием:

  1. Названия регистра и описания его назначения
  2. Адреса регистра или смещением относительно базового адреса
  3. Значения по умолчанию после сброса
  4. Типа доступа к ячейкам регистра (чтение, запись, чтение/запись)
  5. Значения и описания параметров записываемых битов

Разбор кода из первого занятия

Итак, давайте вспомним задачу, которую мы решили на первом уроке используя готовый код примера: нам было необходимо написать программу, которая бы обеспечила попеременное включение двух светодиодов на плате Discovery (возможно и не двух, если у вас другая версия платы Discovery) с временным интервалом.

Давайте еще разок взглянем на код программы, которую мы использовали для того, чтобы заставить наш МК дрыгать двумя ногами на которых расположены наши светодиоды:

Первым делом, при работе с STM32, даже для такой простой задачи как включение и выключение светодиода нам необходимо предварительно ответить на ряд вопросов:

  1. Куда подключены наши светодиоды? К какому выводу микроконтроллера?
  2. Как включить тактирование на нужный порт GPIO?
  3. Как настроить, нужные нам, пины порта GPIO для того чтобы можно было включить светодиод?
  4. Как включить и выключить светодиод?

Куда подключены наши светодиоды? К какому выводу микроконтроллера?

Для того, чтобы посмотреть где что находится на плате Discovery, а в частности, нужные нам светодиоды — нужно открыть Schematic-файл, либо тот который мы скачали с сайта ST, либо прямо из Keil:


Открыв Schematic мы увидим схему всего того, что есть на плате — схему ST-Link, обвязку всей периферии и многое другое. На текущий момент нас интересуют два светодиода, ищем их обозначение:


Как мы видим, наши светодиоды подключены к порту GPIOC на 8 и 9 пин.

Как включить тактирование на нужный порт GPIO?

В целом, любая работа с периферией в микроконтроллерах STM32 сводится к стандартной последовательности действий:

  1. Включение тактирования соответствующего периферийного модуля. Осуществляется это через регистр RCC путем подачи тактового сигнала напрямую с шины на которой находится данный модуль. По умолчанию тактирование всей периферии отключено для минимизации энергопотребления.
  2. Настройка через управляющие регистры, путем изменения параметров специфичных для конкретного периферийного устройства
  3. Непосредственный запуск и использование результатов работы модуля

Внимание! Вопрос касательно системы тактирования, её настройки и использования мы подробно рассмотрим в отдельной статье.

Найти к какой шине подключен наш порт GPIOC можно найти в Datasheet'е на наш МК в разделе Memory Mapping в Таблице 16. STM32F051xx peripheral register boundary addresses.


Как вы уже успели заметить, необходимая нам шина именуется как AHB2. Для того чтобы подробнее ознакомиться с регистром, в котором включается тактирование на нужный нам порт GPIO на шине AHB, надо перейти в соответствующий раздел в Reference Manual. По названию регистров мы можем определить тот, который нужен нам:


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


Смотрим на таблицу и видим нечто напоминающее опции включения тактирования на портах GPIO. Переходим к описанию и находим нужную нам опцию:


Соответственно если мы установим 19 бит в значение «1» то это обеспечит включение тактирования на порт I/O C – то есть на наш GPIOC. К тому же — нам нужно включить отдельно один бит из группы, не затрагивая остальные т.к. мы не должны мешать и изменять без надобности другие настройки.

Основываясь на материалах прошлого урока, мы знаем что для того чтобы выставить определенный бит нужно используя логическую операцию «ИЛИ» сложить текущее значение регистра с маской которая содержит те биты которые необходимо включить. Например, сложим значение регистра RCC->AHBENR по умолчанию, т.е. 0x14 и число 0x80000 тем самым включим тактирование GPIOC путем установки 19 бита:


Каким образом мы можем это сделать из программы? Всё достаточно просто. В данном случае у нас два варианта:

  1. Запись в регистр напрямую численного значения регистра напрямую через его адрес.
  2. Настройка с использованием библиотеки CMSIS

То есть, мы могли бы обращаться к адресам регистров напрямую по адресу и написать так:


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

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

Наш код будет выглядеть следующим образом:


Давайте для ознакомления копнём вглубь библиотеки CMSIS.

Для того, чтобы быстро перейти к месту где объявлена та или иная константа или переменная в Keil реализована удобная функция. Кликаем правой кнопкой по необходимой нам константе, например, на RCC:


И мы переносимся в глубины библиотеки CMSIS, в которой увидим, что все регистры доступные для управления программным способом имеют вид TypeDef-структур, в том числе и наш RCC:


Провалившись подобным образом в RCC_TypeDef мы увидим структуру в которой описаны все поля нашего регистра:


Соответственно, мы можем спокойно обращаться к нужному нам регистру записью вида PERIPH_MODULE->REGISTER и присваивать ему определенное значение.

Помимо мнемонического обозначения регистров есть так же обозначения конкретных битов. Если мы провалимся к объявлению параметра RCC_AHBENR_GPIOCEN из нашей программы, то так же увидим объявление всех параметров:


Таким образом, используя библиотеку CMSIS у нас получается лаконичная читаемая запись нужного нам параметра в регистр, через установку которого мы запускаем тактирование на нужный нам порт:


В качестве задания: определите используя возможности Keil, каким образом получился адрес регистра RCC->AHBENR как 0x40021014.

Как настроить нужные нам пины GPIO для того чтобы можно было включить светодиод?

Итак, мы знаем что нужные нам светодиоды подключены к порту GPIOC к пинам PC8 и PC9. Нам нужно настроить их в такой режим, чтобы загорался светодиод. Хотелось бы сразу же сделать оговорку, что порты GPIO мы рассмотрим подробнее в другой статье и тут мы сконцентрируемся именно на работе с регистрами.

Первым делом нам нужно перевести режим работы пинов PC8 и PC9 в режим Output. Остальные параметры порта можно оставить по умолчанию. Переходим в Reference Manual в раздел 9. General-purpose I/Os (GPIO) и открываем пункт отвечающий за режим работы пинов порта GPIO и видим что за этот параметр отвечает регистр MODER:


Судя по описанию, для установки пинов PC8 и PC9 в режим Output мы должны записать 01 в соответствующие поля регистра GPIOC.

Это можно сделать через прямую установку с помощью числовых значений:

    Формируем число для записи:



После данной инструкции наши пины PC8 и PC9 перейдут в режим Output.

Как включить светодиод?

Если мы обратим внимание на список доступных регистров для управления портом GPIO то можем увидеть регистр ODR:


Каждый из соответствующих битов отвечает за один из пинов порта. Его структуру вы можете увидеть ниже:


Для того, чтобы обеспечить попеременную смену состояний светодиодов надо с определенным временным интервалом включать/выключать 8 и 9 биты. То есть попеременно присваивать регистру значение 0x100 и 0x200.

Сделать это мы можем через прямое присвоение значений регистру:


Можем через использование определений из библиотеки:


Но так как микроконтроллер работает очень быстро — мы не будем замечать смены состояний светодиодов и визуально будет казаться что они оба горят постоянно. Для того чтобы они действительно моргали попеременно мы внесем искусственную задержку в виде цикла который займет МК бесполезными вычислениями на некоторое время. Получится следующий код:


На этом первоначальное знакомство с регистрами и методами работы с ними мы можем закончить.

Проверка результатов работы нашего кода

Небольшое приятное дополнение в конце статьи: в Keil имеется отличный Debug-инструмент с помощью которого мы можем пошагово выполнить нашу программу и просмотреть текущее состояние любого периферийного блока. Для этого после загрузки прошивки после компиляции мы можем нажать кнопку Start Debug Session:


Рабочая среда Keil переключится в режим отладки. Мы можем управлять ходом программы с помощью данных кнопок:


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

Для того чтобы ей воспользоваться — нужно перейти в соответствующий периферийный блок и справа откроется окно с указанием регистров и их значением.


Если вы кликните по одному из пунктов данного меню, вы увидите адрес регистра и его краткое описание. Так же можно просмотреть описание к каждому отдельному параметру регистра:


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

Мы уже рассматривали на предыдущем уроке 3 секции, из которых состоят программы на ассемблере. Эти секции также представляют различные сегменты памяти. Что интересно, если вы замените ключевое слово section на segment , то получите тот же результат. Например:

Результат выполнения программы:

Сегменты памяти

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

Сегменты памяти:

Сегмент данных (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;


Некоторые из этих регистров данных имеют специфическое применение в арифметических операциях:

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) — используется в качестве исходного индекса в строковых операциях.



Регистры управления

Регистром управления является объединенный 32-битный регистр инструкций и 32-битный регистр флагов (регистр процессора, отражающий его текущее состояние). Многие инструкции включают в себя операции сравнения и математические вычисления, которые способны изменить состояния флагов, а некоторые другие условные инструкции проверяют значения флагов состояния, чтобы перенести поток управления в другое место.

Распространенные битовые флаги:

Вспомогательный флаг переноса (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

Сегментные регистры

Сегменты — это специфические части программы, которые содержат данные, код и стек. Есть три основных сегмента:

Кроме регистров CS, DS и SS существуют и другие регистры дополнительных сегментов — ES (Extra Segment), FS и GS, которые предоставляют дополнительные сегменты для хранения данных.

При написании программ на ассемблере, программе необходим доступ к ячейкам памяти. Все области памяти в сегменте относятся к начальному адресу сегмента. Сегмент начинается с адреса, равномерно делимого на десятичное 16 или на шестнадцатеричное 10. Таким образом, крайняя правая шестнадцатеричная цифра во всех таких адресах памяти равна 0 , которая обычно не хранится в сегментных регистрах.

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

Пример на практике

Что такое регистровая RDIMM-память и зачем нужен ECC

Оперативная память в сокращении может называться ОЗУ. Ее также называют оперативным запоминающим устройством, памятью с произвольным доступом, RAM. ОЗУ также можно ласково назвать «оперативкой». RAM логически состоит из ячеек памяти. Каждая ячейка хранит количество бит, равное степени двойки. 2^3=8 бит, 2^4=16 бит, 2^5=32 бит, 2^6=64 бит. У каждой ячейки памяти есть свой адрес. Адрес ячейки «оперативки» выглядит следующим образом: FFFFFFFFF.

Регистровой памятью (Registered DIMM, RDIMM) называют модули ОЗУ, которые имеют на «борту» отдельный регистр для адресов «оперативки» и команд.

Контроллер ОЗУ в процессоре обращается к регистрам, регистры же направляют информацию в микросхемы памяти. Такая организация «оперативки» позволяет увеличить количество модулей на канал RAM за счет снижения электрической нагрузки на контроллер памяти. Контроллер находится либо в северном мосту материнской платы, либо в процессоре. Также вдвое уменьшается емкость модулей памяти, если модуль содержит два регистра.

Регистровая память отличается от обычной, небуферизованной «оперативки», более высокими задержками при чтени и записи информации в модулях ОЗУ. Это происходит из-за того, что модули содержат дополнительный промежуточный узел — буфер. Чтение/запись производит контроллер памяти в процессоре или северном мосту материнской платы. Работа с этим узлом, естественно, требует дополнительного времени работы. Но при этом отметим то, что уменьшается нагрузка на процессор, так как буфер отвечает за непосредственную работу с банками памяти.

Каждый модуль ОЗУ содержит микросхему SPD (Serial Presence Detect). Данная микросхема содержит прошивку модуля памяти. Эта прошивка определяет работу более простых микросхем.


Регистровая и буферизованная память — одно и то же

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

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

Что такое FB-DIMM

FB-DIMM, Full Buffered Dual Inline Memory Module — полностью буферизованная DIMM — это планки ОЗУ DDR2. Плашки ОЗУ при этом используют последовательный интерфейс передачи данных между модулями памяти и контроллером «оперативки». В отличие от стандартных модулей RAM, они используют не 240-pin, а 96-pin из 240 возможных пинов. Такая организация работы позволяет организовывать с помощью контроллеров памяти большее количество каналов на материнской плате. Вплоть до 6 каналов. Данные модули памяти несовместимы с обычными планками «оперативки».

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


Advanced Memory Buffer, AMB — микросхема, которая организует работу модулей памяти FB-DIMM. Эта микросхема располагается прямо на планке «оперативки».


В один канал памяти при такой организации работы модулей ОЗУ возможна установка до 8 планок «оперативки». Это позволяет, в случае с RAM DDR2, добиться емкости ОЗУ до 192 Гигабайт на один сервер.

В связи с тем, что микросхема AMB добавляет свои задержки в работу модуля памяти, данные плашки работают несколько медленнее модулей RDIMM, регистровой ОЗУ. Но, так как общее количество памяти в данном случае возрастает, то общая производительность системы также возрастает.


Краткая история оперативной памяти

Ниже приводится краткая история развития типов ОЗУ. Начинаем мы ее со времени выпуска памяти SDRAM. Это произошло в 1996 году. Пропускная способность данной RAM составила 1.1 GBps.

Следующей памятью в таблице указана память RDRAM. Она была выпущена в 1998 году. Это была абсолютно новая архитектура ОЗУ. Совершенно новый стандарт от фирмы Rambus. Было выпущено несколько поколений памяти. Она отличалась более высокими частотами, стабильными таймингами, вот только при этом задержки функционирования памяти были немного выше. К сожалению, данная память не выдержала конкуренции на рынке и вынуждена была сойти со сцены рынка памяти.

Следующими в таблице указаны линейки RAM DDR. Double Data Rate Synchronous Dynamic Random Access Memory — синхронная динамическая память с произвольным доступом и удвоенной скоростью передачи данных. Этот стандарт ОЗУ был выпущен на рынок в 2000 году. Данная память используется и на текущий момент. При этом развивается стандарт для достижения более высоких скоростей функционирования.

Последним типом RAM DDR, выпущенным на рынок, на данный момент является память DDR4.

Читайте также: