| ??? 03/05/06 16:22 Read: times |
#111300 - asm, in the same style Responding to: ???'s previous message |
pointer-and-length:
;===============================================================================
;--- some nice header should come here...
; Uses real addresses into the buffers as pointers (rather than offset),
; and number of already received bytes.
; Pointers (RX_HEAD, TX_TAIL) are chosen so that the slow address
; calculation occurs outside the interrupt (in "main").
;--- these are only definitions for CR and LF (ASCII control characters) for those,
; who are not able to remember it...
CR EQU 13
LF EQU 10
;===============================================================================
RX_BUFF_SIZE EQU 8
TX_BUFF_SIZE EQU 8
;--- the following are declarations of variables
; it is better to use the DS or such
RX_HEAD EQU 08H ;pointers of receiver FIFO
RX_LENGTH EQU 09H
TX_TAIL EQU 0AH ;pointers of transmitter FIFO
TX_LENGTH EQU 0BH
;the buffers themselves follow
RX_BUFF EQU 0CH ;receiver buffer
;RX_BUFF_END EQU RX_BUFF+RX_BUFF_SIZE-1
TX_BUFF EQU RX_BUFF+RX_BUFF_SIZE ;14H ;transmitter buffer
;TX_BUFF_END EQU TX_BUFF+TX_BUFF_SIZE-1
;-- the following variable would start at TX_BUFF+TXBUFF_SIZE
NEEDTI BIT 01H ;20H.1 ;a flag, determining, whether transmission is
;in progress (0) or not (1)
SP_INIT EQU 30H ;the stack beginning (in fact, one byte below it)
;===============================================================================
ORG 0
LJMP MAIN
ORG 23H
LJMP SER_ISR
;===============================================================================
ORG 40H
MAIN:
MOV SP,#SP_INIT
;--- the initialisation routine - I put it inline, but others prefer to
; have it as a subroutine
SER_INIT:
MOV SCON,#01010000B ;8N1
MOV TMOD,#00100001B
MOV TH1,#-3 ;9600Bd @ XTAL=11.0592MHz
SETB TR1
;
MOV RX_HEAD,#RX_BUFF ;the pointers contain the address rather than an offset
MOV RX_LENGTH,#0 ;and buffers are empty
MOV TX_TAIL,#TX_BUFF
MOV TX_LENGTH,#0
SETB NEEDTI ;flag indicates that transmission is NOT in progress
;
SETB ES
SETB EA ;interrupts enabled (gurus may use MOV IE,xxx)
;
MOV DPTR,#SINIT
LCALL TEXT_OUT ;spits out an invitation string
;
;===============================================================================
;--- now this is supposed to be an example "main".
; I have no idea how to demonstrate the benefits of the interrupt driven
; serial in a more showy way; so this simply waits until at least 3 characters
; are received, then simply spits them out at once.
LOOP:
MOV A,RX_LENGTH ;get the number of characters in receive buffer
CJNE A,#3,MAIN_X2
MAIN_X2:
JC LOOP ;loop until it is >= 3
MOV R2,A
MAIN_X3:
LCALL GET_CHAR ;simply read all these characters
LCALL PUT_CHAR_WAIT ;and put write them back -> echo
DJNZ R2,MAIN_X3
SJMP LOOP ;do this forever....
;
;---
SINIT: ;this is the invigtation string,
DB CR,LF
DB 'SERIAL PORT INITIALISED!'
DB CR,LF,0 ;...terminated by zero, as TEXT_OUT requires it
;===============================================================================
;--- the following are "utilities" to access the serial port, to be called from
; "main" - in C these would be called getch, putch, printf or similar
;***************************
;-- wait until a character is received into the receive buffer, then return it
; input: none; output: received byte in A; uses: R0,C
GET_CHAR_WAIT:
; LCALL GET_CHAR ;this is an alternative way how to accomplish it
; JC GET_CHAR_WAIT ;but it uses up extra 2 bytes from stack
; RET
GET_CHAR_WAIT_X1:
MOV A,RX_LENGTH ;wait until the pointers get different
JNZ GET_CHAR_X1
SJMP GET_CHAR_WAIT_X1
;-- return a character from receiver if available; if not, return carry set
; input: none; output: received byte in A, C clear if A valid; uses: R0,C
GET_CHAR:
MOV A,RX_LENGTH ;if number of bytes in buffer is zero
JNZ GET_CHAR_X1
SETB C ;the buffer is empty, so return with C set
RET
GET_CHAR_X1:
CLR ES ;disable interrupt, if needed (assuming, it is
MOV A,RX_HEAD ; enabled all the time, no need to store it)
CLR C ;prepare the new "tail" pointer
SUBB A,RX_LENGTH ; from the "head" pointer and the length
CJNE A,#RX_BUFF,GET_CHAR_X2
GET_CHAR_X2: ; if it underflows the buffer start
JNC GET_CHAR_X3
ADD A,#RX_BUFF_SIZE ; it should be "wrapped around" the upper end
GET_CHAR_X3:
MOV R0,A ;now we have the "tail" pointer, so let's pick
MOV A,@R0 ; the byte from buffer tail
DEC RX_LENGTH ;indicate that a byte has been removed
SETB ES ;reenable interrupt
CLR C ;and clear carry to indicate the byte is valid
RET
;The reason for disabling the interrupt is the following:
;If during the retrieving routine serial interrupt would occur, it could
;insert a new byte in the receive buffer, in which case the already used
;variables describing the buffer (i.e. RX_HEAD, RX_LENGTH and the buffer
;content itself) would not match those after the interrupt and incorrect
;byte could be picked from the buffer.
;
;***************************
;-- wait until there is a space for one byte in transmit buffer, then insert
; the byte from A into it
; input: A; output: C clear to indicate success; uses: R0,C
PUT_CHAR_WAIT:
; LCALL PUT_CHAR ;as with GET_CHAR_WAIT, an alternative solution
; JC PUT_CHAR_WAIT
; RET
MOV R0,A ;preserve byte to be transmitted into R0
PUT_CHAR_WAIT_X1:
MOV A,TX_LENGTH ;check if buffer is full
CJNE A,#TX_BUFF_SIZE,PUT_CHAR_WAIT_X2
PUT_CHAR_WAIT_X2:
JC PUT_CHAR_X2 ;if not, insert the character
SJMP PUT_CHAR_WAIT_X1 ;otherwise it's full so wait
;-- insert a byte from A into transmit buffer, if there is space for it
; else return carry set to indicate failure (but preserve byte in A)
; input: A; output: C clear if success; uses: R0,C
PUT_CHAR:
MOV R0,A ;preserve byte to be transmitted into R0
MOV A,TX_LENGTH ;check the space in transmit buffer
CJNE A,#TX_BUFF_SIZE,PUT_CHAR_X1
PUT_CHAR_X1:
JC PUT_CHAR_X2
MOV A,R0 ;if no space, restore A
SETB C ;and return C set to indicate failure
RET
PUT_CHAR_X2:
CLR ES ;disable interrupts
MOV A,TX_TAIL ;calculate "head" pointer
ADD A,TX_LENGTH ; check for overflow
CJNE A,#TX_BUFF+TX_BUFF_SIZE,PUT_CHAR_X3
PUT_CHAR_X3:
JC PUT_CHAR_X4 ; if not, continue; else "wrap around" beginning
SUBB A,#TX_BUFF_SIZE ; !!! carry is cleared implicitly! (JC is before)
PUT_CHAR_X4:
XCH A,R0 ;retrieve stored byte and set the pointer
MOV @R0,A
JNB NEEDTI,PUT_CHAR_X5 ;check if transmission is in progress
CLR NEEDTI
SETB TI ;if not, start it by setting TI
PUT_CHAR_X5:
INC TX_LENGTH
SETB ES ;reenable interrupts
CLR C ;indicate success by clearing carry
RET
;Interrupt is disabled for the same reason as in GET_CHAR - see above
;
;***************************
;-- transmit a zero-terminated string pointed to by DPTR
; input: DPTR pointing to string
; output: DPTR pointing after the terminating zero
; uses: A,R0,C,2 bytes of stack
TEXT_OUT:
CLR A
MOVC A,@A+DPTR
INC DPTR
JZ TEXT_OUT_X1
LCALL PUT_CHAR_WAIT
SJMP TEXT_OUT
TEXT_OUT_X1:
RET
;===============================================================================
;--- the serial interrupt service routine itself
;***************************
SER_ISR:
PUSH PSW
PUSH ACC
MOV A,R0 ;alternative: pushing R0 addressed directly if banks are never
PUSH ACC ;switched or if a bank is selected explicitly after pushing PSW
;-- first, the receive ISR
JNB RI,SER_ISR_TX
SER_ISR_RX:
CLR RI ;RI needs to be cleared "manually"
MOV A,RX_LENGTH ;check, if buffer is not full already
CJNE A,#RX_BUFF_SIZE,SER_ISR_RX1
; it is up to the user how to cope with this,
; in PC, a beep comes here...
SJMP SER_ISR_TX ; we simply throw this byte away...
SER_ISR_RX1:
INC RX_LENGTH ;indicate +1 byte into the buffer
MOV R0,RX_HEAD ;now get the "head" pointer
MOV @R0,SBUF ;store the received byte
INC RX_HEAD ;advance the pointer
CJNE R0,#RX_BUFF+RX_BUFF_SIZE-1,SER_ISR_RX2
MOV RX_HEAD,#RX_BUFF ; if needed, wrap around (note that R0 is
SER_ISR_RX2: ;checked but not advanced!)
;rx interrupt finished, fallthrough to tx
;-- second, the transmit ISR
SER_ISR_TX:
JNB TI,SER_ISR_END
CLR TI ;TI needs to be cleared "manually"
MOV A,TX_LENGTH ;check, if there is anything to transmit
JNZ SER_ISR_TX1 ;yes -> continue
SETB NEEDTI ;else buffer is empty -> set flag
SJMP SER_ISR_END ; "Tx not in progress" and exit
SER_ISR_TX1:
DEC TX_LENGTH ;else indicate -1 byte in buffer
MOV R0,TX_TAIL ;get "tail" pointer
MOV SBUF,@R0 ;send the pointed byte from buffer
INC TX_TAIL ;advance the pointer
CJNE R0,#TX_BUFF+TX_BUFF_SIZE-1,SER_ISR_TX2
MOV TX_TAIL,#TX_BUFF ; wrap around if needed (see above - R0 is
SER_ISR_TX2: ; checked but TX_TAIL updated!)
;tx interrupt finished, fallthrough to exit
SER_ISR_END:
POP ACC ;end of interrupt, restore registers etc.etc.
MOV R0,A
POP ACC
POP PSW
RETI
END
;===============================================================================
JW |
| Topic | Author | Date |
| interrupt driven serial: RFC | 01/01/70 00:00 | |
| It must be perfect! | 01/01/70 00:00 | |
| I don't believe | 01/01/70 00:00 | |
| Validation | 01/01/70 00:00 | |
| well ... I've looked at it ... | 01/01/70 00:00 | |
| request for comment on comment | 01/01/70 00:00 | |
| let's take this a little at a time ... | 01/01/70 00:00 | |
| OK then give yours | 01/01/70 00:00 | |
| Intel Original | 01/01/70 00:00 | |
| on byte buffer | 01/01/70 00:00 | |
| c version | 01/01/70 00:00 | |
| Coloring | 01/01/70 00:00 | |
| html syntax colouring | 01/01/70 00:00 | |
| to C or not to C | 01/01/70 00:00 | |
| yeah but thats the point | 01/01/70 00:00 | |
| How true for "real C" | 01/01/70 00:00 | |
excellent idea | 01/01/70 00:00 | |
| asm, in the same style | 01/01/70 00:00 | |
| This may not work well ... | 01/01/70 00:00 | |
| How? | 01/01/70 00:00 |



