
Снова здравствуйте, дамы и господа. Наш Отдел Перспективных Разработок обещал вас порадовать очередной странной вещью, и потому продолжает свой цикл статей о старых, но иногда полезных устройствах.
Суть сегодняшнего предмета для изучения опять сугубо академическая. Хотя бы просто потому, что стандарт 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.
Весь список скан-кодов нет смысла засовывать сюда в статью, его всегда можно найти в интернетах.
В случае приема данных клавиатурой все несколько сложнее. На упра��ляющие коды компьютера положено отвечать, поэтому, разберем, что вообще от него прилетает.
На каждый принятый байт клавиатура должна ответить. Ответом может быть FА в случае успешного приема, 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; }
