| ??? 02/28/06 12:18 Read: times |
#110913 - comments Responding to: ???'s previous message |
Jon,
I promised to comment on your interrupt-driven serial program and I will also comment on mine later if time permits. My comments in red. Of course, I am nothing of perfect etc. so these are only my ideas. Arguable, of course, all of them. Jan Waclawek
;********************************************************************
;********************************************************************
CR EQU 0DH
LF EQU 0AH
;
TXBUFSZ EQU 2 ;Define buffer sizes
2 bytes long Tx buffer... Not really worth, is
it? See also comment on TXBUFSZ in the interrupt routine itself.
RXBUFSZ EQU 8
; ;Indexes
RX_IN EQU 08H ;RX next to go in
RX_OUT EQU 09H ;RX next to go out
TX_IN EQU 0AH ;TX next to go in
TX_OUT EQU 0BH ;TX next to go out
RXBUF EQU 0CH ;Allow RXBUFSZ to next variable
TXBUF EQU 14H ;Allow TXBUFSZ to next variable
the definition of variables using EQU is not
the best idea; if you need to squeeze in some other variable or
change the sizes of buffers, it would require a lot of work. It
is better to use DB or similar pseudoinstructions intended to
reserve space for variables; for buffers (with already defined
size) use DB TXBUFSZ .
;
CHR EQU 16H
End of variables in RAM -> the initial value of
stack should come here (see MOV SP,#17h below; should be like
MOV SP,#SPINIT and SPINIT definition should come here). Again,
the idea is to have as much job done by the assembler itself
automatically, when juggling with the variables.
;
NEEDTI BIT 01H ;Clear it if TI=1 is needed
; ;Set if TI=1 is not needed
;********************************************************************
ORG 0h
LJMP MAIN ;Reset vector
;
;******************************************************************
ORG 23h
LJMP SER_ISR ;Serial port interrupt vector
;
;********************************************************************
ORG 40h
;
MAIN: MOV SP,#17h ;Initialize stack pointer
see comment on initial value of stack ponter above
LCALL SERINIT ;Initialize serial port
SETB EA ;Enable all interrupts
MOV DPTR,#SINIT ;Serial Port Initialized!
LCALL TEXT_OUT ;Send the message out
;
LOOP: ;Repeating loop
LCALL GETCHR
MOV A,R1 this
CPL A and this
JZ LOOP and this
can be written as one instruction: CJNE R1,#0FFh,LOOP
Anyway; IMHO it is not the best idea to indicate a valid
received character in this way - see comment below in GETCHR routine
LCALL PUTCHR
AJMP LOOP ;Loop back
;
It would be nice to have a comment here, like: "the program here waits
for any received character and echoes it back". But, is this a good example for
interrupt-driven serial communication? It can be done in the same way
also using polling... Some other, more complicated, example would be fine.
;********************************************************************
A comment should come here, saying something like:
"this is a string output routine, something like PRINT in BASIC or
"printf" in C or "write" in Pascal. Sends out the string pointed
to by DPTR, terminated by zero.
TEXT_OUT:
CLR A ;dptr has message
MOVC A,@A+DPTR ;get the next character
INC DPTR ;move index to next position
JZ WT2 ;if zero then end of message
MOV R1,A ;else put the chr in the tx buffer
LCALL PUTCHR
;###############################
Now THIS (a delay loop) has nothing to do here!
It indicates some FATAL problem with interrupt-driven communication. The point
of using interrupts was not to delay the "main" program by the serial routines,
but this is contradicting it.
Correctly, the transmitting routine should check for empty space in the
transmit buffer and if available, put the character otherwise wait. There is a
buffer empty space check in PUTCHR, but it only fails to put the character and
THIS routine is suposed to check whether it failed and retry.
In this way, if the string is shorter than the transmit buffer, it would be
placed into the buffer immediately without any delay, and the main program could
move on while the buffer would be transmitted "on the background" by the interrupt
routine - and that's the whole point of interrupt driven communication.
mov r3,#2 ;Temporary delay loop
LP1: mov r2,#225 ;to prevent buffer overrun
djnz r2,$
djnz r3,LP1
;###############################
AJMP TEXT_OUT
A note on the preferencial use of particular form of JMP:
if the jump is explicitly given (not derived by assembler from the genericv JMP),
in short self-contained routines such as this the SJMP is preferred rather than
AJMP (if the length of jump permits it of course). The reason is, if this routine
gets shifted somewhere on the 2k page boundary, AJMP would fail
while SJMP would not.
WT2: RET
;
;********************************************************************
; ;R1 has character
PUTCHR:
SETB C ;using C to store state of EA
JBC EA,PCSKP ;if interrupts enabled, then disable
CLR C ;EA was already disabled
Ehm... what about... MOV C,EA ?
;
PCSKP: PUSH PSW ;store current status, including EA
... but not MOV C,EA is needed; why not PUSH IE ?
MOV CHR,R1 ;store the character
there is NO NEED to store the character in CHR - R1 does not
get corrupted until the character is really stored in the buffer!
CLR C
MOV A,TX_IN
SUBB A,TX_OUT
INCORRECT! This code attempts to get the occupied space in buffer
and compare it against the buffer size (below) to get the empty space.
However, it completely misses the fact that in circular buffer the "head" pointer
(TX_IN) may get BELOW the "tail" pointer (TX_OUT)! Although this implementation
of circular buffer is - ehm - nonstandard (the pointers are NOT the actual
pointers to the buffer - see below) and the described problem will occur less
frequently than in the "normal" implementation (it takes 256 bytes to transmit for
the "wraparound" of the TX_IN/TX_OUT to occur), it is still not a proper
way to go.
CLR C
SUBB A,#TXBUFSZ ;TXBUFSZ = 2
JC TBUFOK ;
MOV R1,#0FFh ;else error RETURN VALUE -1
AJMP PCXIT ;buffer full return
;
TBUFOK:
MOV A,TX_IN
ANL A,#TXBUFSZ-1 ;TXBUFSZ =2-1=1 0 through 1 = 2
Now this is a method which I object.
The pointers don't properly wrap around at the end of buffer; rather,
their topmost bits are masked out when calculating the real address.
It assumes that the buffer sizes are in power of two (should be added as a note
at the buffer length declaration).
It also prevents to use the real addresses as pointers, so they the address
has to be calculated each time.
Maybe it works, but I don't like it. I could imagine working with something
similar, but not in this form - I would keep the PROPER address as pointer,
and I would mask it after incrementing - it requires exact positioning
of the buffer but I think this is more appropriate than this approach.
ADD A,#TXBUF ;# of characters in buffer+buffer address
MOV R0,A ;location in buffer
MOV @R0,CHR ;put character in buffer
INC TX_IN ;move index
JNB NEEDTI,SKPTI ;exit if no more characters in buffer
CLR NEEDTI ;else more to send
SETB TI ;interrupt and send them
SKPTI:
MOV R1,#00h ;done - return value 0
PCXIT:
POP PSW ;restore and return
MOV EA,C
RET
;
;********************************************************************
GETCHR:
SETB C ;use C to store the state of EA
JBC EA,GCSKP ;if all enabled then disable
CLR C ;all were disabled already
GCSKP:
PUSH PSW ;store the current state
The same applies for storing EA as above...
;
CLR C
MOV A,RX_IN ;how many characters in buffer?
SUBB A,RX_OUT
JNZ RBUFNZ ;jump if buffer not empty
the four above lines merge into
MOV A,RX_OUT
CJNE A,RX_IN,RXBUFNZ
MOV R1,#0FFh ;else buffer is empty return value -1
AJMP GCXIT ;exit with buffer empty code
;
RBUFNZ:
MOV R1,RX_OUT ;next to get out
INC RX_OUT ;move index
MOV A,R1
There is no need to move RX_OUT into R1 first,
then move it from R1 into A
ANL A,#RXBUFSZ-1 ;RXBUFSZ = 8-1 =7 0 through 7 = 8
ADD A,#RXBUF ;# of characters in buffer+buffer address
MOV R0,A ;R0 has buffer location
MOV A,@R0 ;get chr from buffer
MOV R1,A ;put chr in R1
GCXIT:
POP PSW ;restore previous state
MOV EA,C ;restore previous interrupt state
RET ;return
;
;********************************************************************
SERINIT: ;Initialize serial port
CLR A
MOV TX_IN,A ;initialize indexes
MOV TX_OUT,A
MOV RX_IN,A
MOV RX_OUT,A
CLR TR1
CLR TI
CLR RI
MOV SCON,#01011010B ;TI SET INDICATES TRANSMITTER READY.
;MODE 1 REN
MOV TMOD,#00100001B ;TIMER 1 MODE 8 BIT AUTO RELOAD
MOV TH1,#0FDh ;TIMER RELOAD VALUE
SETB TR1 ;START TIMER
SETB NEEDTI
SETB ES
RET
;
SINIT:
DB CR,LF
DB 'Serial Port Initialized! '
CRLF: DB CR,LF,0
;********************************************************************
SER_ISR:
PUSH ACC ;store current state
PUSH PSW
MOV PSW,#00h
PUSH 00h
I think this deserves a comment - this is storing R0 from
register bank 0, and MOV PSW,#0 ensures, that the isr will really work with
register bank 0...
JNB RI,TXISR ;if not receive then check xmit
CLR RI ;else clear RI and process
CLR C
MOV A,RX_IN
SUBB A,RX_OUT ;how many characters in buffer?
ANL A,#0-RXBUFSZ ;
Argh... Why this mask??? No need even for that twisted pointer
scheme...
JNZ TXISR ;jump if no characters in buffer
FATAL! The receiver refuses to receive further characters if
there is already one character in buffer - they got lost!!!
MOV A,RX_IN ;else
ANL A,#RXBUFSZ-1 ;number of characters in buffer
ADD A,#RXBUF ;plus buffer address =
MOV R0,A ;buffer position
MOV @R0,SBUF ;put received character in buffer
INC RX_IN ;increment buffer position
;
TXISR:
JNB TI,ISRXIT ;if TI=0 then exit
CLR TI ;else clear TI and process
MOV A,TX_IN ;how many characters in buffer?
XRL A,TX_OUT
JZ TXBUFZ ;jump if buffer empty
MOV A,TX_OUT; CJNE A,TX_IN,TXBUFZ and the following line
can be omitted
MOV A,TX_OUT ;else
ANL A,#TXBUFSZ-1 ;number of characters in buffer
ADD A,#TXBUF ;plus buffer address =
MOV R0,A ;buffer position
MOV A,@R0 ;get character from buffer
MOV SBUF,A ;send character out
INC TX_OUT ;increment buffer position
CLR NEEDTI ;need to set TI
AJMP ISRXIT ;exit ISR
TXBUFZ:
SETB NEEDTI ;buffer was empty dont need to set TI
ISRXIT:
POP 00h ;restore and return
POP PSW
POP ACC
RETI
;********************************************************************
;********************************************************************
|
| Topic | Author | Date |
| Feedback needed | 01/01/70 00:00 | |
| Couple of ideas | 01/01/70 00:00 | |
| Missing | 01/01/70 00:00 | |
| the source... | 01/01/70 00:00 | |
| Stupid EIA | 01/01/70 00:00 | |
| Maybe a Name change? | 01/01/70 00:00 | |
| minor but annoying ... | 01/01/70 00:00 | |
| USB | 01/01/70 00:00 | |
| Limited Experience | 01/01/70 00:00 | |
| known bad USB/serial | 01/01/70 00:00 | |
| FYI - Targus PA088 | 01/01/70 00:00 | |
| thoughts | 01/01/70 00:00 | |
| I am so stupid | 01/01/70 00:00 | |
| Kickstart | 01/01/70 00:00 | |
| attribution | 01/01/70 00:00 | |
| The ONE thing I always have to look up a | 01/01/70 00:00 | |
| Ok - Second revision, but still working, | 01/01/70 00:00 | |
| Nice | 01/01/70 00:00 | |
| OK | 01/01/70 00:00 | |
| MAX202 vs. MAX232A | 01/01/70 00:00 | |
| or the 232A | 01/01/70 00:00 | |
| Polled; Interrupt | 01/01/70 00:00 | |
| polled tx | 01/01/70 00:00 | |
| Assorted small ideas | 01/01/70 00:00 | |
| Not a good idea | 01/01/70 00:00 | |
| Serial speeds? | 01/01/70 00:00 | |
| Enhanced specifications | 01/01/70 00:00 | |
| That's a strange spec | 01/01/70 00:00 | |
| formal stuff | 01/01/70 00:00 | |
| formal | 01/01/70 00:00 | |
| who's the intended audience | 01/01/70 00:00 | |
| 3rd Revision | 01/01/70 00:00 | |
| Busy? | 01/01/70 00:00 | |
| more than a couple of comments... | 01/01/70 00:00 | |
| So..... | 01/01/70 00:00 | |
| what I like or not... | 01/01/70 00:00 | |
| comments | 01/01/70 00:00 | |
| Comments on Comments | 01/01/70 00:00 | |
| bah... | 01/01/70 00:00 | |
| more problems | 01/01/70 00:00 | |
| RE: Problems | 01/01/70 00:00 | |
| Something Strange | 01/01/70 00:00 | |
| Nope.... | 01/01/70 00:00 | |
| interrupt | 01/01/70 00:00 | |
| All members will enjoy | 01/01/70 00:00 | |
Intel serial intro app note | 01/01/70 00:00 |



