Files
aqhomecontrol/avr/modules/flash/io_bitbang.asm
Martin Preuss 0165b02310 flashing older nodes across t03 now works!
older nodes need the new bootloader which doesn't depend on pagesize because
those older nodes used quite large message sizes which are not supported
across t03 nodes (we would need to much RAM on t03 for this).
2025-03-23 22:40:34 +01:00

434 lines
14 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. *
; ***************************************************************************
; ***************************************************************************
; macros
; ---------------------------------------------------------------------------
; @macro UART_BB_M_WAIT_FOR_PIN_LOW IN_REG_DATA, IN_PINNUM
; 0 1
; Wait for a pin to become low
; @param %0 DATA register for input pin (e.g. PINB)
; @param %1 pin number for input (e.g. PORTB1)
; @return CFLAG set if okay, clear otherwise
; @clobbers R17, R22
.macro UART_BB_M_WAIT_FOR_PIN_LOW
ldi r17, 200
l_loop:
sbis @0, @1
rjmp l_reached
Utils_WaitNanoSecs 5000, 0, r22 ; wait for 5us
dec r17
brne l_loop
clc
rjmp l_end
l_reached:
sec
l_end:
.endmacro
; @end
; ---------------------------------------------------------------------------
; @macro UART_BB_M_WAIT_FOR_PIN_HIGH IN_REG_DATA, IN_PINNUM
; 0 1
; Wait for a pin to become high (up to 1ms)
; @param %0 DATA register for input pin (e.g. PINB)
; @param %1 pin number for input (e.g. PORTB1)
; @return CFLAG set if okay, clear otherwise
; @clobbers R17, R22
.macro UART_BB_M_WAIT_FOR_PIN_HIGH
ldi r17, 200
l_loop:
sbic @0, @1
rjmp l_reached
Utils_WaitNanoSecs 5000, 0, r22 ; wait for 5us
dec r17
brne l_loop
clc
rjmp l_end
l_reached:
sec
l_end:
.endmacro
; @end
; ***************************************************************************
; code
.cseg
; ---------------------------------------------------------------------------
; @routine ioRawInit
; Init raw message subsystem.
;
; @clobbers none
ioRawInit:
; setup pins and interrupts
cbi COM_DATA_DDR, COM_DATA_PIN ; set DATA port as input
cbi COM_DATA_OUTPUT, COM_DATA_PIN ; disable internal pullup for TXD
cbi COM_ATTN_DDR, COM_ATTN_PIN ; set ATTN port as input
cbi COM_ATTN_OUTPUT, COM_ATTN_PIN ; disable internal pullup for ATTN
ret
;@end
; ---------------------------------------------------------------------------
; @routine ioRawSendMsg
; Send a message
;
; @clobbers X (R16, R17, R21, R22)
ioRawSendMsg:
ioRawSendMsg_loop:
ldi r16, 0xff ; expect ATTN high
ldi r17, 3
rcall ioWaitForAttnState100ms ; wait for up to 300ms
brcs ioRawSendMsg_attnHigh
ret
ioRawSendMsg_attnHigh:
ldi xl, LOW(flashSendBuffer)
ldi xh, HIGH(flashSendBuffer)
rcall ioRawSendPacket ; R16, R22 (R17, R21, X)
brcc ioRawSendMsg_loop
ret
; @end
; ---------------------------------------------------------------------------
; @routine ioRawWaitForValidMsg
; Wait for valid incoming msg
;
; @return CFLAG set if okay (packet received), cleared on error
; @clobbers: r16, r17 (r18, r19, r20, r21, r22, X)
ioRawWaitForValidMsg:
ldi r16, 0 ; expect ATTN low
ldi r17, 100
rcall ioWaitForAttnState100ms ; wait for up to 10s
brcs ioRawWaitForValidMsg_attnLow
ret
ioRawWaitForValidMsg_attnLow:
ldi xl, LOW(flashRecvBuffer)
ldi xh, HIGH(flashRecvBuffer)
ldi r16, COM2_MAINTENANCE_ADDR
ldi r17, FLASH_RECVBUFFER_MAXLEN-3
rcall ioRawReceivePacketIntoBuffer
brcs ioRawWaitForValidMsg_packetReceived
ret
ioRawWaitForValidMsg_packetReceived:
ldi r16, 0xff ; expect ATTN high
ldi r17, 100
rcall ioWaitForAttnState100ms ; wait for up to 10s
brcc ioRawWaitForValidMsg_end
ldi xl, LOW(flashRecvBuffer)
ldi xh, HIGH(flashRecvBuffer)
rcall com2CheckMessageInBuffer ; (R16, R17, R18, R19, R20, X)
ioRawWaitForValidMsg_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine ioRawReceivePacketIntoBuffer
;
; Receive a packet into buffer pointed to by X.
; Expects interrupts to be disabled.
;
; @param R16 COM address to listen to
; @param R17 maximum value for accepted msg data (i.e. buffersize minus 3)
; @param X buffer to receive to
; @return CFLAG set if okay (packet received), cleared on error
; @return R16 error code if CFLAG is cleared (COM2_ERROR_NOTFORME, COM2_ERROR_IOERROR, COM2_ERROR_DATAERROR)
; @clobbers: r16, r17, r18, X (r19, r20, r21, r22)
ioRawReceivePacketIntoBuffer:
mov r18, r17
push r16
; read destination address
rcall ioRawReceiveByte ; read byte (R16, R17, R20, R21, R22)
pop r17 ; pop from R16 to R17
brcc ioRawReceivePacketIntoBuffer_ioError
#ifndef COM_ACCEPT_ALL_DEST ; accept every destination address
; compare destination address (accept "FF" and own address)
cp r16, r17
breq ioRawReceivePacketIntoBuffer_acceptAddr
cpi r16, 0xff
breq ioRawReceivePacketIntoBuffer_acceptAddr
ldi r16, COM2_ERROR_NOTFORME
rjmp ioRawReceivePacketIntoBuffer_error ; clc/ret
#endif
ioRawReceivePacketIntoBuffer_acceptAddr:
st X+, r16 ; store dest address, lock buffer
; read msg length
rcall ioRawReceiveByte ; read packet length (R16, R17, R20, R21, R22)
brcc ioRawReceivePacketIntoBuffer_ioError
st X+, r16
cp r16, r18 ; (COM2_BUFFER_SIZE-3)
brcc ioRawReceivePacketIntoBuffer_contentError ; packet too long
inc r16 ; account for checksum byte
mov r17, r16
ioRawReceivePacketIntoBuffer_loop:
push r17
rcall ioRawReceiveByte ; read byte (R16, R17, R20, R21, R22)
pop r17
brcc ioRawReceivePacketIntoBuffer_ioError
st X+, r16
dec r17
brne ioRawReceivePacketIntoBuffer_loop
sec
ret
ioRawReceivePacketIntoBuffer_ioError:
ldi r16, COM2_ERROR_IOERROR
rjmp ioRawReceivePacketIntoBuffer_error
ioRawReceivePacketIntoBuffer_contentError:
ldi r16, COM2_ERROR_DATAERROR
ioRawReceivePacketIntoBuffer_error:
clc
ret
; @end
; ---------------------------------------------------------------------------
; @routine ioRawReceiveByte
;
; Read a byte.
; Expects interrupts to be disabled.
;
; @return CFLAG set if okay, clear otherwise
; @return R16 byte received
; @clobbers R16, R20, R21, R22 (R17)
ioRawReceiveByte:
cbi COM_DATA_DDR, COM_DATA_PIN ; set DATA port as input
cbi COM_DATA_OUTPUT, COM_DATA_PIN ; disable internal pullup for RXD
ldi r21, 8 ; bits left
clr r20 ; byte currently receiving
; wait for startbit
rcall ioRawWaitForDataLow ; (R17, R22)
brcc ioRawReceiveByte_error
Utils_WaitNanoSecs COM_HALFBIT_LENGTH, 10, r22 ; goto middle of startbit to maximize sync stability
ioRawReceiveByte_loop:
Utils_WaitNanoSecs COM_BIT_LENGTH, 8, r22 ; 8 cycles used in the complete loop between waits
sec ; +1
sbic COM_DATA_INPUT, COM_DATA_PIN ; LOW: +2, HIGH: +1
rjmp ioRawReceiveByte_shiftIn ; HIGH: +2, rjmp, use set CFLAG
clc ; LOW: +1
ioRawReceiveByte_shiftIn:
ror r20 ; +1
dec r21 ; +1
brne ioRawReceiveByte_loop ; +2, sum per loop: 8 cycles
rcall ioRawWaitForDataHigh ; wait for start of stopbit
brcc ioRawReceiveByte_error
mov r16, r20
sec
ret
ioRawReceiveByte_error:
clc
ret
; @end
; ---------------------------------------------------------------------------
; @routine ioRawSendPacket
;
; Send packet over wire, handle ATTN line.
;
; @param X ptr to buffer to send
; @return CFLAGS set if okay, cleared otherwise (errorcode in R16)
; @clobbers R16, R22 (R17, R21, X)
ioRawSendPacket:
rcall ioRawAcquireBus
brcc ioRawSendPacket_lineBusyError
rcall ioRawWaitForOneBitLength ; wait for one bit duration (R22)
rcall ioRawWaitForOneBitLength ; wait for one bit duration (R22)
adiw xh:xl, COM2_MSG_OFFS_MSGLEN
ld r17, X
sbiw xh:xl, COM2_MSG_OFFS_MSGLEN
inc r17 ; account for dest addr
inc r17 ; account for msglen byte
inc r17 ; account for crc byte
ioRawSendPacket_loop:
ld r16, X+
rcall ioRawSendByte ; send byte (R16, R21, R22)
brcc ioRawSendPacket_releaseBusRet
dec r17
brne ioRawSendPacket_loop
sec
ioRawSendPacket_releaseBusRet:
cbi COM_ATTN_DDR, COM_ATTN_PIN ; release ATTN line (by setting direction to IN)
brcc ioRawSendPacket_ioError
; packet successfully sent
ret
ioRawSendPacket_ioError:
ldi r16,COM2_ERROR_COLLISION
ret
ioRawSendPacket_lineBusyError:
ldi r16,COM2_ERROR_BUSY
ret
; @end
; ---------------------------------------------------------------------------
; @routine ioRawAcquireBus
;
; Reserve bus if free (otherwise return error)
; Expects interrupts to be disabled.
;
; @return CFLAG set if okay (bus acquired), cleared on error
; @clobbers: none
ioRawAcquireBus:
; check for ATTN line: busy?
cbi COM_ATTN_DDR, COM_ATTN_PIN ; set ATTN as input
cbi COM_ATTN_OUTPUT, COM_ATTN_PIN ; disable pullup on ATTN
nop ; needed to sample current input
sbis COM_ATTN_INPUT, COM_ATTN_PIN ; ATTN low?
rjmp ioRawAcquireBus_busy ; jump if it is
sbi COM_ATTN_DDR, COM_ATTN_PIN ; set ATTN as output
cbi COM_ATTN_OUTPUT, COM_ATTN_PIN ; set ATTN low
sec
ret
ioRawAcquireBus_busy:
clc
ret
; @end
; ---------------------------------------------------------------------------
; @routine ioRawWaitForOneBitLength
;
; wait for one bit length (minus cycles for call and ret).
;
; @clobbers r22
ioRawWaitForOneBitLength:
Utils_WaitNanoSecs COM_BIT_LENGTH, 7, r22 ; wait for one bit duration (minus RCALL/RET)
ret
; @end
; ---------------------------------------------------------------------------
; @routine ioRawSendByte
;
; Send a byte.
; We only set the data pin to low at the beginning for the startbit. After that
; we only change the pin direction (e.g. input vs output):
; - for 0 bit: set DDR to output, forcing the data line low
; - for 1 bit: set DDR to input, letting the external pullup R pull the data line to HIGH
; since the output pin is still set to 0 the internal pullup is disabled
; Expects interrupts to be disabled.
;
; @param R16 byte to send
; @return CFLAG set if okay, clear otherwise
; @clobbers R16, R21, R22
ioRawSendByte:
cbi COM_DATA_DDR, COM_DATA_PIN ; set DATA port as input
cbi COM_DATA_OUTPUT, COM_DATA_PIN ; disable internal pullup for DATA
ldi r21, 8 ; +1 bits left
; send startbit
sbi COM_DATA_DDR, COM_DATA_PIN ; +2 set DATA as output
cbi COM_DATA_OUTPUT, COM_DATA_PIN ; +2 set DATA low
Utils_WaitNanoSecs COM_BIT_LENGTH, 1, r22 ; wait for one bit duration
; send data bits
ioRawSendByte_loop: ; 9 for low bit
lsr r16 ; 1+ bit to send -> CARRY
brcs ioRawSendByte_setHigh ; HI: +2, LO: +1
ioRawSendByte_setLow:
sbi COM_DATA_DDR, COM_DATA_PIN ; +2 set DATA as output
cbi COM_DATA_OUTPUT, COM_DATA_PIN ; +2 set DATA low
Utils_WaitNanoSecs COM_BIT_LENGTH, 11, r22
rjmp ioRawSendByte_loopEnd ; +2
ioRawSendByte_setHigh:
cbi COM_DATA_DDR, COM_DATA_PIN ; +2 set DATA as input, pullup R makes it ONE
nop ; +1 (to make pin change available)
Utils_WaitNanoSecs COM_HALFBIT_LENGTH, 0, r22 ; wait for half a bit length for line to safely settle
sbis COM_DATA_INPUT, COM_DATA_PIN ; +1 if no skip, +2 if skipped
rjmp ioRawSendByte_error ; +2 if error (collision: we wanted line to be high but it is low)
Utils_WaitNanoSecs COM_HALFBIT_LENGTH, 11, r22
ioRawSendByte_loopEnd:
dec r21 ; +1
brne ioRawSendByte_loop ; +2, sum per loop: 10 cycles
; send stopbit
cbi COM_DATA_DDR, COM_DATA_PIN ; +2 set DATA as input, pullup R makes it ONE
Utils_WaitNanoSecs COM_BIT_LENGTH, 0, r22 ; wait for one bit length
sec
ret
ioRawSendByte_error:
clc
ret
; @end
; ---------------------------------------------------------------------------
; @routine ioRawWaitForDataLow
;
; Wait up to 1ms for data pin to become low
; @return CFLAG set if okay, clear otherwise
; @clobbers R17, R22
ioRawWaitForDataLow:
cbi COM_DATA_DDR, COM_DATA_PIN ; set DATA port as input
cbi COM_DATA_OUTPUT, COM_DATA_PIN ; disable internal pullup for TXD
UART_BB_M_WAIT_FOR_PIN_LOW COM_DATA_INPUT, COM_DATA_PIN
ret
; @end
; ---------------------------------------------------------------------------
; @routine ioRawWaitForDataHigh
;
; Wait up to 1ms for data pin to become high
; @return CFLAG set if okay, clear otherwise
; @clobbers R17, R22
ioRawWaitForDataHigh:
cbi COM_DATA_DDR, COM_DATA_PIN ; set DATA port as input
cbi COM_DATA_OUTPUT, COM_DATA_PIN ; disable internal pullup for TXD
UART_BB_M_WAIT_FOR_PIN_HIGH COM_DATA_INPUT, COM_DATA_PIN
ret
; @end