Email: Password: Remember Me | Create Account (Free)

Back to Subject List

Old thread has been locked -- no new posts accepted in this thread
???
03/01/06 12:28
Read: times


 
#110967 - interrupt driven serial: RFC
I moved this to a new thread to the main message board as I believe it IS '51-relevant.
For Jon's serial kickstart document, I have written and commented the following routine. As seen in the previous discussion, there have been errors in it, so further scrutiny, discussion, corrections, comments, etc. (also on the comments and the "cleanness" (as it is intended for newbies)) are requested and welcome.

Thanks,

Jan Waclawek

;===============================================================================
;--- some nice header should come here...

;    Uses real addresses into the buffers as pointers (rather than offset).
;    The + is that it is fast (no need to calculate the address).
;    The - is that a byte from buffer is wasted (the byte just behind tail
;    pointer gets never used); i.e. the buffers must be set to be 1 byte
;    longer than the longest message to be received/transmitted at once.

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

;===============================================================================
;--- the following are declarations of variables
;    it is better to use the DS or such 
RX_TAIL      EQU   08H      ;pointers of receiver FIFO
RX_HEAD      EQU   09H
TX_TAIL      EQU   0AH      ;pointers of transmitter FIFO
TX_HEAD      EQU   0BH
RX_BUFF      EQU   0CH      ;receiver buffer
RX_BUFF_END  EQU   13H
TX_BUFF      EQU   RX_BUFF_END+1  ;14H   ;transmitter buffer
TX_BUFF_END  EQU   1FH

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_TAIL,#RX_BUFF   ;the pointers contain the address rather than an offset
   MOV   RX_HEAD,#RX_BUFF   ;and are initialised to the same value,
   MOV   TX_TAIL,#TX_BUFF   ;indicating, that the buffers are empty
   MOV   TX_HEAD,#TX_BUFF
   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:
   LCALL GET_RX_NR          ;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
;***************************
;-- get number of bytes sitting in receive buffer into A
;   input: none; output: number in A; uses: C
GET_RX_NR:                 
   CLR   C
   MOV   A,RX_HEAD
   SUBB  A,RX_TAIL
   JNC   GET_RX_NR_X1          ;in case the head has wrapped around but tail not
   ADD   A,#RX_BUFF_END-RX_BUFF+1   ;add length of buffer to get the proper number
GET_RX_NR_X1:
   RET
;***************************
;-- wait until a character is recveived 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
   MOV   A,RX_TAIL            ;wait until the pointers get different
   CJNE  A,RX_HEAD,GET_CHAR_X1   ;meaning there is something in buffer
   SJMP  GET_CHAR_WAIT

;-- 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_TAIL           ;if pointers are the same, it means
   CJNE  A,RX_HEAD,GET_CHAR_X1
   SETB  C                       ;the buffer is empty, so return with C set
   RET
GET_CHAR_X1:
   INC   A                   ;prepare the new "tail" pointer
   CJNE  A,#RX_BUFF_END+1,GET_CHAR_X2
   MOV   A,#RX_BUFF          ;wrap it around, if necessary
GET_CHAR_X2:
   XCH   A,RX_TAIL           ;store the new pointer and get the old pointer
   MOV   R0,A
   MOV   A,@R0               ;retrieve the byte from buffer tail
   CLR   C                   ;and clear carry to indicate the byte is valid
   RET

;it might seem that the problem with interrupt kicking in after the pointer
;has been updated but the byte pointed by old value of pointer has not been 
;processed (here: picked up), as is in case of PUT_CHAR (see below), occurs
;here, too; and the countermeasure (disabling interrupts) has to be taken.
;The problem would be, that if RX_TAIL already points to the byte after
;the one we are going to pick up, if in that time a new byte arrives and 
;the interrupt kicks in, the new byte would overwrite the byte to be picked
;up.
;Fortunately(?), the nature of the pointers being adresses (as mentioned at the
;very beginning) is, that the byte just before that pointed by the "tail" pointer
;gets never written, hence this problem would not occur.

;***************************   
;-- 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
   MOV   A,TX_HEAD         ;check if tail pointer after advancing would not meet
   INC   A                 ;head pointer
   CJNE  A,#TX_BUFF_END+1,PUT_CHAR_WAIT_X1
   MOV   A,#TX_BUFF
PUT_CHAR_WAIT_X1:
   CJNE  A,TX_TAIL,PUT_CHAR_X2      ;if not, the buffer is not full
   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_HEAD       ;check the space in transmit buffer
   INC   A               ;   - for description see PUT_CHAR_WAIT above
   CJNE  A,#TX_BUFF_END+1,PUT_CHAR_X1
   MOV   A,#TX_BUFF
PUT_CHAR_X1:
   CJNE  A,TX_TAIL,PUT_CHAR_X2
   MOV   A,R0            ;if no space, restore A
   SETB  C               ;and return C set to indicate failure
   RET
PUT_CHAR_X2:
   MOV   C,EA            ;preserve interrupts status
   CLR   EA              ;disable interrupts (see details below)
   XCH   A,TX_HEAD       
   XCH   A,R0
   MOV   @R0,A
   JNB   NEEDTI,PUT_CHAR_X3
   CLR   NEEDTI
   SETB  TI
PUT_CHAR_X3:
   MOV   EA,C            ;reenable interrupts
   CLR   C
   RET

;Here the problem of interrupt kicking in after TX_HEAD has been updated, 
;possibly transmitting prematurely the byte pointed by previous value of TX_HEAD
;prior it is updated, is a real problem; addressed by disabling the interrupts.
;
;In fact, there is no need to disable the interrupts globally, so it would be
;enough to manipulate ES rather than EA; similarly, there might be no need for
;storing the old value (depending on context). So this is a slightly overkill
;solution, but should work.
;***************************
;-- 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_HEAD                ;advance head pointer 
   INC   A
   CJNE  A,#RX_BUFF_END+1,SER_ISR_RX1
   MOV   A,#RX_BUFF               ;  if needed, wrap around
SER_ISR_RX1:
   CJNE  A,RX_TAIL,SER_ISR_RX2    ;if matches tail pointer, buffer is full...
                                  ; 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_RX2:
   XCH   A,RX_HEAD                ;the advanced pointer is stored and the old 
   MOV   R0,A                     ; is used to store
   MOV   @R0,SBUF                 ; the freshly received byte
   
;-- second, the transmit ISR 
SER_ISR_TX:
   JNB   TI,SER_ISR_END
   CLR   TI                       ;TI needs to be cleared "manually"
   MOV   A,TX_TAIL                ;check if tail pointer matches head
   CJNE  A,TX_HEAD,SER_ISR_TX1
   SETB  NEEDTI                   ;if so, buffer is empty -> set flag 
   SJMP  SER_ISR_END              ;   "Tx not in progress" and exit
SER_ISR_TX1:
   INC   A                        ;else advance tail pointer
   CJNE  A,#TX_BUFF_END+1,SER_ISR_TX2
   MOV   A,#TX_BUFF               ;  wrap around if needed
SER_ISR_TX2:
   XCH   A,TX_TAIL                ;store the advanced pointer and use the old
   MOV   R0,A                     
   MOV   SBUF,@R0                 ;  to pick the byte to be transmitted
SER_ISR_END:
   POP   ACC                      ;end of interrupt, restore registers etc.etc.
   MOV   R0,A
   POP   ACC
   POP   PSW
   RETI

   END

;===============================================================================


List of 20 messages in thread
TopicAuthorDate
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      

Back to Subject List