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

Back to Subject List

Old thread has been locked -- no new posts accepted in this thread
???
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

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