В этой статье из серии «музыка на ATtiny2313» я расскажу, как подключить к разработанному ранее на основе ATtiny2313 двухканальному синтезатору PS/2-клавиатуру.
Введение
В сложных электронных проектах, требующих ввода пользовательских данных, часто выгодно использовать компьютерную клавиатуру. В зависимости от микроконтроллера, у него может быть встроенная поддержка как PS/2, так и USB HID стандартов. У ATtiny2313 этого добра нет, но подключить к нему клавиатуру и организовать её правильную работу довольно просто. Об этом и пойдёт речь в статье[1].
Схема устройства
Схема осталась прежней. Что нужно добавить: переходник от прошивочного шлейфа к клавиатуре. Распиновки для стандартного PS/2 коннектора, а также для DIN5 (встречается на старых клавиатурах):
PS/2: 1. KBD Clock (часы) 2. GND 3. KBD Data (данные) 4. N/C 5. +5V 6. N/C |
DIN5: 1. KBD Clock 2. KBD Data 3. N/C 4. GND 5. +5V (VCC) |
Соответствие выводам микроконтроллера[2] следующее:
- PB5 → KBD Clock
- PB6 → KBD Data
Как видно, используется всего 2 контакта — синхролиния (часы) и линия данных. Кроме этого клавиатура отжимает +5В питания с платы микроконтроллера.
Принципы работы клавиатуры
При подключении клавиатуры стандарта AT сначала происходит её инициализация. Если светодиоды-индикаторы работоспособны, то в этот момент они должны синхронно мигнуть. После этого клавиатура готова отсылать и принимать сообщения. Для нашего устройства достаточно одностороннего режима работы — передача сообщений от клавиатуры к хосту.
Передача данных
Для передачи используется простой последовательный протокол. Как было сказано выше, используется два провода. Первый — часы — отвечает за инициирование передачи, определение её направления и регулирование пересылки фрейма. Второй провод несёт непосредственную информацию. Рассмотрим порядок пересылки:
- Сначала уровни на обоих проводах высокие. Это соответствует состоянию ожидания (Idle), то есть ничего не происходит.
- Клавиатура инициирует пересылку, выставив на информационный сигнал в «0» (стартовый бит) и сменив уровень на часах «1»→«0».
- Далее клавиатура пересылает 8 бит данных сообщения от младшего бита к старшему, съем информационного сигнала требуется производить при появлении заднего фронта сигнала часов
- Предпоследний бит называется битом чётности[3] (parity bit) и используется для проверки целостности переданного байта (простой контроль на ошибку передачи).
- Последний бит равняется «1» и называется стоповым битом.
- Пересылка закончена и линии возвращаются в состояние ожидания.
Как видно, фрейм состоит из 11 бит. Переданный байт же соответствует определённой команде. В простейшем случае это код сканирования (scancode). О них дальше и поговорим.
Коды сканирования
Каждой клавише при нажатии соответствует либо один код, либо комбинация кодов (то есть сообщения состоят из нескольких байт). А когда клавишу отпускаешь, прилетает сообщение из нескольких байт, где первый — 0xF0
, а второй (их может быть и несколько) соответствует ранее нажатой клавише. Основные коды приведены на следующем рисунке:
Например, если нажать правый ALT, то клавиатура пошлёт сообщение 0xE0 0x11
, а если затем отпустить — 0xF0 0xE0 0x11
.
На этом теоретическая часть подошла к концу. Посмотрим, как можно реализовать это непосредственно на микроконтроллере.
Программный код
Далее рассмотрим только код, относящийся к снятию сообщений с клавиатуры. В прошлой статье я довольно подробно описывал процедуру создания двухканального синтезатора, и повторяться здесь не буду.
«Шапка» ассемблерного листинга выглядит следующим образом:
; Проект для ATtiny2313 .INCLUDE "tn2313def.inc" ; -------------------- ; Назначения регистров ; -------------------- .DEF State = R8 ; Регистр состояния .DEF SrSave = R9 ; Регистр для восстановления SR .DEF Tone1H = R10 .DEF Tone1L = R11 .DEF Tone2H = R12 .DEF Tone2L = R13 .DEF ToneTH = R14 .DEF ToneTL = R15 .DEF Temp = R17 .DEF Temp1 = R18 .DEF Temp2 = R19 .DEF Temp3 = R20 .DEF Temp4 = R21 .DEF ADDRH = R22 .DEF ADDRL = R23 .DEF RxByte = R24 .DEF BitCnt = R25 ; ------------------------------------------------- ; Назначение битов собственного статусного регистра ; ------------------------------------------------- .EQU BitParity = 0 .EQU BitNoteOff = 1 ; ------ ; Голоса ; ------ .EQU Voice1 = PB3 .EQU Voice2 = PB4 ; ----- ; МАКРО ; ----- ; OUTI порт, значение :: отправить значение в порт .MACRO OUTI ldi R16,@1 out @0,R16 .ENDM ; MEMR регистр, (место) :: чтение из памяти в регистр .MACRO MEMR lds @0, @1 .ENDM ; MEMW (место), регистр :: записать регистр в память .MACRO MEMW sts @0, @1 .ENDM ; MEMWI (место), значение :: записать значение в память .MACRO MEMWI ldi R16, @1 sts @0, R16 .ENDM ; ---- ; SRAM ; ---- .DSEG ; Синтезатор на два голоса voices: .BYTE 2
Приём сообщения от клавиатуры
Для приёма данных от клавиатуры нужно инициализировать прерывание по изменению уровня на цифровом входе синхролинии.
Pcint_Init: ; Включить прерывание по изменению уровня outi GIMSK, (1<<PCIE) outi PCMSK, (1<<PCINT5) ret
В основном цикле программы нужно включить прерывания, а также очистить счётчик битов и статусный регистр. Кроме того, соответствующие изменения надо внести и в вектор прерываний в начале основного кода (добавить rjmp PCINT
по соответствующему адресу).
Когда синхролиния свалится в низкий уровень, произойдёт прерывание. Его обработает следующая процедура:
PCINT: cli in SrSave, SREG push SrSave ; Проверка на задний фронт sbis PINB, PB5 rcall RxKbd pop SrSave out SREG, SrSave sei reti
Если обнаружен задний фронт сигнала (момент «1»→«0»), то вызывается процедура RxKbd
, которая отвечает за обработку информационного сигнала.
RxKbd: ; Очистить бит T clt ; Если PB6 установлен, сохранить в T sbic PINB, PB6 set ; Первый бит фрейма? cpi BitCnt, 0 breq firstBit ; Проверить чётность cpi BitCnt, 9 breq parityCheck ; Последний бит фрейма? cpi BitCnt, 10 breq rxFinished ; Сохранить бит в буфер lsr RxByte bld RxByte, 7 ; Сохранить бит чётности clr Temp1 bld Temp1, BitParity eor State, Temp1 RxBitDone: ; Инкремент счётчика бит inc BitCnt ret firstBit: ; Очистить буфер clr RxByte ; Очистить бит чётности ldi R16, 0xFE and State, R16 rjmp RxBitDone parityCheck: ; Сохранить бит чётности clr Temp1 bld Temp1, BitParity eor State, Temp1 rjmp RxBitDone rxFinished: ; Проверить бит чётности (нечётность): ; если = 1, то всё правильно sbrs State, BitParity rjmp discardByte ; Очистить счётчик битов clr BitCnt ; Вызвать обработчик байта rcall handleByte ret
Как это работает:
- При поступлении стартового бита обнуляется буфер и бит чётности.
- Биты 1-8 складываются в буфер, кроме того над ними последовательно производится операция XOR, результат которой сохраняется в бите специального регистра.
- Последняя операция XOR производится с полученным и присланным битами чётности. При этом полученный XOR-ами бит не инвертируется. Следовательно, в результате операции бит чётности должен быть равен 1, если при пересылке не возникло ошибок.
- После получения последнего бита фрейма управление передаётся процедуре
discardByte
, если проверка чётности провалена, либо в случае успешной передачи процедуреhandleByte
, которая обработает полученный байт.
Рассмотрим конкретный пример. Допустим пользователь нажимает клавишу ESC
, которой соответствует скан-код 0x76
. В таком случае произойдёт следующее:
Обработка полученного байта
Поскольку основная статья всё-таки про подключение клавиатуры, я не буду останавливаться подробно на коде обработчика полученного байта, а просто объясню что там к чему.
Нашей задачей было прикрутить к синтезатору клавиатуру. Первая часть задания выполнена, теперь нужно каким-то образом на основе полученных от клавиатуры сообщений включать или выключать воспроизведение определённых нот. Вот как это сделано:
- Синтезатор имеет два голоса.
- При поступлении сообщения о нажатии клавиши ищется свободный голос. Если такой найден — в него отправляется соответствующая клавише нота.
- При поступлении сообщения об отпускании клавиши в статусный регистр заносится метка «следующее сообщение об отпускании клавиши»; когда обрабатывается следующий байт, проверяется, была ли такая клавиша нажата и голос активирован. Если да — соответствующий голос освобождается.
- Если поступает сообщение о нажатии клавиши
ESC
, оба голоса освобождаются (что-то наподобие Panic Button, если голоса застревают после отпускания клавиш).
Что касается нахождения ноты по клавише, то это сделано по следующей схеме. В памяти кристалла хранится две таблицы. Одна из них уже знакомая таблица нот, где ноты в этот раз заданы в восходящем порядке шагом в полутон. Другая таблица содержит список клавиш, соответствующих этим нотам, также в восходящем порядке. Таким образом, для извлечения ноты необходимо просто найти позицию клавиши во второй таблице и взять ноту на той же позиции из первой. Вот эти таблицы для наглядности:
tblNotes: .dw 19111 ; C2 .dw 18039 ; C#2 .dw 17026 ; D2 .dw 16071 ; D#2 .dw 15169 ; E2 .dw 14317 ; F2 .dw 13514 ; F#2 .dw 12755 ; G2 .dw 12039 ; G#2 .dw 11364 ; A2 .dw 10726 ; A#2 .dw 10124 ; B2 .dw 9556 ; C3 .dw 9019 ; C#3 .dw 8513 ; D3 .dw 8035 ; D#3 .dw 7584 ; E3 .dw 7159 ; F3 .dw 6757 ; F#3 .dw 6378 ; G3 .dw 6020 ; G#3 .dw 5682 ; A3 .dw 5363 ; A#3 .dw 5062 ; B3 .dw 4778 ; C4 .dw 4510 ; C#4 .dw 4257 ; D4 .dw 4018 ; D#4 .dw 3792 ; E4 .dw 3579 ; F4 .dw 3378 ; F#4 .dw 3189 ; G4 .dw 3010 ; G#4 .dw 2841 ; A4 .dw 2681 ; A#4 .dw 2531 ; B4 .dw 2389 ; C5 .dw 2255 ; C#5 .dw 2128 ; D5 .dw 2009 ; D#5 .dw 1896 ; E5 .dw 1790 ; F5 .dw 1689 ; F#5 .dw 1594 ; G5 .dw 1505 ; G#5 .dw 1420 ; A5 .dw 1341 ; A#5 .dw 1265 ; B5 .dw 1194 ; C6 .dw 1127 ; C#6 .dw 1064 ; D6 .dw 1004 ; D#6 .dw 948 ; E6 .dw 895 ; F6 .dw 845 ; F#6 .dw 797 ; G6 .dw 752 ; G#6 .dw 710 ; A6 .dw 670 ; A#6 .dw 633 ; B6 .dw 597 ; C7 scanCodes: .db 0x1A, 0x1B ; Z, S .db 0x22, 0x23 ; X, D .db 0x21, 0x2A ; C, V .db 0x34, 0x32 ; G, B .db 0x33, 0x31 ; H, N .db 0x3B, 0x3A ; J, M .db 0x41, 0x4B ; <, L .db 0x49, 0x4C ; >, : .db 0x4A, 0x15 ; ?, Q .db 0x1E, 0x1D ; 2, W .db 0x26, 0x24 ; 3, E .db 0x25, 0x2D ; 4, R .db 0x2C, 0x36 ; T, 6 .db 0x35, 0x3D ; Y, 7 .db 0x3C, 0x43 ; U, I .db 0x46, 0x44 ; 9, O .db 0x45, 0x4D ; 0, P .db 0x4E, 0x54 ; -, [ .db 0x5B, 0x00 ; ]
Получается следующее отображение клавиш фортепиано на клавиатуру:
Итого: 3 октавы + клавиша «ДО» четвёртой, всего 37 клавиш.
Полный код программы
С комментариями на английском: ATtiny2313XBoard_KeyboardOrgan.asm
Весь код после компиляции занимает 650 байт памяти кристалла.
Результат
Демонстрация[4] полученного электронного органа:
Примечания
- ↑Статья про MIDI, если кто-то её ждёт, будет следующей в цикле статей про электронику.
- ↑Для данных можно использовать любой пин, а вот для часов нужен вывод с настраиваемым прерыванием по изменению уровня в режиме цифрового входа.
- ↑Используется бит нечётности (odd parity): биты, составляющие байт складываются при помощи логической операции XOR и затем берётся инверсное значение. Например, для байта
11011010
:1^1^0^1^1^0^1^0 = 1
,!(1) = 0
и передано будет110110100
- ↑Я использую несколько другую плату, чем на схеме. Дополнительно там есть семисегментный индикатор, при помощи которого можно следить за содержимым ячеек памяти. Думаю, на тему отладки таким образом тоже стоит написать статью когда-нибудь :-)