Подключение клавиатуры PS/2 к БК11М

С одной стороны адаптер принимает нажатия клавиш от стандартной PS/2 клавиатуры, а с другой манипулирует входными линиями микросхемы 1801ВП1-014 притворяясь матрицей клавиш. Количество выходов AtMega8 увеличено с помощью сдвигового регистра. Прошивка написана на диалекте Forth muforth

PS/2

Я не буду переписывать здесь полные описания протокола и электрического соединения PS/2, а также сканкоды клавиш. Это можно посмотреть здесь и здесь. Две картинки чтобы напомнить:

Вот как я подключаю PS/2 разъём:

Схема подключения клавиатуры PS/2
Схема подключения клавиатуры PS/2

R7 и R6 обеспечивают формирование сигналов для выходов с открытым коллектором у клавиатуры, цепи с диодами (D1, D2, D3 и D4) и резисторами R3 и R5 образуют простую защиту от статического электричества. D2 (INT0) подключен к линии CLK, таким образом используется прерывание для синхронизации с клавиатурой при чтении данных. D3 используется как линия DATA.

Питание придётся брать прямо с разъёма БК.

Питание для адаптера
Питание для адаптера

INT0 настраивается для срабатывания по спаду.

%01 equ ISC00
%10 equ ISC01

code init-ps2
   ( ISC00=0, ISC01=1 -- the falling edge of INT0 generates an interrupt request)
   MCUCR h0 lds ISC00 invert h0 andi ISC01 h0 ori MCUCR h0 sts
   PS2CLK  DDRD cbi PS2CLK  PORTD cbi
   PS2DATA DDRD cbi PS2DATA PORTD cbi
   ret ;c

Один из таймеров используется для обнаружения таймаута при обмене с клавиатурой: если с последнего прерывания по линии CLK прошло значительное время, то я считаю, что произошёл сбой и начинаю приём байта с самого начала. Стартовый бит, бит чётности и стоповые биты игнорируются. Полученный байт записывается в кольцевой буфер.

===========================================================================
 INT0 handler. Read data. Ignore start, parity and stop bits.
 After receiving BITS-PER-PACKET bits decode byte.
===========================================================================
code INT0-handler
    r0 push x pushw h0 push
    r0 clr

    -- PS2 wait not expired
    "ff tl ldi TIFR h0 in
    TOV0 h0 sbrs always tl clr 1 TOV0 << h0 ldi TIFR h0 out then
    -- tl = 0  -- PS2 timeout
    -- tl = ff -- no PS2 timeout

    -- X = bitcount addr
    bit-count >hilo xl ldi xh ldi

    -- timeout?
    tl tst .Z if BITS-PER-PACKET h0 ldi x@ h0 st then

    -- reset timeout timer
    256 PS2-WAIT-PERIOD - h0 ldi
    TCNT0 h0 out

    -- read bit if 2 < bitcount < BITS-PER-PACKET
    x@ h0 ld 3 h0 cpi      -- C = 0 if bit-count >= 3
                           -- C = 1 if bit-count < 3
      .C not if
            BITS-PER-PACKET h0 cpi -- C = 0 if bit-count >= BITS-PER-PACKET
                                   -- C = 1 if bit-count < BITS-PER-PACKET
             .C if -- read bit. Note bit C is set now!
                PS2DATA PIND sbis clc
                byte-read tl lds tl ror byte-read tl sts
            then
       then
    x@ h0 ld h0 dec -- h0 = bit-count 1-
    .Z if
              -- input-write
              input-wptr tl lds tl th mov th inc input-wptr th sts  -- input-wptr++
              BUFFER-LEN 1 - tl andi                                -- mask ptr
              input-buffer >hilo xl ldi xh ldi
              tl xl add r0 xh adc
              byte-read tl lds x@ tl st
              BITS-PER-PACKET h0 ldi
           then
    bit-count h0 sts

    h0 pop x popw r0 pop ret ;c

Кольцевой буфер на 32 байта устроен очень просто: указатели для чтения и для записи увеличиваются до переполнения 16-ти битного слова, а для чтения/записи применяется маска. Слово для записи в буфер закомментировано поскольку в процедуре обработки прерывания от клавиатуры используется ассемблерный вариант.

===========================================================================
 Simple ring buffer.
 Uses two free pointers, which masked during access only.
===========================================================================
32 equ BUFFER-LEN   -- Note: need 10 bytes for PrtScreen press/release
BUFFER-LEN var input-buffer
1 var input-rptr
1 var input-wptr
: init-input-buffer 0 dup input-rptr c! input-wptr c! ;
code input-mask ( n - n) BUFFER-LEN 1- tl andi ret ;c
: input-empty? ( - f) input-wptr c@ input-rptr c@ = ;

( return ptr value and increment ptr)
: advance-ptr ( a - n) dup c@ swap over 1+ swap c! ;
comment XX : input-write (  b) input-wptr advance-ptr input-mask input-buffer + c! ; XX
: input-read ( - b) input-rptr advance-ptr input-mask input-buffer + c@ ;

Матрица переключателей

Матрица имеет 10 столбцов X_0-X_9 , 8 строк Y_0-Y_7 ( Y_0 всегда подключен к земле) и несколько служебных сигналов. X_i имеют pullup резисторы 22K, Y_i - pulldown резисторы 180K. Служебные линии, кроме СТОП, имеют pullup резисторы 3.3K.

В начальном состоянии соответствующие линии микросхемы 1801ВП1-014 YY_i работают на вход, резисторы обеспечивают высокий уровень на XX_i и низкий уровень на YY_i . При нажатии клавиши происходит следующее:

  • YY_i получает выскоий уровень с получающегося делителя 22K/180K.
  • YY_i переключается в режим выхода с низким уровнем.
  • XX_i получает низкий уровень.

Что мне нужно для электрической имитации матрицы?

  1. XX_i всегда находятся в режиме ввода, значит можно не волноваться о токоограничивающих резисторах.
  2. Имеется pullup резистор, значит можно не подавать высокий уровень, а просто переходить в высокоимпедансное состояние.
  3. YY_i работают как на вход так и на выход, так что нужно ограничить ток.
Схема линий
Схема линий

Линии X_i (кроме X_2 ) подключены напрямую к выходам микроконтроллера, линии Y_i подключены через токоограничивающие резисторы R8-R14. Оставшиеся сигнальные линии и X_2 из-за исчерпания выводов AtMega8 подключены к сдвиговому регистру через оптопары. Технически оптопары не обязательны, ну разве что для STOP линий, однако они были под рукой:)

Управление X_i и Y_i :

===========================================================================
 Manipulate bk input lines
 X lines is switched between input and output. PortX alwayse quals 0.
 Y lines is switched between 0 and 1. DdrY is always output.

 X connects to input with 22k pullup so no need for current limiting (5/22e3 = 0.227mA).
 Y may be connected to the GND through open collector so it's better to
 use resistors. Max 200mA for all 11 pins. 200mA/11 = 18mA per pin,
 5/18e-3 = 275 Ohm min.

 -- input lines
 X0-9       = 9 pins (-x2)
 PS2 (data/clock) = 2 pins

-- output lines
 Y1-7        = 7 pins
 su + ar2 + zagl + str + pr + space + stop + x2 = 4 pins 74HC595 (7)

 total: 23 pins

 PS pins   = 2 x 10k
 Y1-7 pins = 7 x 330
 74HC595 pins = no resistors
 X0-9 = no resistors

===========================================================================

===========================================================================
 x0 - d0, x1 - d1, x3 - d4
 x4 - c5, x5 - c4, x6 - c2, x7 - c1, x8 - c0, x9 - c3
===========================================================================
code x3-3dstate
   4 DDRD cbi ret ;c
code x3-0
   4 DDRD sbi ret ;c

code y1-1
  1 PORTB sbi ret ;c
code y1-0
  1 PORTB cbi ret ;c
Проверка монтажа
Проверка монтажа

Перекодировка

Всё начинается с конечного автомата, переход по ребру XXX приводит к имитации нажатия STOP, переходы по ребрам scancode приводят к проверке на особые клавиши а затем к считыванию команды из одной из таблиц функций. Команда представляет собой битовый набор, в котором указывается какими линиями нужно манипулировать и каким образом.

Конечный автомат перекодировки
Конечный автомат перекодировки
===========================================================================
 Normal key pressed
===========================================================================
: NORMAL ( b)
   dup "f0 = if drop 2release  ^ then
   dup "e0 = if drop 2extended ^ then
   dup "e1 = if drop 2pause    ^ then

   dup normal-modifiers? if drop ^ then
   dup normal-quirks? if drop ^ then

   .ifdef DEBUG "c log! .then
   call-decode ;

===========================================================================
 Normal key released
===========================================================================
: RELEASE ( b)
   dup "f0 = if drop 2normal ^ then
   dup "e0 = if drop 2normal ^ then

   dup release-modifiers? if drop 2normal ^ then
   dup release-quirks? if drop 2normal ^ then

   .ifdef DEBUG "d log! .then
   call-release-decode 2normal ;

===========================================================================
 Extended key pressed
===========================================================================
: EXTENDED ( b)
   dup "f0 = if drop 2rel-ext ^ then
   dup "e0 = if drop 2normal ^ then

   dup extended-modifiers? if drop 2normal ^ then
   dup extended-quirks? if drop 2normal ^ then

   .ifdef DEBUG "e log! .then
   call-decode 2normal ;

===========================================================================
 Extended key released
===========================================================================
: REL-EXT ( b)
   dup "f0 = if drop 2normal ^ then
   dup "e0 = if drop 2normal ^ then

   dup ext-release-modifiers? if drop 2normal ^ then
   dup ext-release-quirks? if drop 2normal ^ then

   .ifdef DEBUG "f log! .then
   call-release-decode 2normal ;

===========================================================================
 Pause key pressed
===========================================================================
( Pause send next bytes: E1,14,77,E1,F0,14,F0,77
  there is no reason to store first E1 therefore we store remaining bytes)
7 equ PAUSE-CHARS-LEN
name pause-chars "14 c, "77 c, "e1 c, "f0 c, "14 c, "f0 c, "77 c, .even
: PAUSE ( b)
      ['] pause-chars pause-char-ptr c@ +
      asm{ { t z movw th clr pmz tl ld } }
      = invert if 2normal ^ then
      pause-char-ptr c@ 1+
      dup PAUSE-CHARS-LEN < if pause-char-ptr c! ^ then
      drop
      mod-stop-p send-mod mod-stop-r send-mod 2normal ;

Часть таблицы функций:

===========================================================================
 Recode tables for x/y lines [0..ff]
===========================================================================
( normal/release lat)
name lat-normal
   ( 00            01:F9        02         03:F5         04:F3  )
   key-nop c,    key-sbr-p c, key-nop c, key-|==>-p c, key-=|=>-p c,

   ( 05:F1           06:F2        07:F12     08            09:F10 )
   key-povtor-p c, key-kt-p c,  key-nop c, key-nop c,    key-nop c,

   ( 0a:F8         0b:F6          0c:F4         0d:tab       0e:`)
   key-shag-p c, key-indsu-p c, key-|<==-p c, key-tab-p c, key-nop c,

   ( 0f         10         11:LAlt    12:LShift    13         14:LCtrl)
   key-nop c, key-nop c, key-nop c, key-nop c,   key-nop c, key-nop c,

   ( 15:q       16:1       17         18         19         1a:z)
   key-q-p c, key-1-p c, key-nop c, key-nop c, key-nop c, key-z-p c,

Скачивание

Фьюзы: E:FF, H:DE, L:E4

Проект KiCad

Микроконтроллер можно перепрошивать прямо на плате, вот таблица для подключения программатора:

Контакт Назначение
6 - XT2 (J5) SCK
9 - XT2 (J5) MOSI
10 - XT2 (J5) MISO
12 - XT1 (J2) GND
2 - J3 RESET
Прошивка прямо на плате
Прошивка прямо на плате

Клавиши

PS/2 клавиша БК клавиша
Esc КТ
F1 ПОВТ
F2 КТ
F3 =|=>
F4 |<==
F5 |==>
F6 ИНДСУ
F7 БЛОКРЕД
F8 ШАГ
F9 СБР
Pause СТОП
App ВС
Insert ВС
Delete |<==
Shift ПР
Ctrl СУ
Alt АР2
Left Win РУС
Right Win ЛАТ
Home ВС+ left
End ВС+ right
PageUp АР2+ up
PageDown АР2+ down