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