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