.include "2313def.inc"

.EQU  MOSFET_GATE_PORT  = PORTB
.EQU  MOSFET_GATE_OUT   = PB4
.EQU  DISCHARGE_PORT    = PORTB
.EQU  DISCHARGE_OUT     = PB3
.EQU  CHARGE_PUMP_ENABLE_PORT  = PORTB
.EQU  CHARGE_PUMP_ENABLE_OUT   = PB2
.EQU  PORTB_DIR         = 0xFC  ; all outputs but comparator

.EQU  LED_PORT          = PORTD
.EQU  LED_OUT           = PD4
.EQU  CHARGE_PUMP_PORT  = PORTD
.EQU  CHARGE_PUMP_OUT   = PD6
.EQU  MOSFET_GATE_PORT2 = PORTD
.EQU  MOSFET_GATE_OUT2  = PD0
.EQU  MOSFET_GATE_OUT3  = PD1
.EQU  MOSFET_GATE_OUT4  = PD2
.EQU  MOSFET_GATE_OUT5  = PD3
.EQU  MOSFET_GATE_OUT6  = PD5
.EQU  PORTD_DIR         = 0xFF  ; all outputs

; чтобы минимизировать количество тактов для управления ключом, заводим регистры, в которых будут нужные значения битов
.DEF  PORTB_MOSFET_ON   = r2
.EQU  PORTB_MOSFET_ON_VALUE   = (1 << MOSFET_GATE_OUT) + (1 << CHARGE_PUMP_ENABLE_OUT)  ; FET on, discharge off
.DEF  PORTB_MOSFET_OFF  = r3
.EQU  PORTB_MOSFET_OFF_VALUE  = (1 << CHARGE_PUMP_ENABLE_OUT)    ; FET off, discharge off
.DEF  PORTD_MOSFET_ON   = r4
.DEF  ORG_PORTD_MOSFET_ON = r5
.EQU  PORTD_MOSFET_ON_VALUE  = ~(1 << LED_OUT)  ; FET on, LED on, charge pump: 1
.DEF  PORTD_MOSFET_OFF  = r6
.DEF  ORG_PORTD_MOSFET_OFF = r7
.EQU  PORTD_MOSFET_OFF_VALUE = 0  ; FET off, LED on, charge pump: 0

.DEF  ON_STATE_RUN        = r8
.DEF  OFF_STATE_RUN       = r9

.DEF  VLOW_CYCLE_COUNT    = r10

.EQU  MAX_VHIGH_CYCLESL   = 0
.EQU  MAX_VHIGH_CYCLESM   = 0
.EQU  MAX_VHIGH_CYCLESH   = 45   ; ~30s

.DEF  TEMP        = r16         ; регистр общего назначения

.DEF  DUTY        = r17
.EQU  MAX_DUTY    = 22

.DEF  VHIGH_CYCLE_COUNTL = r18
.DEF  VHIGH_CYCLE_COUNTM = r19
.DEF  VHIGH_CYCLE_COUNTH = r20


.DSEG
Signature:   .BYTE 4         ; проверяется после инициализации; если установлено волшебное слово, то преобразователь выключается, иначе
                             ; включается и устанавливается волшебное слово

.CSEG

             .ORG  0
             
             rjmp  Initialization

;**************************************************************************************************
; основной цикл
;
; каждая итерация соответствует одному периоду частоты преобразования;
; преобразователь работает на фиксированной частоте;
; регулирование выходного напряжения осуществляется изменением скважности: если выходное напряжение ниже опорного, скважность
;   увеличивается, иначе уменьшается и выполняется пропуск цикла, так что в установившемся режиме скважность колеблется между
;   соседними значениями;
; минимальная и максимальная длительности включенного состояния ключа ограничены издержками на контроль выходного напряжения,
;   в этой реализации минимизирована длительность включенного состояния, т.к. требуемое выходное напряжение относительно невелико,
;   и зона регулирования смещена в область минимальной скважности;
; если при минимальной скважности выходное напряжение выше опорного, выполняется пропуск цикла;
; если скважность колеблется в пределах минимального значения и пропускается достаточное количество циклов,
;   значит, нагрузка не подключена и преобразователь отключается;
; если при максимальной скважности выходное напряжение ниже опорного в течение нескольких циклов, возможно КЗ, преобразователь отключается;
; чтобы использовать один байт ZL для ijmp (ZH=0), размещаем код по младшим адресам;

start_cycle:
             wdr
             out   PORTB, PORTB_MOSFET_ON
             out   PORTD, PORTD_MOSFET_ON
             mov   ZL, ON_STATE_RUN
             ijmp
             nop   ; 1
             nop   ; 2
             nop   ; 3
             nop   ; 4
             nop   ; 5
             nop   ; 6
             nop   ; 7
             nop   ; 8
             nop   ; 9
             nop   ; 10
             nop   ; 11
             nop   ; 12
             nop   ; 13
             nop   ; 14
             nop   ; 15
             nop   ; 16
             nop   ; 17
             nop   ; 18
             nop   ; 19
             nop   ; 20
             nop   ; 21
             nop   ; 22
on_run_end:
             ; выключаем ключ
             out   PORTB, PORTB_MOSFET_OFF
             out   PORTD, PORTD_MOSFET_OFF
             mov   ZL, OFF_STATE_RUN
             ijmp
off_run_start:
             nop   ; 1
             nop   ; 2
             nop   ; 3
             nop   ; 4
             nop   ; 5
             nop   ; 6
             nop   ; 7
             nop   ; 8
             nop   ; 9
             nop   ; 10
             nop   ; 11
             nop   ; 12
             nop   ; 13
             nop   ; 14
             nop   ; 15
             nop   ; 16
             nop   ; 17
             nop   ; 18
             nop   ; 19
             nop   ; 20
             nop   ; 21
             nop   ; 22
check_output_voltage:
             ; проверяем выходное напряжение
             sbic  ACSR, ACO
             rjmp  vout_high
             ; выходное напряжение ниже опорного
             ; зону регулирования можно сместить в область большей скважности перестановкой сюда команды открывания ключа
             cpi   DUTY, MAX_DUTY
             breq  vout_low_countdown
             inc   DUTY
             dec   ON_STATE_RUN
             inc   OFF_STATE_RUN
             rjmp  start_cycle
vout_low_countdown:
             inc   VLOW_CYCLE_COUNT    ; допускаем 256 циклов
             breq  vlc_cnt0
             rjmp  start_cycle
vlc_cnt0:
             ; возможна перегрузка
             rcall turn_off
             ; быстро мигаем светодиодом ~3с
             ldi   TEMP, 10
             mov   OFF_STATE_RUN, TEMP
fast_blink:
             cbi   LED_PORT, LED_OUT
             ldi   r16,3
             rcall Delay
             sbi   LED_PORT, LED_OUT
             ldi   r16,3
             rcall Delay
             dec   OFF_STATE_RUN
             brne  fast_blink
             rjmp  power_down
vout_high:
             ; выходное напряжение выше опорного
             clr   VLOW_CYCLE_COUNT
             tst   DUTY
             brne  decrease_duty
             ; выравнивание длины кода
             nop
             nop
             nop
             nop
             nop
             nop
sc_cnt0:
             nop
             nop
             rjmp  skip_cycle
decrease_duty:
             inc   ON_STATE_RUN
             dec   OFF_STATE_RUN
             dec   DUTY
             breq  sc_cnt0
             mov   PORTD_MOSFET_ON, ORG_PORTD_MOSFET_ON
             mov   PORTD_MOSFET_OFF, ORG_PORTD_MOSFET_OFF
             ldi   VHIGH_CYCLE_COUNTL, MAX_VHIGH_CYCLESL
             ldi   VHIGH_CYCLE_COUNTM, MAX_VHIGH_CYCLESM
             ldi   VHIGH_CYCLE_COUNTH, MAX_VHIGH_CYCLESH
skip_cycle:
             wdr
             sbi   CHARGE_PUMP_PORT, CHARGE_PUMP_OUT  ; charge pump должна работать всегда
             subi  VHIGH_CYCLE_COUNTL, 1
             sbc   VHIGH_CYCLE_COUNTM, ZH ;0
             sbc   VHIGH_CYCLE_COUNTH, ZH ;0
             tst   VHIGH_CYCLE_COUNTL
             brne  sc_cnt3
             ; младший байт нулевой, переключаем светодиод (используем младший бит старшего байта),
             ; таким образом, без нагрузки светодиод будет лениво помигивать до выключения преобразователя
             bst   VHIGH_CYCLE_COUNTH, 0
             bld   PORTD_MOSFET_ON, LED_OUT
             bld   PORTD_MOSFET_OFF, LED_OUT
             cbi   CHARGE_PUMP_PORT, CHARGE_PUMP_OUT
             tst   VHIGH_CYCLE_COUNTM
             brne  sc_cnt2
             tst   VHIGH_CYCLE_COUNTH
             breq  go_off
             nop
sc_cnt1:
             nop
             nop
             nop
             nop
             nop
             nop
             rjmp  check_output_voltage
sc_cnt2:
             rjmp  sc_cnt1
go_off:
             ; выключаемся
             rcall turn_off
             rjmp  power_down
sc_cnt3:
             ldi   TEMP, 4
sc_loop:
             dec   TEMP
             brne  sc_loop
             nop
             cbi   CHARGE_PUMP_PORT, CHARGE_PUMP_OUT
             rjmp  check_output_voltage

;**************************************************************************************************
; подпрограммы

Delay:       ; параметр - TEMP; модифицирует ZL, ZH, TEMP
             clr   ZH
             clr   ZL
dloop:
             dec   ZL
             brne  dloop
             wdr
             dec   ZH
             brne  dloop
             dec   TEMP
             brne  dloop
             ret

turn_off:    ; выключение преобразователя; использует Delay
             out   PORTB, PORTB_MOSFET_OFF
             out   PORTD, PORTD_MOSFET_OFF
             cbi   CHARGE_PUMP_ENABLE_PORT, CHARGE_PUMP_ENABLE_OUT
             sbi   DISCHARGE_PORT, DISCHARGE_OUT
             ldi   TEMP, 1
             rcall Delay
             cbi   DISCHARGE_PORT, DISCHARGE_OUT  ; иначе в базу транзистора буде течь ток; в режиме спячки это никчему
             sbi   LED_PORT, LED_OUT
             ret
             
power_down:  ; впадаем в спячку до ресета
             ldi   TEMP, (1 << WDTOE) + (1 << WDE)
             ldi   ZL, (1 << WDTOE)
             out   WDTCR, TEMP
             out   WDTCR, ZL
             clr   TEMP
             sts   Signature, TEMP     ; затираем сигнатуру чтобы включиться при след. нажатии кнопки ресет
             ldi   TEMP, (1 << SM) + (1 << SE)
             out   MCUCR, TEMP
             sleep
             rjmp  power_down
             
;**************************************************************************************************
; инициализация

Initialization:
             ; инициализируем порты
             ldi   TEMP, PORTB_DIR
             out   DDRB, TEMP
             ldi   TEMP, PORTD_DIR
             out   DDRD, TEMP
             sbi   LED_PORT, LED_OUT   ; LED off, остальное - нули
             ; инициализируем указатель стека
             ldi   TEMP, low(RAMEND)
             out   SPL, TEMP
             
             ; проверяем контрольную сумму FLASH
             clr   ZH
             clr   ZL
             clr   TEMP
flash_checksum:
             lpm
             adiw  ZL, 1
             add   TEMP, r0
             cpi   ZH, high( CodeEnd << 1 )
             brne  flash_checksum
             cpi   ZL, low( CodeEnd << 1)
             brne  flash_checksum
             lpm
             cp    TEMP, r0
;rjmp  continue_init
             breq  continue_init
             ; неправильная контрольная сумма; висим, периодически выдаём две вспышки светодиода
FLASH_CHECKSUM_ERROR:
             cbi   LED_PORT, LED_OUT
             ldi   r16,4
             rcall Delay
             sbi   LED_PORT, LED_OUT
             ldi   r16,4
             rcall Delay
             cbi   LED_PORT, LED_OUT
             ldi   r16,4
             rcall Delay
             sbi   LED_PORT, LED_OUT
             ldi   r16,15
             rcall Delay
             rjmp  FLASH_CHECKSUM_ERROR

continue_init:
             ; инициализируем регистры
             ldi   TEMP, PORTB_MOSFET_ON_VALUE
             mov   PORTB_MOSFET_ON, TEMP
             ldi   TEMP, PORTB_MOSFET_OFF_VALUE
             mov   PORTB_MOSFET_OFF, TEMP
             ldi   TEMP, PORTD_MOSFET_ON_VALUE
             mov   PORTD_MOSFET_ON, TEMP
             mov   ORG_PORTD_MOSFET_ON, TEMP
             ldi   TEMP, PORTD_MOSFET_OFF_VALUE
             mov   PORTD_MOSFET_OFF, TEMP
             mov   ORG_PORTD_MOSFET_OFF, TEMP

             clr   DUTY                ; мягкий запуск (слегка;)
             ldi   TEMP, low(on_run_end)
             mov   ON_STATE_RUN, TEMP
             ldi   TEMP, low(off_run_start)
             mov   OFF_STATE_RUN, TEMP
             clr   ZH

             clr   VLOW_CYCLE_COUNT
             ldi   VHIGH_CYCLE_COUNTL, MAX_VHIGH_CYCLESL
             ldi   VHIGH_CYCLE_COUNTM, MAX_VHIGH_CYCLESM
             ldi   VHIGH_CYCLE_COUNTH, MAX_VHIGH_CYCLESH
             
             ; проверяем, выключаться или нет
             lds   TEMP, Signature
             cpi   TEMP, 'f'
             brne  turn_on
             lds   TEMP, Signature + 1
             cpi   TEMP, 'u'
             brne  turn_on
             lds   TEMP, Signature + 2
             cpi   TEMP, 'c'
             brne  turn_on
             lds   TEMP, Signature + 3
             cpi   TEMP, 'k'
             brne  turn_on
             rcall turn_off
             rjmp  power_down

turn_on:
             ; устанавливаем сигнатуру
             ldi   TEMP, 'f'
             sts   Signature, TEMP
             ldi   TEMP, 'u'
             sts   Signature + 1, TEMP
             ldi   TEMP, 'c'
             sts   Signature + 2, TEMP
             ldi   TEMP, 'k'
             sts   Signature + 3, TEMP
             
             ; заряжаем charge pump, открывая тем самым ключ
             sbi   CHARGE_PUMP_ENABLE_PORT, CHARGE_PUMP_ENABLE_OUT
             clr   TEMP
charge_pump_charge:
             sbi   CHARGE_PUMP_PORT, CHARGE_PUMP_OUT
             nop
             nop
             nop
             cbi   CHARGE_PUMP_PORT, CHARGE_PUMP_OUT
             dec   TEMP
             brne  charge_pump_charge
             
             ldi   TEMP, (1 << WDE)
             out   WDTCR, TEMP
             rjmp  start_cycle
             

;**************************************************************************************************

CodeEnd:
             .DB    0x77        ; контрольная сумма
