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

Back to Subject List

Old thread has been locked -- no new posts accepted in this thread
???
09/28/06 09:26
Read: times


 
#125265 - Modbus code
Responding to: ???'s previous message

Here is some modbus code written in assembler - it was originally written in 'c' but converted to assembler to fit into a small micro. However - the structure is much the same.

Modbus sees everything as 16 bit registers or bits. This code only implements the 16 bit registers.

These regsters may have configuration or process values. The config is stored in eeprom and the process values are stored in ram. The modbus_config_table structure is used by the routines
read_modbus_reg and write_modbus_reg to find out where to read/write the values. This structure also has the initialisation values.

The routine cold_init is used to initialise the ram based registers with the default value stored in the structure and tests for a token in the eeprom to see if it needs to be initialised.

The serial interrupt code sends/receives the modbus packets. This code translates the ascii packets to/from RTU format. When a packet is received, a flag is set to activate a task that calls the routine process_modbus. This routine checks the received packet and forms the response. This code pretty much follows the logic in the modbus spec. You can add extra commands in here if required.

Use the routines read_modbus_reg and write_modbus_reg to access the modbus registers.

The CRC routine can be found in 'c' in the modbus spec. You can choose between two methods - looped as is done here - slow but small in code size or table driven - large but fast.

Beware of atomicity issues - if you don't know what they are, you'd best learn as they apply here. This code ran under a co-operative tasker so modbus register accesses were mutually exclusive between the process_modbus code and the read/write_modbus_reg code. Ignore the above comments at your own peril.

Apart from that, MODBUS is a very simple and widely supported protocol for instrumentation and control applications. Useless for anything else though.

Have Fun.

  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




List of 14 messages in thread
TopicAuthorDate
Modubus code... Russell Bull            01/01/70 00:00      
   Life's like that!            01/01/70 00:00      
   Tell me more...            01/01/70 00:00      
      commercial application... Yes!            01/01/70 00:00      
   Modbus code            01/01/70 00:00      
      Would have been great if it was in C            01/01/70 00:00      
         Free is what free gets            01/01/70 00:00      
            Thanks anyway            01/01/70 00:00      
            modbus in c            01/01/70 00:00      
               Read the above.            01/01/70 00:00      
                  yes, but            01/01/70 00:00      
                  I dont mind...            01/01/70 00:00      
                     Happiness            01/01/70 00:00      
                        Oh! yes great joy.....            01/01/70 00:00      

Back to Subject List