Files
aqhomecontrol/avr/modules/uart_hw2/comonuart1.asm

738 lines
19 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_UART_HW2_COMONUART1_H
#define AVR_MODULES_UART_HW2_COMONUART1_H
.dseg
comOnUart1_iface: .byte UART_HW2_IFACE_SIZE
.cseg
; ---------------------------------------------------------------------------
; @routine ComOnUart1_Init @global
;
; @clobbers R16, R17, Y (X)
ComOnUart1_Init:
rcall comOnUart1StopRx
rcall comOnUart1StopTx
ldi yl, LOW(comOnUart1_iface)
ldi yh, HIGH(comOnUart1_iface)
rcall NET_Interface_Init ; (R16, R17, X)
ldi r16, UART_HW2_MODE_IDLE
std Y+UART_HW2_IFACE_OFFS_MODE, r16
clr r16
std Y+NET_IFACE_OFFS_IFACENUM, r16
rcall comOnUart1SetAttnInput
inr r16, COM_IRQ_ADDR_ATTN1
sbr r16, (1<<COM_IRQ_BIT_ATTN1) ; enable pin change irq for ATTN line
outr COM_IRQ_ADDR_ATTN1, r16
inr r16, GIMSK ; enable pin change irq PCIE0 or PCIE1
sbr r16, (1<<COM_IRQ_GIMSK_ATTN1)
outr GIMSK, r16
ldi r16, (1<<COM_IRQ_GIFR_ATTN1) ; clear pending irq by writing 1 to ATTN bit
outr GIFR, r16
; set baudrate
.if clock == 8000000
ldi r16, 25 ; (19.2Kb/s at 8MHz)
ldi r17, 0
.endif
.if clock == 1000000
ldi r16, 2 ; (19.2Kb/s at 1MHz)
ldi r17, 0
.endif
outr UBRR1H, r17
outr UBRR1L, r16
; set character format
ldi r16, (1<<USBS1)|(3<<UCSZ10)
outr UCSR1C, r16
sec
ret
; @end
; ---------------------------------------------------------------------------
; @routine ComOnUart1_Periodically @global
;
; @clobbers R16, Y
ComOnUart1_Periodically:
push r15
in r15, SREG
cli
ldi yl, LOW(comOnUart1_iface)
ldi yh, HIGH(comOnUart1_iface)
rcall NET_Interface_Periodically
ldd r16, Y+UART_HW2_IFACE_OFFS_MODECOUNTER
inc r16
breq ComOnUart1_Periodically_end
std Y+UART_HW2_IFACE_OFFS_MODECOUNTER, r16
ComOnUart1_Periodically_end:
out SREG, r15
pop r15
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1SetMode
;
; @param R16 mode
; @clobbers R17
comOnUart1SetMode:
push r15
in r15, SREG
cli
ldd r17, Y+UART_HW2_IFACE_OFFS_MODE
cp r16, r17
breq comOnUart1SetMode_end
std Y+UART_HW2_IFACE_OFFS_MODE, r16
clr r17
std Y+UART_HW2_IFACE_OFFS_MODECOUNTER, r17
comOnUart1SetMode_end:
out SREG, r15
pop r15
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1StartReading
;
; @param Y pointer to interface data in SRAM
; @clobbers R16, R17, X
comOnUart1StartReading:
mov xl, yl
mov xh, yh
adiw xh:xl, UART_HW2_IFACE_OFFS_BUFFER
std Y+UART_HW2_IFACE_OFFS_BUFPOS_LOW, xl
std Y+UART_HW2_IFACE_OFFS_BUFPOS_HIGH, xh
ldi r16, UART_HW2_BUFFER_SIZE
std Y+UART_HW2_IFACE_OFFS_BUFLEFT, r16
clr r16
std Y+UART_HW2_IFACE_OFFS_BUFUSED, r16
ldi r16, UART_HW2_MODE_READING
rcall comOnUart1SetMode ; (R17)
rcall comOnUart1StartRx ; should be the last call here
ret
; @end
; ---------------------------------------------------------------------------
; @routine ComOnUart1_Run @global
;
; @return CFLAG set if something done, cleared otherwise
; @clobbers all
ComOnUart1_Run:
ldi yl, LOW(comOnUart1_iface)
ldi yh, HIGH(comOnUart1_iface)
ldd r16, Y+UART_HW2_IFACE_OFFS_MODE
clr r18
ComOnUart1_Run_loop:
push r18
push r16 ; current state
rcall comOnUart1RunMode ; (all but Y)
pop r17 ; previous state (pop from r16 into r17)
pop r18
; read new state
ldd r16, Y+UART_HW2_IFACE_OFFS_MODE
cp r16, r17
breq ComOnUart1_Run_loopEnd
ldi r18, 1
rjmp ComOnUart1_Run_loop ; state changed, run again
ComOnUart1_Run_loopEnd:
tst r18
clc
breq ComOnUart1_Run_end
sec
ComOnUart1_Run_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1RunMode
;
; @clobbers all
comOnUart1RunMode:
cpi r16, UART_HW2_MODE_NUM
brcs ComOnUart1_Run_jump
ldi r16, UART_HW2_MODE_IDLE ; unknown mode, set to idle
rcall comOnUart1SetMode ; (R17)
ret
ComOnUart1_Run_jump:
ldi zl, LOW(comOnUart1ModeJumpTable)
ldi zh, HIGH(comOnUart1ModeJumpTable)
add zl, r16
adc zh, r16
sub zh, r16
ijmp
comOnUart1ModeJumpTable:
rjmp comOnUart1RunIdle
rjmp comOnUart1RunReading
rjmp comOnUart1RunSkipping
rjmp comOnUart1RunWriting
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1RunIdle
;
; @param Y pointer to interface data in SRAM
; @clobbers R16, R17, R22, R24, R25, X
comOnUart1RunIdle:
push r15
in r15, SREG
cli
; look for outbound message
rcall NET_Interface_PeekNextOutgoingMsgNum ; r16=msgNum
brcs comOnUart1RunIdle_haveMsg
out SREG, r15
pop r15
rjmp comOnUart1RunIdle_end
comOnUart1RunIdle_haveMsg:
mov r24, r16
ldi r16, UART_HW2_MODE_WRITING
rcall comOnUart1SetMode ; (R17)
mov r16, r24
out SREG, r15
pop r15
push r16
rcall NET_Buffer_Locate ; (R17)
adiw xh:xl, 1
rcall comOnUart1SendMsg ; (R16, R17, R22, R24, R25, X)
push r15
inr r15, SREG ; save SREG (no CLI, we want to save CFLAG only)
ldi r16, UART_HW2_MODE_IDLE
rcall comOnUart1SetMode ; (R17)
out SREG, r15 ; restore SREG
pop r15
pop r16
brcc comOnUart1RunIdle_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
comOnUart1RunIdle_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1RunReading
;
; @param Y pointer to interface data in SRAM
; @clobbers none
comOnUart1RunReading:
; TODO: check for timeout
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1RunSkipping
;
; @param Y pointer to interface data in SRAM
; @clobbers R16 (R17)
comOnUart1RunSkipping:
rcall comOnUart1IsAttnLow ; (none)
brcs comOnUart1RunSkipping_end ; still low
ldi r16, UART_HW2_MODE_IDLE ; is high now, leave skipping mode
rcall comOnUart1SetMode ; (R17)
comOnUart1RunSkipping_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1HandleMsgReceived
;
; @param Y pointer to interface data in SRAM
; @clobbers R16, R17, R18, X, Z (R19, R20, R24, R25)
comOnUart1HandleMsgReceived:
mov xl, yl
mov xh, yh
adiw xh:xl, UART_HW2_IFACE_OFFS_BUFFER
mov zl, xl ; Z=buffer in IFACE
mov zh, xh
rcall NETMSG_CheckMessageInBuffer ; (R16, R17, R18, R19, R20, X)
brcc comOnUart1RunMsgReceived_econtent
; msg valid, alloc buffer
rcall NET_Buffer_Alloc ; X=buffer, R16=bufnum (R16, R17, X)
brcc comOnUart1RunMsgReceived_enobuf
mov r18, r16 ; buffer num
rcall NET_Interface_SetIfaceNumInBuffer ; (R16, R17)
adiw xh:xl, 1 ; skip buffer header
ldd r17, Y+UART_HW2_IFACE_OFFS_BUFUSED ; always is at least 2 here
comOnUart1RunMsgReceived_copyLoop:
ld r16, Z+
st X+, r16
dec r17
brne comOnUart1RunMsgReceived_copyLoop
mov r16, r18 ; buffer num
rcall NET_AddIncomingMsgNum ; (R17, R18, X)
brcc comOnUart1RunMsgReceived_enoadd
ldi r16, NET_IFACE_OFFS_PACKETSIN_LOW
rcall NET_Interface_IncCounter16 ; (R24, R25)
rjmp comOnUart1RunMsgReceived_setIdleAndEnd
comOnUart1RunMsgReceived_enoadd:
rcall NET_Buffer_ReleaseByNum
rjmp comOnUart1RunMsgReceived_enobuf
comOnUart1RunMsgReceived_enobuf:
ldi r16, NET_IFACE_OFFS_ERR_NOBUF_LOW
rjmp comOnUart1RunMsgReceived_err
comOnUart1RunMsgReceived_econtent:
ldi r16, NET_IFACE_OFFS_ERR_CONTENT_LOW
comOnUart1RunMsgReceived_err:
rcall NET_Interface_IncCounter16 ; (R24, R25)
comOnUart1RunMsgReceived_setIdleAndEnd:
ldi r16, UART_HW2_MODE_IDLE
rcall comOnUart1SetMode ; (R17)
comOnUart1RunMsgReceived_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1RunWriting
;
; @param Y pointer to interface data in SRAM
; @clobbers none
comOnUart1RunWriting:
; TODO: check for timeout
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1AcquireAttn
;
; Set ATTN low only if it is high.
; @return CFLAG set if ATTN acquired (including waiting time), cleared otherwise
;
; @clobbers R22
comOnUart1AcquireAttn:
rcall comOnUart1IsAttnLow ; (none)
brcc comOnUart1AcquireAttn_isHigh
clc
rjmp comOnUart1AcquireAttn_end
comOnUart1AcquireAttn_isHigh:
; set ATTN low
rcall comOnUart1SetAttnLow ; (none)
Utils_WaitNanoSecs COM_BIT_LENGTH, 0, r22 ; wait for one bit duration
Utils_WaitNanoSecs COM_BIT_LENGTH, 0, r22 ; wait for one bit duration
sec
comOnUart1AcquireAttn_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1StartRx
;
; @clobbers R16
comOnUart1StartRx:
inr r16, UCSR1B
sbr r16, (1<<RXCIE1) | (1<<RXEN1) ; enable RX complete interrupt, enable receive
outr UCSR1B, r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1StopRx
;
; @clobbers R16
comOnUart1StopRx:
inr r16, UCSR1B
cbr r16, (1<<RXCIE1 | (1<<RXEN1)) ; disable RX complete interrupt, disable receive
outr UCSR1B, r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1StartTx
;
; @clobbers R16
comOnUart1StartTx:
inr r16, UCSR1A
cbr r16, (1<<TXC1) ; clear TXCn interrupt
outr UCSR1A, r16
inr r16, UCSR1B
cbr r16, (1<<UDRIE1) | (1<<TXCIE1) ; disable TX UDRE and TXC0 interrupt, enable transceive
sbr r16, (1<<TXEN1) ; enable TX UDRE and TXC0 interrupt, enable transceive
outr UCSR1B, r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1StopTx
;
; @clobbers R16
comOnUart1StopTx:
inr r16, UCSR1B
cbr r16, (1<<UDRIE1) | (1<<TXCIE1) | (1<<TXEN1) ; disable TX UDRE and TXC0 interrupt, disable transceive
outr UCSR1B, r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1SetAttnInput
;
; Set ATTN line as input (making it effectively HIGH due to pull-up resistor on
; the line)
;
; @clobbers none
comOnUart1SetAttnInput:
cbi COM_ATTN1_DDR, COM_ATTN1_PIN ; set ATTN as input
.ifdef COM_ATTN1_PUE
; cbi COM_ATTN1_PUE, COM_ATTN_PIN ; disable pullup on ATTN
.else
cbi COM_ATTN1_OUTPUT, COM_ATTN1_PIN ; disable pullup on ATTN
.endif
push r16
inr r16, COM_IRQ_ADDR_ATTN1
sbr r16, (1<<COM_IRQ_BIT_ATTN1) ; enable pin change irq for ATTN line
outr COM_IRQ_ADDR_ATTN1, r16
pop r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1SetAttnLow
;
; Set ATTN line low.
;
; @clobbers none
comOnUart1SetAttnLow:
push r16
inr r16, COM_IRQ_ADDR_ATTN1
cbr r16, (1<<COM_IRQ_BIT_ATTN1) ; disable pin change irq for ATTN line
outr COM_IRQ_ADDR_ATTN1, r16
pop r16
sbi COM_ATTN1_DDR, COM_ATTN1_PIN ; set ATTN as output
cbi COM_ATTN1_OUTPUT, COM_ATTN1_PIN ; set ATTN low
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1IsAttnLow
;
; Check whether ATTN is low
;
; @return CFLAG set if ATTN1 is low
; @clobbers none
comOnUart1IsAttnLow:
clc
sbis COM_ATTN1_INPUT, COM_ATTN1_PIN ; ATTN high?
sec ; no
ret
; @end
; ---------------------------------------------------------------------------
; @routine ComOnUart1_RxCharIsr @global @isr
;
; @clobbers none
ComOnUart1_RxCharIsr:
push r15
in r15, SREG
push r16
push r17
push r18
push r19
push r20
push r24
push r25
push xl
push xh
push yl
push yh
push zl
push zh
ldi yl, LOW(comOnUart1_iface)
ldi yh, HIGH(comOnUart1_iface)
rcall comOnUart1RxCharIsr ; (R16, R17, R18, R19, R20, R24, R25, X, Z)
pop zh
pop zl
pop yh
pop yl
pop xh
pop xl
pop r25
pop r24
pop r20
pop r19
pop r18
pop r17
pop r16
out SREG, r15
pop r15
reti
; @end
; ---------------------------------------------------------------------------
; @routine ComOnUart1AttnChangeIsr @global @isr
;
; @clobbers none
ComOnUart1_AttnChangeIsr:
push r15
in r15, SREG
rcall ComOnUart1_HandleAttnChange
out SREG, r15
pop r15
reti
; @end
; ---------------------------------------------------------------------------
; @routine ComOnUart1_HandleAttnChange @global
;
; @clobbers none
ComOnUart1_HandleAttnChange:
push r16
push r17
push xl
push xh
push yl
push yh
ldi yl, LOW(comOnUart1_iface)
ldi yh, HIGH(comOnUart1_iface)
rcall comOnUart1ActOnAttn ; (R16, R17, X)
pop yh
pop yl
pop xh
pop xl
pop r17
pop r16
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1ActOnAttn @global
;
; @param Y pointer to interface data in SRAM
; @clobbers R16 (R17, X)
comOnUart1ActOnAttn:
ldd r16, Y+UART_HW2_IFACE_OFFS_MODE
rcall comOnUart1IsAttnLow ; (none)
brcc comOnUart1ActOnAttn_attnHigh
; ATTN low
cpi r16, UART_HW2_MODE_IDLE
brne comOnUart1ActOnAttn_end ; not idle
rcall comOnUart1StartReading ; (R16, R17, X)
rjmp comOnUart1ActOnAttn_end
; ATTN high
comOnUart1ActOnAttn_attnHigh:
cpi r16, UART_HW2_MODE_SKIPPING
brne comOnUart1ActOnAttn_end
ldi r16, UART_HW2_MODE_IDLE ; leave skipping mode
rcall comOnUart1SetMode ; (R17)
comOnUart1ActOnAttn_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1RxCharIsr @global
;
; @param Y pointer to interface data in SRAM
; @clobbers R16, R17, R18, R19, R20, R24, R25, X, Z
comOnUart1RxCharIsr:
; check for errors
inr r16, UCSR1A ; check for errors
andi r16, (1<<FE1) | (1<<DOR1) | (1<<UPE1)
brne comOnUart1RxCharIsr_hwerr
inr r16, UCSR1A ; read status: data available?
sbrs r16, RXC1
rjmp comOnUart1RxCharIsr_end ; jmp if no data
inr r16, UDR1 ; r16=received char
; check mode
ldd r17, Y+UART_HW2_IFACE_OFFS_MODE
cpi r17, UART_HW2_MODE_READING
brne comOnUart1RxCharIsr_overrun
; store char
ldd xl, Y+UART_HW2_IFACE_OFFS_BUFPOS_LOW
ldd xh, Y+UART_HW2_IFACE_OFFS_BUFPOS_HIGH
st X+, r16 ; last byte written
std Y+UART_HW2_IFACE_OFFS_BUFPOS_LOW, xl
std Y+UART_HW2_IFACE_OFFS_BUFPOS_HIGH, xh
; handle counters
ldd r18, Y+UART_HW2_IFACE_OFFS_BUFUSED
inc r18
std Y+UART_HW2_IFACE_OFFS_BUFUSED, r18
ldd r17, Y+UART_HW2_IFACE_OFFS_BUFLEFT
dec r17
std Y+UART_HW2_IFACE_OFFS_BUFLEFT, r17
breq comOnUart1RxCharIsr_complete
; check msg size
cpi r18, 2
brne comOnUart1RxCharIsr_end
; determine msg size
inc r16 ; last byte was payload length, add byte for crc
; cp r16, r17 ; compare remaining length against remaining space
; brcc comOnUart1RxCharIsr_emsgsize ; msg too long
cp r17, r16 ; compare remaining length against remaining space
brcs comOnUart1RxCharIsr_emsgsize ; msg too long
std Y+UART_HW2_IFACE_OFFS_BUFLEFT, r16 ; set new number of bytes left
tst r16
brne comOnUart1RxCharIsr_end ; jmp if still bytes left to receive
comOnUart1RxCharIsr_complete:
rcall comOnUart1StopRx
rcall comOnUart1HandleMsgReceived ; (R16, R17, R18, R19, R20, R24, R25, X, Z)
rjmp comOnUart1RxCharIsr_end
comOnUart1RxCharIsr_hwerr:
ldi r16, NET_IFACE_OFFS_ERR_IO_LOW
rjmp comOnUart1RxCharIsr_err
comOnUart1RxCharIsr_overrun:
ldi r16, NET_IFACE_OFFS_ERR_NOBUF_LOW
rjmp comOnUart1RxCharIsr_err
comOnUart1RxCharIsr_emsgsize:
ldi r16, NET_IFACE_OFFS_ERR_MSGSIZE_LOW
comOnUart1RxCharIsr_err:
rcall NET_Interface_IncCounter16 ; (R24, R25)
rcall comOnUart1StopRx ; (R16)
ldi r16, UART_HW2_MODE_SKIPPING ; skipping mode is left after ATTN becomes high again
rcall comOnUart1SetMode ; (R17)
comOnUart1RxCharIsr_end:
ret
; @end
; ---------------------------------------------------------------------------
; @routine comOnUart1SendMsg
;
; @param Y pointer to interface data in SRAM
; @param X pointer to buffer to send
; @return CFLAG set if writing started, cleared otherwise
; @clobbers R16, R17, X (R22, R24, R25)
comOnUart1SendMsg:
rcall comOnUart1AcquireAttn ; (R22)
brcc comOnUart1SendMsg_ebusy
adiw xh:xl, NETMSG_OFFS_MSGLEN
ld r17, X
sbiw xh:xl, NETMSG_OFFS_MSGLEN
subi r17, -3 ; add dest addr, msglen, crc
rcall comOnUart1StartTx ; (R16)
; TODO: check size!
; r17=number of bytes to write, X=buffer
comOnUart1SendMsg_loop:
; wait until transceiver ready
inr r16, UCSR1A
sbrs r16, UDRE1
rjmp comOnUart1SendMsg_loop
; clear TXC flag by sending a 1
sbr r16, (1<<TXC1)
outr UCSR1A, r16
; write byte to uart data register
ld r16, X+
outr UDR1, r16
dec r17
brne comOnUart1SendMsg_loop
; wait until all data send (i.e. send buffer empty and all bits shifted out)
comOnUart1SendMsg_loopComplete:
inr r16, UCSR1A
sbrs r16, TXC1
rjmp comOnUart1SendMsg_loopComplete
rcall comOnUart1StopTx ; (R16)
ldi r16, NET_IFACE_OFFS_PACKETSOUT_LOW
rcall NET_Interface_IncCounter16 ; (R24, R25)
rcall comOnUart1SetAttnInput ; release ATTN (none)
rcall Utils_WaitFor10MicroSecs ; make sure ATTN is at least high for a short period (R22)
sec
rjmp comOnUart1SendMsg_end
comOnUart1SendMsg_ebusy:
ldi r16, NET_IFACE_OFFS_ERR_BUSY_LOW
rcall NET_Interface_IncCounter16 ; (R24, R25)
clc
comOnUart1SendMsg_end:
ret
; @end
#endif ; AVR_MODULES_UART_HW2_COMONUART1_H