§ Как работает протокол PS/2
Этот материал будет очень обширным, потому что сегодня я буду делать сразу по 3-м направлениям — эмуляцию для верилятора, сам код верилога и немного по физической реализации на отладочной плате. Начну с теории, о том, как передаются данные от клавиатуры к контроллеру.Схема работы протокола. Есть 2 провода, по которым передаются данные, как от клавиатуры, так и к ней. Передачу данных к клавиатуре я делать пока не буду, а вот от клавиатуры — да.
Большую часть времени оба провода содержат сигнал 1 (притянуты к Vcc). Когда клавиатура начинает передавать данные, она опускает в 0 оба провода, что сигнализирует о начале передачи.
- Первый такт на позитивном фронте принимает старт-бит, который всегда равен 0 и всегда должен быть равен 0, иначе это ошибка
- Второй такт принимает бит 0 и записывает его в результирующий регистр
- Далее, принимаются все оставшиеся биты 1-7
- После приема битов идет бит четности, который нужен для проверки того, что данные пришли успешно. Бит четности равен 0, если количество принятых единиц было нечетно и 1, если четно
- Самый последний такт называется "стоп"-бит, на проводе Data всегда должно быть значение 1
§ Событие нажатия кнопки в эмуляторе
Теперь, давайте добавим обработчик события при нажатии на кнопку. Когда на клавиатуре нажимается кнопка, то по протоколу PS2 отсылается ее сканкод. Я постарался обработать как можно больше клавиш и записал все, что смог найти.1// Сканирование нажатой клавиши 2// https://ru.wikipedia.org/wiki/Скан-код 3void kbd_scancode(int scancode, int release) { 4 5 switch (scancode) { 6 7 // Коды клавиш A-Z 8 case SDL_SCANCODE_A: if (release) kbd_push(0xF0); kbd_push(0x1C); break; 9 case SDL_SCANCODE_B: if (release) kbd_push(0xF0); kbd_push(0x32); break; 10 case SDL_SCANCODE_C: if (release) kbd_push(0xF0); kbd_push(0x21); break; 11 case SDL_SCANCODE_D: if (release) kbd_push(0xF0); kbd_push(0x23); break; 12 case SDL_SCANCODE_E: if (release) kbd_push(0xF0); kbd_push(0x24); break; 13 case SDL_SCANCODE_F: if (release) kbd_push(0xF0); kbd_push(0x2B); break; 14 case SDL_SCANCODE_G: if (release) kbd_push(0xF0); kbd_push(0x34); break; 15 case SDL_SCANCODE_H: if (release) kbd_push(0xF0); kbd_push(0x33); break; 16 case SDL_SCANCODE_I: if (release) kbd_push(0xF0); kbd_push(0x43); break; 17 case SDL_SCANCODE_J: if (release) kbd_push(0xF0); kbd_push(0x3B); break; 18 case SDL_SCANCODE_K: if (release) kbd_push(0xF0); kbd_push(0x42); break; 19 case SDL_SCANCODE_L: if (release) kbd_push(0xF0); kbd_push(0x4B); break; 20 case SDL_SCANCODE_M: if (release) kbd_push(0xF0); kbd_push(0x3A); break; 21 case SDL_SCANCODE_N: if (release) kbd_push(0xF0); kbd_push(0x31); break; 22 case SDL_SCANCODE_O: if (release) kbd_push(0xF0); kbd_push(0x44); break; 23 case SDL_SCANCODE_P: if (release) kbd_push(0xF0); kbd_push(0x4D); break; 24 case SDL_SCANCODE_Q: if (release) kbd_push(0xF0); kbd_push(0x15); break; 25 case SDL_SCANCODE_R: if (release) kbd_push(0xF0); kbd_push(0x2D); break; 26 case SDL_SCANCODE_S: if (release) kbd_push(0xF0); kbd_push(0x1B); break; 27 case SDL_SCANCODE_T: if (release) kbd_push(0xF0); kbd_push(0x2C); break; 28 case SDL_SCANCODE_U: if (release) kbd_push(0xF0); kbd_push(0x3C); break; 29 case SDL_SCANCODE_V: if (release) kbd_push(0xF0); kbd_push(0x2A); break; 30 case SDL_SCANCODE_W: if (release) kbd_push(0xF0); kbd_push(0x1D); break; 31 case SDL_SCANCODE_X: if (release) kbd_push(0xF0); kbd_push(0x22); break; 32 case SDL_SCANCODE_Y: if (release) kbd_push(0xF0); kbd_push(0x35); break; 33 case SDL_SCANCODE_Z: if (release) kbd_push(0xF0); kbd_push(0x1A); break; 34 35 // Цифры 36 case SDL_SCANCODE_0: if (release) kbd_push(0xF0); kbd_push(0x45); break; 37 case SDL_SCANCODE_1: if (release) kbd_push(0xF0); kbd_push(0x16); break; 38 case SDL_SCANCODE_2: if (release) kbd_push(0xF0); kbd_push(0x1E); break; 39 case SDL_SCANCODE_3: if (release) kbd_push(0xF0); kbd_push(0x26); break; 40 case SDL_SCANCODE_4: if (release) kbd_push(0xF0); kbd_push(0x25); break; 41 case SDL_SCANCODE_5: if (release) kbd_push(0xF0); kbd_push(0x2E); break; 42 case SDL_SCANCODE_6: if (release) kbd_push(0xF0); kbd_push(0x36); break; 43 case SDL_SCANCODE_7: if (release) kbd_push(0xF0); kbd_push(0x3D); break; 44 case SDL_SCANCODE_8: if (release) kbd_push(0xF0); kbd_push(0x3E); break; 45 case SDL_SCANCODE_9: if (release) kbd_push(0xF0); kbd_push(0x46); break; 46 47 // Keypad 48 case SDL_SCANCODE_KP_0: if (release) kbd_push(0xF0); kbd_push(0x70); break; 49 case SDL_SCANCODE_KP_1: if (release) kbd_push(0xF0); kbd_push(0x69); break; 50 case SDL_SCANCODE_KP_2: if (release) kbd_push(0xF0); kbd_push(0x72); break; 51 case SDL_SCANCODE_KP_3: if (release) kbd_push(0xF0); kbd_push(0x7A); break; 52 case SDL_SCANCODE_KP_4: if (release) kbd_push(0xF0); kbd_push(0x6B); break; 53 case SDL_SCANCODE_KP_5: if (release) kbd_push(0xF0); kbd_push(0x73); break; 54 case SDL_SCANCODE_KP_6: if (release) kbd_push(0xF0); kbd_push(0x74); break; 55 case SDL_SCANCODE_KP_7: if (release) kbd_push(0xF0); kbd_push(0x6C); break; 56 case SDL_SCANCODE_KP_8: if (release) kbd_push(0xF0); kbd_push(0x75); break; 57 case SDL_SCANCODE_KP_9: if (release) kbd_push(0xF0); kbd_push(0x7D); break; 58 59 // Специальные символы 60 case SDL_SCANCODE_GRAVE: if (release) kbd_push(0xF0); kbd_push(0x0E); break; 61 case SDL_SCANCODE_MINUS: if (release) kbd_push(0xF0); kbd_push(0x4E); break; 62 case SDL_SCANCODE_EQUALS: if (release) kbd_push(0xF0); kbd_push(0x55); break; 63 case SDL_SCANCODE_BACKSLASH: if (release) kbd_push(0xF0); kbd_push(0x5D); break; 64 case SDL_SCANCODE_LEFTBRACKET: if (release) kbd_push(0xF0); kbd_push(0x54); break; 65 case SDL_SCANCODE_RIGHTBRACKET: if (release) kbd_push(0xF0); kbd_push(0x5B); break; 66 case SDL_SCANCODE_SEMICOLON: if (release) kbd_push(0xF0); kbd_push(0x4C); break; 67 case SDL_SCANCODE_APOSTROPHE: if (release) kbd_push(0xF0); kbd_push(0x52); break; 68 case SDL_SCANCODE_COMMA: if (release) kbd_push(0xF0); kbd_push(0x41); break; 69 case SDL_SCANCODE_PERIOD: if (release) kbd_push(0xF0); kbd_push(0x49); break; 70 case SDL_SCANCODE_SLASH: if (release) kbd_push(0xF0); kbd_push(0x4A); break; 71 case SDL_SCANCODE_BACKSPACE: if (release) kbd_push(0xF0); kbd_push(0x66); break; 72 case SDL_SCANCODE_SPACE: if (release) kbd_push(0xF0); kbd_push(0x29); break; 73 case SDL_SCANCODE_TAB: if (release) kbd_push(0xF0); kbd_push(0x0D); break; 74 case SDL_SCANCODE_CAPSLOCK: if (release) kbd_push(0xF0); kbd_push(0x58); break; 75 case SDL_SCANCODE_LSHIFT: if (release) kbd_push(0xF0); kbd_push(0x12); break; 76 case SDL_SCANCODE_LCTRL: if (release) kbd_push(0xF0); kbd_push(0x14); break; 77 case SDL_SCANCODE_LALT: if (release) kbd_push(0xF0); kbd_push(0x11); break; 78 case SDL_SCANCODE_RSHIFT: if (release) kbd_push(0xF0); kbd_push(0x59); break; 79 case SDL_SCANCODE_RETURN: if (release) kbd_push(0xF0); kbd_push(0x5A); break; 80 case SDL_SCANCODE_ESCAPE: if (release) kbd_push(0xF0); kbd_push(0x76); break; 81 case SDL_SCANCODE_NUMLOCKCLEAR: if (release) kbd_push(0xF0); kbd_push(0x77); break; 82 case SDL_SCANCODE_KP_MULTIPLY: if (release) kbd_push(0xF0); kbd_push(0x7C); break; 83 case SDL_SCANCODE_KP_MINUS: if (release) kbd_push(0xF0); kbd_push(0x7B); break; 84 case SDL_SCANCODE_KP_PLUS: if (release) kbd_push(0xF0); kbd_push(0x79); break; 85 case SDL_SCANCODE_KP_PERIOD: if (release) kbd_push(0xF0); kbd_push(0x71); break; 86 case SDL_SCANCODE_SCROLLLOCK: if (release) kbd_push(0xF0); kbd_push(0x7E); break; 87 88 // F1-F12 Клавиши 89 case SDL_SCANCODE_F1: if (release) kbd_push(0xF0); kbd_push(0x05); break; 90 case SDL_SCANCODE_F2: if (release) kbd_push(0xF0); kbd_push(0x06); break; 91 case SDL_SCANCODE_F3: if (release) kbd_push(0xF0); kbd_push(0x04); break; 92 case SDL_SCANCODE_F4: if (release) kbd_push(0xF0); kbd_push(0x0C); break; 93 case SDL_SCANCODE_F5: if (release) kbd_push(0xF0); kbd_push(0x03); break; 94 case SDL_SCANCODE_F6: if (release) kbd_push(0xF0); kbd_push(0x0B); break; 95 case SDL_SCANCODE_F7: if (release) kbd_push(0xF0); kbd_push(0x83); break; 96 case SDL_SCANCODE_F8: if (release) kbd_push(0xF0); kbd_push(0x0A); break; 97 case SDL_SCANCODE_F9: if (release) kbd_push(0xF0); kbd_push(0x01); break; 98 case SDL_SCANCODE_F10: if (release) kbd_push(0xF0); kbd_push(0x09); break; 99 case SDL_SCANCODE_F11: if (release) kbd_push(0xF0); kbd_push(0x78); break; 100 case SDL_SCANCODE_F12: if (release) kbd_push(0xF0); kbd_push(0x07); break; 101 102 // Расширенные клавиши 103 case SDL_SCANCODE_LGUI: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x1F); break; 104 case SDL_SCANCODE_RGUI: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x27); break; 105 case SDL_SCANCODE_APPLICATION: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x2F); break; 106 case SDL_SCANCODE_RCTRL: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x14); break; 107 case SDL_SCANCODE_RALT: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x11); break; 108 case SDL_SCANCODE_KP_DIVIDE: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x4A); break; 109 case SDL_SCANCODE_KP_ENTER: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x5A); break; 110 111 case SDL_SCANCODE_INSERT: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x70); break; 112 case SDL_SCANCODE_HOME: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x6C); break; 113 case SDL_SCANCODE_END: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x69); break; 114 case SDL_SCANCODE_PAGEUP: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x7D); break; 115 case SDL_SCANCODE_PAGEDOWN: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x7A); break; 116 case SDL_SCANCODE_DELETE: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x71); break; 117 118 case SDL_SCANCODE_UP: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x75); break; 119 case SDL_SCANCODE_DOWN: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x72); break; 120 case SDL_SCANCODE_LEFT: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x6B); break; 121 case SDL_SCANCODE_RIGHT: kbd_push(0xE0); if (release) kbd_push(0xF0); kbd_push(0x74); break; 122 123 // Клавиша PrnScr 124 case SDL_SCANCODE_PRINTSCREEN: { 125 126 if (release == 0) { 127 128 kbd_push(0xE0); kbd_push(0x12); 129 kbd_push(0xE0); kbd_push(0x7C); 130 131 } else { 132 133 kbd_push(0xE0); kbd_push(0xF0); kbd_push(0x7C); 134 kbd_push(0xE0); kbd_push(0xF0); kbd_push(0x12); 135 } 136 137 break; 138 } 139 140 // Клавиша Pause 141 case SDL_SCANCODE_PAUSE: { 142 143 kbd_push(0xE1); 144 kbd_push(0x14); if (release) kbd_push(0xF0); kbd_push(0x77); 145 kbd_push(0x14); if (release) kbd_push(0xF0); kbd_push(0x77); 146 break; 147 } 148 } 149}Этот метод встраивается в класс App.
Первым аргументом идет int scancode — он приходит из события SDL2. Второй аргумент int release равен либо 0 — что означает, что клавиша нажата, либо 1 — клавиша отпущена.
Когда нажимается клавиша, то, в зависимости от того, какой номер был у этой клавиши, отсылается некоторый код клавиши. Он может не один, есть клавиши, которые отсылают целую последовательность кодов.
К примеру, если нажать пробел, то будет отослан код
0x29
. Если пробел отпустить — то будет отослал код 0xF0, а потом 0x29. В случае нажатия клавиши "Направо", будет отосланы 0xE0, 0x74, а если отпустить, то 0xE0, 0xF0, 0x74. При разработке программного обеспечения, которое это будет обрабатывать, надо это учитывать.Можно заметить, что используется метод kbd_push, который занимается тем, что добавляет новый символ в очередь на отправку:
1void kbd_push(int data) { 2 3 if (kbd_top >= 255) return; 4 kbd[kbd_top] = data; 5 kbd_top++; 6}В очередь kbd будет добавлен новый код, и увеличена высота очереди. Максимальное количество элементов в очереди 256.
И да, надо объявить новые переменные и буферы в зоне protected класса App:
1unsigned char kbd[256]; 2int kbd_top, kbd_phase, kbd_ticker;
- kbd[256] — очередь
- kbd_top — верх очереди
- kbd_phase и kbd_ticker — необходимы для симуляции протокола ps2
1kbd_top = 0; 2kbd_phase = 0; 3kbd_ticker = 0;Необходимо добавить код, который будет отслеживать новые события, которые приходят от SDL2, код добавляется в метод main():
1// Прием событий 2while (SDL_PollEvent(& evt)) { 3 4 switch (evt.type) { 5 6 case SDL_QUIT: 7 return 0; 8 9 case SDL_KEYDOWN: 10 11 kbd_scancode(evt.key.keysym.scancode, 0); 12 break; 13 14 case SDL_KEYUP: 15 16 kbd_scancode(evt.key.keysym.scancode, 1); 17 break; 18 } 19}При появлении события SDL_KEYDOWN (нажата клавиша), будет вызван kbd_scancode с параметром 0, который добавил в очередь сканкод, основанный на сканкоде SDL. При отпускании клавиши, будет вызвал kbd_scancode с параметром 1.
Итак, приготовления готовы, при нажатии и отпускании клавиш добавляется в очередь, но теперь нужно выполнить обработку этих данных, симулировать отсылку их по протоколу PS2.
Задача такая. На каждый такт (25 мгц) вызывается метод, который устанавливает ps2_clock и ps2_data в нужные значения, симулируя протокол. Вот и всё.
1int ps_clock = 1, ps_data = 1; 2 3while (app->main()) { 4... 5 app->kbd_pop(ps_clock, ps_data); 6... 7}Первоначально, ps_clock и ps_data должны быть равны 1.
Рассмотрим то, как будет работать метод kbd_pop, который работает на каждом такте (25 Мгц):
1void kbd_pop(int& ps_clock, int& ps_data) { 2 3 // В очереди нет клавиш для нажатия 4 if (kbd_top == 0) return; 5 6 // 25000000/2000 = 12.5 kHz Очередной полутакт для PS/2 7 if (++kbd_ticker >= 2000) { 8 9 ps_clock = kbd_phase & 1; 10 ... 11 kbd_ticker = 0; 12 kbd_phase++; 13 } 14}Как видно из кода, если в очереди нет больше данных, то есть kbd_top == 0, то ничего не делается. Если данные есть, то спустя 2000 тактов (работает клавиатура не быстро), будет установлен ps_clock = 0 или 1. Сначала ставится 0, потом после kbd_phase++ становится 1, и так далее. Фаза kbd_phase необходима, чтобы точно знать, в какой момент и что выдавать в ps_data. Таймер kbd_ticker сбрасывается в 0, чтобы через 2000 тактов снова сделать полутакт.
1switch (kbd_phase) { 2 3 // Старт-бит [=0] 4 case 0: case 1: ps_data = 0; break; 5 6 // Бит четности 7 case 18: case 19: 8 9 ps_data = 1; 10 for (int i = 0; i < 8; i++) 11 ps_data ^= !!(kbd[0] & (1 << i)); 12 13 break; 14 15 // Стоп-бит [=1] 16 case 20: case 21: ps_data = 1; break; 17 18 // Небольшая задержка между нажатиями клавиш 19 case 22: case 23: 20 case 24: case 25: 21 22 ps_clock = 1; 23 ps_data = 1; 24 break; 25 26 // Завершение 27 case 26: 28 29 // Удалить символ из буфера 30 for (int i = 0; i < kbd_top - 1; i++) 31 kbd[i] = kbd[i+1]; 32 33 kbd_top--; 34 kbd_phase = -1; 35 ps_clock = 1; 36 break; 37 38 // Отсчет битов от 0 до 7 39 // 0=2,3 | 1=4,5 | 2=6,7 | 3=8,9 40 // 4=10,11 | 5=12,13 | 6=14,15 | 7=16,17 41 default: 42 43 ps_data = !!(kbd[0] & (1 << ((kbd_phase >> 1) - 1))); 44 break; 45}Вот здесь представлен код основной части симулятора протокола.
- Для полутактов 0 и 1 на шину ps_data выставляется 0 — старт-бит
- Далее идут пары полутактов от 2 до 17 включительно (8 бит). На шину данных выставляется последовательно биты от 0 до 7 (начиная с 0) из первого символа в буфере kbd, очередь работает в режиме FIFO
- Полутакты 18,19 выставляют бит четности, который равен 1, если количество единиц в отправленном символе было четно, и 0, если нет
- В 20,21 записывается стоп-бит, ps_data=1, это последний такт, после него ps_data=1, ps_clock=1
- В течении 22-26 полутакта это состояние продлевается
- На 26 полутакте из буфера kbd удаляется первый символ и очередь уменьшается на -1. В данном случае ставится kbd_phase=-1, потому что в конце метода идет инкремент kbd_phase, и он снова становится равным 0.
Но этот материал уже в следующей статье.
Скачать код можно здесь.