.DEVICE ATmega16
.include "m16def.inc"


.equ ActvSens    = 0xFE  ;статус-байт Active Sensing = 0xFE посылается передатчиком не реже 270-300 мс 
.equ NoteOn      = 0x90  ;Взять ноту
.equ NoteOff     = 0x80  ;Отпустить ноту
.equ AllNotesOff = 0x7B
.equ SysEx       = 0xF0  ;System Exclusive:        F0(SysEx) ID Byte1 Byte2... ByteN F7(EoSysEx)
.equ EoSysEx     = 0xF7  ;End Of System Exclusive

.equ BuffCMDSize = 30    ;размер буфера комманд
.equ BuffKeySize = 30    ;размер буфера нажатых клавиш

.equ MCAFlag     = 0     ;флаг добалвения миди комманд в очередь

.list
.def temp =r16


.def KeyPinOut1=r17
.def KeyPinOut2=r18

.def NChanel        =r10
.def OktavKeyCnt    =r11
.def OktavShiftConst=r12
.def OktavShift     =r13
.def CurKeyCode     =r14

.def MIDIBUFF1    =r21
.def MIDIBUFF2    =r22
.def MIDIBUFF3    =r23
.def UserFLAGs    =r3 ;Пользовательский флаговый регистр
                      ;UserFlag0 = MCAFlag
//r19 r24 r25 то что пока свободно

.DSEG
MIDIquee:      .BYTE BuffCMDSize ;максимальный размер очереди миди сообщений - 30 (байт) для 10 клавиш по 3 сообщения в очереди
PressedKeyTab: .BYTE BuffKeySize ;максимальный число нажатых одноврменно клавиш - 30 (20 для двух "пианистов" + 10 для служебных)

.cseg
.org 0x0000
rjmp RESET       ;Reset Handler
.org OVF0addr    
rjmp Timer_int   ;Timer0 Overflow Interrupt Vector Address
.org UDREaddr
rjmp USARTClear  ;UART Data Register Empty Interrupt Vector Address
.org UTXCaddr
rjmp UART_Tx_ok  ;UART Transmit Complete Interrupt Vector Address
.org ADCCaddr
rjmp ADC_Int	 ;ADC Interrupt Vector Address

RESET:
     ; тут начинается инициализация стека
     ; потом все подготовительные операции
     ; настройка портов, таймеров и т.п.

;Инициализация стека
ldi temp, low(RAMEND)
out SPL, temp
ldi temp, high(RAMEND)
out SPH, temp

;Инициализация портов

;Инициализация порта A
ldi temp, 0xFF
out DDRA, temp  ;Порт A на ввод
ldi temp, 0xFF
out PORTA, temp ;Включаем подтягивающие резисторы

;Инициализация порта B
ldi temp, 0xFF
out DDRB, temp  ;Порт B на ввод
ldi temp, 0xFF
out PORTB, temp ;Включаем подтягивающие резисторы

;Инициализация порта С
ldi temp, 0x00
out DDRC, temp  ;Порт С на вывод

;Инициализация порта D
ldi temp, 0x0D
out DDRD, temp  ;Порт D на смешаный ввод/вывод
ldi temp, 0x0D
out PORTD, temp ;Включаем подтягивающие резисторы на входах


;Инициализация USART (31,25 Кбит/с) 
;USART.Mode=32125,N,1(8Mhz)
ldi temp,0x00  ;U2x=0 + (1<<TXC) ??
out UCSRA,temp
ldi temp,(1<<TXEN);Tx_Enable
out UCSRB,temp 
ldi temp,(1<<URSEL)|(3<<UCSZ0);Asink+8Bit(Parity_Dsbl,Stp_Bit_1)
out UCSRC,temp
ldi temp,0x00
out UBRRH,r16
ldi temp,0x0F
out UBRRL,temp 
;+ первая посылка
ldi temp,ActvSens
out UDR,temp

;Инициализация таймера
ldi temp,0x00
out TCNT0,temp
ldi temp,0x00
out OCR0,temp
ldi temp,(5<<CS00);Clk(8Mhz)/1024
out TCCR0,temp
ldi temp,0x00
out TIFR,temp
ldi temp,(1<<TOIE0);Timer_OverFlow_int
out TIMSK,temp

sei ; разрешаем прерывания

; Начальные параметры работы
ldi KeyPinOut1,0xFE ;11111110
ldi KeyPinOut2,0xFF ;11111111
ldi temp,0x00       ;1 клавиша
mov CurKeyCode,temp
ldi temp,0x30            ;текущая октава начинается клавишей №48 (в ХЕКСе 0х30)
mov OktavShift,temp      ;начальное значение для рабочих нужд
mov OktavShiftConst,temp ;то-же самое в безопасное место
ldi temp,0x0C            ;по 12 клавиш в октаве
mov OktavKeyCnt,temp     ;(в безопасное место)
ldi temp,0x00            ;работаем на нулевом MIDI-канале
mov NChanel,temp         ;(в безопасное место)
ldi temp,0x00
mov UserFLAGs,temp       ;0 все биты флага сброшены
ldi YH,High(PressedKeyTab) ;начальный адрес хранения включенных клавиш
ldi YL,Low(PressedKeyTab)
ldi XH,high(MIDIquee)      ;начальный адрес буфера очереди  MIDI-сообщений
ldi XL,low(MIDIquee)
;==========================================================;
;Начало работы устройства (тело программы)
;==========================================================;
Body:
out PORTC,KeyPinOut1
out PORTD,KeyPinOut2
nop ;для синхронизации порта


rcall CrntKey
nop ;для синхронизации "тиков" динамики
SBIS PINA,0 ;отпущена клавиша в первой опрашиваемой октаве
rcall KeyUp
SBIC PINB,0 ;нажата клавиша в первой опрашиваемой октаве
RJMP NPin1
nop ;для синхронизации "тиков" динамики
rcall KeyPressed

NPin1:
ADD OktavShift,OktavKeyCnt
rcall CrntKey
SBIS PINA,1 ;отпущена клавиша во второй опрашиваемой октаве
rcall KeyUp
SBIC PINB,1 ;нажата клавиша во второй опрашиваемой октаве
RJMP NPin2
rcall KeyPressed

NPin2:
ADD OktavShift,OktavKeyCnt
rcall CrntKey
SBIS PINA,2  ;отпущена клавиша в третей опрашиваемой октаве
rcall KeyUp
SBIC PINB,2  ;нажата клавиша в третей опрашиваемой октаве
RJMP NPin3
rcall CrntKey
rcall KeyPressed

NPin3:
ADD OktavShift,OktavKeyCnt
rcall CrntKey
SBIS PINA,3  ;отпущена клавиша в четвертой опрашиваемой октаве
rcall KeyUp
SBIC PINB,3  ;нажата клавиша в четвертой опрашиваемой октаве
RJMP NPin4
rcall KeyPressed

NPin4:
ADD OktavShift,OktavKeyCnt
rcall CrntKey
SBIS PINA,4  ;отпущена клавиша в пятой опрашиваемой октаве
rcall KeyUp
SBIC PINB,4  ;нажата клавиша в пятой опрашиваемой октаве
RJMP NPinNext
rcall KeyPressed

NPinNext:           ;ниодна клавиша не нажата
mov OktavShift,OktavShiftConst ;начать опрашивать с "первой"
INC CurKeyCode      ;сдвинуть код клавиши на "вторую"
COM KeyPinOut1      ;сдвинуть 0 в первом выходном регистре на одну позицию влево
MOV r9,KeyPinOut1
ADD KeyPinOut1,r9
COM KeyPinOut1
CPI KeyPinOut1,0xFF ;если двигать дальше некуда
BRNE BodyEnd
CPI KeyPinOut2,0xFF
BRNE JLabel
ldi KeyPinOut2,0xF7 ;начинаем двигать 0 во втором выходном регистре начиная с 5 ножки
JLabel:
COM KeyPinOut2
MOV r9,KeyPinOut2
ADD KeyPinOut2,r9
COM KeyPinOut2
CPI KeyPinOut2,0xFF ;если во втором регистре тоже некуда дальше двигать
BRNE BodyEnd 
ldi KeyPinOut1,0xFE ;начинаем двигать 0 с начала первого выходного регистра
ldi temp,0x00       ;устанавливаем номер опроса клавиши с первой
mov CurKeyCode,temp
BodyEnd:
rjmp Body
;==========================================================;
;(Конец тела программы)
;==========================================================;

CrntKey:
  MOV MIDIBUFF1,OktavShift    ;текщая октава
  ADD MIDIBUFF1,CurKeyCode    ;+номер клавиши в октаве = код клавиши (второй фрагмент сообщения)
ret


KeyPressed:           ;Процедура:Если клавиша нажата ++++
  ;проверяем есть ли такая клавиша в списке нажатых
  LDI  temp,Low(PressedKeyTab);начало списка кнопок
  MOVW ZL,YL                  ;текущая позиция (конец) списка кнопок
  ;(цикл)
 IsKeyLoop:
  CP    ZL,temp                ;=конец-начало 
  BREQ  KeyPressd1st           ;если =0 (достигнуто начало списка кнопок)
  LD    MIDIBUFF2,-Z           ;иначе грузим код кнопки в буфер2
  CP    MIDIBUFF2,MIDIBUFF1    ;сравниваем с текущим кодом
  BREQ  SkipPressed            ;если такая клавиша уже была - выходим  и пропускаем добавление
  rjmp  IsKeyLoop              ;иначе цикл
 KeyPressd1st:
  ldi   MIDIBUFF3,NoteOn       ;"взять ноту" (0х9n)
  ldi   temp,0x00
  mov   UserFLAGs,temp
  rcall AddQueCode
  SBRC  UserFLAGs,0
ret ;сообщения о включении ноты не добавлены в очередь => выходим
  MOV   temp,YL                ;текущий конец буфера нажатых клавиш
  SUBI  temp,Low(PressedKeyTab);минус начало буфера (=размер буфер)
  CPI   temp,BuffCMDSize       ;текущий размер буфера минус максимальный размер
  BRPL  SkipPressed            ;если не минус (буфер закончился) => выходим
  ST    Y+,MIDIBUFF1           ;заносим в список (буфер) включенных клавиш
 SkipPressed:
ret ;конец процедуры +++
 
AddQueCode:          ;процедура добавления сообщений в очередь
  MOV  temp,XL                ;текущий конец очереди сообщений
  SUBI temp,Low(MIDIquee)     ;минус начало очереди (=размер)
  LDI  MIDIBUFF2,0x03
  ADD  temp,MIDIBUFF2         ;+3 комманды (место для 3х фрагментов)
  CPI  temp,BuffCMDSize       ;(размер+3)-(максимальная длинна)
  BRPL AddQueCodeEnd          ;если не минус очередь переполненна => выходим
  MOV  MIDIBUFF2,NChanel      ;номер канала по которому идет передача
  ADD  MIDIBUFF2,MIDIBUFF3    ;+комманда (NoteOn/NoteOff/..) 
  SBIC UCSRA,5            ;если Transmiter'у нечего передавать
  RJMP Skip1Cmd           ;сохраняем только 2 фрагмента
  ST   X+,MIDIBUFF2           ;пишем в очередь первый миди фрагмент (NoteOn/NoteOff/..)
 Skip1Cmd:
  ST   X+,MIDIBUFF1           ;пишем второй фрагмент (номер ноты)
  LDI  temp,0x3F          ;динамика 63 (временно!!!!)<======================тут нужно вставить переменную!!!!!
  ST   X+,temp            ;пишем в очередь третий фрагмент
  SBIS UCSRA,5            ;если Transmiter'у есть что передавать пропускаем вывод первой!!
;ret                       ;выходим ;иначе - инициализируем передачу
  out UDR,MIDIBUFF2       ;иначе выводим первую миди комманду
  ldi temp,(1<<UDRIE)|(1<<TXEN);разрешения для прерывания + предатчик разрешен
  out UCSRB,temp          ;включаем флаг
ret
 AddQueCodeEnd:
  ldi temp,(1<<MCAFlag)
  mov UserFLAGs,temp       ;сообщение НЕ добавлено
ret ;конец процедуры +++


KeyUp: ;Процедура Отпускаем клавишу
  ;проверяем есть ли такая клавиша в списке нажатых
  LDI  temp,Low(PressedKeyTab);начало списка клавиш
  MOVW ZL,YL                  ;текущая позиция (конец) списка кнопок
  ;(цикл)
 IsKeyLoop2:
  CP   ZL,temp                ;=конец-начало 
  BREQ KeyUpped               ;если =0 (достигнуто начало списка кнопок)
  LD   MIDIBUFF2,-Z           ;иначе грузим код кнопки в буфер2
  CPSE   MIDIBUFF2,MIDIBUFF1  ;если такая клавиша уже была => отпускаем её
  rjmp IsKeyLoop2             ;иначе цикл
  ;Генерация NoteOff => в очередь - если удалось занести
  ldi   MIDIBUFF3,NoteOff     ;"взять ноту" (0х9n)
  ldi   temp,0x00
  mov   UserFLAGs,temp
  rcall AddQueCode
  SBRC  UserFLAGs,0
ret
  ;цикл сдвигания кодов клавиш от текущей до конца списка
  INC  ZL
 KeyShiftLoop2:       ;сдвигаем список на 1 вперед
  CP   YL,ZL          ;=конец-текщая_позиция 
  BREQ DecKeyBuff     ;если =0 (достигнута последняя запись списка)
  LD   MIDIBUFF1,Z
  ST   -Z,MIDIBUFF1
  INC  ZL
  INC  ZL
  rjmp KeyShiftLoop2
  ;(удаляем клавишу со списка)
 DecKeyBuff:
  DEC  YL
 KeyUpped:
ret

;=========================ПЕРЫВАНИЯ=============================================
Timer_int:  ;Active Sensing посылается передатчиком в не реже 270-300 мс (статус-байт 0xFE (0FEh)) если передачи нету и/или буфер пустой
  SBIS UCSRA,5      ;если там и так есть что передавать
  RJMP TimerIntEnd  ;выходим
  ldi temp,ActvSens ;иначе - выгружаем комманду 0xFE
  out UDR,temp      ;и выплевываем через USART
TimerIntEnd:
  reti

USARTClear: ;буфер пустой Читаем след. фрагмент в очереди, если нету: выводим + обнуляем
  LDI  ZL,Low(MIDIquee)  ;начало череди сообщений
  LDI  ZH,High(MIDIquee) 
  CP   XL,ZL         ;=конец-начало 
  BREQ ClearUDRFlag  ;если =0 (достигнуто начало списка сообщений)
  LD   MIDIBUFF1,Z+  ;иначе первое сообщение в очереди => буфер1
  out  UDR,MIDIBUFF1 ;выводим
 KeyShiftLoop:       ;сдвигаем очередь на 1 вперед
  CP   XL,ZL         ;=конец-текщая_позиция 
  BREQ DecQuee       ;если =0 (достигнутконец очереди)
;  BRMI DecQuee
  LD   MIDIBUFF1,Z
  ST   -Z,MIDIBUFF1
  INC  ZL
  INC  ZL
  rjmp KeyShiftLoop            
 DecQuee:
  DEC XL             ;очередь-1
reti                 ;и выход
 ClearUDRFlag:
  ldi  temp,(0<<UDRIE)|(1<<TXEN);запрещаем прерывания при освобождении буфера
  out  UCSRB,temp
reti  ;конец прерывания USARTClear +++


UART_Tx_ok: ;передатчик пустой (для срочных системных сообщений)
nop
reti

ADC_Int:    ;АЦП резистора завершено, высчитуем копируем в буфер значения
nop
reti
