' ' -----[ Program Description ]-------------------------------------------------- ' ' This program implements the ModBus RTU protocol in BASCOM universal I/O device ' It uses STK200 development kit for testing purpouse. ' It's a polled slave. ' ' -----[ Disclaimer ]----------------------------------------------------------- ' ' This example is offered on an "AS IS" basis, no warranty expressed or implied. ' The programers disclaim liability of any damages associated with the use of ' the hardware or software described herein. ' You use it on your own risk. We are not able to provide any free support. ' ' Copyright (c) 2001 Mike Eitel all rights reserved ' ' -----[ Revision History ]----------------------------------------------------- ' ' 011121 - Version 1.00AVR Ver 1.00 slave AVR >8515 Mike Eitel ' 011128 - Version 1.10AVR Ver 1.10 slave AVR 2313 Mike Eitel ' 011221 - Version AVRMODDIO16 Ver 1.20 slave AVR 2313 ERAM Mike Eitel ' ' -----[ Aliases ]-------------------------------------------------------------- $regfile = "2313def.dat" ' Define serial communication parameters $baud = 9600 '$crystal = 4000000 ' Frequency of STK 200 'Ubrr = 25 ' For 9600 baud $crystal = 8000000 ' Frequency of fast STK 200 Ubrr = 51 ' For 9600 baud ' ' -----[ Constants ]------------------------------------------------------------ ' ' Telegramms solved ' This device is only capable of 2 bytes data transfer (03) (06) !!! ' Request Read Holding Register(03) SS 03 RH RL PH PL CL CH ' Answer Read Holding Register(03) SS 03 BC 1H 1L CL CH ' ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ' DB( 1 2 3 4 5 6 7 8 ) ' ' Request Preset Single Register(06) SS 06 RH RL 1H 1L CL CH ' Answer Preset Single Register(06) SS 06 RH RL 1H 1L CL CH ' ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ' DB( 1 2 3 4 5 6 7 8 ) ' ' ' ' SS = Slave Address ' ' 03 = Command 03 ' ' RH = Starting Address High ' ' RL = Starting Address Low ' ' PH = Nr. of Points High ' ' PL = Points Nr.(16 bits) Low ' ' CL = CRC Low ' ' CH = CRC High ' ' BC = Byte Count ' ' 1H = Data 1 High ' ' 1L = Data 1 Low ' 'Const Myaddress = &H01 ' Address for this node (1-253) Const Initializing = &HFE ' Address to change Myaddress Const Dataarea = 10 ' Databuffer + 2 Slaveparameters Const Maxchar = 10 ' Nr. of characters for serial buffer Const Telegrambytes = 10 ' Nr. of transmitted Bytes Const Crcpoly = &HA001 ' Modbus CRC-Polynom Const Pause = 2 ' Wait time befor answer ' Pseudo constants programmed via telegram Dim Myaddress As Eram Byte At &H02 ' Address for this node (2-253) Dim Myparameter As Eram Byte At &H03 ' Address for this node (2-253) ' ' -----[ Variables ]------------------------------------------------------------ ' Dim I As Byte ' Helper for looping Dim K As Byte ' Helper for looping Dim Datas(dataarea) As Byte ' Place to keep the dynamic datas Dim Bytebuffer(maxchar) As Byte ' Receive serial-buffer Dim Db(telegrambytes) As Byte ' Datas in telegramm Dim Sendbytes As Byte ' Number of transmitted Bytes Dim Sdbcounter As Byte ' Lenght of send telegram Dim Sdbcountertmp As Byte ' Helper for send telegram 'Dim Senderbit As Byte ' Helper Byte for bit 6 of portd Dim Bytesreceived As Byte ' Signalling several received characters Dim Bytecounter As Byte ' Counter of received characters Dim Bytereader As Byte ' Counter of transformed characters Dim Crc As Word ' CRC word in communication Dim Crc_in As Byte ' The to be calculated value Dim Crc_low_ok As Bit ' Low CRC value is the same Dim Crc_high_ok As Bit ' Low CRC value is the same Dim Crc_ok As Bit ' CRC value is the same Dim Lastbyte As Byte ' Last Lowbyte of the inputs Dim Multiplexa As Byte ' Multiplexcounter 16In- & 16Outputs 'Dim Multiplexb As Byte ' Helper for multiplexer Dim Multiplexc As Byte ' Helper pointer for fast I/O reaction Dim Multiplexd As Byte ' Helper for fast I/O reaction ' -----[ Initialization ]--------------------------------------------- ' ' Define port parameters ' Port B Is Used For Data Exchange With The Latches 'Ddrb = &HFF ' WRITE to multiple I/O Data port 'Ddrb = &H00 ' READ from multiple I/O Data port ' Port D is used for communication and for latch controll Ddrd = &B01111100 ' Set Portd Pin 2..6 as Outputs ' Ddrd.0 ' RXD ' Ddrd.1 ' TXD ' Ddrd.2 ' Strobe Enable Read chip 1 ' Ddrd.3 ' Strobe Enable Read chip 2 ' Ddrd.4 ' Strobe output latches Allegro UCN 5801A 1 ' Ddrd.5 ' Strobe output latches Allegro UCN 5801A 1 ' Ddrd.6 ' Enable for RS422 send pin ' Ddrd.7 ' NC for 2313 Portd = &B00001100 ' Setup interrupts to defined startup Ucr = &B11011000 ' Allow receive & transmit ISR ' Enable Utxc ' Disable send isr ' Enable Urxc ' Enable receive isr ' Disable Udre ' Enable empty send buffer isr On Urxc Rec_isr ' Define serial receive ISR On Utxc Send_isr ' Define serial send ISR On Udre Ude_isr Nosave ' Define empty serial buffer ISR Enable Serial ' Allow protokoll treatment starts Config Timer0 = Timer , Prescale = 64 ' Define update speed On Timer0 Timer0_isr ' Define ISR when timer 0 overflows ' Enable Timer0 ' Allow I/O treating ' Define startup values after Powerup For I = 1 To 44 ' Dammed trick to clear all vars Datas(i) = &H00 ' Preset them to 0 Next I ' Disregarding boundary checks ' Transfer from ERAM slave address and parameters K = Dataarea ' Take last data cell Datas(k) = Myaddress ' Set new Slave address Decr K ' Find slave parameters cell Datas(k) = Myparameter ' Set new Slave parameters Enable Interrupts ' Start now the system timing '------[ Program ]---------------------------------------------------- ' _com_z: ' Startingpoint for telegrams If Bytesreceived >= 1 Then ' Received something Bytereader = Bytecounter - Bytesreceived Incr Bytereader Decr Bytesreceived Db(1) = Bytebuffer(bytereader) ' transfer to receive telegram buffer ' If received address is this node read next Byte from master, ' if not return to start If Db(1) <> Datas(dataarea) Then ' Normal address If Db(1) <> Initializing Then ' Address to change Node Nr. Goto _com_z ' Wait for next incoming data End If End If ' Start CRC check routine and calculate CRC for all received bytes Crc = &HFFFF ' Initialize as demanded Crc_in = Db(1) ' Two lines to call up ----- Gosub _calc_crc ' ----- the checksum Else Goto _com_z ' Wait for next incoming data End If _com_1: If Bytesreceived >= 1 Then ' Received something Incr Bytereader Decr Bytesreceived If Bytereader >= Maxchar Then ' Buffer full = generate unread message Bytereader = 1 ' Rewrap character serial counter End If Db(2) = Bytebuffer(bytereader) ' transfer into receive telegram buffer ' CRC check routine and calculate CRC for all received bytes Crc_in = Db(2) ' Two lines to call up ----- Gosub _calc_crc ' ----- the checksum Else Goto _com_1 ' Wait for next incoming data End If _com_2: ' Get telegram into binary mode If Bytesreceived < 6 Then Goto _com_2 ' Wait for next incoming data End If For I = 3 To 8 Incr Bytereader If Bytereader >= Maxchar Then ' Buffer full = generate unread message Bytereader = 1 ' Rewrap character serial counter End If Db(i) = Bytebuffer(bytereader) ' Data(x) transfer to memory ' CRC check routine and calculate CRC for all received bytes If I <= Bytesreceived Then Crc_in = Db(i) ' Two lines to call up ----- Gosub _calc_crc ' ----- the checksum End If Next I ' Clear the receive buffer to make a calculated response possible Bytecounter = 1 ' Reset for next telegram Bytesreceived = 0 ' Reset for next telegram ' Check the CRC of the message for errors If Bytebuffer(bytereader) = High(crc)then ' First the last byte Crc_high_ok = 1 ' 1.Check passed Else Crc_high_ok = 0 ' 1.Check failed End If Decr Bytereader ' Go one byte back If Bytebuffer(bytereader) = Low(crc)then ' Then the low byte Crc_low_ok = 1 ' 2.Check passed Else Crc_low_ok = 0 ' 2.Check failed End If Crc_ok = Crc_low_ok And Crc_high_ok ' Check both bytes If Crc_ok = 0 Then ' If any CRC errors then Goto _com_z ' Restart undone End If ' Else go on _com_4: ' No CRC errors in packet so check what to do and start reading / writing Select Case Db(2) ' Decide read or write Case 03 : ' READING datas 1..n ' Request Read Holding Register(03) SS 03 RH RL PH PL CL CH ' Answer Read Holding Register(03) SS 03 BC 1H 1L CL CH ' ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ' DB( 1 2 3 4 5 6 7 8 ) ' Db(3) = &H02 ' Answer with 2 byte for BC Sendbytes = 5 ' ST,CO-mmand plus 5 nibbles K = Db(4) ' Data start register Incr K ' Modbus addaption ..1 = first Db(4) = Datas(k) ' Reading first Incr K ' Next byte Db(5) = Datas(k) ' Reading second Gosub _send ' Give answer Goto _com_z ' All done, go back to Start Case 06 : ' WRITING datas 1..n ' Request Preset Single Register(06) SS 06 RH RL 1H 1L CL CH ' Answer Preset Single Register(06) SS 06 RH RL 1H 1L CL CH ' ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ' DB( 1 2 3 4 5 6 7 8 ) ' Sendbytes = 6 ' ST,COmmand plus 6 nibbles K = Db(4) ' Data start register Incr K ' Modbus addaption ..1 = first Datas(k) = Db(5) ' Reading first Incr K ' Next byte Datas(k) = Db(6) ' Reading second If Db(1) = Initializing Then ' Test for general access If K = Dataarea Then ' Set Parameters to EERAM Myparameter = Db(5) ' Set new Slave parameters If Db(6) < 253 Then ' Usefull address < 253 If Db(6) > 1 Then ' Address starts at 2 Myaddress = Db(6) ' Set new Slave address End If ' End address test high End If ' End address test low End If ' End Test parameters setting End If ' End initialisating address Gosub _send ' Give answer Goto _com_z ' All done, go back to Start End Select Goto _com_z ' Not identified command End ' End of the main program ' -----[ End ]-------------------------------------------------------- ' -----[ Subroutines ]------------------------------------------------ ' ' Subroutine for calculating CRC value in variable Temp2 _calc_crc: Crc = Crc Xor Crc_in ' Do the first XOR For K = 0 To 7 ' Loop for all bits Rotate Crc , Right ' Rotate to win the low bit If Crc > &H7FFF Then ' LSB bit was found a "1" shiftet Crc = Crc And &H7FFF ' Delete the shiftet LSB Crc = Crc Xor Crcpoly ' Polynominal value XOR ' Else ' LSB bit was found a "0" shiftet End If Next K ' LOW Byte = first in telegram !! Return ' Subroutine for sending values _send: ' Start CRC generate routine and calculate CRC for all sending bytes Crc = &HFFFF ' Initialize as demanded For I = 1 To Sendbytes Crc_in = Db(i) ' Two lines to call up----- Gosub _calc_crc ' ----- the checksum Next I Incr Sendbytes ' Take the next Db(sendbytes) = Low(crc) ' First the low byte Incr Sendbytes ' take the next Db(sendbytes) = High(crc) ' First the high byte Incr Sendbytes ' Increment for send buffer size 'Send Packet To Master , Including The Sync Byte _again: If Sdbcounter < 1 Then ' Safety loop, case of to much sending Set Portd.6 ' Set switch to send modus pin ' Senderbit = &H40 ' Set bit for portd.6 in timer0 cycle Sdbcounter = Sendbytes - 2 ' Set buffer to full lenght Waitms Pause ' Switch over time Udr = Db(1) ' Start ISR work by 1.Byte sending Else Goto _again ' Safety loop should never run End If Return ' -----[ Interrupt routines ]------------------------------------------------ ' Interrupt routine for preparing serial port ' On powerup it's treated once ! ' This routine should not be needed but only with this routine AVR works fine ! Ude_isr: Ucr = &B11011000 ' Number of character flag Return ' Interrupt routine for reading serial input Rec_isr: If Bytecounter < Maxchar Then ' Does it fit into the buffer? Incr Bytecounter ' Increase buffer counter Else Bytecounter = 1 ' Safety rewrap character serial counter End If Bytebuffer(bytecounter) = Udr ' Add to buffer array Incr Bytesreceived Return ' Interrupt routine for sendig serial output Send_isr: If Sdbcounter > 0 Then ' (0) = Dirty trick to see buffer size ? Sdbcountertmp = Sendbytes - Sdbcounter Udr = Db(sdbcountertmp) ' Increase buffer counter Decr Sdbcounter Else Reset Portd.6 ' Clean switch to send modus pin ' Senderbit = &H00 ' Clear bit for portd.6 in timer0 cycle End If Return ' Interrupt routine for multiplexing In / Outputs Timer0_isr: ' Controlls the multiplexing----- ' ---- of data versus I/O's ' Port D is used for communication and for latch controll ' Ddrd.2 ' Strobe Enable Read chip 1 ' Ddrd.3 ' Strobe Enable Read chip 2 ' Ddrd.4 ' Strobe output latches Allegro UCN 5801A 1 ' Ddrd.5 ' Strobe output latches Allegro UCN 5801A 1 ' Ddrd.6 ' Enable for RS422 send pin Select Case Multiplexa Case 0: ' READ 1 --- High Byte --- Reset Portd.2 ' Set gate of first reading chip ' Ddrb = &HFF ' Switch WRITE multiplex I/O Data ' Portb = Datas(1) ' Write data to multipexer I/O Ddrb = &H00 ' Switch READ multiplex I/O Data Datas(1) = Pinb ' Read data from multipexer I/O Case 1: ' READ 2 --- Low Byte --- Reset Portd.3 ' Set gate of second reading chip ' Ddrb = &HFF ' Switch WRITE multiplex I/O Data ' Portb = Datas(2) ' Write data to multipexer I/O Ddrb = &H00 ' Switch READ multiplex I/O Data Datas(2) = Pinb ' Read data from multipexer I/O Multiplexc = Datas(2) And Myparameter ' Check only wished pins If Lastbyte <> Multiplexc Then Lastbyte = Multiplexc ' Store modified value Incr Datas(8) ' Counter low of checked pins If Datas(8) = 0 Then Incr Datas(7) ' Counter high of checked pins End If ' End second byte End If ' End counting Case 2: ' WRITE 3 --- High Byte --- If Datas(dataarea) = &HFF Then ' Test outputs when unprogrammed Datas(3) = Datas(1) ' Increment the one byte Datas(4) = Datas(2) ' Decrement the other byte End If Ddrb = &HFF ' Switch WRITE multiplex I/O Data ' If db(5) is requesting high input pin, force output high. When ' input reaches high level forcing is surpressed. Generally output ' is OR overwriten high by db(3) Multiplexc = Not Datas(1) ' Check input pins for high status Multiplexd = Datas(5) And Multiplexc ' Compare demand and Portb = Multiplexd Or Datas(3) ' set. Transfer the data to I/O Set Portd.4 ' Set strobe for first writing chip Case 3: ' WRITE 4 --- Low Byte --- Ddrb = &HFF ' Switch WRITE multiplex I/O Data ' If db(6) is requesting high input pin, force output high. When ' input reaches high level forcing is surpressed. Generally output ' is OR overwriten high by db(4) Multiplexc = Not Datas(2) ' Check input pins for high status Multiplexd = Datas(6) And Multiplexc ' Compare demand and Portb = Multiplexd Or Datas(4) ' set. Transfer the data to I/O Set Portd.5 ' Set strobe for second writing chip End Select Incr Multiplexa ' Cycle counter for the datas 1..4 Multiplexa = Multiplexa And &H03 ' To cut down cycle counter to 0 ..3 Set Portd.2 ' Set bit for first reading chip Set Portd.3 ' Set bit for second reading chip Reset Portd.4 ' Set bit for first writing chip Reset Portd.5 ' Set bit for second writing chip Return