Как стать автором
Обновить

Самодельная клавиатура PS/2 – это просто

Время на прочтение14 мин
Количество просмотров15K

Снова здравствуйте, дамы и господа. Наш Отдел Перспективных Разработок обещал вас порадовать очередной странной вещью, и потому продолжает свой цикл статей о старых, но иногда полезных устройствах.

Суть сегодняшнего предмета для изучения опять сугубо академическая. Хотя бы просто потому, что стандарт PS/2 уже фактически не нужен, и разработчики железа оставляют его порой разве что для возможности 1.5 старых ценителей подключить свои древние замызганные, но такие привычные клавиатуры и мышки.

Однако, сама концепция клавиатуры как устройства ввода не меняется в зависимости от протокола связи с основным компьютерным устройством – будь то PS/2 или USB. Это вся та же матрица клавиш, которые опрашиваются по пересечению контактной сетки, и контроллер клавиатуры, в зависимости от совпавшего пересечения, отправляет в порт компьютера кодированный скан-код клавиши, как при нажатии так и при отпускании.

Лично я взялся за подобную разработку по одной простой причине: Потребовалась клавиатура для самодельного компьютера Z80-MBC2. Схемы видеокарты вывода для него собираются на достаточно мощных чипах (особенно, по сравнению с самим компьютером, что довольно иронично) stm32 или esp32, и которые вполне тянут параллельно с выводом информации и обработку ввода, например, с порта PS/2.

Сам компьютер довольно небольших размеров, плюс, мне захотелось сделать клавиатуру именно компактных карманных размеров (для реализации своего плана создания впоследствии кибердеки на базе упомянутого Z80-MBC2), а все стандартные полноразмерные клавиатуры из магазина слишком огромные.

Законченных же схем клавиатур выложенных энтузиастами в интернетах не найти, особенно, PS/2.

Ну что же, сделаем что-то сами.

Теория

Схема работы клавиатурного процессора складывается по следующей схеме:

  • Циклично опрашивая сетку пересечений клавишных проводников, определить пересечение, возникающее при нажатой клавише, отправить ее код через порт и запомнить клавишу;

  • При определении факта ранее запомненной и отпущенной клавиши, отправить ее код отпускания;

  • Ожидать данные с порта на прием, и при их поступлении, интерпретировать полученные коды управления для принудительного задания параметров клавиатуры – тайм-аута повторения клавиш, включения/выключения светодиодов и т.п.

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

Протокол PS/2 предполагает передачу данных по двухпроводной шине (Как обычно, DATA и CLOCK) стробами из последовательностей битов.

Частота линии CLOCK, по разным источникам, может приниматься от 10 до 16.7кГц. Точнее и подробнее можно почитать про это в описаниях к драйверу и спецификациях к микросхеме контроллера 8048/8042, который изначально и предоставлял реализацию порта PS/2 для устройств. Сейчас же он эмулируется другими средствами, и варианты частот CLOCK могут варьироваться в еще более широких пределах. Лично я, в процессе написания статьи, игрался уже с готовым устройством и повышал частоту CLOCK до 20кгц, и всё вполне работало.

Для передачи по DATA от клавиатуры в компьютер потребуется соблюсти следующие требования:

  • Ожидать, пока по линии CLOCK не появится логическая «1» в районе 50 мкс, далее, последовательно;

  • Передать по DATA старт-бит (всегда 1);

  • Передать последовательно 8 бит данных;

  • Передать бит четности;

  • Передать стоп-бит (всегда 1).

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

  • CLOCK ставится компьютером в «0» на 100 мкс;

  • Компьютером устанавливается по DATA логический 0;

  • Компьютер устанавливает по CLOCK логическую 1, которая фиксируется клавиатурой как старт-бит;

  • Далее клавиатура сама формирует сигнал CLOCK, а компьютер передает согласно нему последовательность из 8 бит, затем бит четности, и стоп-бит;

  • Клавиатура посылает по DATA бит 0, обозначая подтверждение конца приема.

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

Касаясь отправки данных самой клавиатурой, здесь все просто:

У каждой клавиши есть свой скан-код нажатия и скан-код отпускания. В классическом случае скан-коды однобайтные при нажатии, но, при отпускании используется двухбайтный код, состоящий из флага отпускания F0 и скан-кода нажатия той же клавиши. Некоторые специальные клавиши имеют многобайтные коды, в основном, двухбайтные. В отдельных случаях длина последовательности может составлять даже 8 байт (например, как у клавиши Pause/Break). Коды специальных клавиш, как правило, начинаются с первого байта E0.

Весь список скан-кодов нет смысла засовывать сюда в статью, его всегда можно найти в интернетах.

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

На каждый принятый байт клавиатура должна ответить. Ответом может быть в случае успешного приема, FE в случае неуспешного (например, несовпадающего CRC), дабы компьютер повторил отправку, и FC в случае иной (какой???) ошибки.

Первый из принятых байтов интерпретируется как команда:

ED – сообщает о том, что следующий байт содержит байт состояния индикаторов (светодиодов).

F3 – Сообщает о том, что следующий байт кодирует длительность повтора нажатой клавиши.

F2 – Запрашивает двухбайтный код идентификатора оборудования. Данный код прилетает в момент идентификации оборудования компьютером и его не лишне было бы обрабатывать чтобы сообщить, что клавиатура подключена. В ответ на команду, собственно, клавиатура должна отправить двухбайтный код оборудования, например, AB83. Гугл говорит, что это answer of a standard Cherry keyboard, ну, пусть так и будет.

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

Подробно все коды команд нет смысла рассматривать в рамках статьи, их и их обработку можно найти, например, в интернетах, в библиотеке для работы с PS/2 к какому-нибудь контроллеру. В моем случае, я так же не писал всю обработку с нуля, а за основу взял первую попавшуюся библиотеку PS2Dev. Но, об этом далее.

Аппаратная часть

Реализацию в железе я начал с того, что обычным образом нашел в столе – микросхемку контроллера Attiny2313A и жмень сдвиговых регистров 74HC595 и 74HC165.

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

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

Поскольку клавиш для адресации предполагалось больше чем 64 (т.е. 8 ног выходного регистра с пересечением 8 ног входного регистра), я применил по два составных 16-битных регистра, что позволило адресовать уже 256 клавиш (т.е. 16 ног на 16 ног). Впоследствии при проектировании клавиатуры выяснилось, что столько клавиш просто не нужно, и я добавил возможность варьировать длину входного регистра – т.е. применять 8-битный регистр из одной микросхемы либо 16-битный из двух. В прошивке же полная поддержка всех 256 клавиш вполне поддерживается, потому, плату клавиатуры всегда можно перепроектировать для поддержки большего количества клавиш.

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

Схема под катом

На схеме в левой части два последовательно соединенных регистра 74HC595 образуют 16-битный регистр линий выходного сигнала.

В нижней части схемы представлен входной 16-битный регистр, собранный из 74HC165. У данного регистра имеется переключатель: Соединяя переключатель по варианту 1 или по варианту 2 можно выбирать либо соединение двух микросхем, полноценный 16-битный регистр и поддержку до 256 клавиш, либо 1 микросхему, 8-битный регистр и поддержку до 128 клавиш (обычно, больше и не нужно). Во втором случае второй чип 74HC165 и вторую резисторную сборку 10К на плату можно не ставить. Переключение данного переключателя подразумевает изменение параметра в прошивке (#define CHIP_J_COUNT).

Посередине находится собственно сетка пересекающихся линий проводников, на пересечения которой сажаются отдельные клавиши.

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

  • В цикле последовательно устанавливать один бит на выходном регистре, включая только одну строчку сетки пересечений клавиш, остальные опуская в «0».

  • За каждую установленную строчку считать сразу весь входной регистр (столбцов сетки) и попытаться определить на нем установленный бит. Если такой бит найден, то, по двумерному массиву кодов клавиш вычислить код нужной нажатой клавиши (первая координата – номер строки по номеру бита в выходном регистре, вторая координата – номер столбца по номеру бита, считанного со входного регистра), отправить ее код в порт PS/2 и запомнить нажатое состояние.

  • Если же при проверке входного регистра в нем по определенному адресу значится «0», а ранее известно, что клавиша с данным адресом была нажата, то, это означает, что клавиша была отпущена. Следовательно, нужно отправить коды отпущенной клавиши и освободить сохраненное состояние нажатой клавиши.

  • Далее контроллер слушает, не прилетело ли входных команд от компьютера (и реагирует), и следует снова по бесконечному циклу.

Полная схема готовой клавиатуры в PDF открывается по ссылке, если нужно.

В гитхабе найдете и схемы подключения регистров к Attiny, и плату, и прошивку.

Ах да. Провод PS/2 можно выдрать из обычной клавиатуры. Ну, или, кроме шуток, заказать на АлиЭкспрессе. Я вообще от дохлой шариковой мышки отодрал.

Программная часть

Как ранее упоминал, я не стал писать прошивку с абсолютного нуля, поскольку, вариантов полно в интернете, и использовал первую попавшуюся PS2Dev для Arduino. Но, для маленькой Attiny ее пришлось переработать. Я взял лишь отдельные функции, максимально ускорил их и минимизировал использование памяти как смог. Возможно, кто-то выжал бы из этого больше, тут смотрите сами.

Для своего варианта клавиатуры я использовал лишь два индикаторных светодиода (реально из них нужен только CAPS LOCK, второй просто для чего-нибудь), не использовал дополнительную цифровую панель, не использовал поддержку задержки клавиш (клавиатура предполагалась для самодельного компьютера, и все это просто не нужно). Возможно, попозже чуть доработаю, когда буду проектировать USB-вариант.

Вы вольны собрать для себя любую конфигурацию по желанию.

Прилагаю исходник прошивки, он откомментирован и доступен там же в ГитХабе, если нужно.

Код прошивки Attiny2313A
// https://github.com/Harvie/ps2dev/
// https://codeandlife.com/2013/06/28/minimal-ps2-keyboard-on-attiny2313/

#include <avr/io.h> //подключаем библиотеку аппаратных описаний
#include <util/delay.h> //поключаем библиотеку задержек

//#define KEYMAPSIZE 256

//Битовый массив нажатых клавиш. Если клавиша нажата, то в слове по номеру строки бит по номеру столбца будет установлен
word pr[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

//Для использования 1 чипа 165 оставить значение 8
//Для двух чипов - 16
#define CHIP_J_COUNT 8

//Массив скан-кодов клавиш
const unsigned char keymap[] PROGMEM =
{
  0x76, 0x26, 0x4E, 0x2D, 0x5B, 0x33, 0x22, 0x4A,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x05, 0x25, 0x55, 0x2C, 0x5A, 0x3B, 0x21, 0x59,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x06, 0x2E, 0x5D, 0x35, 0x58, 0x42, 0x2A, 0x14,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x04, 0x36, 0x66, 0x3C, 0x1C, 0x4B, 0x32, 0x11,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x0C, 0x3D, 0x0D, 0x43, 0x1B, 0x4C, 0x31, 0x29,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x0E, 0x3E, 0x15, 0x44, 0x23, 0x41, 0x3A, 0xE0,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x16, 0x46, 0x1D, 0x4D, 0x2B, 0x12, 0x41, 0xE1,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x1E, 0x45, 0x24, 0x54, 0x34, 0x1A, 0x49, 0xE2,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,

  0x03, 0xE3, 0xE4, 0x29, 0x29, 0x29, 0x29, 0x29,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x0B, 0xE5, 0xE6, 0x29, 0x29, 0x29, 0x29, 0x29,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x83, 0xE7, 0xE8, 0x29, 0x29, 0x29, 0x29, 0x29,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x0A, 0xE9, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x01, 0xEA, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x09, 0xEB, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x78, 0xEC, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,
  0x07, 0xED, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29,   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29
};

//Длина максимальной последовательности расширенных кодов для клавиши
#define EXT_K_SIZE 8

//Массив клавиш с расширенными кодами.
//Если в массиве основных кодов выше указан код >= E0, то будет выбрана последовательность
//из данного массива с номером (Номер - E0), т.е., код E0 - первая, E1 - вторая, и т.д.
//Коды 0x00 будут игнорироваться
const unsigned char extcodes[]/*14][EXT_K_SIZE]*/ PROGMEM = {
  0xE0, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //R ALT  (E0)
  0xE0, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //R CTRL (E1)
  0xE1, 0x14, 0x77, 0xE1, 0xF0, 0x14, 0xE0, 0x77,   //PAUSE  (E2)
  0xE0, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //UP     (E3)
  0xE0, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //END    (E4)
  0xE0, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //DOWN   (E5)
  0xE0, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //PG UP  (E6)
  0xE0, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //LEFT   (E7)
  0xE0, 0x12, 0xE0, 0x7C, 0x00, 0x00, 0x00, 0x00,   //PT SCR (E8)
  0xE0, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //RIGHT  (E9)
  0xE0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //INS    (EA)
  0xE0, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //HOME   (EB)
  0xE0, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //PG DN  (EC)
  0xE0, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00    //DEL    (ED)
};

//То же самое для кодов отпускания клавиш
const unsigned char extcanscodes[]/*14][EXT_K_SIZE]*/ PROGMEM = {
  0xE0, 0xF0, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,   //R ALT  (E0)
  0xE0, 0xF0, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,   //R CTRL (E1)
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   //PAUSE  (E2)
  0xE0, 0xF0, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00,   //UP     (E3)
  0xE0, 0xF0, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00,   //END    (E4)
  0xE0, 0xF0, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,   //DOWN   (E5)
  0xE0, 0xF0, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00,   //PG UP  (E6)
  0xE0, 0xF0, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00,   //LEFT   (E7)
  0xE0, 0xF0, 0x7C, 0xE0, 0xF0, 0x12, 0x00, 0x00,   //PT SCR (E8)
  0xE0, 0xF0, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,   //RIGHT  (E9)
  0xE0, 0xF0, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,   //INS    (EA)
  0xE0, 0xF0, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00,   //HOME   (EB)
  0xE0, 0xF0, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00,   //PG DN  (EC)
  0xE0, 0xF0, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00    //DEL    (ED)
};






//Зададим пины светодиода Св1
#define LED1 7
#define LED1_PORT PORTD
#define LED1_PIN 5

//Зададим пины светодиода Св2
#define LED2 8
#define LED2_PORT PORTD
#define LED2_PIN 6

//Задаем пины для управления PS/2
#define PS2_CLOCK_DDR DDRB
#define PS2_CLOCK_PORT PORTB
#define PS2_CLOCK_PIN 0
#define PS2_CLOCK_INPUT PINB
#define ps2clock 9

#define PS2_DATA_DDR DDRB
#define PS2_DATA_PORT PORTB
#define PS2_DATA_PIN 1
#define PS2_DATA_INPUT PINB
#define ps2data 10

//Задаем пины 74HC595
#define RPORT PORTD
#define RDATA_PORT PORTD
#define RDATA_PIN 0
#define RCLOCK_PORT PORTD
#define RCLOCK_PIN 1
#define RLATCH_PORT PORTD
#define RLATCH_PIN 4
#define RDATA 0
#define RCLOCK 1
#define RLATCH 6

//Задаем пины 74HC165
#define IPORT PORTB
#define IDATA_DDR DDRB
#define IDATA_PORT PORTB
#define IDATA_PIN 4
#define ICLOCK_PORT PORTB
#define ICLOCK_PIN 2
#define ILATCH_PORT PORTB
#define ILATCH_PIN 3
#define IDATA 13
#define ICLOCK 11
#define ILATCH 12

//байт состояния светодиодов
unsigned char leds;
unsigned char km;

//Установить вывод PS/2 CLOCK в HIGH
static void clockHigh(void) {
  PS2_CLOCK_DDR &= ~_BV(PS2_CLOCK_PIN); // set as input
  PS2_CLOCK_PORT |= _BV(PS2_CLOCK_PIN); // set pullup
}

//Установить вывод PS/2 CLOCK в LOW
static void clockLow(void) {
  PS2_CLOCK_PORT &= ~_BV(PS2_CLOCK_PIN); // zero output value
  PS2_CLOCK_DDR |= _BV(PS2_CLOCK_PIN); // set as output
}

//Установить вывод PS/2 DATA в HIGH
static void dataHigh(void) {
  PS2_DATA_DDR &= ~_BV(PS2_DATA_PIN); // set as input
  PS2_DATA_PORT |= _BV(PS2_DATA_PIN); // set pullup
}

//Установить вывод PS/2 DATA в LOW
static void dataLow(void) {
  PS2_DATA_PORT &= ~_BV(PS2_DATA_PIN); // zero output value
  PS2_DATA_DDR |= _BV(PS2_DATA_PIN); // set as output
}

//Ускоренные процедуры чтения пинов PS/2
#define readClockPin() (PS2_CLOCK_INPUT & (1 << PS2_CLOCK_PIN))
#define readDataPin() (PS2_DATA_INPUT & (1 << PS2_DATA_PIN))
#define readReg165Pin() (PINB & (1 << IDATA_PIN))

//Тайминги ожидания при установке выводов PS/2
#define CLK_FULL 40 // 40+40 us for 12.5 kHz clock
#define CLK_HALF 20
//Таймаут проверки состояния пинов PS/2 при чтении
#define TIMEOUT 30

//Инициализация порта PS/2
void init_ps2() {
  //Установим выводы PS/2 CLOCK и DATA в HIGH
  clockHigh();
  dataHigh();

  //Отправим по PS/2 код 0xAA, означающий готовность клавиатуры к работе
  keyb_write(0xAA);
  _delay_us(10);
}


//Отдельная процедура - Сделать строб на выводе PS/2 CLOCK
void do_clock_lo_hi() {
  _delay_us(CLK_HALF);
  clockLow(); // start bit
  _delay_us(CLK_FULL);
  clockHigh();
  _delay_us(CLK_HALF);
}

//Проверка состояния выводов PS/2 CLOCK и DATA
int keyb_check() {
  return (!readClockPin() || !readDataPin());
}

//Универсальный ответ клавиатуры - подтверждение об успешном приеме
void ack() {
  keyb_write(0xFA);
  //_delay_us(CLK_HALF);
  }



//Считывание с регистра 74HC165
uint16_t shiftIn165()
{
  uint16_t value = 0;

  //Включим-выключим защелку
  ILATCH_PORT &= ~(1 << ILATCH_PIN); //LOW
  ILATCH_PORT |= (1 << ILATCH_PIN);  //HIGH

  //Считаем побитно содержимое регистра
  for (uint8_t i = 0; i < CHIP_J_COUNT; ++i)
  {
    ICLOCK_PORT &= ~(1 << ICLOCK_PIN); //LOW
    value |= bitRead(PINB, IDATA_PIN) << ((CHIP_J_COUNT - 1) - i); //digitalRead(IDATA) << (15 - i);
    ICLOCK_PORT |= (1 << ICLOCK_PIN);  //HIGH
  }

  return value;
}

//Функция чтения данных с порта PS/2
int keyboard_read(unsigned char * value) {
  unsigned int data = 0x00;
  unsigned int bit = 0x01;

  unsigned char calculated_parity = 1;
  unsigned char received_parity = 0;

  unsigned long waiting_since = millis();
  while ((readDataPin()) || (!readClockPin()) /*(digitalRead(ps2data) != LOW) || (digitalRead(ps2clock) != HIGH)*/ ) {
    if ((millis() - waiting_since) > TIMEOUT) return -1;
  }

  do_clock_lo_hi();

  while (bit < 0x0100) {
    if (readDataPin()/*digitalRead(ps2data) == HIGH*/) {
      data = data | bit;
      calculated_parity = calculated_parity ^ 1;
    } else {
      calculated_parity = calculated_parity ^ 0;
    }
    bit = bit << 1;
    do_clock_lo_hi();
  }

  if (readDataPin()/*digitalRead(ps2data) == HIGH*/) {
    received_parity = 1;
  }

  do_clock_lo_hi();

  _delay_us(CLK_HALF);
  dataLow();
  clockLow();
  _delay_us(CLK_FULL);
  clockHigh();
  _delay_us(CLK_HALF);
  dataHigh();

  *value = data & 0x00FF;

  if (received_parity == calculated_parity) {
    return 0;
  } else {
    return -2;
  }
}



//Запись байта в PS/2
int keyb_write(unsigned char data)
{
  _delay_us(1000);

  unsigned char i;
  unsigned char parity = 1;

  if (/*digitalRead(ps2clock) == LOW*/ !readClockPin()) {
    return -1;
  }

  if (/*digitalRead(ps2data) == LOW*/ !readDataPin()) {
    return -2;
  }

  dataLow();

  do_clock_lo_hi();

  for (i = 0; i < 8; i++)
  {
    if (data & 0x01)
    {
      dataHigh();
    } else {
      dataLow();
    }

    do_clock_lo_hi();

    parity = parity ^ (data & 0x01);
    data = data >> 1;
  }
  // parity bit
  if (parity)
  {
    dataHigh();
  } else {
    dataLow();
  }
  do_clock_lo_hi();

  // stop bit
  dataHigh();
  do_clock_lo_hi();

  _delay_us(1000);

  return 0;
}



int keyboard_reply(unsigned char cmd, unsigned char *leds) {

  unsigned char val;
  //unsigned char enabled;

  switch (cmd) {
    case 0xFF: //reset
      ack();
      //the while loop lets us wait for the host to be ready
      keyb_write(0xAA);
      break;
    case 0xFE: //resend
      ack();
      break;
    case 0xF6: //set defaults
      //enter stream mode
      ack();
      break;
    case 0xF5: //disable data reporting
      //FM
      //enabled = 0;
      ack();
      break;
    case 0xF4: //enable data reporting
      //FM
      //enabled = 1;
      ack();
      break;
    case 0xF2: //Сообщить device id
      ack();
      //0xAB83 - идентификатор оборудования стандартной клавиатуры ps/2
      keyb_write(0xAB);
      //_delay_us(CLK_HALF);
      keyb_write(0x83);
      /*digitalWrite(LED1, HIGH);
        _delay_us(80);
        digitalWrite(LED1, LOW);*/
      break;
    case 0xF0: //set scan code set
      ack();
      if (!keyboard_read(&val)) ack(); //do nothing with the rate
      break;
    case 0xEE: //echo
      //ack();
      keyb_write(0xEE);
      break;
    case 0xED: //set/reset LEDs
      ack();
      if (!keyboard_read(leds)) ack(); //do nothing with the rate
      return 1;
      break;
  }
  return 0;
}

//Проверка чтения прилетевшей от компьютера информации
int keyboard_read_check(unsigned char *leds) {
  unsigned char c;
  if ( keyb_check() ) {
    if (!keyboard_read(&c)) return keyboard_reply(c, leds);
  }
  return 0;
}


int main() {
  //Установим выходы регистра 27HC595 и светодиодов
  DDRD = (1 << RDATA_PIN) | (1 << RCLOCK_PIN) | (1 << RLATCH_PIN) | (1 << LED1_PIN) | (1 << LED2_PIN);

  //Установим оба светодиода в LOW
  LED1_PORT &= ~(1 << LED1_PIN);
  LED2_PORT &= ~(1 << LED2_PIN);

  //Инициализируем пины PS/2
  init_ps2();

  //установим выходы регистра 27HC165
  DDRB |= (1 << ICLOCK_PIN) | (1 << ILATCH_PIN);
  DDRB &= ~(1 << IDATA_PIN);

  //Главный бесконечный цикл
  while (1) {
    //Цикл опроса регистра 595
    for (byte i = 0; i < 16; i++) {
      //сформируем адрес строки опроса клавиш в 16битном сдвиговом 
      //регистре
      word ww = (word)1 << i;
      //И запишем его в порт 595
      RLATCH_PORT &= ~(1 << RLATCH_PIN);  //LOW
      shiftOut(RDATA, RCLOCK, MSBFIRST, highByte(ww));
      shiftOut(RDATA, RCLOCK, MSBFIRST, lowByte(ww));
      RLATCH_PORT |= (1 << RLATCH_PIN);  //HIGH

      //Считаем значение с регистра 165
      word b = shiftIn165();

      //Проверяем по строкам от 0 до CHIP_J_COUNT (8 или 16)
      for (byte j = 0; j < CHIP_J_COUNT; j++) {
        if (bitRead(b, j) == 1) {  // Проверяем на нажатие
          if (bitRead(pr[i], j) == 0) { //Если клавиша еще не нажата, то отправим ее код
            //LED1_PORT |= (1 << LED1_PIN);  //HIGH
            km = pgm_read_byte(&keymap[j + /*KEYMAPSIZE*/16 * i]);
            //Проверим, не должна ли клавиша сообщать расширенный код
            if (km >= 0xE0) {
              //Если код из таблицы кодов >= E0, то читаем 
              //последовательность из массива
              //с номером (Код - E0)
              for (int k = 0; k < EXT_K_SIZE; k++) {
                unsigned char ekm = pgm_read_byte(&extcodes[k + EXT_K_SIZE * (km - 0xE0)]);
                if (ekm > 0x00) {
                  keyb_write(ekm);                  
                }
              }
            } else {
              //Если не расширенный код, отправляем просто код клавиши
              keyb_write(km);
            }
            bitWrite(pr[i], j, 1);
            LED2_PORT |= (1 << LED2_PIN);
          }
        } else {  //Проверяем на отпускание
          if (bitRead(pr[i], j) == 1) { //Если клавиша была нажата, то, 
                                        //отправим код отпускания
            km = pgm_read_byte(&keymap[j + 16 * i]);
            if (km >= 0xE0) { // По вышеописанному принципу - если код 
                              //клавиши должен быть расширенным,
                              //то выдаем расширенную последовательность из массива extcanscodes
              for (int k = 0; k < EXT_K_SIZE; k++) {
                unsigned char ekm = pgm_read_byte(&extcanscodes[k + EXT_K_SIZE * (km - 0xE0)]);
                if (ekm > 0x00) {
                  keyb_write(ekm);
                  //LED2_PORT &= ~(1 << LED2_PIN);
                }
              }
            } else { // Или выдаем просто один код из массива keymap
              keyb_write(0xF0);
              keyb_write(km);
            }
            bitWrite(pr[i], j, 0);
            LED2_PORT &= ~(1 << LED2_PIN);
          }

        }


      }  //j

    }

    //Опрос входящих данных с порта PS/2 и проверка байта состояния светодиодов
    if (keyboard_read_check(&leds)) {
      //Если СAPS включен, зажжем светодиод Св1
      //digitalWrite(LED1, leds);
      if (bitRead(leds, 2) == 1) {
        //digitalWrite(LED1, HIGH);
        LED1_PORT |= (1 << LED1_PIN);
      } else {
        //digitalWrite(LED1, LOW);
        LED1_PORT &= ~(1 << LED1_PIN);
      }
    }


  }  //конец главного цикла

  return 1;
}

Теги:
Хабы:
+58
Комментарии26

Публикации

Изменить настройки темы

Истории

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн