
  CHIP  8052                                              
;
;       Modbus code. (c)2005 Russell Bull.
;   
;       this module implements the Modbus rx/tx code and the register
;       structure.
;       In order to save ram, certain modbus registers are only implemented in EEprom, the read/write_modbus_reg
;       routines take care of any shenanigans.
;
        public  t2_isr                    ; timer2 interrupt code      
        public  read_modbus_reg           ; reads a modbus reg into R7:R6
        public  write_modbus_reg          ; writes a modbus reg into R7:R6
        public  cold_init                 ; loads defaults into ram based regs & eeprom base regs(if required)
        public  serial_isr                ; int vector for the serial port       
        public  MODBUS_READ_HOLDING_REGS
        public  modbus_buff                     
        public  process_modbus            ; task to process modbus messages      
        public  stack
        public  NUM_MODBUS_REGS
        public  our_addr        
        extern  MODBUS_TYPE        
        extern  RX_PKT
;
;       kernel externals
;       
        extern  TASK0_RUN 
        extern  dispatch
        extern  kick_dog
;
;
;        
        extern  _R0
        extern  _R1
        extern  _R2
        extern  _R3
        extern  _R4
        extern  _R5
        extern  _R6
        extern  _R7
        extern  bank1_R0
        extern  bank1_R1
        extern  bank1_R2
        extern  bank1_R3
        extern  bank1_R4
        extern  bank1_R5
        extern  bank1_R6
        extern  bank1_R7
        
;
;  MODBUS command values
;                                              
MODBUS_PRESET_REGS    equ  16
MODBUS_READ_HOLDING_REGS   equ  3
;
MAX_RW_REGS             equ     16+1            ;maximum number of registers than can be read/written at one time
SIZEOF_MODBUS_BUFF      equ     (MAX_RW_REGS *2)+ 10              ;modbus rx/tx buffer 

INIT_TAG                  equ  0aa55h  ;tag stored in eeprom to see if we're initialised yet

CR                              equ     13
LF                              equ     10
        CODE
;----------------------------------------------------------------------------
;
;
;  process modbus. decodes the modbus packet in modbus_buff
;  and performs the required command
;  ** assumes the rx code has checked the modbus address **
;  zaps:most regs!
;
;----------------------------------------------------------------------------
process_modbus

        jb      MODBUS_TYPE,pm_c         ;if modbus_type is ascii then we don't need to check the CRC
;
;       modbus transport is RTU, check the CRC before continuing
;        
        mov     r0,#modbus_buff
        mov     a,bank1_R7              ;get the rx packet size        
        mov     bank1_R7,#0             ;reset the packet size
        
        cjne    a,#7,pm_1
pm_1    jc      pm_z                    ;ignore packet if it is too small

        clr     c
        subb    a,#2                    ;-2 for CRC bytes
        mov     r6,a
        
        lcall   calculate_crc
        
        jz      pm_c                    ; if CRC was ok - process the packet
        
pm_z        
        ljmp    L72                     ;bad crc-skip the packet        

pm_c
        setb    RX_PKT                  ;flash asterisk on the lcd for good rx pkts
        mov     r0,#modbus_buff+1
        mov     a,@r0                   ;get the modbus command
        cjne    a,#MODBUS_READ_HOLDING_REGS,L59
;
;  read holding regs command
;
        mov     r0,#modbus_buff+3  ;get the starting reg#
        mov     a,@r0
        mov     r2,a
        mov     r0,#modbus_buff+5  ;get # of regs
        mov     a,@r0
        mov     r5,a
        
        clr     c
        subb    a,#MAX_RW_REGS
        jc      pm_11
                             
        ljmp    L62                     ;if too many registers requested

pm_11        
        mov     a,r5
        add     a,r5      ;register count times 2
        mov     r0,#modbus_buff+2
        mov     @r0,a      ;set the reply byte count
        inc  r0
;
;  copy mregs[] into the transmit buffer
;
pm_2
pm2_2        
        mov     a,r2
        lcall   read_modbus_reg         ;A has the modbus register#
        jnz     L51                     ;if register error
        mov     a,r7
        mov     @r0,a                   ;store high
        inc     r0
        mov     a,r6
        mov     @r0,a                   ;store low
        inc     r0
        inc     r2                      ;next register#
        djnz    r5,pm2_2                ;loop until regs copied
pm_3:
        clr     c
        mov     a,r0
        subb    a,#modbus_buff          ;calc message length
        sjmp    send_modbus             ;& send it
L51:
;
;  register request exceeded the # of registers
;
        mov     r0,#modbus_buff+1
        mov     a,@r0                   ;get modbus cmd
        orl     a,#80h                  ;set error flag
        mov     @r0,a
        mov     r0,#modbus_buff+2  
        mov     a,#2                    ;bad address
        mov     @r0,a
        mov     a,#4                    ;length =4 including the LRC
        sjmp    send_modbus
                                  
L59
        cjne    a,#MODBUS_PRESET_REGS,L44
;
;  Modbus preset registers command
;
        mov     r0,#modbus_buff+3
        mov     a,@r0                   ;get the starting reg#
        mov     r2,a                    ;into R2
prc_4        
        mov     r0,#modbus_buff+5
        mov     a,@r0                   ;get # of regs
        mov     r5,a                    ;into R5
        
        clr     c
        subb    a,#MAX_RW_REGS
        jnc     L62                     ;if too many registers requested
        
        mov     a,r5
        add     a,r2                    ;add + count
        clr     c
        subb    a,#NUM_MODBUS_REGS
        jnc     L62
prc_1
        mov     a,r5                    ;test reg count
        jz      prc_3                   ;if all done
prc_2
        mov     r0,#modbus_buff+7       ;r0 ->modbus_buff[7]
;
;  get the register from the rx buffer into R7:R6
;
        mov     a,@r0
        mov     r7,a                    ;reg high
        inc     r0
        mov     a,@r0
        mov     r6,a                    ;reg low
        inc     r0
        mov     a,r2                    ;get modbus reg#
        lcall   write_modbus_reg
;
;  next!!
;
        inc     r2
        djnz    r5,prc_1                ;count--
prc_3
        mov     a,#6                    ;set reply pkt length
        sjmp    send_modbus
;
;  bad register passed
;
L62
        mov     r0,#modbus_buff+1
        mov     a,@r0
        orl     a,#080h                 ;set error flag
        mov     @r0,a
        inc     r0
        mov     a,#2                    ;bad address
        mov     @r0,a
        mov     a,#4                    ;pkt length =4 inc LRC
        sjmp    send_modbus
;
;  catch all for command not supported
;
L44
        mov     r0,#modbus_buff+1
        mov     a,@r0
        orl     a,#080h         ;set error flag
        mov     @r0,a
        inc     r0
        mov     a,#1            ;bad command
        mov     @r0,a
        mov     a,#4            ;pkt length =4 inc LRC
;
;  send the modbus data out
;       A has the packet length
;       modbus data is assumed to be in modbus_buff
;
send_modbus
        orl     a,a
        jz      L72                     ;if length ==0, skip transmit
        jb      MODBUS_TYPE,send_ascii

        push    a
        mov     r6,a                    ;packet length
        mov     r0,#modbus_buff
        call    calculate_crc
        pop     a
        add     a,#2                    ;+crc bytes
;
;       use MODBUS RTU as the packet transport
;
        clr     ea                      ;no interrupts
        dec     a                       ;-1
        clr     EN_485                  ;set 485 buffer to tx
        mov     bank1_R6,a
        mov     bank1_R0,#modbus_buff+1
        mov     bank1_R2,#1             ;set tx state        
        mov     r0,#modbus_buff         ;get the first character
        mov     a,@r0
        mov     SBUF,a                  ;and send it to start the tx interrupts
        
        setb    ea
        sjmp    L72
;
;       use MODBUS ascii as the packet transport
;        
send_ascii        
        clr     EN_485                  ;set 485 buffer to tx
        inc     a                       ;+1 on the packet size for the LRC
        mov     bank1_R6,a              ;set the packet length
        mov     bank1_R0,#modbus_buff
        mov     bank1_R2,#0             ;clear tx state
        mov     bank1_R4,#0             ;clear LRC
        mov     SBUF,#':'               ;send the start token (fires the tx interrupt)
        clr     TI
L72
L42
L41
        clr     TASK0_RUN               ;reset task request
        mov     bank1_R3,#0             ;reset the rx state
        ljmp    dispatch
;----------------------------------------------------------------------------
;
;  write MODBUS register
;  entry:
;  R7:R6 has the modbus register value to write
;  A has the MODBUS register#
;  returns: A == 0 if ok, A ==1 if bad register#
;  zaps: A,PSW
;
;----------------------------------------------------------------------------
write_modbus_reg
        push    dph
        push    dpl
        push    _R0
        push    b
        cjne    a,#NUM_MODBUS_REGS,wmr_1  ;test for max reg#
wmr_1  
        jnc  wmr_bad        ;if reg# >=NUM_MODBUS_REGS
;
;       dptr->modbus_options_table
;
        mov     b,#SIZEOF_MB
        mul     ab        ;calc table offset
        mov     dptr,#modbus_options_table
        add     a,dpl
        mov     dpl,a
        mov     a,b
        addc    a,dph
        mov     dph,a        ;dptr->modbus_options_table[reg#]
;
;  check the register options
;  
        mov     a,#MB_FLAGS
        movc    a,@a+dptr      ;get the options byte
        jnb     a.MB_EEPROM,wmr_2
;
;  EEprom option is set, write the register to eeprom
;  
        call    kick_dog                        ; keep the dog happy whilst we write to eeprom
        mov     a,#MB_OFFSET
        movc    a,@a+dptr                       ; get the eeprom addr
        clr     c
        rrc     a                               ; calc the page addr
        clr     c
        rrc     a
        mov     EADRL,a                         ;set the page address
        mov     ECON,#01h                       ;read page -'cos we do a read_modify_write        
        jc      wmr_11                          ; if we're writing the hi two bytes in the page
;
;       write the two low bytes in the page
;     
        mov     a,R7
        mov     EDATA1,a
        mov     a,R6
        mov     EDATA2,a
        sjmp    wmr_12
;
;       write the two low bytes in the page
;     
wmr_11  
        mov     a,R7
        mov     EDATA3,a
        mov     a,R6
        mov     EDATA4,a
;
;       erase the eeprom page, then write it
;
wmr_12
        mov     ECON,#05h      ;erase page (we sleep for 2mS)
        mov     ECON,#02h      ;write the page (we sleep for 250uS)
        sjmp    wmr_3
;
;       modbus register is in ram
;
wmr_2
        mov     a,#MB_OFFSET
        movc    a,@a+dptr                 
        mov     r0,a                            ;r0-> modbus register in ram
        mov     a,r7
        mov     @r0,a                           ;store hi byte
        inc     r0
        mov     a,r6
        mov     @r0,a                           ;store low byte
wmr_3
        clr     a
wmr_x        
        pop     b
        pop     _R0
        pop     dpl
        pop     dph
        ret  
wmr_bad
        mov     a,1                              ;return a bad status
        sjmp    wmr_x  
;----------------------------------------------------------------------------
;
;  read MODBUS register
;  entry:
;  A has the MODBUS register#
;  returns: A == 0 if ok, A ==1 if bad register#, R7:R6 with the modbus reg value
;  zaps: A,PSW
;
;----------------------------------------------------------------------------
read_modbus_reg    
        push    dph
        push    dpl 
        push    _R0
        push    b
        cjne    a,#NUM_MODBUS_REGS,rmr_1  ;test for max reg#
rmr_1  
        jnc     rmr_bad        ;if reg# >=NUM_MODBUS_REGS
;
;  check the register options
;  
        mov     b,#SIZEOF_MB
        mul     ab        ;calc table offset
        mov     dptr,#modbus_options_table
        add     a,dpl
        mov     dpl,a
        mov     a,b
        addc    a,dph
        mov     dph,a        ;dptr->modbus_options_table[reg#]
        mov     a,#MB_FLAGS
        movc    a,@a+dptr      ;get the options byte
        jnb     a.MB_EEPROM,rmr_2
;
;  EEprom option is set, read the register from eeprom
;  
        mov     a,#MB_OFFSET
        movc    a,@a+dptr                       ; get the eeprom addr
        clr     c
        rrc     a                               ; calc the page addr
        clr     c
        rrc     a
        mov     EADRL,a        ;set the page address
        mov     ECON,#01h      ;read page
        jc      rmr_11                          ;if we're reading the hi two bytes in the page
;
;       read the low two bytes from the eeprom page
;
        mov     r7,EDATA1
        mov     r6,EDATA2
        sjmp    rmr_3        
;
;       read the hi two bytes from the eeprom page
;
rmr_11        
        mov     r7,EDATA3
        mov     r6,EDATA4
        sjmp    rmr_3
;
;       modbus register is in ram
;
rmr_2
        mov     a,#MB_OFFSET
        movc    a,@a+dptr                 
        mov     r0,a                            ;r0-> modbus register in ram
        mov     a,@r0
        mov     r7,a                            ;read hi byte
        inc     r0
        mov     a,@r0
        mov     r6,a                            ;read low byte
rmr_3
        clr     a
rmr_x
        pop     b
        pop     _R0
        pop     dpl
        pop     dph
        ret  
rmr_bad
        mov     a,1                             ;return a bad status
        sjmp    rmr_x  
;----------------------------------------------------------------------------
;
;
;  called on power-up to initialise the modbus registers
;  reads all the values from the modbus_options_table default values
;  into the eeprom if the INIT_TOKEN is not set and reads the defaults
;       into the ram based registers
;
;----------------------------------------------------------------------------
cold_init
        mov     r5,#NUM_MODBUS_REGS
        mov     dptr,#modbus_options_table
ci_loop
        mov     a,#MB_FLAGS
        movc    a,@a+dptr                        ;get the options var
        jb      a.MB_EEPROM,ci_2                 ;if an eeprom based register, skip as no init required
;
;
;
        mov     a,#MB_OFFSET
        movc    a,@a+dptr        
        mov     r0,a                            ;r0->ram modbus register
        
        mov     a,#MB_DEFAULT
        movc    a,@a+dptr
        mov     @r0,a                           ;store the hi default value
        inc     r0
        
        mov     a,#MB_DEFAULT+1
        movc    a,@a+dptr
        mov     @r0,a                           ;store the low default value
ci_2
        mov     a,#SIZEOF_MB
        add     a,dpl
        mov     dpl,a
        mov     a,#0
        addc    a,dph
        mov     dph,a                           ;->next record
        djnz    r5,ci_loop        
;
;       test INIT_TOKEN for the correct value. If not, load the eeprom with the default values from the modbus_options_table
;                

        mov     EADRL,#<INIT_TOKEN
        mov     ECON,#1
        mov     a,EDATA1
        cjne    a,#>INIT_TAG,ci_do
        mov     a,EDATA2
        cjne    a,#<INIT_TAG,ci_do
        ret 
                    ;if init tag was valid
ci_do  
        mov     r5,#NUM_MODBUS_REGS
        mov     dptr,#modbus_options_table
        mov     r2,#0        ;modbus register #1
ci_1
        mov     a,#MB_FLAGS
        movc    a,@a+dptr
        jnb     a.MB_EEPROM,ci_3                ;skip init if a ram based register          
;
;       eeprom based register...init it.
;        
        mov     a,#MB_DEFAULT
        movc    a,@a+dptr
        mov     r7,a
        mov     a,#MB_DEFAULT+1
        movc    a,@a+dptr
        mov     r6,a
        push    dph
        push    dpl
        mov     a,r2                            ;get modbus reg#
        lcall   write_modbus_reg
        pop     dpl
        pop     dph
;
;       next register..
;        
ci_3  
        inc     r2     
        mov     a,#<SIZEOF_MB
        add     a,dpl
        mov     dpl,a
        mov     a,#>SIZEOF_MB
        addc    a,dph
        mov     dph,a        ;next entry
        djnz    r5,ci_1
  
        mov     EADRL,#<INIT_TOKEN
        mov     EDATA1,#>INIT_TAG
        mov     EDATA2,#<INIT_TAG
        mov     ECON,#05h       ;erase page (we sleep for 2mS)
        nop                     ;I'm suspicious!!!
        mov     ECON,#02h       ;write the page (we sleep for 250uS)
        nop
        ret
;
;
;    calculates and appends the CRC for a MODBUS RTU message
;    R0->modbus msg
;    R6 has the msg length   
;    return value ==0 if crc on a rx packet was ok else 1 = crc error
;    zaps:A,B,R0,R2,R4,R5,R6
;
;       crc_lo R4
;       crc_hi R5
;
;
;
calculate_crc

        mov     r4,#0ffh
        mov     r5,#0ffh                ;preload the crc accumulator

cc_lp
        mov     a,@r0                   ;get a byte from the buffer
        inc     r0
        
        xrl     a,r4
        mov     r4,a                    ;CRC ^= *buff
        
        mov     r2,#8                   ;for x=1 to 8
cc_1
        clr     c
        mov     a,r5
        rrc     a
        mov     r5,a
        
        mov     a,r4
        rrc     a
        mov     r4,a                    ;CRC >>=1
        
        jnc     cc_2
        
        mov     a,#0a0h
        xrl     a,r5
        mov     r5,a
        
        mov     a,#01h
        xrl     a,r4
        mov     r4,a                    ;if (carry) CRC ^= 0xa001

cc_2
        djnz    r2,cc_1                 ;next x
        
        djnz    r6,cc_lp
        
        mov     b,#0                    ;B has the error status
        mov     a,@r0
        xrl     a,r4                    ;if crc_lo != *buff then err = 1
        jz      cc_3

        mov     b,#1                    ;flag error

cc_3
        mov     a,r4
        mov     @r0,a
        inc     r0                      ; *buff++ = crc_lo
        
        mov     a,@r0
        xrl     a,r5
        jz      cc_4
        
        mov     b,#1                    ;flag error

cc_4
        mov     a,r5
        mov     @r0,a
        inc     r0                      ;*buff++ = crc_hi
        
        mov     a,b                     ;get the return result
        ret
;----------------------------------------------------------------------------
;
;
;  outputs the modbus ascii data. converts the buffer data to ascii and
;  appends the modbus LRC and cr/lf
;
;  we use register bank 1 exclusively
;
;  R0 is tx_ptr
;  R1 is rx_ptr
;  R2 is tx_state
;  R3 is rx_state
;  R4 is tx_lrc
;  R5 is rx_lrc
;  R6 is tx_length
;  R7 is rx_length
;
serial_isr
        jb      MODBUS_TYPE,ascii
        jb      TI,txrtu
        jb      RI,rxrtu
        sjmp    serial_exit
ascii  
        jb      TI,txascii
        jnb     RI,serial_exit
        ljmp    rxascii
serial_exit
        reti    
;
;
;       modbus RTU rx code
;  we use register bank 1 exclusively
;
;  R0 is tx_ptr
;  R1 is rx_ptr
;  R2 is tx_state
;  R3 is rx_state
;  R4 is tx_lrc
;  R5 is rx_lrc
;  R6 is tx_length
;  R7 is rx_length
;
;
;
rxrtu
        push    psw
        push    a
        mov     psw,#00001000b    ;select register bank#1  
       
        mov     a,r3                    ;get the rx state
        cjne    a,#0,rxrtu_1
;
;       RTU rx state 0, grab the rx data & store into the buffer
;        
;        mov     r5,#3                   ;load the timeout with 2 character times
        mov     a,r7                    ;test the rx length
        clr     c
        subb    a,#SIZEOF_MODBUS_BUFF-2
        jnc     rxrtu0_1                 ;rx count too large! exit
        
        mov     a,SBUF                  ;rx buff ok, grab the rx char
        clr     RI
        mov     @r1,a                   ;store it
        inc     r1                      ;rx->++
        inc     r7                      ;rx length++
;
;       use timer2 for end of packet timeout
;    
        clr     TR2
        mov     TH2,#0fah
        mov     TL2,#060h               ;3 char times
        setb    TR2

        sjmp    rxrtu_x
        
rxrtu_1
;
;       RTU rx state1, ignore any more rx chars for the moment
;
rxrtu0_1
        mov     a,SBUF                  ;junk the rx character
        clr     RI
rxrtu_x
  pop  a
  pop  psw
  reti
;
;
;       the tx code is a little trickier since we use the tx as a timer for the
;       rx packet timeout when we're not actually transmitting a packet
;
;                
txrtu
        push    psw
        push    a
        mov     psw,#00001000b    ;select register bank#1  
        
        mov     a,r2                    ;get the tx state
        cjne    a,#0,txrtu_1
;
;       RTU tx state 0. nothing happening so disable the interrupts
;        
        setb    EN_485                  ;don't send anything to the outside!
        clr     TI
        sjmp    txrtu_x        
        
;        mov     a,r5                    ;get the rx timer
;        jz      txrtu_x                 ;already 0, don't do anything!
        
;        djnz    r5,txrtu_x              ;dec the timer
;
;       timer just hit 0, activate the rx process code
;        
;  mov  r1,#modbus_buff
;  mov  a,@r1      ;get the first byte (modbus addr)
;  xrl  a,our_addr    ;test with our address
;  jnz  txrtu0_2    ;if not us
;  setb  TASK0_RUN    ;if a good packet, activate the rx task
;  mov  r3,#1      ;set state for rx idle, when packet is processed, we are reset
;  sjmp  txrtu_x
;
;       restart the RTU rx code
;
;txrtu0_2
;        mov     r1,#modbus_buff         ;reset the rx->
;        mov     r7,#0                   ;reset the byte count
;        mov     r3,#0                   ;reset the rx state
;        
;        sjmp    txrtu_x

txrtu_1
        cjne    a,#1,txrtu_x
;
;       RTU tx state 1. here we send tx data to the outside world
;       
;                        
        clr     EN_485
        mov     a,@r0
        mov     SBUF,a
        clr     TI
        inc     r0
        djnz    r6,txrtu_x
;
;       last character of the packet, next state is 0
;        
        mov     r2,#0        

txrtu_x
        pop     a
        pop     psw
        reti
;
;
;  implements the MODBUS ASCII protocol. 
;  user routine must set tx_ptr (R0), tx_length (R6) and send the ':' start token
;  to fire the tx interupt
;
;
txascii
  push  psw
  push  a
  mov  psw,#00001000b    ;select register bank#1  
  mov  a,r2      ;get the state into A
  cjne  a,#0,tx_st1
;
;  state = 0
;
  mov  a,@r0
  add  a,r4
  mov  r4,a      ;accumulate the checksum
  mov  a,@r0
  swap  a      ;get hi nibble
  anl  a,#0fh
  cjne  a,#10,mti_1
mti_1
  jnc  L96  
  add  a,#'0'      ;ascii offset 0..9
  sjmp  L97
L96
  add  a,#'0' + 7    ;ascii offset A..F
L97
  mov  SBUF,a
  clr  TI
  mov  r2,#1      ;next state = 1
  sjmp  L95_X
tx_st1
  cjne  a,#1,tx_st2
;
;  state = 1. send ascii low nibble
;
  mov  a,@r0
  inc  r0
  anl  a,#0fh      ;get low nibble
  cjne  a,#10,mti_2
mti_2  jnc  L96_1  
  add  a,#'0'      ;ascii offset 0..9
  sjmp  L97_1
L96_1
  add  a,#'0' + 7    ;ascii offset A..F
L97_1
  mov  SBUF,a
  clr  TI
  dec  r6
  mov  a,r6
  cjne  a,#1,mti_3    ;length == 1?
  sjmp  L102      ;yep! calc the lrc
mti_3  
  jnz  L103      ;if the last byte
  mov  r2,#2      ;next state =2,send CR
  sjmp  L95_X
L103
  mov  r2,#0      ;next state = 0,send next byte
  sjmp  L95_X
L102
  mov  a,r4      ;get the lrc
  cpl  a      ;ones complement
  inc  a      ;plus 1
  mov  @r0,a      ;store the lrc
  mov  r2,#0      ;next state =send hi nibble
  sjmp  L95_X
tx_st2
  cjne  a,#2,tx_st3
;
;  state = 2. send CR
;
  mov  SBUF,#CR    ;send CR
  clr  TI
  mov  r2,#3      ;next state = 3 send line feed
  sjmp  L95_X
tx_st3
  cjne  a,#3,tx_st4
;
;  state = 3. send line feed
;
  mov  SBUF,#LF    ;send LF
  clr  TI
  mov  r2,#4      ;next state = 4
  sjmp  L95_X
tx_st4
  cjne  a,#4,L95_X
;
;  state = 4. end of transmit, set 485 buffer to receive and sit
;  here until the tx code re-activates us
;  
  clr  TI
  setb  EN_485
L95_X
  pop  a
  pop  psw
  reti
;
;
;  implements the MODBUS ASCII receive protocol
;
;
rxascii
  push  psw
  push  a
  push  b
  mov  psw,#00001000b    ;select register bank#1  
  mov  b,SBUF      ;get the rx char into B
  clr  RI
;
;  always test for the start token
;
  mov  a,b
  cjne  a,#':',L129    ;start token?
  mov  r5,#0      ;rx_lrc = 0
  mov  r7,#0      ;rx_length = 0
  mov  r1,#modbus_buff    ;->start of rx buffer
  mov  r3,#1      ;next state = 1  
  sjmp  L116_X

L129


rx_st1
  mov     a,r3                    ; get the rx state
        cjne  a,#1,rx_st2
;
;  rx_state = 1. expect a hex ascii char or a carriage return
;  for end of packet
;
  clr  c
  mov  a,b
  subb  a,#'0'    ;minus ascii offset
  jc  L127    ;if number < '0'
  cjne  a,#9+1,rx_st1_2
rx_st1_2
  jnc  L125    ;if rx char > 9
  sjmp  L126
L125
  clr  c
  subb  a,#7
  cjne  a,#0ah,rx_st1_3
rx_st1_3
  jc  L127      ;if rx char is < 'A'  
  cjne  a,#0fh+1,rx_st1_4
rx_st1_4
  jnc  L127      ;if rx char > 'F'
L126
  anl  a,#0fh
  swap  a
  mov  @r1,a
  mov  r3,#2      ;next state = 2
  sjmp  L116_X
L127
  mov  a,b
  cjne  a,#CR,L124    ;carriage return? (end of packet?)
;
;  end of packet
;
  mov  a,r5      ;get our rx_lrc
  jnz  L124      ;if lrc was bad!
  mov  r1,#modbus_buff
  mov  a,@r1      ;get the first byte (modbus addr)
  xrl  a,our_addr    ;test with our address
  jnz  L124      ;if not us
  setb  TASK0_RUN    ;if a good packet, activate the rx task
  mov  r3,#4      ;idle in an illegal state, when packet is processed, we are reset
  sjmp  L116_X
L124
  mov  r3,#0      ;next state = 0
  sjmp  L116_X
rx_st2
  cjne  a,#2,L116_X
;
;  state = 2. expect hex ascii for low byte nibble
;
  clr  c
  mov  a,b
  subb  a,#'0'    ;minus ascii offset
  jc  L137    ;if number < '0'
  cjne  a,#9+1,rx_st2_2
rx_st2_2
  jnc  L135    ;if rx char > 9
  sjmp  L133
L135
  clr  c
  subb  a,#7
  cjne  a,#0ah,rx_st2_3
rx_st2_3
  jc  L127      ;if rx char is < 'A'  
  cjne  a,#0fh+1,rx_st2_4
rx_st2_4
  jnc  L127      ;if rx char > 'F'
L133
  anl  a,#0fh
  orl  a,@r1      ;'or' in the hi nibble
  mov  @r1,a    
  add  a,r5      ;get rx_lrc
  mov  r5,a      ;accumulate the lrc
  inc  r1      ;rx_ptr++
  inc  r7      ;rx_length++
  cjne  r7,#SIZEOF_MODBUS_BUFF,rx_st2_5
rx_st2_5
  jnc  L137      ;if rx packet is too large!  
  mov  r3,#1      ;otherwise next state = 1
  sjmp  L116_X
L137
  mov  r3,#0      ;next state = 0
L116_X
  pop  b
  pop  a
  pop  psw
  reti  
;
;
;       timer2 timeout comes here when an end of a modbus rtu packet is detected by 3 char timeout
;
;        
t2_isr        
        clr     TR2                     ;stop timer2 running
        clr     TF2                     ;reset the interrupt flag
        push    psw
        push    a
        mov     psw,#00001000b          ;select register bank#1  
        
        mov     r1,#modbus_buff
        mov     a,@r1                   ;get the first byte (modbus addr)
        xrl     a,our_addr              ;test with our address
        jnz     t2_1                    ;if not us
        setb    TASK0_RUN               ;if a good packet, activate the rx task
        mov     r3,#1                   ;set state for rx idle, when packet is processed, we are reset
        sjmp    t2_x
;
;       restart the RTU rx code
;
t2_1
        mov     r1,#modbus_buff         ;reset the rx->
        mov     r7,#0                   ;reset the byte count
        mov     r3,#0                   ;reset the rx state
t2_x
        pop     a
        pop     psw
        reti
;----------------------------------------------------------------------------
;
;
;  rom constant data
;
;  
;----------------------------------------------------------------------------
;  constant structure for the modbus registers. Has flags to determine if the register
;  is stored in ram or eeprom and the pointer to the ram/eeprom address where to register
;  contents are stored. Also has the default value that is loaded into the registers
;  on startup for ram based registers or when first programmed for eeprom based registers.
;  There is an entry for every modbus register in order of the modbus register number.
;
;
SIZEOF_MB       equ     4       ;structure size
MB_FLAGS        equ     0
MB_OFFSET       equ     1       ;offset value for index into eeprom OR internal ram (MB_EEPROM if set is eeprom)
MB_DEFAULT      equ     2       ;default value for init. 1 word
;
; bits in MB_FLAGS
;
MB_EEPROM       equ     0       ;bit 0 of the flags .1 = register read/written to eeprom, 0 =read/write to ram


modbus_options_table
                GLOBALS ON
;  0       Software Version Number
MB_VERS equ     ($-modbus_options_table)/SIZEOF_MB
        db      1.SHL.MB_EEPROM ;options
        db      <MBEE_VERS      ;ram/eeprom addr
        dw      VERSION         ;default value (loaded into eeprom)


modbus_buff     ds      SIZEOF_MODBUS_BUFF

stack    equ    $       ;stack starts from here- it grows upwards on a 8051

