;**** **** **** **** **** **** **** **** **** **** **** **** ****
;
; BLHeli bootloader for SiLabs MCUs. Based upon AVRootloader (copyright HR)
;
;**** **** **** **** **** **** **** **** **** **** **** **** ****

XTAL EQU 25000000

BOOT_DELAY EQU XTAL/4                   ; About 250ms (don't set to fast to avoid connection problems)
BOOT_BAUDRATE EQU 19200                 ; Only used if no baudrate detection activated,XTAL is than important
BOOT_VERSION EQU 6                      ; Version 6 (must be not changed)
BOOT_PAGES EQU 1                        ; Number of flash segments for bootloader

UART_LOOP EQU 26                        ; Depends upon timing of putc,getc
BAUDTIME EQU ((XTAL/BOOT_BAUDRATE)/3)-UART_LOOP

SUCCESS EQU 030h
ERRORVERIFY EQU 0C0h
ERRORCOMMAND EQU 0C1h
ERRORCRC EQU 0C2h
ERRORPROG EQU 0C5h

POLYNOM EQU 0A001h                      ; CRC Polynom

Xl EQU R0                               ; Temporary X
Xh EQU R1
Paral EQU R2                            ; Params for UART
Parah EQU R3
Cmdl EQU R4                             ; Commands
Cmdh EQU R5
Cntl EQU R6                             ; Baudtime
Cnth EQU R7

DSEG AT 20h
Bit_Reg: DS 1                           ; Bit storage register
Byte_Reg: DS 1                          ; Byte storage register
Crcl: DS 1                              ; CRC 16Bit
Crch: DS 1
Baudl: DS 1                             ; Baudtime
Baudh: DS 1
Bit_Cnt: DS 1                           ; Counter in UART loops
Byte_Cntl: DS 1                         ; Generic counter
Byte_Cnth: DS 1
BL_Flash_Key_1: DS 1                    ; Flash keys
BL_Flash_Key_2: DS 1

CSEG AT CSEG_BOOT_START                 ; Bootloader start
init:
    clr  IE_EA

    ; Select register bank 0 for main program routines
    clr  PSW.3                          ; Select register bank 0 for main program routines

    ; Disable the WDT.
    mov  WDTCN, #0DEh                   ; Disable watchdog
    mov  WDTCN, #0ADh

    ; Initialize stack
    mov  SP, #0c0h                      ; Stack = 64 upper bytes of RAM
    ; Initialize clock
    mov  CLKSEL, #00h                   ; Set clock divider to 1
IF MCU_TYPE == MCU_BB2
    ; Initialize VDD monitor
    orl  VDM0CN, #080h                  ; Enable the VDD monitor
ENDIF
    mov  Baudl, #38h                    ; Wait 100us
    mov  Baudh, #03h
    acall waitf

    ; Initialize flash keys
    mov  BL_Flash_Key_1, #0A5h
    mov  BL_Flash_Key_2, #0F1h

    ; Initialize ports
    orl  RTX_MDIN, #(1 SHL RTX_PIN)     ; Set digital
    anl  RTX_MDOUT, #NOT(1 SHL RTX_PIN) ; Disable pushpull
    setb RTX_PORT.RTX_PIN               ; Set data high
    mov  RTX_SKIP, #0FFh
    mov  XBR2, #40h                     ; ; Enable crossbar

    ; Set number of connect attempts before exiting bootloader
    mov  Cmdh, #250

abd:
    ; Identifier scanning
    mov  Xl, #(low(BOOT_DELAY / 6)+1)
    mov  Xh, #(high(BOOT_DELAY / 6)+1)
    mov  Cmdl, #(high((BOOT_DELAY / 6) SHR 8)+1)
    mov  Crcl, #0
    mov  Crch, #0
    mov  DPTR, #BOOT_SIGN
    mov  Parah, #(BOOT_MSG - BOOT_SIGN)
    mov  Baudl, #low(BAUDTIME)
    mov  Baudh, #high(BAUDTIME)

wait_for_low:
    jnb  RTX_PORT.RTX_PIN, ($+5)
    ajmp wait_for_low

; Identifier (BOOT_SIGN) scanning with timeout and checksum
id1:
    jb   RTX_PORT.RTX_PIN, id3          ; Look for high
    djnz Xl, id1                        ; Subtract 1 from X (BOOT_DELAY)
    djnz Xh, id1
    djnz Cmdl, id1

    ajmp exit

id3:
    jnb  RTX_PORT.RTX_PIN, id4          ; Look for low
    djnz Xl, id3                        ; Subtract 1 from X (BOOT_DELAY)
    djnz Xh, id3
    djnz Cmdl, id3

    ajmp exit

id4:
    acall getx                          ; Read character
    clr  A
    movc A, @A+DPTR                     ; Load BOOT_SIGN character
    inc  DPTR
    clr  C
    subb A, Paral                       ; Compare with read character
    jz   id5
    djnz Cmdh, abd                      ; Retry if not last connect attempt
    ajmp exit

id5:
    djnz Parah, id1

    acall getw                          ; Read CRC
    jz   ($+4)                          ; Check CRC
    ajmp abd

    ; Send info about chip/bootloader (BOOT_MSG + BOOT_INFO)
    mov  Parah, #((BOOT_INFO - BOOT_MSG) + 4)
in1:
    clr  A
    movc A, @A+DPTR                     ; Load character
    mov  Paral, A
    inc  DPTR
    acall putc
    djnz Parah, in1

; Main commandloop
; 0=Run/restart
; 1=Program flash, 2=Erase flash, 3=Read flash
; 0xFF=Set address, 0xFE=Set buffer, 0xFD=Keep alive
main:
    mov  Paral, #SUCCESS
mai1:
    acall putc
    mov  Crcl, #0                       ; Reset CRC
    mov  Crch, #0
    acall getw                          ; Get command
    mov  A, Paral
    mov  Cmdl, A
    mov  A, Parah
    mov  Cmdh, A
    clr  C
    mov  A, Cmdh
    subb A, #0FEh
    jc   mai2                           ; Jump if not set address or set buffer

    acall getw                          ; Address or number of bytes
    mov  Byte_Cntl, Paral               ; Store number of bytes for set buffer
    mov  Byte_Cnth, Parah
    mov  A, Cmdh
    jnb  ACC.0, mai2                    ; Jump if set buffer

    mov  DPL, Paral                     ; Store flash address (for set address)
    mov  DPH, Parah

mai2:
    acall getw                          ; Get CRC
    mov  Paral, #ERRORCRC
    jnz  mai1
    clr  C
    mov  A, Cmdh
    subb A, #0FEh
    jz   setbuf                         ; If command is set buffer,receive data
    jnc  main

    cjne Cmdh, #0, mai4                 ; Jump if command != 0 (and not set buffer)

    ; Run application/restart bootloader
    mov  A, Cmdl
    jz   rst
exit:
    mov  Bit_Access, #0                 ; Clear variable used by flash lock detect
    mov  Bit_Access_Int, #0FFh          ; Set variable to indicate that program execution came from bootloader

    ; Lock flash by setting invalid values
    mov  BL_Flash_Key_1, #0
    mov  BL_Flash_Key_2, #0

    ljmp 0000h
rst:
    ajmp init

; Set buffer
setbuf:
    mov  Xl, Byte_Cntl                  ; Set number of bytes
    mov  Xh, Byte_Cnth
    inc  Xl
    inc  Xh
set4:
    djnz Xl, set5
    djnz Xh, set5
    ajmp set6

set5:
    acall getc                          ; Receive data
    mov  A, Paral
    movx @Xl, A                         ; Store data in XRAM
    ajmp set4

set6:
    inc  Cmdh
    ajmp mai2

mai4:
    clr  C
    mov  A, Cmdh
    subb A, #3
    jnc  mai5                           ; Jump if command >= 3

    ; Program/erase
    mov  A, Cmdh
    mov  C, ACC.0
    mov  Bit_Reg.0, C
    mov  Paral, #ERRORPROG
    clr  C
    mov  A, DPL
    subb A, #low(CSEG_BOOT_START)
    mov  A, DPH
    subb A, #high(CSEG_BOOT_START)
    jnc  mai1                           ; Jump if in bootloader segment
    jb   Bit_Reg.0, pro3                ; Jump if program command

    ; Erase flash
    orl  PSCTL, #02h                    ; Set the PSEE bit
    orl  PSCTL, #01h                    ; Set the PSWE bit
    mov  FLKEY, BL_Flash_Key_1          ; First key code
    mov  FLKEY, BL_Flash_Key_2          ; Second key code
    movx @DPTR, A
    jnb  Bit_Reg.0, pro6                ; Jump if erase command

; Program flash
pro3:
    mov  Xl, Byte_Cntl                  ; Set number of bytes
    mov  Xh, Byte_Cnth
    inc  Xl
    inc  Xh
    orl  PSCTL, #01h                    ; Set the PSWE bit
    anl  PSCTL, #0FDh                   ; Clear the PSEE bit
pro4:
    djnz Xl, pro5
    djnz Xh, pro5
    ajmp pro6

pro5:
    clr  C
    mov  A, DPH                         ; Check that address is not in bootloader area
    subb A, #BOOTLOADER_OFFSET
    jc   ($+5)

    inc  DPTR                           ; Increment flash address
    ajmp pro4

    movx A, @Xl                         ; Read from XRAM
    mov  FLKEY, BL_Flash_Key_1          ; First key code
    mov  FLKEY, BL_Flash_Key_2          ; Second key code
    movx @DPTR, A                       ; Write to flash
    inc  DPTR                           ; Increment flash address
    ajmp pro4

pro6:
    anl  PSCTL, #0FCh                   ; Clear the PSEE and PSWE bits
    ajmp main                           ; Successfully done erase or program

; Read flash
mai5:
    mov  Paral, #ERRORCOMMAND           ; Illegal command
    cjne Cmdh, #3, mai6                 ; Jump if not read flash command

rd1:
    clr  A
    movc A, @A+DPTR                     ; Read from flash
    inc  DPTR                           ; Increment flash address
    mov  Paral, A
    acall putp
    djnz Cmdl, rd1                      ; Decrement bytes to read

    acall putw                          ; CRC
    ajmp main

mai6:
    ajmp mai1

; Send char with crc
putw:
    mov  Paral, Crcl
    mov  Parah, Crch
    acall putc
    mov  A, Parah
    mov  Paral, A
putp:
    mov  A, Paral
    xrl  Crcl, A
    mov  Bit_Cnt, #8
put1:
    clr  C
    mov  A, Crch
    rrc  A
    mov  Crch, A
    mov  A, Crcl
    rrc  A
    mov  Crcl, A
    jnc  put2

    xrl  Crch, #high(POLYNOM)
    xrl  Crcl, #low(POLYNOM)

put2:
    djnz Bit_Cnt, put1

; Send char
putc:
    acall waitf
    acall waitf
    mov  Bit_Cnt, #10
    mov  A, Paral
    cpl  A
put3:
    jb   Bit_Reg.1, ($+5)
    setb RTX_PORT.RTX_PIN               ; Set pin high
    jnb  Bit_Reg.1, ($+5)
    clr  RTX_PORT.RTX_PIN               ; Set pin low
    acall waitf
    clr  C
    rrc  A
    jc   put4

    clr  Bit_Reg.1

put4:
    djnz Bit_Cnt, put3
    ret

; Receive char/word
getw:
    acall getc
    mov  A, Paral
    mov  Parah, A
getc:
    jb   RTX_PORT.RTX_PIN, ($+5)        ; Wait for high
    ajmp getc

get1:
    jnb  RTX_PORT.RTX_PIN, ($+5)        ; Wait for low
    ajmp get1

getx:
    mov  Bit_Cnt, #8
    mov  Cntl, Baudl
    mov  Cnth, Baudh
    clr  C
    mov  A, Cnth                        ; Wait half a baud
    rrc  A
    mov  Cnth, A
    mov  A, Cntl
    rrc  A
    mov  Cntl, A
    acall waith

get2:
    acall waitf                         ; Wait one baud
    clr  C
    mov  A, Paral
    rrc  A
    jnb  RTX_PORT.RTX_PIN, ($+5)
    orl  A, #080h

    mov  Paral, A
    jnb  ACC.7, ($+6)
    xrl  Crcl, #low(POLYNOM)

    clr  C
    mov  A, Crch
    rrc  A
    mov  Crch, A
    mov  A, Crcl
    rrc  A
    mov  Crcl, A
    jnc  get3

    xrl  Crch, #high(POLYNOM)
    xrl  Crcl, #low(POLYNOM)

get3:
    djnz Bit_Cnt, get2
    mov  A, Crcl
    xrl  A, Crch
    xrl  A, Crch
    mov  Crcl, A
    ret

; UART delays
waitf:
    mov  Cntl, Baudl
    mov  Cnth, Baudh

waith:
    inc  Cntl
    inc  Cnth

wait1:
    djnz Cntl, wait1
    djnz Cnth, wait1

    setb Bit_Reg.1
    ret

BOOT_SIGN: DB "BLHeli"
BOOT_MSG: DB "471d"                     ; Interface-MCU_BootlaoderRevision
BOOT_INFO: DB SIGNATURE_001,SIGNATURE_002,BOOT_VERSION,BOOT_PAGES
