; ***************************************************************************
; copyright   : (C) 2025 by Martin Preuss
; email       : martin@libchipcard.de
;
; ***************************************************************************
; * This file is part of the project "AqHome".                              *
; * Please see toplevel file COPYING of that project for license details.   *
; ***************************************************************************



; ***************************************************************************
; defines

.equ SGP30_FLAGS_PRESENT_BIT    = 7
.equ SGP30_FLAGS_DATAVALID_BIT  = 6


.equ SGP30_CMD_INIT             = 0x2003
.equ SGP30_CMD_MEASURE          = 0x2008

.equ SGP30_VALUE_TVOC           = 0x01
.equ SGP30_VALUE_CO2            = 0x02



; ***************************************************************************
; data

.dseg

sgp30DataBegin:
  sgp30Flags:            .byte 1
  sgp30LastCo2:          .byte 2
  sgp30LastTvoc:         .byte 2
sgp30DataEnd:




; ***************************************************************************
; code

.cseg



; ---------------------------------------------------------------------------
; SGP30_Init
;

SGP30_Init:
  ldi xh, HIGH(sgp30DataBegin)
  ldi xl, LOW(sgp30DataBegin)
  clr r16
  ldi r17, (sgp30DataEnd-sgp30DataBegin)
  rcall Utils_FillSram
  
  rjmp sg30InitIfNeeded
; @end



; ---------------------------------------------------------------------------
; SGP30_Fini
;

SGP30_Fini:
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine SGP30_GetValue @global
;
; @param   R16     value to get (SGP30_VALUE_CO2, SGP30_VALUE_TVOC)
; @return  CFLAG   set if value available, cleared otherwise
; @return  R19:R18 value
; @return  R21:R20 denom (e.g. 100, meaning value must be divided by 100)
; @clobbers 

SGP30_GetValue:
  lds r18, sgp30Flags
  andi r18, (1<<SGP30_FLAGS_DATAVALID_BIT)
  breq SGP30_GetValue_clcRet
  cpi r16, SGP30_VALUE_TVOC
  breq SGP30_GetValue_retTvoc
  cpi r16, SGP30_VALUE_CO2
  breq SGP30_GetValue_retCo2
SGP30_GetValue_clcRet:
  clc
  ret
SGP30_GetValue_retTvoc:
  lds r18, sgp30LastTvoc
  lds r19, sgp30LastTvoc+1
  ldi r20, 1
  clr r21
  rjmp SGP30_GetValue_secRet
SGP30_GetValue_retCo2:
  lds r18, sgp30LastCo2
  lds r19, sgp30LastCo2+1
  ldi r20, 1
  clr r21
SGP30_GetValue_secRet:
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine sg30InitIfNeeded
;
; Expects interrupts being disabled!
;
; @return CFLAG set if okay, clear on error
; @clobbers R16 (R17, R18, R22)

sg30InitIfNeeded:
  lds r16, sgp30Flags
  andi r16, (1<<SGP30_FLAGS_PRESENT_BIT)
  breq sg30InitIfNeeded_doit
  sec
  ret
sg30InitIfNeeded_doit:
  ; check presence
  rcall sgp30CheckPresence     ; (R16, R17, R18, R22)
  brcc sg30InitIfNeeded_error
  ; call init function
  rcall sgp30Init
  brcc sg30InitIfNeeded_error
  lds r16, sgp30Flags
  ori r16, (1<<SGP30_FLAGS_PRESENT_BIT)
  sts sgp30Flags, r16
  sec
  ret
sg30InitIfNeeded_error:
  clc
  ret



; ---------------------------------------------------------------------------
; @routine sgp30CheckPresence
;
; Expects interrupts being disabled!
;
; @return CFLAG set if okay, clear on error
; @clobbers R16 (R17, R18, R22)

sgp30CheckPresence:
  ldi r16, (SGP30_ADDR*2)   ; write access
  rjmp twiCheckPresence     ; (R16, R17, R18, R22)
; @end



; ---------------------------------------------------------------------------
; @routine SGP30_EverySecond
;
;
; @return CFLAG set if okay, clear on error
; @clobbers R16, R17, R18, R19, R20, R21, R22

SGP30_EverySecond:
  rjmp SGP30_Measure  ; (R16, R17, R18, R19, R20, R21, R22)
; @end



; ---------------------------------------------------------------------------
; @routine SGP30_Measure
;
;
; @return CFLAG set if okay, clear on error
; @clobbers R16, R17, R18, R19, R20, R21, R22

SGP30_Measure:
  in r15, SREG
  push r15
    cli
    lds r16, sgp30Flags
    andi r16, (1<<SGP30_FLAGS_PRESENT_BIT)
    breq SGP30_Measure_error
    rcall sgp30Measure                  ; R19:R18=TVOC, R21:R20=CO2eq
    brcc SGP30_Measure_error
    lds r17, sgp30Flags
    ori r17, (1<<SGP30_FLAGS_DATAVALID_BIT)
    sts sgp30LastTvoc, r18
    sts sgp30LastTvoc+1, r19
    sts sgp30LastCo2, r20
    sts sgp30LastCo2+1, r21
    sts sgp30Flags, r17
SGP30_Measure_done:
  pop r15
  out SREG, r15
  sec
  ret
SGP30_Measure_error:
  pop r15
  out SREG, r15
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine sgp30Init
;
; Sends the given command to the sensor and reads the result.
; Synchronization is done by sending re-start sequences until the sensor answers with
; the value.
;
; @return CFLAG   set if okay, clear on error
; @clobbers R14, R16 (R17, R18, R22)

sgp30Init:
  ; send request
  ldi r20, HIGH(SGP30_CMD_INIT)
  ldi r21, LOW(SGP30_CMD_INIT)
  rcall sgp30SendCommand               ; (R16, R20, R21, R17, R18, R22)
  brcc sgp30Init_error
  ; no response expected, just STOP
  rcall twiStop                        ; (R22)
  sec
  ret
sgp30Init_error:
  rcall twiStop                        ; (R22)
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine sgp30Measure
;
; Sends the given command to the sensor and reads the result.
; Synchronization is done by sending re-start sequences until the sensor answers with
; the value.
;
; @return CFLAG   set if okay, clear on error
; @return r19:r18 TVOC
; @return r21:r20 CO2 equivalent
; @clobbers R14, R16, R18, R19, R22

sgp30Measure:
  ldi r20, HIGH(SGP30_CMD_MEASURE)
  ldi r21, LOW(SGP30_CMD_MEASURE)
  rcall sgp30SendCommand               ; (R16, R20, R21, R17, R18, R22)
  brcc sgp30Measure_error

  ; read response
  rcall twiReceiveByteSendAck          ; (R16, R17, R18, R22)
  brcc sgp30Measure_error
  mov r21, r16                         ; MSByte
  rcall twiReceiveByteSendAck          ; (R16, R17, R18, R22)
  brcc sgp30Measure_error
  mov r20, r16                         ; LSByte  (CO2)
  rcall twiReceiveByte                 ; R16=CRC8, no ACK (R16, R17, R18, R22)
  brcc sgp30Measure_error

  rcall twiReceiveByteSendAck          ; (R16, R17, R18, R22)
  brcc sgp30Measure_error
  mov r19, r16                         ; MSByte
  rcall twiReceiveByteSendAck          ; (R16, R17, R18, R22)
  brcc sgp30Measure_error
  mov r18, r16                         ; LSByte
  push r18
    rcall twiReceiveByte               ; R16=CRC8, no ACK (R16, R17, R18, R22)
  pop r18
  brcc sgp30Measure_error
  
  rcall twiStop                        ; (R22)
  sec
  ret
sgp30Measure_error:
  rcall twiStop                        ; (R22)
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine sgp30SendCommand
;
; Sends the given command to the sensor and reads the result.
; Synchronization is done by sending re-start sequences until the sensor answers with
; the value.
;
; @param R20 1st command byte
; @param R21 2nd command byte
; @return CFLAG   set if okay, clear on error
; @clobbers R16, R20, R21 (R17, R18, R22)

sgp30SendCommand:
  ; send request
  rcall twiStart                        ; (R22)
  ldi r16, (SGP30_ADDR*2)               ; write access
  rcall twiSendByteExpectAck            ; (R16, R17, R18, R22)
  brcc sgp30SendCommand_error
  mov r16, r20
  rcall twiSendByteExpectAck            ; (R16, R17, R18, R22)
  brcc sgp30SendCommand_error
  mov r16, r21
  rcall twiSendByteExpectAck            ; (R16, R17, R18, R22)
  brcc sgp30SendCommand_error

  ; receive response
  ldi r20, 15                           ; wait max 15ms
sgp30SendCommand_loop1:
    ldi r21, 10                         ; 1ms
sgp30SendCommand_loop2:                 ; (R22)
      rcall twiRestart
      ldi r16, (SGP30_ADDR*2)+1         ; read access
      rcall twiSendByteExpectAck        ; (R16, R17, R18, R22)
      brcs sgp30SendCommand_gotResponse ; chip responds, receive values
      rcall Utils_WaitFor50MicroSecs    ; wait for 100usecs total
      rcall Utils_WaitFor50MicroSecs
      dec r21
    brne sgp30SendCommand_loop2
    dec r20
  brne sgp30SendCommand_loop1
  rjmp sgp30SendCommand_error
sgp30SendCommand_gotResponse:
  sec
  ret
sgp30SendCommand_error:
  rcall twiStop                     ; (R22)
  clc
  ret
; @end



