$include (at89cx051.mcu)

; i2c
SCL     EQU     P3_3
SDA     EQU     P3_4
WP      EQU     P3_5

RELE    EQU     P3_7
HL      EQU     P3_2



; КОНСТАНТЫ ИНТЕРФЕЙСА ЗАГРУЗКИ
CODE_DEV_READY   EQU  005h   ; код, посылаемый устройством как признак его готовности
CODE_SERV_READY  EQU  010h   ; код, посылаемый в ответ сервером, подтверждающий готовность
CODE_DEV_FINALY  EQU  008h   ; код, посылаемый устройством как признак установления связи
CODE_DEV_OK      EQU  027h   ; код удачного окончания операции, выдаваесмый устройством
CODE_MODE_READ   EQU  001h   ; код, посылаемый сервером для чтения памяти устройства
CODE_MODE_WRITE  EQU  002h   ; код, посылаемый сервером для записи памяти устройства
CODE_MODE_PLAY   EQU  003h   ; код, посылаемый сервером для проигрывания мелодии устройством
CODE_MODE_NOTE   EQU  004h   ; код, посылаемый сервером для проигрывания устройством ноты
CODE_MODE_OFF    EQU  005h   ; код, посылаемый сервером для выключения питания устройства


WRITE_PAGE_SIZE  EQU  64     ; размер страницы, которыми ведется запись

HL_UART_COUNT    EQU  32     ; через сколько переданных/прочитанных байт менять состояние светодиода

CHIP_ADDRES_1    EQU  10100000b


STACK            EQU 056h

; ПЕРЕМЕННЫЕ ПРОГРАММЫ
NOTE_T2_HI         EQU   10   ; полупериод проигрываемой ноты (старший байт)
NOTE_T2_LO         EQU   11   ; -//- младший байт
NOTE_VOLUME        EQU   12   ; уровень громкости проигрываемой ноты
NOTE_DURABILITY    EQU   13   ; длительность текущей ноты (или паузы) в интервалах по 50 мС
HL_COUNTER         EQU   14   ; счетчик для мигания светодиодом





CSEG AT 0
; ТОЧКА ВХОДА В ПРОГРАММУ
	  ljmp     start

ORG    0Bh
; ВЕКТОР ПРЕРЫВАНИЯ ТАЙМЕРА T/C0
    ljmp    interrupt_handler_tc0

ORG    30h

start:
; включаем питание
     setb    RELE
     clr     HL
; настраиваем стек
     mov     SP, #STACK
; настраиваемся на таблицу полупериодов нот
     mov     DPTR, #d_notes
; иннициализируем последовательный порт
     lcall   serial_init
     
     mov     NOTE_VOLUME, #255
     mov     HL_COUNTER, #HL_UART_COUNT



; проверяем, не подключено ли чего к порту..
    mov      A, #100                       ; A - количество итераций проверки активности интерфейса
verify_downloader_cycle:
    mov      B, #100                       ; B - величина задержки каждой итерации
verify_downloader_wait:
    djnz     B, verify_downloader_wait
    jnb      RXD, downloader_found
    dec      A
    jnz      verify_downloader_cycle




    

play_start:
    mov      A, #CHIP_ADDRES_1
    lcall    play_melody

power_off:
    clr      RELE
repeat:
    jmp      repeat



; код работы с загрузчиком мелодий по параллельному интерфейсу
downloader_found:

    setb    HL

    ; посылаем код признака готовности
    mov     A, #CODE_DEV_READY
    lcall   serial_send
    
    ; дожидаемся от сервера подтверждения готовности
downloader_wait_ready:
    lcall   serial_read
    lcall   serial_send
    cjne    A, #CODE_SERV_READY, downloader_wait_ready

    ; посылаем признак установления соединения
    mov     A, #CODE_DEV_FINALY
    lcall   serial_send

    
    ; точка входа в инженерное меню управления с ПК
downloader_menu:
;    clr     HL
    ; читаем код команды
    lcall   serial_read
    ; эхом отправляем код назад
    lcall   serial_send
    cjne    A, #CODE_MODE_READ, downloader_menu_verify_write
    jmp     loader_send_data
downloader_menu_verify_write:
    cjne    A, #CODE_MODE_WRITE, downloader_menu_verify_play
    jmp     loader_flash_data
downloader_menu_verify_play:
    cjne    A, #CODE_MODE_PLAY, downloader_menu_verify_note
    jmp     loader_play_data
downloader_menu_verify_note:
    cjne    A, #CODE_MODE_NOTE, downloader_menu_verify_off
    jmp     loader_play_note
downloader_menu_verify_off:
    cjne    A, #CODE_MODE_OFF, downloader_menu
    jmp     loader_power_off
    
    




;-------------------------------------------------
; пересылка содержимого микросхем памяти на сервер
loader_send_data:
    ; читаем номер микросхемы
    lcall   serial_read
    mov     R1, A
    lcall   i2c_set_addres
    ; читаем количество страниц
    lcall   serial_read
    mov     R2, A                             ; R2 - сколько страниц осталось прочитать
    mov     R3, #0FFh                         ; R3 - сколько осталось прочитать в текущей странице

    lcall   i2c_start
    mov     A, R1
    inc     A
    lcall   i2c_out
    lcall   i2c_wait_ack

loader_send_cycle:
    lcall   i2c_in
    lcall   serial_send
    lcall   i2c_send_ack
    
    djnz    R3, loader_send_cycle             ; цикл по байтам страницы
    mov     R3, #0FFh
    djnz    R2, loader_send_cycle             ; цикл по страницам


    ; завершение чтения по i2c
    ; т.к. в цикле последней командой выполнилась ACK, надо прочитать еще один байт
    lcall   i2c_in
    ; передаем микросхеме памяти NOACK + STOP
    lcall   i2c_send_noack
    lcall   i2c_stop

    jmp     downloader_menu             ; после передачи данных возвращаемся в меню


;-------------------------------------------
; загрузка данных по последовательному порту
loader_flash_data:
    ; отключаем защиту от записи всех микросхем памяти
    clr     WP
    ; получаем адрес микросхемы
    lcall   serial_read
    mov     R1, A
    lcall   i2c_start
    lcall   i2c_out
    lcall   i2c_wait_ack
    ; получаем старший байт адреса
    lcall   serial_read
    lcall   i2c_out
    lcall   i2c_wait_ack
    ; получаем младший байт адреса
    lcall   serial_read
    lcall   i2c_out
    lcall   i2c_wait_ack

    ; цикл записи страницы
    mov     R2, #WRITE_PAGE_SIZE

    
loader_flash_cycle:
    lcall   serial_read
    lcall   i2c_out
    lcall   i2c_wait_ack

    djnz    R2, loader_flash_cycle
    
    lcall   i2c_stop

    ; теперь микросхеме нужно время для записи в течении которого она не будет отзываться на свой адрес
    ; для этого будем передавать ей команду START и ее адрес для чтения до тех пор, пока не получим подтверждения
    mov     A, R1
    lcall   i2c_wait_writing

    ; включаем защиту от записи всех микросхем памяти
    setb    WP

    jmp     downloader_menu             ; после передачи данных возвращаемся в меню
    
    
;----------------------------------
; прослушивание загруженной мелодии
loader_play_data:
    ; читаем адрес микросхемы
    lcall   serial_read
    lcall   play_melody
    
    ; сообщаем о завершении прослушивания
    mov     A, #CODE_DEV_OK
    lcall   serial_send

    jmp     downloader_menu             ; после передачи данных возвращаемся в меню


;-------------------
; прослушивание ноты
loader_play_note:
    ; чтобы светодиод мигал в такт нотам сбрасываем счетчик
    mov     HL_COUNTER, #HL_UART_COUNT
    ; читаем номер ноты
    lcall   serial_read
    mov     R1, A
    ; читаем ее длительность
    lcall   serial_read
    
    lcall   play_note
    
    ; сообщаем о завершении прослушивания
    mov     A, #CODE_DEV_OK
    lcall   serial_send
    mov     HL_COUNTER, #HL_UART_COUNT
    jmp     downloader_menu             ; после передачи данных возвращаемся в меню


;-------------------
; выключение питания
loader_power_off:
    jmp     power_off

$include (i2c.a51)
$include (serial.a51)





; ============================================================================
; проигрывание мелодии
; адрес микросхемы передается в регистре A
play_melody:
    ; адресуем микросхему памяти
    mov     R1, A

    lcall   i2c_set_addres


    lcall   i2c_start
    mov     A, R1
    inc     A

    lcall   i2c_out
    lcall   i2c_wait_ack
    
    
    
; ПРОИГРЫВАНИЕ МЕЛОДИИ
    jmp     play_notes_cycle_go ; входим в цикл воспроизведения мелодии
    
play_notes_cycle:
    lcall   i2c_send_ack
play_notes_cycle_go:            ; это точка входа в цикл
    lcall   i2c_in              ; 0 - пауза, 0xFF - изменение громкости, иначе - нота
    mov     R1, A
    lcall   i2c_send_ack
    lcall   i2c_in              ; длительность ноты. если 0 - выходим из цикла
    jz      play_notes_done
    
    lcall   play_note

    jmp     play_notes_cycle
play_notes_done:


    
; ПРОИГРЫВАНИЕ WAV-ФРАГМЕНТА
    ; читаем в R1 время задержки
    lcall   i2c_send_ack
    lcall   i2c_in
    mov     R1, A


play_wav_cycle:
    lcall   i2c_send_ack
    lcall   i2c_in
    mov     P1, A             ; выставляем напряжение на ЦАПе
    

    ; если уровень выше 0xE0, то инвертируем светодиод
    cjne    A, #0E0h, play_wave_cmp
play_wave_cmp:
    jc      play_wave_not_inverse
    cpl     HL
play_wave_not_inverse:


    ; выполняем задержку
    mov     B, R1
play_wav_wait_cycle:
    djnz    B, play_wav_wait_cycle

    jnz     play_wav_cycle
    
    
    
; ПРОИГРЫВАНИЕ МЕЛОДИИ ПОСЛЕ
play_notes_cycle_2:
    lcall   i2c_send_ack
    lcall   i2c_in              ; 0 - пауза, 0xFF - изменение громкости, иначе - нота
    mov     R1, A
    lcall   i2c_send_ack
    lcall   i2c_in              ; длительность ноты. если 0 - выходим из цикла
    jz      play_notes_done_2

    lcall   play_note

    jmp     play_notes_cycle_2
play_notes_done_2:


    
    
    mov     P1, #0
    
    lcall   i2c_send_noack
    lcall   i2c_stop
    
    ret
    
    
; ============================================================================
; выполняет задержку на величину полупериода текущей проигрываемой ноты (NOTE_T2_HI, NOTE_T2_LO)
; время задержки: 12 + 2*NOTE_T2_LO                                (если NOTE_T2_HI == 0)
;                 10 + 2*NOTE_T2_LO + 514*NOTE_T2_HI               (если NOTE_T2_HI != 0)
; NOTE_T2_LO не должен быть 0!
delay_t2:
    ; сначала задержка на NOTE_T2_HI * 255 мс
    mov     R1, NOTE_T2_HI
    cjne    R1, #0, delay_t2_hi
    jmp     delay_t2_no_hi
delay_t2_hi:
    mov     B, #255
delay_t2_255_loop:
    djnz    B, delay_t2_255_loop
    djnz    R1, delay_t2_hi
delay_t2_no_hi:
    mov     B, NOTE_T2_LO
delay_t2_lo:
    djnz    B, delay_t2_lo
    ret
; ============================================================================



; ============================================================================
; проигрывает ноту
;   в регистре R1 получает номер ноты (0-пауза, 0xFF-изменение громкости)
;   в регистре A получает длительность ноты (или уровень громкости, если R1==0xFF)
play_note:
    ; инвертируем состояние светодиода
    cpl     HL

    ; провереем, не изменение ли это громкости
    cjne    R1, #0FFh, play_note_note
    ; изменяем громкость звучания и выходим
    mov     NOTE_VOLUME, A
    ret
    ; это воспроизведене ноты
play_note_note:


    ; загружаем полупериод ноты из таблицы
    mov     R2, A                       ; длительность ноты теперь в R2
    mov     A, R1
    rl      A                           ; A = A * 2
    movc    A, @A + DPTR
    mov     NOTE_T2_HI, A
    mov     A, R1
    rl      A
    inc     A                           ; A = A * 2 + 1
    movc    A, @A + DPTR
    mov     NOTE_T2_LO, A

    mov     TL0, #LOW(NOT(25000-1))     ; загрузка таймера
    mov     TH0, #HIGH(NOT(25000-1))    ; для задержки 25 мс
    setb    EA                          ; разрешаем прерывания
    setb    ET0                         ; разрешение прерывания от Т/С0
    mov     R3, SP                      ; запоминаем указатель на вершину стека
    setb    TR0                         ; старт Т/С0
    
    cjne    R1, #0, play_note_cycle
play_pause_cycle:
    mov     P1, #0
    jmp     play_pause_cycle

play_note_cycle:
    mov     P1, NOTE_VOLUME
    lcall   delay_t2
    mov     P1, #0
    lcall   delay_t2
    jmp     play_note_cycle
    
; далее, выход из этой процедуры будет осуществлен в прерывании после восстановления стека,
; поэтому команда ret тут не нужна

    
    
; *************************************************************************************************
; ТАБЛИЦА ПОЛУПЕРИОДОВ НОТ
; *************************************************************************************************
d_notes:
  $include (notes.a51)


; *************************************************************************************************
; Обработчик прерывания таймера T/C0
; *************************************************************************************************
interrupt_handler_tc0:
    dec     R2
    cjne    R2, #0, interrupt_handler_reinit
    clr     TR0 ; TCON.4                ; останов T/C0
    mov     SP, R3                      ; восстанавливаем стек
    reti
interrupt_handler_reinit:
    mov     TL0, #LOW(NOT(25000-1))     ; загрузка таймера
    mov     TH0, #HIGH(NOT(25000-1))    ; для задержки 25 мс
    reti


; ============================================================================
; инкрементирует счетчик и изменяет состояние светодиода, если пришло время
count_and_inverse_hl:
    djnz    HL_COUNTER, count_and_inverse_hl_ret
    mov     HL_COUNTER, #HL_UART_COUNT
    cpl     HL
count_and_inverse_hl_ret:
    ret

; ============================================================================

END


