News:

Let's find out together what makes a PIC Tick!

Main Menu

I need the MODBUS RTU code sample.

Started by Elektroart, Feb 07, 2021, 09:51 AM

Previous topic - Next topic

Elektroart

Is there any possibility to share MODBUS RTU code sample for Master and Slave.
It should include the CRC.

See_Mos

Modbus an fieldbus were the two topics that I was going to search for when the old forum went down.

I have been asked to test some fieldbus rotary encoders to see if they work or not so I also would appreciate any pearls of wisdom.

TimB

Quote from: Elektroart on Feb 07, 2021, 09:51 AMIs there any possibility to share MODBUS RTU code sample for Master and Slave.
It should include the CRC.

I have some modbus RX code.

Mine runs on interrupts so is completely transparent.
However using HRSIn with time out is all you need at the RX side (along with the CRC). Basically wait for a start char then keep receiving until you timeout. The time out indicates the packet has ended. Then parse the messages. How you do that depends on the protocol of the device your talking to.
 
The TX end it is much the same except you send a packet then use the time out. If no reply in x ms then resend. Count the number of time you do that and make decisions on what to do next.

Unfortunately you really need to put the hours in reading the specs on the protocol. Then you can ask for fine detail. If your asking for a complete package then you doing it wrong.

I can dig out the interrupt based RX side but I doubt it will help you unless you know what your doing.

Tim 

See_Mos

thanks Tim, Yes, I need to do some reading.  It was only last Thursday when I was asked to look at these.  I approached the manufacturer but they could not give me any information.


TimB


Hi See_Mos

Not that I'm very good at reading myself. I'm happy to do what I can to help with anything.

Tim

See_Mos

Thanks Tim
The project has come to a halt.  I spoke with the end user yesterday who informed me that the encoder was found with water inside so i opened it up.  Corrosion has damaged the circuit board and the optics so it will have to be scrapped.

OG

#6
From the old forum (by Tim).
Is this work yours? @TimB
I'm publishing without your permission.

QuoteHi, below is some code I wrote for an interrupt drive modbus slave. There is loads you do not need but you can copy sections out that are relevant.

So basically

1 Set up interrupt vectors

On_Interrupt GoTo Usart_Int 'High interrupts eg have priority

On_Low_Interrupt GoTo TMR_INTERRUPT ' Low interrupts


2 Enable interrupt on usart

RCSTA = $90 ' Enable serial port & continuous receive
TXSTA = $20 ' Enable transmit, BRGH = 0
SPBRG = 51 ' 19200 Baud @ 64MHz, 0.16%

; ENABLE USART RECEIVE INTERRUPTS
RCIP = 1 ; USART HAS HIGH PRIORITY
RCIE = 1
CREN = 1
SPEN = 1

Then some interrupt code

Usart_Int:
Reset_Bank
If OERR = 1 Then GoTo USART_ERR ; Check for usart overrun

HiIntFSRSave = FSR_0 ; Save the FRS Reg
If RCIF = True Then ; If this is a RX interrupt
HighIntTemp = RCREG ; Make a working copy of the data

In the code you need to write it to your array keeping tabs on where you are and if you need to start from the start

DataInBuffer[DataInBufferPointer] = HighIntTemp


Exit the interrupt and have the code in case of buffer over run

USART_EXIT:
FSR_0 = HiIntFSRSave
USART_EXIT2:
Retfie Fast

USART_ERR: ; Handle a usart overrun error
WREG = RCREG
CREN = 0
CREN = 1
PacketFrameState = 0 ; Packet is empty
ResetFrameTimer ; We need to wait for the next packet so
BufferFull = False ; Buffer cannot be full at this point
GoTo USART_EXIT2


You will need to think about these things

1 How to decide to start the packet again eg if there is an interrupt in the flow you need to restart from the start of a new packet and not collect gibberish. Modbus is good in that it works on if there is a break in data for more than x char lengths is the end of a packet in the code below I use a time to do all that and flag the main code there is data. In the main code I check how much was sent and if its not enough ignore it and set pointers to the collect a whole new packet.

I hope it all helps. It's a task to do but worth it in the end.


'****************************************************************
'*  Name    : UNTITLED.BAS                                      *
'*  Author  : [select VIEW...EDITOR OPTIONS]                    *
'*  Notice  : Copyright (c) 2011 [select VIEW...EDITOR OPTIONS] *
'*          : All Rights Reserved                               *
'*  Date    : 12/12/2011                                        *
'*  Version : 1.0                                               *
'*  Notes   :                                                   *
'*          :                                                   *
'****************************************************************




    ; Compiler related stuff
   
    Device 18F87K22
   
    Xtal = 16
    ;define Create_Coff = On
   
    GoTo _OverSubs
   
   
    Symbol GIE = INTCON.7       ; Global Interrupt Enable Bit
    Symbol PEIE = INTCON.6      ; Peripheral Interrupt Enable Bit
    Symbol TMR0IE = INTCON.5    ; TMR0 Overflow Interrupt Enable Bit
    Symbol INT0IE = INTCON.4    ; INT0 External Interrupt Enable Bit
    Symbol RABIE = INTCON.3     ; RA And RB Port Change Interrupt Enable Bit
    Symbol TMR0IF = INTCON.2    ; TMR0 Overflow Interrupt Flag Bit
    Symbol INT0IF = INTCON.1    ; INT0 External Interrupt Flag Bit
    Symbol RABIF =  INTCON.0    ; RA And RB Port Change Interrupt Flag Bit(1)
   
    Symbol RX9D = RCSTA.0  ; 9th bit of received data (Can be parity bit) (Usart 1)
    Symbol OERR = RCSTA.1  ; Overrun Error (Usart 1)
    Symbol FERR = RCSTA.2  ; Framing Error (Usart 1)
    Symbol ADDEN = RCSTA.3 ; Address Detect Enable (Usart 1)
    Symbol CREN = RCSTA.4  ; Continuous Receive Enable (Usart 1)
    Symbol SREN = RCSTA.5  ; Single Receive Enable (Usart 1)
    Symbol RX9 = RCSTA.6   ; 9-bit Receive Enable (Usart 1)
    Symbol SPEN = RCSTA.7  ; Serial Port Enable (Usart 1)


    Symbol TX9D = TXSTA.0    ; 9th bit of transmit data. Can be parity bit. (Usart 1)
    Symbol TRMT = TXSTA.1    ; Transmit Shift Register Status flag (Usart 1)
    Symbol BRGH = TXSTA.2    ; High Baud Rate Select bit (Usart 1)
    Symbol SYNC = TXSTA.4    ; USART Mode Select bit (Usart 1)
    Symbol TXEN = TXSTA.5    ; Transmit Enable (Usart 1)
    Symbol TX9 = TXSTA.6     ; 9-bit Transmit Enable (Usart 1)
    Symbol NOT_TX8 = TXSTA.6 ; 8-bit Transmit Enable (Usart 1)
    Symbol TX8_9 = TXSTA.6   ; 9/8-bit Transmit Enable (Usart 1)
    Symbol CSRC = TXSTA.7    ; Clock Source Select bit (Usart 1)
   
   
    Symbol TMR1IF = PIR1.0 ; TMR1 Overflow Interrupt Flag bit
    Symbol TMR2IF = PIR1.1 ; TMR2 to PR2 Match Interrupt Flag bit
    Symbol CCP1IF = PIR1.2 ; CCP1 Interrupt Flag bit
    Symbol SSPIF = PIR1.3  ; Master Synchronous Serial Port Interrupt Flag bit
    Symbol TXIF = PIR1.4   ; EUSART Transmit Interrupt Flag bit
    Symbol RCIF = PIR1.5   ; EUSART Receive Interrupt Flag bit
    Symbol ADIF = PIR1.6   ; A/D Converter Interrupt Flag bit
   
    Symbol TMR1ON = T1CON.0     ; Timer1 ON
    Symbol TMR1CS = T1CON.1     ; Timer1 Clock Source Select
    Symbol NOT_T1SYNC = T1CON.2 ; Timer1 External Clock Input Synchronization Control
    Symbol T1OSCEN = T1CON.3    ; Timer1 Oscillator Enable Control
    Symbol T1CKPS0 = T1CON.4    ; Timer1 Input Clock Prescale Select bits
    Symbol T1CKPS1 = T1CON.5    ; Timer1 Input Clock Prescale Select bits
    Symbol T1RUN = T1CON.6      ; Timer1 System Clock Status bit
    Symbol RD16 = T1CON.7       ; 16-bit Read/Write Mode Enable bit
   
    Symbol TMR1IP = IPR1.0 ; TMR1 Overflow Interrupt Priority bit
    Symbol TMR2IP = IPR1.1 ; TMR2 to PR2 Match Interrupt Priority bit
    Symbol CCP1IP = IPR1.2 ; CCP1 Interrupt Priority bit
    Symbol SSPIP = IPR1.3  ; Master Synchronous Serial Port Interrupt Priority bit
    Symbol TXIP = IPR1.4   ; EUSART Transmit Interrupt Priority bit
    Symbol RCIP = IPR1.5   ; EUSART Receive Interrupt Priority bit
    Symbol ADIP = IPR1.6   ; A/D Converter Interrupt Priority bit
   
    Symbol TMR1IE = PIE1.0   ; TMR1 Overflow Interrupt Enable bit
    Symbol TMR2IE = PIE1.1   ; TMR2 to PR2 Match Interrupt Enable bit
    Symbol CCP1IE = PIE1.2   ; CCP1 Interrupt Enable bit
    Symbol SSPIE = PIE1.3    ; Master Synchronous Serial Port Interrupt Enable bit
    Symbol TXIE = PIE1.4     ; EUSART Transmit Interrupt Enable bit
    Symbol RCIE = PIE1.5     ; EUSART Receive Interrupt Enable bit
    Symbol ADIE = PIE1.6     ; A/D Converter Interrupt Enable bit
    Symbol reserved = PIE1.7 ; Maintain this bit clear   
   
   
    Symbol T0PS0 = T0CON.0                      ; Timer0 Prescaler Select bit
    Symbol T0PS1 = T0CON.1                      ; Timer0 Prescaler Select bit
    Symbol T0PS2 = T0CON.2                      ; Timer0 Prescaler Select bit
    Symbol PSA = T0CON.3                        ; Timer0 Prescaler Assignment bit
    Symbol T0SE = T0CON.4                       ; Timer0 Source Edge Select bit
    Symbol T0CS = T0CON.5                       ; Timer0 Clock Source Select bit
    Symbol T08BIT = T0CON.6                     ; Timer0 8-bit/16-bit Control bit
    Symbol TMR0ON = T0CON.7                     ; Timer0 On/Off Control bit   
   
    Symbol NOT_BOR = RCON.0 ; Brown-out Reset Status bit
    Symbol NOT_POR = RCON.1 ; Power-on Reset Status bit
    Symbol NOT_PD = RCON.2  ; Power-down Detection Flag bit
    Symbol NOT_TO = RCON.3  ; Watchdog Time-out Flag bit
    Symbol RI = RCON.4      ; RESET Instruction Flag bit
    Symbol IPEN = RCON.7    ; Interrupt Priority Enable bit   
   
   
   
   
    Dim FSR_0 As FSR0L.Word
    Dim FSR_1 As FSR1L.Word
    Dim FSR_2 As FSR2L.Word   
   
    Dim TIMER1REG As TMR1L.Word
    Dim TIMER0REGL As TMR0L
    Dim TIMER0REGH As TMR0H
   
    Dim FrameTimerIntflag As TMR0IF
   
; general programming defines


    Symbol True = 1
    Symbol False = 0   
   
   
; $Defines
  
   
;*=============================================================*
;* Description  Interrupt based defines                        *
;*=============================================================*
    Dim HiIntFSRSave As Word System
   
       
    On_Interrupt GoTo Usart_Int
   
    On_Low_Interrupt GoTo TMR_INTERRUPT








    ; Timer 1 reloads
   
    Symbol FUDGE_FACTOR = 5
    Symbol TMR1_VAL = 25541                                         ; VALUE FOR 100us INTERRUPTS


    ;Timer 2 reloads
    Symbol FrameTimerReload_19200 = 56743
    Symbol FrameTimerReload_9600 = 48743


; Main variables
   
    ; interrupt related
    Dim HighIntTemp As Byte                                         ; temp var
    Dim PacketFrameState As Byte                                    ; State Machine
    Dim LowIntTemp As Byte
    Dim IntProcess As Bit
       
   
   
   
    ; Interrupt timer based timers
   
    Dim Hz10_Tick As Byte
   
    Dim No_Timers As 10
   
    Dim Hz10Timers [No_Timers] As Word
   
    Dim Timer0W As Word At Hz10Timers                               ; Word based timers
    Dim Timer1W As Word At Hz10Timers + 2
    Dim TImer2W As Word At Hz10Timers + 4
    Dim Timer3W As Word At Hz10Timers + 6
    Dim Timer4W As Word At Hz10Timers + 8
    Dim Timer5W As Word At Hz10Timers + 10                       
    Dim Timer6W As Word At Hz10Timers + 12
    Dim TImer7W As Word At Hz10Timers + 14
    Dim Timer8W As Word At Hz10Timers + 16
    Dim Timer9W As Word At Hz10Timers + 18   
      
   
    Dim FrameTimerSeen As Bit                                       ; Flag !
   
    Symbol NodeAddress = 0
    Symbol DataCollection = 1
   
   
   
   
    ; Buffers
    Symbol RXDataBufferSize = 32                                    ; Or packet buffer is this big
    Dim DataInBuffer[RXDataBufferSize] As Byte                      ; Our main buffer
    Dim DataInBufferPointer As Byte                                 ; Buffer array pointer
    Dim RXEndOfData As Byte                                         ; Pointer to just before the CRC


    Symbol TXDataBufferSize = 32
    Dim DataOutBuffer[TXDataBufferSize] As Byte                     ; Our out main buffer
    Dim DataOutBufferPointer As Byte                                ; Buffer array pointer
    Dim TXEndOfData As Byte                                         ; Pointer to just before the CRC
    Dim TXOutPointer As Byte                                        ; Pointer in the buffer
    Dim TXDataSent As Bit                                           ; Data sent
    ; Packet info
    Dim InBufferDataReady As Bit                                    ; Flag to say there is data ready to puLL out of the buffer
    Dim BufferFull As Bit
   
; Sub variables


    Dim CommandFunction As Byte
    Dim CommandFunctionTemp As Byte
    Dim RegAddress As Word
    Dim NumberOfRegsToRead As Word
    Dim NumberOfRegsToReadTemp As Byte
    Dim RegAddressTemp As Word
    Dim RegDataTemp As Word
    Dim AddressTemp As Word
    Dim ErrorCode As Byte
   
    Dim RegData As Word
   
   
    Dim AddressRangeValid As Bit
    Dim RegTest As Bit
    Dim DoMemRead As Bit
   
   
    Dim Index1 As Byte
    Dim Index2 As Word
    Dim TempB1 As Byte
    Dim TempB2 As Byte
    Dim TempW1 As Word
    Dim TempW2 As Word
   
    Dim TimerNumber As Word
    Dim TimerPointer As Word
    Dim TimerValue As Word
   
    Dim FSR2Reg As Word At FSR2L                                    ; Special alais to the FSR2 to make it a word


    ; Modbus Default listen to command
    Symbol DefualtNodeAddress = 254
   
    ; Modbus Commands
    Symbol Read_Regs = $03
    Symbol Write_Reg = $06
   
    Symbol NotValidCommand = $01
    Symbol NotValidRange = $02


    ; Modbus command buffer pointers
    Symbol FuncLoc = 1                                                ; Function address in the incoming data array   
    Symbol RegAddressHigh = 2
    Symbol RegAddressLow = 3
    Symbol RegRangeHigh = 4
    Symbol RegRangeLow = 5
    Symbol RegDataHigh = 4   
    Symbol RegDatalow = 5 
   
    ; Modbus command vars
  
    ; CRC vars
    Dim ModbusCRC As Word
    Dim CRCIndex1 As Byte
    Dim CRCIndex2 As Byte
    Dim CRCTemp As Byte
    Dim CRCEndOfData As Byte
    Dim CRCDirection As Bit
    Dim CRC_OK As Bit
   
    Symbol _In = 1
    Symbol _Out = 0
   
    ; Safty reset timer
    Dim WatchDogTimer As Byte                                       ; Main code needs to keep resetting this or the whole pic is reset


    ; Baudrate related vars
    Dim Baudrate As Byte




; Temp stuff!


    Dim FrameTime As Word
    
    Dim OurNodeAddress As Byte


    Dim MinPacketSize As 3
   
   
; commands
    $define ResetFrameTimer GoSub ResetFrameSub
    $define ClrWdTimr WatchDogTimer = 0
   
    $define CheckCRC GoSub CalcCRCRoutine
    $define ParsePacket GoSub ParsePacketSub


    $define DisableInts GIE = 0
    $define EnableInts  GIE = 1
   
    




; Command subs
   
ResetFrameSub:
    TIMER0REGH = FrameTime.Byte1                                    ; We need to do this because of the way tmr0 in 16 bit mode needs to be loaded
    TIMER0REGL = FrameTime.Byte0                                    ;
    TMR0IF = 0                                                      ; Clear the flag
    TMR0ON = 1                                                      ; start the timer running
    TMR0IE = 1                                                      ; Enable the interrupt
    Return   


;***************************************************************
;
;
;   Included files
;
;
;***************************************************************
   
    Include "Reg_Declarations.inc"






    
;*=============================================================*
;   Usart interrupt
;   Hanldes the RX and TX of data
;*=============================================================*    
      
   
Usart_Int:
    Reset_Bank
    If OERR = 1 Then GoTo USART_ERR                                 ; Check for usart overrun
   
    HiIntFSRSave = FSR_0                                            ; Save the FRS Reg
   
       
    If RCIF = True Then                                             ; If this is a RX interrupt
        HighIntTemp = RCREG                                         ; Make a working copy of the data
        Select PacketFrameState                                     ; What stage of the data collection are we?
       
        Case NodeAddress                                            ; We are waiting for the nodeAddress
            IntProcess = False
            If HighIntTemp = OurNodeAddress Then                    ; Is this address for us?
                IntProcess = True 
            ElseIf HighIntTemp = DefualtNodeAddress Then
                IntProcess = True
            EndIf
            If IntProcess = True Then
                If PacketFrameState = 0 Then                        ; If using our timing system we are not in the middle of a packet to some were else
                    Inc PacketFrameState                            ; Yes so we move onto the next stage and collect the data
                    ResetFrameTimer                                 ; Reset the Frame timer as we have just recieved an address
                    DataInBufferPointer = 0                         ; reset the buffer pointer
                    DataInBuffer[DataInBufferPointer] = HighIntTemp
                    Inc DataInBufferPointer                         ; Move our pointer up
                    BufferFull = False                              ; Buffer cannot be full at this point
                EndIf
            EndIf
            EndIf
           
       
        Case DataCollection
            DataInBuffer[DataInBufferPointer] = HighIntTemp         ; Load the data into our buffer
            ResetFrameTimer                                         ; rest the frame buffer timer
            If DataInBufferPointer < RXDataBufferSize Then          ; make sure we are below our buffer size
                Inc DataInBufferPointer                             ; Move our pointer up
                BufferFull = False                                  ; Buffer cannot be full at this point
            Else
                BufferFull = True
            EndIf
       
        EndSelect
    
     EndIf
; Thats it! End of the data collection routine     


  
     ; Code to send the data from the TX buffer
     If TXIE = 1 Then                                               ; Only do the rest if the TX unit is on, its our way to control the system
         If TXIF = True Then                                        ; If this is a TX interrupt
            If DataOutBufferPointer > TXEndOfData Then              ; Check if there is more data to send
                TXIE = 0                                            ; Buffer empty so turn of interrupts for now
                TXDataSent = True                                   ; Flag the buffer has been sent so it can be used again
            Else
                TXREG = DataOutBuffer[DataOutBufferPointer]         ; Send the data
                Inc DataOutBufferPointer                            ; Move the pointer down
            EndIf                                              
         EndIf
     EndIf
     


   
   
USART_EXIT:
     FSR_0 = HiIntFSRSave
USART_EXIT2:                                               
     Retfie Fast
  
USART_ERR:                                                          ; Handle a usart overrun error
     WREG = RCREG
     CREN = 0
     CREN = 1
     PacketFrameState = 0                                           ; Packet is empty
     ResetFrameTimer                                                ; We need to wait for the next packet so
     BufferFull = False                                             ; Buffer cannot be full at this point
     GoTo USART_EXIT2   
   
   
   
; Timer based interrupts low Level
   
TMR_INTERRUPT:       
    Context Save   
    Reset_Bank
   
    LowIntTemp = DataInBufferPointer                                ; Make a temp copy of this var 
   
    DisableInts
    If TMR0ON = True Then                                           ; Is TMR0 running eg we are looking at a time frame flag
        If TMR0IF = True Then                                       ; If the frame timer interrupt flag is set then
            TMR0ON = False                                          ; Turn off the tmr so we do not get here again until required to              
            TMR0IF = False                                          ; Clear the flag
            PacketFrameState = 0                                    ; Reset our frame state
            If LowIntTemp > MinPacketSize Then                      ; We have to be larger than the min packet size to say we have a valid packet          
                If InBufferDataReady = False Then                   ; flag there is data ready if its not been flaged already 
                    InBufferDataReady = True
                    RXEndOfData = LowIntTemp                        ; Say were the end of the data section is
                EndIf
            EndIf
         EndIf           
    EndIf
    EnableInts
   


; handle our 100hz timers   
   
    If TMR1IF = True Then      
        ; Reload our timer
        Clear TMR1ON                                                ; STOP THE TIMER
        TIMER1REG = TIMER1REG + TMR1_VAL                            ; LOAD TMR1
        Set TMR1ON                                                  ; START THE TIMER AGAIN   
       
        ; Byte timers                                               ; Range limited to 1 to 255 ~ 2.5 seconds
        If Hz10_Tick > 0 Then
            Dec Hz10_Tick
        Else
            Hz10_Tick = 9
                     
            ; 10hz timers
           
            If Timer0W > 0 Then
                Dec Timer0W
            EndIf
                                                      
            If Timer1W > 0 Then                                    
                Dec Timer1W
            EndIf
   
            If TImer2W > 0 Then
                Dec TImer2W
            EndIf
          
            If Timer3W > 0 Then
                Dec Timer3W
            EndIf
           
            If Timer4W > 0 Then
                Dec Timer4W
            EndIf
           
            If Timer5W > 0 Then
                Dec Timer5W
            EndIf
           
            If Timer6W > 0 Then
                Dec Timer6W
            EndIf
           
            If TImer7W > 0 Then
                Dec TImer7W
            EndIf
           
            If Timer8W > 0 Then
                Dec Timer8W
            EndIf
           
            If Timer9W > 0 Then
                Dec Timer9W
            EndIf


           
        EndIf


    EndIf
   
   
LowIntExit:
     Inc WatchDogTimer
     If WatchDogTimer > 250 Then
         GoTo Restart_Routine
     EndIf
    Clear TMR1IF                                                    ; Clear TMR1 interrupt flag    
    Context Restore
   
   
   


InitialiseCore:


'----- SET UP TIMER 1 INTERRUPT ----------------------------
   
    TMR1IP = 0                                                      ; Timer1 has low priority
    TIMER1REG = TMR1_VAL
    T1CON = %00000000                                               ; Set up Tmr1 to have 1:1 prescaler and act as a timer
    TMR1IF = 0                                                      ; Clear Tmr1 interrupt flag


    TMR1IE = 0                                                      ; Do not Enable Tmr1 as peripheral interrupt source yet
    TMR1ON = 1
    TXIF = 0
    TMR1IE = 1
   


; Set up the usart to run at the right speed and interrupt on high level ints   


    Baudrate = ERead BaudSetting                                    ; Read in the current boardrate


    If Baudrate = 192 Then      
        RCSTA = $90 ' Enable serial port & continuous receive
        TXSTA = $20 ' Enable transmit, BRGH = 0
        SPBRG = 51  ' 19200 Baud @ 64MHz, 0.16%




'        RCSTA = $90                                                 ; Enable serial port & continuous receive
'        TXSTA = $20                                                 ; Enable transmit, BRGH = 0
'        SPBRG = 12                                                  ; 19200 Baud @ 16MHz, 0.16%
        FrameTime = FrameTimerReload_19200                                            
    Else
        RCSTA = $90 ' Enable serial port & continuous receive
        TXSTA = $20 ' Enable transmit, BRGH = 0
        SPBRG = 103 ' 9600 Baud @ 64MHz, 0.16%


'        RCSTA = $90                                                 ; Enable serial port & continuous receive
'        TXSTA = $20                                                 ; Enable transmit, BRGH = 0
'        SPBRG = 25                                                  ; 9600 Baud @ 16MHz, 0.16%
        FrameTime = FrameTimerReload_9600
    EndIf
   
    ; ENABLE USART RECEIVE INTERRUPTS
    RCIP = 1                                                        ; USART HAS HIGH PRIORITY
    RCIE = 1    
    CREN = 1
    SPEN = 1




    ; Set up the TX side
                                                     
    SYNC = 0
    TXIP = 1                                                        ; TX Usart Int is high priority


    TXDataSent = True                                               ; We need to set this or we can never send back any data
   


; Set up and enable T0 as a 16 bit timer low interrpt enabled
    PSA = 1                                                         ; ASSIGN THE PRESCALER TO OSCILLATOR
    T0PS0 = 0                                                       ; NO PRESCALER
    T0PS1 = 0                                                       ; ON INCREMENT TMR0
    T0PS2 = 0                                               
    T0CS = 0                                                        ; ASSIGN TMR0 CLOCK TO INTERNAL SOURCE
   
    T08BIT = 0                                                      ; Enable 16bit mode
    INTCON2.2 = 0                                                   ; Tmr0 interrupt is low priority
    TMR0H = 0                                                       ; Clear tmr0 initially
    TMR0L = 0
    TMR0ON = 0
    TMR0IF = 0                                                      ; Clear the interrupt flag
    TMR0IE = 1                                                      ; Enable tmr0 overflow interrupt


; Get our node address
    OurNodeAddress = ERead NodeAddressPointer
   
   
; Turn on interrupts
    IPEN   = 1                                                      ; ENABLE INTERRUPT PRIORITY HANDLING                                               
    PEIE = 1
    GIE = 1   
   
    Return   
   
;*****************************************************
;
;  CRC calculation
;
;*****************************************************


CalcCRCRoutine:
  
    ModbusCRC = $FFFF
    CRCIndex1 = 0
    Repeat
        If CRCDirection = _In Then
            CRCTemp = DataInBuffer[CRCIndex1]
        Else
            CRCTemp = DataOutBuffer[CRCIndex1]
        EndIf
       
        ModbusCRC = ModbusCRC ^ CRCTemp      
        CRCIndex2 = 0
        Repeat
            If ModbusCRC.0 = 1 Then
                ModbusCRC = $A001 ^ (ModbusCRC >> 1)
            Else
                ModbusCRC = ModbusCRC >> 1
            EndIf
            Inc CRCIndex2
        Until CRCIndex2 > 7
        Inc CRCIndex1
    Until CRCIndex1 > CRCEndOfData
   
    CRC_OK = False
    If ModbusCRC = 0 Then
        CRC_OK = True
    EndIf
   
    Return




;*****************************************************
;
;  Sending a packet back, handles the crc and priming the int handler
;
;*****************************************************
  
  
   $define SendTXPacket (pTXEndOfData)          '
   TXEndOfData = pTXEndOfData                   '
   GoSub SendTXPacketSub
  
  
SendTXPacketSub:
    While TXDataSent = False                                        ; we cannot do any more unless the process is free to do so
        ClrWdTimr
        Nop
        Nop
    Wend
       
    CRCDirection = _Out                                             ; Point to the out buffer
    CRCEndOfData = TXEndOfData                                      ; Say were it ends
    CheckCRC
    Inc TXEndOfData
    DataOutBuffer[TXEndOfData] = ModbusCRC.Byte1
    Inc TXEndOfData
    DataOutBuffer[TXEndOfData] = ModbusCRC.Byte0   
   
    DataOutBufferPointer = 1                                        ; Set the pointer to 1 as we do not want to send the node address 2 x
    TXDataSent = False                                              ; all data has not been sent
   
    TXIE = 1                                                        ; Enable TX interrupts    
   
    TXREG = OurNodeAddress                                          ; Start by sending our node address to get the process going
    ; The rest should happen automatically
   
    Return




;*****************************************************
;
;  Check Address Range
;
;*****************************************************
   
    $define CheckAddressRange (pRegAddress, pNumberOfRegsToRead) '
    RegAddressTemp = pRegAddress                     '
    NumberOfRegsToReadTemp = pNumberOfRegsToRead.Byte0 '
    GoSub TestAddressRange


TestAddressRange:
    AddressRangeValid = True                                        ; Pre set the result to true
    Index1 = 0
    Repeat                                                          ; Loop through all the address's
        GoSub CheckAddressRangeSub
        If RegTest = False Then
            AddressRangeValid = False
            Break
        EndIf
        Inc Index1
        Inc RegAddressTemp                                          ; Incrument the reg no and the count
    Until Index1 >= NumberOfRegsToReadTemp
   
    Return
   


CheckAddressRangeSub:
   
    AddressTemp = Regs_Supported_Table                              ; Point to the lookup table  
    Index2 = 0
    Repeat                                                          ; loop
        TempW1 = CRead AddressTemp
        If TempW1 = $5555 Then
            RegTest = False
            Break
        EndIf
        If TempW1 = RegAddressTemp Then
            RegTest = True
            Break
        EndIf    
        Inc Index2
        AddressTemp = AddressTemp + 2                               ; Incrument the look up pointer
    Until Index2 > MaxNoregs                                        ; Safe guard to cover missing the end marker
    Return
   
;*****************************************************
;
;  Send register data
;
;*****************************************************


    $define ReadRegs (pCommandFunction,pRegAddress,pNumberOfRegsToRead)  '
    CommandFunctionTemp = pCommandFunction              '
    RegAddressTemp = pRegAddress                        '
    NumberOfRegsToReadTemp = pNumberOfRegsToRead.Byte0  '
    GoSub ReadRegsSub
    


ReadRegsSub:
    Index1 = 0
    DataOutBuffer[Index1] = OurNodeAddress                          ; Start with our node address
    Inc Index1
    DataOutBuffer[Index1] = CommandFunctionTemp                     ; Now the command asked of us
    Inc Index1
    TempB1 = NumberOfRegsToReadTemp * 2                             ; x by 2 as we are counting bytes
    DataOutBuffer[Index1] = TempB1                                  ; The number of bytes we are going to send
    Inc Index1
   
    DoMemRead = True                                                ; Pre condition the flag
      
    Select RegAddressTemp
    Case 0, 1, 2, 3, 6, 7, 15, 16, 17, 18
        RegAddressTemp = RegAddressTemp * 2                         ; As the data is in words and our address for eeprom is bytes we need to do this
        Index2 = 0                                                  ; Loop through the Eeprom
        Repeat
            TempW2 = ERead  RegAddressTemp                          ; read in the data from the Eeprom
            Inc RegAddressTemp
            Inc RegAddressTemp
            DataOutBuffer[Index1] = TempW2.Byte1                    ; Place the data in the buffer
            Inc Index1
            DataOutBuffer[Index1] = TempW2.Byte0                    ; Place the data in the buffer
            Inc Index1
            Inc Index2
        Until Index2 >= NumberOfRegsToReadTemp
        DoMemRead = False                                           ; As we have done all the array stuffing via eeprom we do not need the INDF stuff
             
    Case 101 To 108
        TempB1 = RegAddressTemp - 101
        FSR2Reg = VarPtr (Regs_100)   
       
    Case 111 To 118
        TempB1 = RegAddressTemp - 111
        FSR2Reg = VarPtr (Regs_110)
               
    Case 1001 To 1008
        TempB1 = RegAddressTemp - 1001
        FSR2Reg = VarPtr (Regs_1000)
       
    Case 2001 To 2016
        TempB1 = RegAddressTemp - 2001
        FSR2Reg = VarPtr (Regs_2000)       
       
    Case 3001 To 3016
        TempB1 = RegAddressTemp - 3001
        FSR2Reg = VarPtr (Regs_3000)
   
    Case 4000 To 4007
        TempB1 = RegAddressTemp - 4001
        FSR2Reg = VarPtr (Regs_4000)
       
    EndSelect  
   
    If DoMemRead = True Then   
        Index2 = 0
        Repeat
            TempW2.Byte1 = POSTINC2                                 ; Read the memory back and place the data in the BigEndian format
            TempW2.Byte0 = POSTINC2
            DataOutBuffer[Index1] = TempW2.Byte0                    ; Place the data in the buffer
            Inc Index1
            DataOutBuffer[Index1] = TempW2.Byte1                    ; Place the data in the buffer
            Inc Index1
            Inc Index2
        Until Index2 >= NumberOfRegsToReadTemp                      ; Loop until all the data has been sent
    EndIf  


    Dec Index1                                                      ; We need to reduce this or the pointer is in the wrong place
    SendTXPacket (Index1)                                           ; Now send the packet
    Return


;*****************************************************
;
;  Send an AK back
;
;*****************************************************




    $define SendWriteAck (pCommandFunction,pRegAddress,pRegData)   '   
    CommandFunctionTemp = pCommandFunction             '
    RegAddressTemp = pRegAddress                       '
    RegDataTemp = pRegData                             '
    GoSub SendAckSub
   


SendAckSub:
   
    Index1 = 0
    DataOutBuffer[Index1] = OurNodeAddress                          ; Start with out node address
    Inc Index1
    DataOutBuffer[Index1] = CommandFunctionTemp                     ; Now the command
    Inc Index1
    DataOutBuffer[Index1] = RegAddressTemp.Byte1                    ; and the address
    Inc Index1
    DataOutBuffer[Index1] = RegAddressTemp.Byte0                   
    Inc Index1
    DataOutBuffer[Index1] = RegDataTemp.Byte1                       ; and the Data
    Inc Index1
    DataOutBuffer[Index1] = RegDataTemp.Byte0                     
    SendTXPacket (Index1)                                           ; Now send the packet
   
    Return
   
;*****************************************************
;
;  Write to a register
;
;*****************************************************


    $define WriteReg (pRegAddress, pRegData)  '
    RegAddressTemp = pRegAddress              '
    RegDataTemp = pRegData                    '
    GoSub WriteRegSub
   


WriteRegSub:


    
    Select RegAddressTemp


    Case 0, 1, 2, 3, 6, 7, 15, 16, 17, 18
        RegAddressTemp = RegAddressTemp * 2
        EWrite RegAddressTemp, [RegDataTemp]
   
    Case 101 To 108
        TempB1 = RegAddressTemp - 101
        Regs_100[TempB1] = RegDataTemp    
   
    Case 111 To 118
        TempB1 = RegAddressTemp - 111
        Regs_110[TempB1] = RegDataTemp
       
       
    Case 1001 To 1008
        TempB1 = RegAddressTemp - 1001
        Regs_1000[TempB1] = RegDataTemp


       
    Case 2001 To 2016
        TempB1 = RegAddressTemp - 2001
        Regs_2000[TempB1] = RegDataTemp
       
       
    Case 3001 To 3016
        TempB1 = RegAddressTemp - 3001
        Regs_3000[TempB1] = RegDataTemp
   
    Case 4000 To 4007
        TempB1 = RegAddressTemp - 4001
        Regs_4000[TempB1] = RegDataTemp
       
    EndSelect
   
    Return


;*****************************************************
;
;  Return an error mesage
;
;*****************************************************


    $define ReturnError (pCommandFunction, pErrorCode)  '
    CommandFunctionTemp = pCommandFunction              '
    ErrorCode = pErrorCode                              '
    GoSub ReturnErrorSub   
   
ReturnErrorSub:
    CommandFunctionTemp.7 = 1                                       ; Set the upper bit to indicate an error


    Index1 = 0
    DataOutBuffer[Index1] = OurNodeAddress                          ; Start with out node address
    Inc Index1
    DataOutBuffer[Index1] = CommandFunctionTemp                     ; Now the command
    Inc Index1
    DataOutBuffer[Index1] = ErrorCode                               ; and the error code                    
    SendTXPacket (Index1)                                           ; Now send the packet
   
    Return   
   
;*****************************************************
;
;  Parse a packet
;
;*****************************************************




ParsePacketSub:
   
    CommandFunction = DataInBuffer[FuncLoc]                         ; Function
    RegAddress.Byte1 = DataInBuffer[RegAddressHigh]                 ; Read in the address
    RegAddress.Byte0 = DataInBuffer[RegAddressLow]
   
   
   
      
    Select CommandFunction                                          ; Now work on the request
    Case Read_Regs                                                  ; Read regs
        NumberOfRegsToRead.Byte1 = DataInBuffer[RegRangeHigh]
        NumberOfRegsToRead.Byte0 = DataInBuffer[RegRangeLow]
        CheckAddressRange (RegAddress,NumberOfRegsToRead )          ; Test if this reg address range is ok
        If AddressRangeValid = True Then
            ReadRegs (CommandFunction,RegAddress,NumberOfRegsToRead)
        Else
            ReturnError (CommandFunction,NotValidRange)
        EndIf
    Case Write_Reg                                                  ; Write reg
        RegData.Byte1 = DataInBuffer[RegDataHigh]
        RegData.Byte0 = DataInBuffer[RegDatalow]
        CheckAddressRange (RegAddress,0 )                           ; Test if this reg address range is ok
        If AddressRangeValid = True Then
            WriteReg (RegAddress,RegData)
            SendWriteAck (CommandFunction,RegAddress,RegData)
        Else
            ReturnError (CommandFunction,NotValidRange)
        EndIf      
    Case Else
            ReturnError (CommandFunction,NotValidCommand)        
    EndSelect 


   
    Return
   
   
Restart_Routine:
    While 1 = 1
        Reset
    Wend 
   


;*****************************************************
;
;  Timer Routines
;
;*****************************************************




    $define SetTimer (pTimerNumber,pValue)      '   
    TimerNumber = pTimerNumber                  '
    TimerValue = pValue                         '
    GoSub SetTimerSub
   
   
SetTimerSub:
    If TimerNumber > No_Timers Then
        Return
    EndIf
   
    TempB1 = TimerNumber * 2
    TimerPointer = VarPtr (Hz10Timers)
    TimerPointer = TimerPointer + TempB1
    
    DisableInts
    FSR2Reg = TimerPointer
    POSTINC2 = TimerValue.Byte0
    POSTINC2 = TimerValue.Byte1
    EnableInts
   
    Return




;********************************************************************


   
    $define HasTimerStopped (pTimerNumber,pStatus)  '
    TimerNumber = pTimerNumber                      '
    GoSub TestTimerFor0Sub                          '
    pStatus = WREG
   
TestTimerFor0Sub:


    TempB1 = TimerNumber * 2
    TimerPointer = VarPtr (Hz10Timers)
    TimerPointer = TimerPointer + TempB1
    
    DisableInts
    FSR2Reg = TimerPointer
    TimerValue.Byte0 = POSTINC2
    TimerValue.Byte1 = POSTINC2
    EnableInts
   
    If TimerValue = 0 Then
        WREG = 1
    Else
        WREG = 0
    EndIf
   
    Return




;********************************************************************


   
    $define ReadTimerValue (pTimerNumber,pStatus)  '
    TimerNumber = pTimerNumber                  '
    GoSub ReadTimerSub                         '
    pStatus = TimerValue
   
ReadTimerSub:


    TempB1 = TimerNumber * 2
    TimerPointer = VarPtr (Hz10Timers)
    TimerPointer = TimerPointer + TempB1
    
    DisableInts
    FSR2Reg = TimerPointer
    TimerValue.Byte0 = POSTINC2
    TimerValue.Byte1 = POSTINC2
    EnableInts
   
    Return
   
   
   
;*****************************************************
;
;  PerformSysMaintenance
;
;*****************************************************


    $define PerformSysMaintenance GoSub PerformSysMaintenanceSub
   
       
PerformSysMaintenanceSub:
    If InBufferDataReady = True Then
        InBufferDataReady = False
        CRCDirection = _In
        CRCEndOfData = RXEndOfData
        CheckCRC
        If CRC_OK = True Then
            ParsePacket
        EndIf
     EndIf
    
    
     Return   


           


     
_OverSubs:
    Clear
    GoSub InitialiseCore