Перейти к основному содержимому

Клавиатура PS/2

Для кого этот документ

  • Студентам курса "Периферийные устройства"
  • Необходимое ПО: Quartus II

Подключение клавиатуры к процессорной системе DE 2-115 Media Computer

Цель работы:

Изучение принципа работы клавиатуры, интерфейса PS/2, контроллера PS/2.

Объекты изучения:

  • Клавиатура;
  • интерфейс PS/2;
  • контроллер интерфейса.

Исходные файлы, используемые в работе

  • Файл PS2_from_FIFO_to_HEX содержит программу чтения байт данных из буфера FIFO контроллера PS/2 с выводом их на HEX индикаторы стенда. Вначале программа гасит все Нех индикаторы стенда. Затем она читает содержимое регистра данных порта PS/2, анализирует бит Rvalid в прочитанном слове, и если этот бит установлен, то есть данные в буфере присутствуют, то отображает младший байт прочитанного слова на HEX индикаторах. Первый переданный устройством байт отображается на индикаторах HEX1, HEX0, второй- HEX3, HEX2, и так далее. Если восьми индикаторов стенда не будет хватать, чтобы отобразить все считанные из буфера байты, то выводимые на Нех индикаторы значения будут перемещены вправо на 1 байт, а освободившиеся два левых HEX индикатора отобразят следующий байт из буфера FIFO.

  • Файл PS2_From_FIFO_to_OP_and_LCD содержит программу пересылки содержимого буфера FIFO контроллера PS/2 в оперативную память процессорной системы и вывода прочитанных байт в верхней строке LCD экрана в шестнадцатеричном виде. В нижней строке экрана указывается количество выводимых на экран байт. Если все байты в строке не помещаются, выведенное содержимое буфера смещается влево, а справа выводится следующий байт.

  • Файл PS2_Typematic_rate_Delay_to_JTAG содержит программу, предназначенную для выполнения пятой части работы. В ней вначале программируется интервальный таймер на непрерывную работу и разрешаются прерывания от клавиатуры. Прерывания будут происходить каждый раз, когда в буфере FIFO контроллера PS/2 будет появляться очередной байт, переданный клавиатурой. Обработчик прерывания считывает текущее значение интервального таймера (тайм-код), фиксируя тем самым момент поступления байта от клавиатуры, переданный клавиатурой байт данных и пересылает их в ОП в два массива. Тайм-коды, пересылаются в первый массив, начиная с адреса SDRAMTIME (по умолчанию 0x1000) пословно, а сами принятые от клавиатуры байты данных в другой, со смещением DELTA (по умолчанию 0x1000). После приема нескольких байт (эта величина задана в виде константы NUM_OF_BYTES) работа обработчика завершается, а в терминал JTAG UART выводится вычисленная частота повтора и задержка автоповтора символов.

Подготовка к лабораторной работе

  1. Ознакомьтесь с краткими теоретическими сведениями, представленными в файле «Подготовка к лабораторной работе».
  2. Ознакомьтесь с информацией из источников, приведенных в списке литературы:
    • описание порта PS/2 представлено в [1] пп.3.4.5. – 3.4.6.;
    • описание интерфейса PS/2 приведено в [3] п.2;
    • формат регистров для взаимодействия программы с контроллером PS/2 представлен в [3] п.3;
    • описание клавиатуры PS/2 приведено в [2];
    • приложение Altera Monitor Program описано в [4] и в [1] п.2.
  3. Уясните логику работы программ, приведенных в приложении 3, с именами:
    • PS2_from_FIFO_to_HEX,
    • PS2_From_FIFO_to_OP_and_LCD,
    • PS2_Typematic_rate_Delay_to_JTAG.
  4. Подготовьте правильные значения аргументов для команд выполнения некоторых пунктов задания, в соответствии с индивидуальным вариантом, приведенным в приложении 2.

Вопросы для самоконтроля

  1. Как работает клавиатура компьютера?
  2. Каково назначение интерфейса PS/2?
  3. Какого типа разъемы используются в устройствах PS/2?
  4. Какое количество линий используется в интерфейсе PS/2? Каково их назначений?
  5. Каково назначение программно - доступных регистров порта PS/2 в процессорной системе «DE 2-115 Media computer»?
  6. Каково назначение отдельных полей регистров порта PS/2 в процессорной системе «DE 2-115 Media computer»?
  7. Буфер какого типа используется в контроллере PS/2? Для чего он предназначен?
  8. Какие коды формируются клавиатурой при нажатии клавиш?
  9. Какие коды формируются клавиатурой при отпускании клавиш?
  10. Какие коды формируются клавиатурой при нажатии и отпускании служебных клавиш?

Порядок выполнения работы

1. Подключение стенда

  1. Подключите клавиатуру PS/2 к учебному стенду, используя соответствующий разъем стенда (верхняя часть правой стороны).
  2. Подсоедините стенд к источнику питания и инструментальному компьютеру, используя соответствующие кабели, входящие в комплект поставки.
  3. Запустите приложение AMP (IFPGAMP) и создайте в нем новый проект. Разместите проект в папке с вашей фамилией. Следует напомнить, что для указания пути к проекту следует избегать русскоязычных символов.
  4. Выберите в качестве процессорной системы DE2-70 Media Computer / DE2-115 Media Computer / DE2-115 Computer.
  5. Выберите в следующем диалоговом окне тип программы – Assembly Program.
  6. В качестве исходного файла выберите программу PS2_from_FIFO_to_HEX.
  7. В параметрах системы выберите: Host connection: USB-Blaster (тип соединения с хостом); Processor: Nios2 (процессорная система); Terminal device: JTAG_UART (для обмена символьной информацией между стендом и инструментальным компьютером).
  8. В настройках памяти выберите Linker Section Presets: Basic (определение адресов для размещения кода). Программа должна начинаться с нулевого адреса.

2. Исследование команд управления клавиатурой

  1. Используя вкладку Memory, отправьте команду Reset (0xFF) в порт данных клавиатуры. Наблюдайте ответное сообщение со стороны клавиатуры, читая содержимое буфера FIFO контроллера. Используйте для этого кнопку Refresh, причем выполняйте чтение этого порта до тех пор, пока буфер FIFO не окажется пуст. Отразите в отчете принятое от клавиатуры сообщение.

  2. Кратковременно нажмите 1 раз на любую клавишу цифрового ряда основной клавиатуры для заполнения FIFO буфера, после чего считайте содержимое буфера FIFO, используя кнопку Refresh и отразите в отчете. Отправьте команду Resend (0xFE) и осмыслите наблюдаемый результат. Экспериментально определите, как работает команда Resend, нажимая другие клавиши клавиатуры и поместите в отчет ваше заключение.

  3. Отправьте в порт данных клавиатуры команду Read ID (0xF2). Зафиксируйте в отчете переданные в ответ клавиатурой два идентификационных байта.

  4. Экспериментально определите поведение клавиатуры после отправки ей команды запрета сканирования (0хF5).

  5. Убедитесь, что клавиатура продолжит отправлять скан коды после отправки ей команды разрешения сканирования (0хF4). Проверьте, будут ли скан-коды клавиш, нажатых при запрете сканирования, находиться в буфере после разрешения?

  6. Экспериментально определите реакцию клавиатуры на отправленную команду Echo (0xEE). Повторите выполнение этого пункта задания несколько раз.

  7. Определите экспериментально, как работает команда Set/Reset LEDs (0xED). Следует учесть, что команда содержит байт аргументов, который задает состояние светодиодов, входящих в состав клавиатуры. Попробуйте зажечь светодиоды как по отдельности, так и все сразу. Внесите в отчет содержимое байта с аргументами для зажигания пары светодиодов в соответствии с вашим вариантом.

  8. Отправьте в порт данных клавиатуры команду конфигурирования клавиатуры по умолчанию Set Default (0xF6). В следующей части задания следует экспериментально определить, какому режиму работы клавиатуры соответствуют эти настройки.

3. Исследование скан кодов нажатия и отпускания клавиш

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

  1. Запустите программу PS2_from_FIFO_to_HEX.

  2. Кратковременно нажмите на основной части клавиатуры клавишу с цифрой 1. Используя вышеупомянутую программу наблюдайте переданные клавиатурой данные на HEX индикаторах стенда и занесите их в заготовленную таблицу. Повторите выполнение этого пункта задания для следующих девяти клавиш цифрового ряда основной части клавиатуры. Проверьте, соответствует ли скан-коды цифровых клавиш основной клавиатуры скан-кодам клавиш дополнительной клавиатуры (Numpad) и отразите в отчете.

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

  4. Повторите выполнение пункта 3.2. для клавиш Enter, Backspace, Alt, Ctrl. Проверьте, совпадают ли скан-коды этих клавиш для левой и правой частей клавиатуры.

  5. Остановите выполнение программы.

  6. Переключите клавиатуру в режим Set#1. Для этого в клавиатуру отправьте команду Set Scan Code Set (0xF0). Клавиатура отвечает байтом подтверждения, после чего ждет байт с аргументом 0х01, 0х02 или 0х03, что соответствует установке клавиатуры в заданный режим формирования скан кодов. Если задать в качестве аргумента 0x00, то клавиатура возвращает номер текущего набора. Для установки клавиатуры в режим Set#1 отправьте 0x01 в байте аргумента.

  7. Убедитесь, что клавиатура установлена в режим Set#1, повторно отправив команду 0хF0, но с аргументом 0x00.

  8. Повторно выполните пункты 3.1. – 3.5. для режима Set#1, заполняя таблицу.

  9. Повторите пункты 3.6. – 3.7. задания для режима Set#3.

  10. Повторно выполните пункты 3.1. – 3.5. для режима Set#3, заполняя таблицу.

  11. Чтобы погасить все HEX индикаторы и подготовиться к выполнению следующих пунктов задания остановите выполнение программы, используя команду Stop из меню Actions или соответствующую клавишу инструментальной панели. Затем, перезапустите программу, последовательно выполняя команды Restart и Continue из меню Actions и вновь остановите ее выполнение.

  12. Используя вкладку Memory, повторно отправьте в порт данных клавиатуры команду Reset (0xFF).

4. Определение размера буфера FIFO в контроллере PS2

  1. Завершите текущий сеанс работы с программой выполнив в меню Actions команду Disconnect. Далее во вкладке Program Settings удалите предыдущую программу и выберите программу PS2_From_FIFO_to_OP_and_LCD из папки с исходными файлами.

  2. Скомпилируйте и загрузите программу, используя команду Compile & Load.

  3. Для того чтобы очистить область ОП в диапазоне адресов 0x1000 – 0x2000, запустите программу PS2_From_FIFO_to_OP_and_LCD и остановите ее, используя команду Stop. Используя вкладку Memory убедитесь, что указанная область ОП очищена.

  4. Нажмите одну из клавиш клавиатуры и удерживайте её в течении примерно 15 секунд. Во вкладке Memory наблюдайте содержимое регистра данных контроллера. По содержимому поля RAVAIL определите количество байт, отправленных клавиатурой в процессорную систему за прошедшее время.

  5. Повторите выполнение предыдущего пункта задания, нажимая уже другую клавишу в течении примерно 15 секунд. Проверьте, изменилось ли значение поля RAVAIL? Экспериментально определите размер буфера FIFO контроллера PS/2, повторяя нажатие других клавиш примерно в течении 15 секунд.

  6. Продолжите выполнение программы, используя команду Continue из меню Actions, чтобы переслать содержимое буфера FIFO контроллера PS/2 в ОП. В процессе работы программы оставшееся количество байт в буфере FIFO будет отражаться на зеленых светодиодах. Признаком того, что программа перенесла все содержимое буфера FIFO в ОП является затухание зеленых светодиодов. После этого нужно приостановить выполнение программы. Используя вкладку Memory, наблюдайте содержимое ОП, прочитанное из буфера FIFO по адресу 0x1000. Используйте побайтовое отображение содержимого ОП. Определите количество считанных из буфера FIFO байт. Найдите в нем скан коды нажатия и отпускания нажатых в пунктах 4.4. – 4.5. клавиш и отразите в отчете. Для снимка экрана используйте клавишу PrintScreen на клавиатуре инструментального ПК или используйте камеру вашего смартфона.

5. Определение условия формирования прерывания от клавиатуры. Определение временных параметров автоповтора клавиатуры

  1. Завершите сеанс работы с текущей программой, выполнив в меню Actions команду Disconnect. Далее в меню Program Settings удалите предыдущую программу и выберите программу PS2_Typematic_rate_Delay_to_JTAG из папки с исходными файлами.

  2. Выполните компиляцию и загрузите программу, используя команду Compile & Load.

  3. Экспериментально определите условие формирования сигнала прерывания при отправке данных из клавиатуры в процессорную систему. Для этого вначале следует убедиться, что данных, переданных клавиатурой в процессорную систему, в настоящий момент времени в буфере FIFO нет. Затем надо разрешить прерывания, установив в единицу соответствующий бит RE регистра управления PS/2. Далее кратковременно нажимайте любую клавишу клавиатуры и наблюдайте состояние регистра управления. В случае необходимости повторите это действие несколько раз. Определите, при каком условии устанавливается разряд RI его регистра управления, соответствующий сформированному запросу прерывания. Определите, при каком условии запрос прерывания снимается.

  4. Запустите программу. Нажмите и удерживайте любую клавишу клавиатуры. После обработки нескольких байт (по умолчанию задано 20), программа выведет в терминальное окно AMP (IFPGAMP) временные характеристики клавиатуры. Определите, какие временные параметры соответствуют параметрам, установленным по умолчанию. Отразите в отчете ваше заключение.

  5. Остановите выполнение программы, используя команду Stop из меню Actions или соответствующую клавишу инструментальной панели. Откройте вкладку Memory и наблюдайте в ОП три массива. Начиная с адреса 0x1000 содержатся таймкоды, соответствующие моментам заполнения буфера FIFO. С адреса 0x2000 скан-коды, отправляемые клавиатурой. С адреса 0x3000 размещается массив, сформированный программой. Он содержит временные интервалы между соседними моментами времени заполнения буфера FIFO.

  6. Измените временные параметры клавиатуры, отправив команду Set Typematic Rate/Delay (0хF3) в порт данных клавиатуры. Следует напомнить, что за байтом команды необходимо отправить байт аргументов, задающий временные характеристики клавиатуры. Установите максимальную частоту повтора символов (Typematic Rate) (30 символов в секунду). Установите счетчик команд на начало программы с помощью команды Restart из меню Actions и повторите выполнение п 5.4. – 5.5. Проверьте, согласуются ли экспериментальные данные с ожидаемыми.

  7. Установите максимальную задержку автоповтора символов (Typematic Delay) (1 секунда). Установите счетчик команд на начало программы с помощью команды Restart из меню Actions и повторите выполнение п 5.4. – 5.5. Проверьте, согласуются ли экспериментальные данные с ожидаемыми.

  8. Установите частоту повтора и задержку автоповтора символов в соответствии с вашим вариантом, и повторите выполнение п 5.4. – 5.5. Проверьте, согласуются ли экспериментальные данные с ожидаемыми. Результат занесите в отчет.

Отчетные материалы

Отчет должен включать в себя:

  1. Цель работы.
  2. Краткие сведения из теоретической части.
  3. Информацию по выполнению пунктов задания.
  4. Краткое заключение.

Контрольные вопросы

  1. Какой разряд байта аргумента команды Set/Reset LEDs соответствует тому или иному светодиоду клавиатуры?
  2. Каким образом был определен размер буфера FIFO контроллера PS/2?
  3. Каким характеристикам соответствуют параметры клавиатуры по умолчанию?
  4. Сколько байт отправляет клавиатура при нажатии и отпускании клавиши в режиме Set#1?
  5. Сколько байт отправляет клавиатура при нажатии и отпускании клавиши в режиме Set#2?
  6. Сколько байт отправляет клавиатура при нажатии и отпускании клавиши в режиме Set#3?
  7. Какое количество байт отправляется клавиатурой при нажатии и отпускании клавиш crl, alt, shift, space, backspace?
  8. Как проверить, в каком режиме находится клавиатура?
  9. Что делает команда Resend?
  10. Как выполняется команда Echo?
  11. Какая минимальная и максимальная задержка автоповтора символов?
  12. Состав байта аргумента команды Set Typematic Rate/Delay.
  13. Какой командой запрашивается ID устройства?
  14. Какой командой запрещается сканирование клавиатуры?
  15. Какой командой разрешается сканирование клавиатуры?

Литература

  1. Ефремов Н. В., Бородин А. А. Инструментальные средства проектирования и отладки цифровых систем на программируемом кристалле фирмы «Altera». Учебное пособие. — М.: ФГБОУ ВПО МГУЛ, 2012. — 151 с.
  2. Клавиатура PS/2. [Электронный ресурс] // OSDev Wiki. — URL: https://wiki.osdev.org/PS/2_Keyboard.
  3. Контроллер интерфейса PS/2. [Электронный ресурс] // OSDev Wiki. — URL: https://osdev.fandom.com/ru/wiki/%D0%9A%D0%BE%D0%BD%D1%82%D1%80%D0%BE%D0%BB%D0%BB%D0%B5%D1%80_%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81%D0%B0_PS/2#%D0%98%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_PS/2.
  4. Altera Monitor Program. [Электронный ресурс] // URL: http://www-ug.eecg.toronto.edu/desl/docs/Monitor_tutorial.pdf.

Приложение 1

Таблица для записи скан-кодов клавиш:

КлавишаSet #1Set #2Set #3
Коды нажатияКоды отпусканияКоды нажатияКоды отпусканияКоды нажатияКоды отпускания
1
2
3
4
5
6
7
8
9
0
Enter
Backspace
Left Alt
Right Alt
Left Ctrl
Right Ctrl
Ф
А
М
И
Л
И
Я

Приложение 2

Варианты заданий:

№ ВариантаКомбинация светодиодов для п. 2.7.Комбинация частоты и задержки для п. 5.8.
1CapsLock и NumLockTypematic Rate: 24; Delay: 0,25
2CapsLock и ScrollLockTypematic Rate: 20,7; Delay: 0,75
3ScrollLock и NumLockTypematic Rate: 16; Delay: 1
4CapsLock и NumLockTypematic Rate: 12; Delay: 0,25
5CapsLock и ScrollLockTypematic Rate: 10; Delay: 0,5
6ScrollLock и NumLockTypematic Rate: 8; Delay: 1
7CapsLock и NumLockTypematic Rate: 5; Delay: 0,25
8CapsLock и ScrollLockTypematic Rate: 2; Delay: 1
9ScrollLock и NumLockTypematic Rate: 3; Delay: 0,75
10CapsLock и NumLockTypematic Rate: 4; Delay: 0,25
11CapsLock и ScrollLockTypematic Rate: 15; Delay: 1
12ScrollLock и NumLockTypematic Rate: 6; Delay: 0,5

Приложение 3

В зависимости от версии процессорной системы могут использоваться другие адреса портов периферийных устройств. Для процессорной системы DE 2-115 Media Computer используются адреса:

.equ PS2_DATA, 0x10000100
.equ LED_R, 0x10000000
.equ LED_G, 0x10000010
.equ HEX_DATA_L, 0x10000020
.equ HEX_DATA_H, 0x10000030
.equ LSD_ADDR_COMM, 0x10003050
.equ LSD_ADDR_VAL, 0x10003051
.equ TIMER, 0x10002000
.equ JTAG_UART_BASE, 0x10001000

Листинг 1

Исходный файл программы PS2_from_FIFO_to_HEX.s

.equ PS2_DATA, 0xFF200100
.equ HEX_DATA_L, 0xFF200020
.equ HEX_DATA_H, 0xFF200030
# Задержка перед сбросом состояния 7-сегментных индикаторов.
.equ HEX_RESET_DELAY, 0x7A120
# Значения для отображения 16-х цифр на 7-сегментных индикаторах.
.equ HEX_0, 0x3F
.equ HEX_1, 0x06
.equ HEX_2, 0x5B
.equ HEX_3, 0x4F
.equ HEX_4, 0x66
.equ HEX_5, 0x6D
.equ HEX_6, 0x7D
.equ HEX_7, 0x07
.equ HEX_8, 0x7F
.equ HEX_9, 0x6F
.equ HEX_A, 0x77
.equ HEX_B, 0x7C
.equ HEX_C, 0x39
.equ HEX_D, 0x5E
.equ HEX_E, 0x79
.equ HEX_F, 0x71

.text
.global _start
_start:
# Начальный адрес стека.
movia sp, 0x3FFFFFC
# Очистка 7-сегментых индикаторов.
call CLEAR_HEX_DISPLAY
# Сброс состояния 7-сегментных индикаторов.
reset_hex:
# Данные буфера FIFO, отображаемые на 7-сегментных индикаторах.
mov r8, zero
# Количество байт данных, отображаемых на 7-сегментных индикаторах.
mov r9, zero
reset_hex_delay:
# Задержка перед сбросом состояния 7-сегментных индикаторов.
movia r10, HEX_RESET_DELAY
read_loop:
# Сброс состояния, если задержка равна 0.
ble r10, zero, reset_hex
# Чтение регистра PS2_Data в r2.
call READ_PS2_DATA
# Выделение 15 бита RVALID.
srli r11, r2, 15
andi r11, r11, 1
# Декремент задержки.
subi r10, r10, 1
# Ожидание установки RVALID в 1.
beq r11, zero, read_loop
# Выделение поля (байта) DATA.
andi r6, r2, 0xFF
# Добавление байта к отображаемым данным буфера FIFO.
mov r4, r8
mov r5, r9
call APPEND_BYTE
mov r8, r2
mov r9, r3
# Очистка 7-сегментных индикаторов.
call CLEAR_HEX_DISPLAY
# Вывод данных буфера FIFO на 7-сегментные индикаторы.
mov r4, r8
# Кол-во 16-х цифр = кол-во байт данных * 2.
muli r5, r9, 2
# Вывод на индикаторы.
call HEX_DISPLAY
br reset_hex_delay

/* Возвращает значение регистра PS2_Data в r2. */
READ_PS2_DATA:
movia r2, PS2_DATA
ldwio r2, (r2)
ret

/* Добавляет байт в конец (левую часть) исходного значения. */
APPEND_BYTE:
# Сохранение регистров.
stw r8, (sp)
mov r2, r4
mov r3, r5
movi r8, 3
# Если длина последовательности < 4, пропускаем смещение.
bleu r3, r8, APPEND_BYTE_BODY
# Смещаем последовательность влево.
srli r2, r2, 8
# Декрементируем длину последовательности.
subi r3, r3, 1
APPEND_BYTE_BODY:
# Определяем смещение.
muli r8, r3, 8
# Смещаем добавляемый байт влево.
sll r8, r6, r8
# Добавляем байт к значению.
or r2, r2, r8
# Инкрементируем длину последовательности.
addi r3, r3, 1
# Восстановление регистров.
ldw r8, (sp)
ret

/* Очищает 7-сегментные индикаторы. */
CLEAR_HEX_DISPLAY:
# Сохранение регистров.
stw r8, (sp)
# Очистка первой четверки индикаторов.
movia r8, HEX_DATA_L
stwio zero, (r8)
# Очистка первой четверки индикаторов.
movia r8, HEX_DATA_H
stwio zero, (r8)
# Восстановление регистров.
ldw r8, (sp)
ret

/* Возвращает адрес 7-сегментного индикатора. */
HEX_DISPLAY_ADDR:
# Сохранение регистров.
stw r8, (sp)
stw r9, -4(sp)
# Маска на 3 бита (7 = 0b111).
andi r8, r4, 7
# Выбор регистра данных.
movi r9, 3
bgtu r8, r9, HEX_DISPLAY_ADDR_H
HEX_DISPLAY_ADDR_L:
movia r2, HEX_DATA_L
add r2, r2, r4
br HEX_DISPLAY_ADDR_END
HEX_DISPLAY_ADDR_H:
movia r2, HEX_DATA_H
add r2, r2, r4
subi r2, r2, 4
HEX_DISPLAY_ADDR_END:
# Восстановление регистров.
ldw r8, (sp)
ldw r9, -4(sp)
ret

/* Выводит цифры на индикаторы. */
HEX_DISPLAY_DIGIT:
# Сохранение регистров.
stw ra, (sp)
stw r2, -4(sp)
stw r8, -8(sp)
stw r9, -12(sp)
subi sp, sp, 16
# Получение адреса индикатора в r2.
mov r8, r4
mov r4, r5
call HEX_DISPLAY_ADDR
mov r4, r8
# Маска на 16-ю цифру.
andi r8, r4, 0xF
# Получение значения для отображения цифры на индикаторе.
movia r9, HEX_DIGITS
add r8, r8, r9
ldb r8, (r8)
call HEX_SAVE
# Отображение последовательности цифр на индикаторе.
stwio r8, (r2)
# Восставновление регистров.
addi sp, sp, 16
ldw ra, (sp)
ldw r2, -4(sp)
ldw r8, -8(sp)
ldw r9, -12(sp)
ret

/* Выводит значение на 7-сегментные индикаторы. */
HEX_DISPLAY:
# Сохранение регистров.
stw ra, (sp)
stw r4, -4(sp)
stw r5, -8(sp)
stw r8, -12(sp)
subi sp, sp, 16
# r8 хранит кол-во отображаемых цифр.
mov r8, r5
# r5 хранит текущий номер индикатора.
mov r5, zero
HEX_DISPLAY_LOOP:
# Цикл отображения цифр.
beq r5, r8, HEX_DISPLAY_END
call HEX_DISPLAY_DIGIT
srli r4, r4, 4
addi r5, r5, 1
br HEX_DISPLAY_LOOP
HEX_DISPLAY_END:
# Восстановление регистров.
addi sp, sp, 16
ldw ra, (sp)
ldw r4, -4(sp)
ldw r5, -8(sp)
ldw r8, -12(sp)
ret

/* Возвращает последовательность значений для отображения на индикаторах */
HEX_SAVE:
# Сохранение регистров.
stw ra, (sp)
stw r4, -4(sp)
subi sp, sp, 8
# Определяем смещение для записи значения в последовательность.
mov r4, zero
beq r5, zero, HEX_SAVE_0
movi r4, 1
beq r5, r4, HEX_SAVE_1
movi r4, 5
beq r5, r4, HEX_SAVE_1
movi r4, 2
beq r5, r4, HEX_SAVE_2
movi r4, 6
beq r5, r4, HEX_SAVE_2
movi r4, 3
beq r5, r4, HEX_SAVE_3
movi r4, 7
beq r5, r4, HEX_SAVE_3
movi r4, 4
beq r5, r4, HEX_SAVE_0
HEX_SAVE_0:
mov r12, zero
or r12, r12, r8
br HEX_SAVE_EX
HEX_SAVE_1:
slli r8, r8, 8
or r12, r12, r8
br HEX_SAVE_EX
HEX_SAVE_2:
slli r8, r8, 16
or r12, r12, r8
br HEX_SAVE_EX
HEX_SAVE_3:
slli r8, r8, 24
or r12, r12, r8
HEX_SAVE_EX:
mov r8, r12
# Восстановление регистров.
addi sp, sp, 8
ldw ra, (sp)
ldw r4, -4(sp)
ret

.data
HEX_DIGITS:
.byte HEX_0, HEX_1, HEX_2, HEX_3, HEX_4, HEX_5, HEX_6, HEX_7, HEX_8, HEX_9, HEX_A, HEX_B, HEX_C, HEX_D, HEX_E, HEX_F

.end

Листинг 2

Исходный файл программы PS2_From_FIFO_to_OP_and_LCD.s

.equ PS2_DATA, 0xFF200100
.equ LED_R, 0xFF200000
.equ LED_G, 0xFF200010
.equ LSD_ADDR_COMM, 0xFF203050
.equ LSD_ADDR_VAL, 0xFF203051
# Адреса памяти для записи данных.
.equ SDRAM, 0x1000
.equ SDRAM_EDGE, 0x2000
.equ TEMP, 0x5000
# Команда установки курсора в первую строку.
.equ LSD_POINTER_FIRST,0x80
# Команда установки курсора во вторую строку.
.equ LSD_POINTER_SECOND,0xc0
# Команда очистки экрана.
.equ LSD_DROP,0b00000001
# Значение пробела в аски.
.equ ASCII_WHITESPACE,0x20
# Значение нуля в аски.
.equ ASCII_NULL,0x30
# Значение буквы А в аски.
.equ ASCII_A,0x37

.text
.global _start
_start:
# Начальный адрес стека.
movia sp, 0x3fffffC
# Очистка сегмента памяти для записи данных из буфера.
movia r4, SDRAM
movia r5, SDRAM_EDGE
call CLEAR_MEM
# Очистка LCD.
call CLEAR
# Адрес порта PS2.
movia r3, PS2_DATA
# Адреса LED.
movia r7, LED_G
# Количество символов, отображаемых на LCD.
mov r11, r0
# Адрес ОП для записи данных из буфера FIFO.
movi r12, SDRAM
# Адрес ОП для вывода на LCD.
movi r13, SDRAM
# Адрес ОП для временного хранения данных.
movi r15, TEMP
read_ps2_buffer:
# Погасить LED.
stwio zero, (r7)
# Маска 15 бита для проверки порта.
movia r4, 0x8000
# Маска для отсечения 1 байта регистра.
movia r6, 0xff
# Чтение PS2_DATA.
ldwio r10, (r3)
# Выделение 15 бита для проверки наличия записи.
and r5,r10,r4
bne r5, r4, read_ps2_buffer
# Помещаем в r2 PS2_DATA * маска(0xff).
and r2, r10, r6
# Записываем r2 -> ОП с адресом r12.
stb r2, (r12)
# Смещаемся на 1 байт.
addi r12, r12, 1
mov r5, r10
srli r5, r5, 16
# Зажигание диодов.
stwio r5, (r7)
# Выводим на строку максимум 5 байт.
movi r20, 5
beq r11, r20, SHIFT
# Выводим строку со следующим байтом ОП.
addi r11, r11, 1
br PRINT
SHIFT:
# Если строка LCD заполнена, то смещаем адрес ОП для вывода на LCD.
addi r13, r13, 1
PRINT:
mov r5, r13
mov r6, r11
call PRINTLCD
br read_ps2_buffer

/* Очистка области памяти. */
CLEAR_MEM:
# Сохранение регистра.
stw r4, (sp)
CLEAR_MEM_LOOP:
stw zero, (r4)
addi r4, r4, 4
blt r4, r5, CLEAR_MEM_LOOP
# Восстановление регистра.
ldw r4, (sp)
ret

/* Вывод на LCD содержимого области памяти. */
PRINTLCD:
# Сохранение регистров.
stw r9, (sp)
stw r11, -4(sp)
stw r7, -8(sp)
stw r5, -12(sp)
stw r4, -16(sp)
stw r10,-20(sp)
stw r12,-24(sp)
stw r13,-28(sp)
stw r14,-32(sp)
stw r15,-36(sp)
stw r16,-40(sp)
stw ra, -44(sp)
subi sp, sp, 48
# Очистка LCD.
call CLEAR
# Обнуляем счетчик.
movia r4, 0
movia r14,0
mov r16,r15
LOOP:
# Загрузка слова из области памяти.
ldb r7, (r5)
# Если счетчик = количеству символов, то выходим.
beq r4, r6, DONE
stb r7, (r16)
# Увеличиваем счетчик 1.
addi r4, r4, 1
# Считываем следующий байт.
addi r5, r5, 0x1
movia r10,5
bge r4,r10,ONLAST
# Записываем следующий байт.
addi r16, r16, 0x1
ONLAST:
call RENDER
br LOOP
DONE:
# Восстановление регистров.
addi sp, sp, 48
ldw r9, (sp)
ldw r11, -4(sp)
ldw r7, -8(sp)
ldw r5, -12(sp)
ldw r4, -16(sp)
ldw r10,-20(sp)
ldw r12,-24(sp)
ldw r13,-28(sp)
ldw r14,-32(sp)
ldw r15,-36(sp)
ldw r16,-40(sp)
ldw ra, -44(sp)
ret

/* Получаем ASCII значение символа. */
GETASCII:
movia r10,0x9
# Если значение больше 9, приводим к букве, иначе к цифре.
ble r9, r10,TONUM1
# Добавляем A в ASCII чтобы получить представления значения ячейки памяти в ASCII.
addi r9, r9, ASCII_A
ret
TONUM1:
# Добавляем 0 в ASCII чтобы получить представления значения ячейки памяти в ASCII.
addi r9, r9, ASCII_NULL
ret

/* Очистка LCD экрана */
CLEAR:
# Кладем в r9 команду очищения экрана.
movia r9,LSD_DROP
# Кладем в r11 адрес команд LCD.
movia r11, LSD_ADDR_COMM
stbio r9, (r11)
ret

/* Вывод в конце второй строки LCD количества выведенных символов. */
PRINTCOUNT:
mov r7,r6
mov r12,r6
movia r13,0
GETNUMBERSTOSTACK:
# Если все проверили - выходим.
beq r7,zero, MOVECURSOR
movia r10,10
# Получаем остаток от деления на 10.
div r7,r7,r10
mul r9,r7,r10
sub r9,r12,r9
mov r12,r7
stw r9,(sp)
subi sp, sp, 4
addi r13,r13,1
br GETNUMBERSTOSTACK
MOVECURSOR:
movia r10,LSD_POINTER_SECOND
addi r10,r10,0x10
sub r10,r10,r13
# Кладем в r9 команду сдвига.
mov r9, r10
# Сдвигаем экран.
movia r11, LSD_ADDR_COMM
# Cдвигаем курсор.
stbio r9, (r11)
COUNTPRINT:
# Если все проверили - выходим.
beq r13,zero, END
addi sp, sp, 4
ldw r9,(sp)
addi r9, r9, ASCII_NULL
# Получаем адрес индикатора.
movia r11, LSD_ADDR_VAL
# Печатаем цифру.
stbio r9, (r11)
addi r13,r13,-1
br COUNTPRINT
END:
# Кладем в r9 команду сдвига.
movia r9, LSD_POINTER_FIRST
# Cдвигаем экран.
movia r11, LSD_ADDR_COMM
# Cдвигаем курсор.
stbio r9, (r11)
ret

RENDER:
# Сохранение регистра.
stw ra, (sp)
subi sp, sp, 4
# Очистка LCD.
call CLEAR
# Вывод количества символов.
call PRINTCOUNT
movia r13,0
mov r12,r15
RENDERLOOP:
movia r10,5
beq r13, r4, ENDRENDER
beq r13,r10, ENDRENDER
ldb r7, (r12)
# Получаем второй символ байта данных, с помощью маски.
andi r9, r7, 0xf0
# Сдвиг вправо чтобы получить нужный код.
srli r9, r9, 0x4
# Получаем ASCII значение.
call GETASCII
# Получаем адрес индикатора.
movia r11, LSD_ADDR_VAL
# Печатаем цифру.
stbio r9, (r11)
# Получаем первый символ байта данных, с помощью маски.
andi r9, r7, 0xf
# Получаем ASCII значение.
call GETASCII
# Получаем адрес индикатора.
movia r11, LSD_ADDR_VAL
# Печатаем цифру.
stbio r9, (r11)
# Cимвол пробела.
movia r9, ASCII_WHITESPACE
# Печатаем цифру.
stbio r9, (r11)
# Задержка.
addi r12,r12,1
addi r13,r13,1
br RENDERLOOP
ENDRENDER:
# Задержка.
movia r9, 0
DELAY:
# Вычитаем в цикле 1.
addi r9,r9,-1
# Если стало равно 0, то переходим к следующей цифре.
bgt r9,zero, DELAY
movia r10,5
movia r7,0
blt r4,r10,RETURNRENDER
movia r10,4
mov r12,r15
SWAP:
beq r7,r10,RETURNRENDER
addi r13,r12,0x1
ldb r9, (r13)
stb r9, (r12)
addi r12,r12,0x1
addi r7,r7,1
br SWAP
RETURNRENDER:
# Восстановление регистра.
addi sp, sp, 4
ldw ra, (sp)
ret

.end

Листинг 3

Исходный файл программы PS2_Typematic_rate_Delay_to_JTAG.s

.equ PS2_DATA, 0xFF200100
.equ TIMER, 0xFF202000
.equ JTAG_UART_BASE, 0xFF201000
.equ DATA_MASK, 0xff
.equ RVALID_MASK, 0x8000
.equ NUM_OF_BYTES, 20
# Адрес памяти для записи данных.
.equ SDRAMTIME_START, 0x1000
# Смещение для адреса данных.
.equ DELTA, 0x1000

.text
.org 0x20
handler:
# Сохраняем регистры.
stw r8, (sp)
stw r9, -4(sp)
stw r10, -8(sp)
stw r11, -12(sp)
# Если прерывание не внешнее, то выходим.
rdctl r10, ctl4
beq r10, r0, exit
# Декрементируем регистр ea на 1 команду.
subi ea, ea, 4
# Если прерывание не от клавиатуры, то выходим.
andi r11, r10, 0x80
beq r11, r0, exit
# Игнорируем младший байт 0xFA (ACK) or 0xFE (Resend).
movia r8, PS2_DATA
ldwio r9, (r8)
andi r9, r9, DATA_MASK
# Если 0xFA (ACK) or 0xFE (Resend), то выходим.
movia r10, 0xFA
movia r11, 0xFE
beq r9, r10, exit
beq r9, r11, exit
# Если BYTES_LEFT = 0, то запрещаем прерывания от клавиатуры и выходим.
movia r9, PS2_DATA
movia r8, BYTES_LEFT
ldw r10, (r8)
bne r10, r0, continue
stbio r0, 4(r9)
br exit
continue:
# Уменьшаем счетчик обработанных байт.
subi r10, r10, 1
stw r10, (r8)
# Получет значение времени таймера.
movia r8, TIMER
stwio r0, 0x10(r8)
ldhuio r10, 0x10(r8)
ldhuio r11, 0x14(r8)
# Записываем значение времени таймера в ОП.
movia r8, SDRAMTIME
ldw r9, (r8)
sth r10, (r9)
sth r11, 2(r9)
# Смещаем адрес SDRAMTIME на 4 байта.
addi r9, r9, 4
stw r9, (r8)
# Получаем значение байта PS2 в r10.
movia r8, PS2_DATA
ldwio r10, (r8)
andi r10, r10, DATA_MASK
# Записываем значения байта в ОП.
movia r8, SDRAMKEY
ldw r9, (r8)
stw r10, (r9)
# Смещаем адрес SDRAM на 4 байта.
addi r9, r9, 4
stw r9, (r8)
exit:
# Восстанавливаем регистры.
ldw r8, (sp)
ldw r9, -4(sp)
ldw r10, -8(sp)
ldw r11, -12(sp)
eret

.global _start
_start:
# Начальный адрес стека.
movia sp, 0x3fffffC
# Адреса сегментов памяти.
movi r8, SDRAMTIME_START
addi r9, r8, DELTA
addi r10, r9, DELTA
addi r11, r10, DELTA
# Инициализация переменных.
movia r12, SDRAMTIME
stw r8, (r12)
movia r12, SDRAMKEY
stw r9, (r12)
movia r12, SDRAMDIFF
stw r10, (r12)
movia r12, BYTES_LEFT
movia r13, NUM_OF_BYTES
stw r13, (r12)
# Очистка сегментов памяти.
mov r4, r8
mov r5, r9
call CLEAR_MEM
mov r4, r9
mov r5, r10
call CLEAR_MEM
mov r4, r10
mov r5, r11
call CLEAR_MEM
# Адрес регистра PS2_Data
movia r2, PS2_DATA
# Подготовка масок.
movia r4, RVALID_MASK
movia r6, DATA_MASK
flush_buffer_init:
# Очистка буфера.
ldwio r10, (r2)
and r5,r10,r4
beq r5, r4, flush_buffer_init
# Разрешаем прерывания.
movi r15,0x80
wrctl ienable,r15
movi r7,1
wrctl status,r7
# Разрешить прерывания от клавиатуры в контроллере.
movi r9, 1
stbio r9, 0x4(r2)
# Настройка таймера.
movia r8, TIMER
# Команда STOP.
movi r3, 0b1000
# Остановка таймера.
sthio r3, 4(r8)
sthio r0, (r8)
# Максимальное значение.
movia r18, 0xffff
sthio r18,8(r8)
sthio r18,0xC(r8)
# Команда START.
movi r3, 0b0100
# Запуск таймера.
sthio r3, 4(r8)
WAIT_LOOP:
# Ожидание, пока прочитаются все байты.
movia r3, BYTES_LEFT
ldw r3, (r3)
bne r3, r0, WAIT_LOOP
# Адрес начала сегмента памяти с таймкодами.
movia r4, SDRAMTIME_START
# Адрес конца сегмента памяти с таймкодами.
movia r2, SDRAMTIME
ldw r2, (r2)
subi r9, r2, 4
# Адрес сегмента памяти с задержками.
addi r5, r4, DELTA
addi r5, r5, DELTA
LOOP_DIFF:
# Запись задержек клавиатуры.
ldw r6, (r4)
addi r4, r4, 4
ldw r7, (r4)
sub r8, r6, r7
movia r10, 50000
divu r8, r8, r10
stw r8, (r5)
addi r5, r5, 4
bltu r4, r9, LOOP_DIFF
# Вывод typematic delay и typematic rate в консоль.
movia r4, TYPEMATIC_DELAY_STR
# Отправляем строку в JTAG UART.
call LOG_STR
movia r8, SDRAMTIME_START
addi r8, r8, DELTA
addi r8, r8, DELTA
ldw r4, (r8)
# Печать символов.
call LOG_NUM
# Печать пробела.
call LOG_LINE
movia r4, TYPEMATIC_RATE_STR
# Отправляем строку в JTAG UART.
call LOG_STR
addi r8, r8, 4
ldw r4, (r8)
movi r5, 1000
div r4, r5, r4
# Печать символов.
call LOG_NUM
# Печать пробела.
call LOG_LINE
br _start

/* Очистка области памяти. */
CLEAR_MEM:
# Сохранение регистра.
stw r4, (sp)
CLEAR_MEM_LOOP:
stw zero, (r4)
addi r4, r4, 4
blt r4, r5, CLEAR_MEM_LOOP
# Восстановление регистра.
ldw r4, (sp)
ret

/* Отправляет символ из r4 в JTAG UART. */
LOG_CHAR:
# Сохранение регистров.
stw r8, (sp)
stw r9, -4(sp)
stw r10, -8(sp)
# Загрузка базового адреса JTAG UART в r8.
movia r8, JTAG_UART_BASE
# Чтение регистра управления.
ldwio r9, 4(r8)
# Выделение поля WSPACE (доступное место в буфере записи).
srli r9, r9, 16
# Запись символа в поле DATA регистра данных.
stbio r4, (r8)
# Восстановление регистров.
ldw r8, (sp)
ldw r9, -4(sp)
ldw r10, -8(sp)
ret

/* Отправляет число из r4 в JTAG UART. &/
LOG_NUM:
# Сохранение регистров.
stw ra, (sp)
stw r4, -4(sp)
stw r8, -8(sp)
stw r9, -12(sp)
stw r10, -16(sp)
stw r11, -20(sp)
subi sp, sp, 24
mov r8, r4
movi r9, 10
# Изначальный адрес стека в r11.
mov r11, sp
# Проверка является ли число положительным.
bge r8, zero, LOG_NUM_POSITIVE
# Если число отрицательное, печатаем минус.
movi r4, 0x2D
call LOG_CHAR
# И делаем число положительным.
muli r8, r8, -1
LOG_NUM_POSITIVE:
# Получаем остаток от деления на 10 в r4.
div r10, r8, r9
mul r4, r10, r9
sub r4, r8, r4
# Добавляем цифру в стек.
stw r4, (sp)
subi sp, sp, 4
# Цикл пока результат деления != 0.
mov r8, r10
bne r8, zero, LOG_NUM_POSITIVE
LOG_NUM_DIGITS:
# Печать цифр.
addi sp, sp, 4
ldw r4, (sp)
addi r4, r4, 0x30
call LOG_CHAR
bne sp, r11, LOG_NUM_DIGITS
# Восстановление регистров.
addi sp, sp, 24
ldw ra, (sp)
ldw r4, -4(sp)
ldw r8, -8(sp)
ldw r9, -12(sp)
ldw r10, -16(sp)
ldw r11, -20(sp)
ret

/* Отправляет строку по адресу r4 в JTAG UART. */
LOG_STR:
# Сохранение регистров.
stw ra, (sp)
stw r4, -4(sp)
stw r8, -8(sp)
subi sp, sp, 12
# Адрес текущего символа в r8.
mov r8, r4
LOG_STR_LOOP:
# Печать символов.
ldb r4, (r8)
beq r4, zero, LOG_STR_END
call LOG_CHAR
addi r8, r8, 1
br LOG_STR_LOOP
LOG_STR_END:
# Восстановление регистров.
addi sp, sp, 12
ldw ra, (sp)
ldw r4, -4(sp)
ldw r8, -8(sp)
ret

/* Отправляет символ перехода на новую строку в JTAG UART. */
LOG_LINE:
# Сохранение регистров.
stw ra, (sp)
stw r4, -4(sp)
subi sp, sp, 8
# Печать пробела.
movi r4, 0xA
call LOG_CHAR
# Восстановление регистров.
addi sp, sp, 8
ldw ra, (sp)
ldw r4, -4(sp)
ret

.data
SDRAMTIME:
.word 0
SDRAMKEY:
.word 0
SDRAMDIFF:
.word 0
BYTES_LEFT:
.word 0
TYPEMATIC_DELAY_STR:
.asciz "Typematic delay: "
TYPEMATIC_RATE_STR:
.asciz "Typematic rate: "

.end

Чеклист выполнения

  • Работа выполнена
  • Отчет подготовлен