??? 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 |