Files
aqhomecontrol/avr/modules/com2w/com2w1.asm
Martin Preuss bdd710fc5c avr: started working on new SPI-like COM protocol.
use a clock and a data line to introduce synchronisation into the
protocol to be able to work with the wide range of mcu speeds (no need for
exact timing, no need for exact calibration).
2025-07-19 09:42:02 +02:00

639 lines
16 KiB
NASM

; ***************************************************************************
; 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. *
; ***************************************************************************
#ifndef AVR_MODULES_COM2W_COM2W1_H
#define AVR_MODULES_COM2W_COM2W1_H
.dseg
com2w1_iface: .byte COM2W_IFACE_SIZE
.cseg
; ---------------------------------------------------------------------------
; @routine COM2W1_Init
;
; @clobbers
COM2W1_Init:
ldi yl, LOW(com2w1_iface)
ldi yh, HIGH(com2w1_iface)
rcall NET_Interface_Init ; (R16, R17, X)
ldi r16, COM2W_MODE_IDLE
rcall com2wSetMode ; (R17)
; setup CLK line (as input, disable internal pull-up resistor)
cbi COM_CLK1_DDR, COM_CLK1_PIN ; set CLK as input
.ifdef COM_CLK1_PUE
inr r16, COM_CLK1_PUE
cbr r16, (1<<COM_CLK1_PIN) ; disable pullup on CLK
outr COM_CLK1_PUE, r16
.else
cbi COM_CLK1_OUTPUT, COM_CLK1_PIN ; disable pullup on CLK
.endif
; setup DATA line (as input, disable internal pull-up resistor)
cbi COM_DATA1_DDR, COM_DATA1_PIN ; set DATA as input
.ifdef COM_DATA1_PUE
inr r16, COM_DATA1_PUE
cbr r16, (1<<COM_DATA1_PIN) ; disable pullup on DATA
outr COM_DATA1_PUE, r16
.else
cbi COM_DATA1_OUTPUT, COM_DATA1_PIN ; disable pullup on DATA
.endif
; setup pin-change interrupt for CLK
rcall com2w1EnableClkIrq
inr r16, COM_IRQ_ADDR_CLK1
sbr r16, (1<<COM_IRQ_BIT_CLK1) ; enable pin change irq for ATTN line
outr COM_IRQ_ADDR_CLK1, r16
inr r16, GIMSK ; enable pin change irq PCIE0 or PCIE1
sbr r16, (1<<COM_IRQ_GIMSK_CLK1)
outr GIMSK, r16
ldi r16, (1<<COM_IRQ_GIFR_CLK1) ; clear pending irq by writing 1 to ATTN bit
outr GIFR, r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine COM2W1_Periodically @global
;
; @clobbers R16, Y
COM2W1_Periodically:
ldi yl, LOW(com2w1_iface)
ldi yh, HIGH(com2w1_iface)
rcall com2wPeriodically
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1EnableClkIrq
;
; @clobbers
com2w1EnableClkIrq:
push r16
inr r16, COM_IRQ_ADDR_CLK1
sbr r16, (1<<COM_IRQ_BIT_CLK1) ; enable pin change irq for CLK line
outr COM_IRQ_ADDR_CLK1, r16
pop r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1DisableClkIrq
;
; @clobbers none
com2w1DisableClkIrq:
push r16
inr r16, COM_IRQ_ADDR_CLK1
cbr r16, (1<<COM_IRQ_BIT_CLK1) ; disable pin change irq for CLK line
outr COM_IRQ_ADDR_CLK1, r16
pop r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1ClkSetHigh
;
; @clobbers none
com2w1ClkSetHigh:
cbi COM_CLK1_DDR, COM_CLK1_PIN ; set CLK as input
.ifdef COM_CLK1_PUE
; cbi COM_CLK1_PUE, COM_CLK1_PIN ; disable pullup on CLK
.else
cbi COM_CLK1_OUTPUT, COM_CLK1_PIN ; disable pullup on CLK
.endif
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1ClkSetLow
;
; @clobbers none
com2w1ClkSetLow:
sbi COM_CLK1_DDR, COM_CLK1_PIN ; set CLK as output
cbi COM_CLK1_OUTPUT, COM_CLK1_PIN ; set CLK low
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1DataSetHigh
;
; @clobbers none
com2w1DataSetHigh:
cbi COM_DATA1_DDR, COM_DATA1_PIN ; set DATA as input
.ifdef COM_DATA1_PUE
; cbi COM_DATA1_PUE, COM_CLK1_PIN ; disable pullup on DATA
.else
cbi COM_DATA1_OUTPUT, COM_DATA1_PIN ; disable pullup on DATA
.endif
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1DataSetLow
;
; @clobbers none
com2w1DataSetLow:
sbi COM_DATA1_DDR, COM_DATA1_PIN ; set DATA as output
cbi COM_DATA1_OUTPUT, COM_DATA1_PIN ; set DATA low
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1ReadNextBit
;
; @clobbers r16, r17, r18, r20, r22
com2w1ReadNextBit:
ldi r20, 50 ; wait for up to 250us for clock rise
rcall com2w1WaitForClockHighMulti5Us ; (R20, R22)
brcc com2w1ReadNextBit_end
; clock is high now, read bit
inr r17, COM_DATA1_INPUT
; reset read timer (for leaving skipping mode)
clr r16
std Y+NET_IFACE_OFFS_READTIMER, r16
; check mode
ldd r16, Y+COM2W_IFACE_OFFS_MODE
cpi r16, COM2W_MODE_READING
brne com2w1ReadNextBit_end
; handle received bit
ldd r16, Y+COM2W_IFACE_OFFS_CURRBYTE
ldd r18, Y+COM2W_IFACE_OFFS_BITCOUNTER
andi r17, (1<<COM_DATA1_PIN)
clc
breq com2w1ReadNextBit_clockData
sec
com2w1ReadNextBit_clockData:
ror r16
std Y+COM2W_IFACE_OFFS_CURRBYTE, r16
inc r18 ; bit counter
std Y+COM2W_IFACE_OFFS_BITCOUNTER, r18
cpi r18, 8
brne com2w1ReadNextBit_end
; write byte into buffer
push xl
push xh
rcall com2wByteRecvd ; (r16, r17, r18, X)
pop xh
pop xl
; prepare for next byte
clr r16
std Y+COM2W_IFACE_OFFS_BITCOUNTER, r16
std Y+COM2W_IFACE_OFFS_CURRBYTE, r16
com2w1ReadNextBit_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1WaitForClockHighMulti5Us
;
; Wait for high CLK
;
; @param R20 multiple of 5us to wait (e.g. "2" for "10" us, max: 64)
; @return CFLAG set if okay (state reached), cleared on error
; @clobbers: r20, r22
com2w1WaitForClockHighMulti5Us:
.if clock == 8000000
add r20, r20 ; *2
add r20, r20 ; *4
add r20, r20 ; *8
.endif
.elif clock == 1000000
; nothing to do
.else
.error "Unhandled clock speed"
.endif
com2w1WaitForClockHighMulti5Us_loop: ; 5 cycles per loop
sbic COM_CLK1_INPUT, COM_CLK1_PIN ; +2 if skipped, +1 if not
rjmp com2w1WaitForClockHighMulti5Us_stateReached ; +2
dec r20 ; +1
brne com2w1WaitForClockHighMulti5Us_loop ; +2
clc ; +1
ret ; +4
com2w1WaitForClockHighMulti5Us_stateReached:
sec ; +1
ret ; +4
; @end
; ---------------------------------------------------------------------------
; @routine com2w1WaitForClockLowMulti5Us
;
; Wait for low CLK
;
; @param R20 multiple of 5us to wait (e.g. "2" for "10" us, max: 64)
; @return CFLAG set if okay (state reached), cleared on error
; @clobbers: r20, r22
com2w1WaitForClockLowMulti5Us:
.if clock == 8000000
add r20, r20 ; *2
add r20, r20 ; *4
add r20, r20 ; *8
.endif
.elif clock == 1000000
; nothing to do
.else
.error "Unhandled clock speed"
.endif
com2w1WaitForClockLowMulti5Us_loop: ; 5 cycles per loop
sbis COM_CLK1_INPUT, COM_CLK1_PIN ; +2 if skipped, +1 if not
rjmp com2w1WaitForClockLowMulti5Us_stateReached ; +2
dec r20 ; +1
brne com2w1WaitForClockLowMulti5Us_loop ; +2
clc ; +1
ret ; +4
com2w1WaitForClockLowMulti5Us_stateReached:
sec ; +1
ret ; +4
; @end
; ---------------------------------------------------------------------------
; @routine com2w1SendMsg
;
; @param X pointer to bytes to send
; @param Y pointer to interface data in SRAM
; @return CFLAG set if message sent, cleared otherwise
; @clobbers R16, R18 (R20, R22, R24, R25, X)
com2w1SendMsg:
ldi r20, 11 ; wait for about 55us for clock low
rcall com2w1WaitForClockLowMulti5Us
brcs com2w1SendMsg_busy ; CLK got low while waiting, so line is busy
push r15
in r15, SREG
cli ; atomic disable irq and set CLK low
rcall com2w1DisableClkIrq ; (none)
rcall com2w1ClkSetLow ; reserve bus (none)
out SREG, r15
pop r15
adiw xh:xl, NETMSG_OFFS_MSGLEN
ld r18, X
sbiw xh:xl, NETMSG_OFFS_MSGLEN
inc r18 ; adjust for DESTADDR
inc r18 ; adjust for MSGLEN
inc r18 ; adjust for CRCBYTE
rcall com2wWaitTime1 ; longer wait period (R22)
rcall com2w1SendBytes ; (r16, r17, r18, r22, X)
rcall com2w1ClkSetHigh ; make sure bus is released
rcall com2w1DataSetHigh
rcall com2w1EnableClkIrq ; (none)
ldi r16, NET_IFACE_OFFS_PACKETSOUT_LOW
rcall NET_Interface_IncCounter16 ; (R24, R25)
sec
ret
com2w1SendMsg_busy:
ldi r16, NET_IFACE_OFFS_ERR_BUSY_LOW
rcall NET_Interface_IncCounter16 ; (R24, R25)
clc
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1SendBytes
;
; @param R18 number of bytes to send
; @param X pointer to bytes to send
; @param Y pointer to interface data in SRAM
; @clobbers: r16, r18, X (r17, r22)
com2w1SendBytes:
com2w1SendBytes_loop:
rcall com2w1ClkSetLow ; (none)
rcall com2wWaitTime1 ; longer wait period (R22)
ld r16, X+
rcall com2w1SendByte ; (R16, R17, R22)
dec r18
brne com2w1SendBytes_loop
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1SendByte
;
; @param R16 byte to send
; @clobbers: r16, r17 (r22)
com2w1SendByte:
ldi r17, 8
com2w1SendByte_loop:
rcall com2w1ClkSetLow
rcall com2wWaitTime1 ; longer wait period (R22)
lsr r16
brcs com2w1SendByte_send1
rcall com2w1DataSetLow
rjmp com2w1SendByte_sent
com2w1SendByte_send1:
rcall com2w1DataSetHigh
com2w1SendByte_sent:
rcall com2wWaitTime2 ; shorter wait period (R22)
push r15
in r15, SREG
cli ; ensure time period by disabling irqs
rcall com2w1ClkSetHigh
rcall com2wWaitTime1 ; longer wait period (R22)
out SREG, r15
pop r15
dec r17
brne com2w1SendByte_loop
ret
; @end
; ---------------------------------------------------------------------------
; @routine COM2W1_Run @global
;
; @return CFLAG set if something done, cleared otherwise
; @clobbers all
COM2W1_Run:
ldi yl, LOW(com2w1_iface)
ldi yh, HIGH(com2w1_iface)
rjmp com2w1RunMode ; (all but Y)
; @end
; ---------------------------------------------------------------------------
; @routine com2w1RunMode
;
; @clobbers all
com2w1RunMode:
cpi r16, COM2W_MODE_NUM
brcs COM2W1_Run_jump
ldi r16, COM2W_MODE_IDLE ; unknown mode, set to idle
rcall com2wSetMode ; (R17)
sec
ret
COM2W1_Run_jump:
ldi zl, LOW(com2w1ModeJumpTable)
ldi zh, HIGH(com2w1ModeJumpTable)
add zl, r16
adc zh, r16
sub zh, r16
ijmp
com2w1ModeJumpTable:
rjmp com2w1RunIdle
rjmp com2w1RunReading
rjmp com2w1RunSkipping
rjmp com2w1RunWriting
; @end
; ---------------------------------------------------------------------------
; @routine com2w1RunIdle
;
; @param Y pointer to interface data in SRAM
; @clobbers R16, R17, R22, R24, R25, X
com2w1RunIdle:
rjmp com2w1RunIdle_end ; DEBUG
push r15
in r15, SREG
cli
; look for outbound message
rcall NET_Interface_PeekNextOutgoingMsgNum ; r16=msgNum
brcs com2w1RunIdle_haveMsg
out SREG, r15
pop r15
clc
rjmp com2w1RunIdle_end
com2w1RunIdle_haveMsg:
mov r24, r16
ldi r16, COM2W_MODE_WRITING
rcall com2wSetMode ; (R17)
mov r16, r24
out SREG, r15
pop r15
push r16
rcall NET_Buffer_Locate ; (R17)
adiw xh:xl, 1
rcall com2w1SendMsg ; (R16, R17, R22, R24, R25, X)
push r15
inr r15, SREG ; save SREG (no CLI, we want to save CFLAG only)
ldi r16, COM2W_MODE_IDLE
rcall com2wSetMode ; (R17)
out SREG, r15 ; restore SREG
pop r15
pop r16
brcc com2w1RunIdle_end
push r15
in r15, SREG
cli
rcall NET_Interface_GetNextOutgoingMsgNum ; take current msg off the queue
rcall NET_Buffer_ReleaseByNum ; (R16, X)
out SREG, r15
pop r15
sec
com2w1RunIdle_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1RunReading
;
; @param Y pointer to interface data in SRAM
; @clobbers none
com2w1RunReading:
; check for timeout (Y+NET_IFACE_OFFS_READTIMER)
ldd r16, Y+NET_IFACE_OFFS_READTIMER
cpi r16, COM2W_READING_MAXREADCOUNTER
brcc com2w1RunReading_goIdle
ldd r16, Y+COM2W_IFACE_OFFS_MODECOUNTER
cpi r16, COM2W_READING_MAXMODECOUNTER
brcc com2w1RunReading_goIdle
clc
rjmp com2w1RunReading_end
com2w1RunReading_goIdle:
ldi r16, NET_IFACE_OFFS_ERR_IO_LOW
rcall NET_Interface_IncCounter16 ; (R24, R25)
ldi r16, COM2W_MODE_IDLE
rcall com2wSetMode ; (r17)
sec
com2w1RunReading_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1RunSkipping
;
; @param Y pointer to interface data in SRAM
; @clobbers r16 (r17)
com2w1RunSkipping:
; check for timeout (Y+NET_IFACE_OFFS_READTIMER)
ldd r16, Y+NET_IFACE_OFFS_READTIMER
cpi r16, COM2W_SKIPPING_MAXREADCOUNTER
brcc com2w1RunSkipping_goIdle
ldd r16, Y+COM2W_IFACE_OFFS_MODECOUNTER
cpi r16, COM2W_SKIPPING_MAXMODECOUNTER
brcc com2w1RunSkipping_goIdle
clc
rjmp com2w1RunSkipping_end
com2w1RunSkipping_goIdle:
ldi r16, COM2W_MODE_IDLE
rcall com2wSetMode ; (r17)
sec
com2w1RunSkipping_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1RunWriting
;
; @param Y pointer to interface data in SRAM
; @clobbers none
com2w1RunWriting:
; TODO: check for timeout
clc
ret
; @end
; ---------------------------------------------------------------------------
; @routine COM2W1_ClkChangeIsr @global @isr
;
; @clobbers none
COM2W1_ClkChangeIsr:
push r15
in r15, SREG
push r16
inr r16, COM_CLK1_INPUT ; read clk state early
rcall COM2W1_HandleClockInterrupt
pop r16
out SREG, r15
pop r15
reti
; @end
; ---------------------------------------------------------------------------
; @routine COM2W1_HandleClockInterrupt @global
;
; @param r16 data from COM_CLKn_INPUT
; @clobbers none
COM2W1_HandleClockInterrupt:
push r16
push r17
push r18
push xl
push xh
push yl
push yh
ldi yl, LOW(com2w1_iface)
ldi yh, HIGH(com2w1_iface)
rcall com2w1ActOnClock ; (r16, r17, r18, X)
pop yh
pop yl
pop xh
pop xl
pop r18
pop r17
pop r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine com2w1ActOnClock
;
; @param r16 data from COM_CLKn_INPUT
; @clobbers r16 (r17, r18, X)
com2w1ActOnClock:
andi r16, (1<<COM_CLK1_PIN)
brne com2w1ActOnClock_end
ldd r17, Y+COM2W_IFACE_OFFS_MODE
cpi r17, COM2W_MODE_READING
breq com2w1ActOnClock_readBit
cpi r17, COM2W_MODE_IDLE
brne com2w1ActOnClock_end
rcall com2wStartReading ; (r16, r17, X)
com2w1ActOnClock_readBit:
push r20
push r22
rcall com2w1ReadNextBit ; (r16, r17, r18, r20, r22)
pop r22
pop r20
com2w1ActOnClock_end:
ret
; @end
#endif ; AVR_MODULES_COM2W_COM2W1_H