element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      • Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Sci Fi Your Pi
  • Challenges & Projects
  • Design Challenges
  • Sci Fi Your Pi
  • More
  • Cancel
Sci Fi Your Pi
Blog Escape the Past: DF1 Communications
  • Blog
  • Forum
  • Documents
  • Files
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: frellwan
  • Date Created: 31 Jul 2015 2:53 AM Date Created
  • Views 1628 views
  • Likes 2 likes
  • Comments 0 comments
  • escape_the_past
Related
Recommended

Escape the Past: DF1 Communications

frellwan
frellwan
31 Jul 2015

This week I was able to get the DF1 protocol operational. It is not a compete implementation of the protocol, but it implements read and write operations for data transfer to/from a SLC 500 processor.


RS-232 communication

As mentioned in a previous post I am using a USB to RS232(Product LinkProduct Link) cable to be able to communicate to a SLC 500 PLC. The data sheet can be found here http://www.farnell.com/datasheets/814051.pdf. I had to make up a 9 pin (DB9) Female connector to connect to the PLC.

 

The cable pin out can be seen below:


image


The TXD on the cable is connected to the RXD (pin 2) on the SLC and the RXD on the cable is connected to the TXD (pin 3) on the SLC. The GND on the cable is connected to the GND (pin 5) on the SLC. Once connected it works very well. The USB side of the cable has 2 LED's - a red for transmit and a green for receive. So it is very easy to see when signal transmission is happening. I have been very pleased with the performance of the cable.

 

 

DF1 Protocol

I am communicating to an Allen Bradley SLC500 PLC using the DF1 protocol over a  RS-232RS-232 serial connection The protocol can be found in the DF1 Protocol and Command Set Reference Manual http://literature.rockwellautomation.com/idc/groups/literature/documents/rm/1770-rm516_-en-p.pdf). I have found this to not be a complete description of all of the commands.

 

A typical command looks like this:

 

image

 

 

The protocol uses variable length packets that, with the exception of a couple of commands, start with standard header information of DEST, SRC, CMD, STS, and TNS. SRC and DEST are self explanatory, identifying the address of the device sending the command and the address of the device the message is being sent to. The DF1 protocol has many different CMD’s, but the ones I have programmed are in the ‘0F’ family – Protected Read and Write. The STS is an error code – really only intended for response packets, so set to 0 for commands. The TNS is a unique identifier for each message. The way I have implemented the protocol is to start with TNS of 1 and increment until it reaches 0xFFFF and then start over again.

 

Application specific data includes the protocol function. For reads and writes, the ADDR field is used to identify the data register to read/write to (e.g. N7:30) The SIZE byte specifies how many bytes to read/write. The DATA field is used when writing data to a register(s) and includes the data to be written.

 

The DATA field is delineated along word boundaries (16 bits/2 bytes). The Data must be encoded in ‘Little Endian’ format, where the low byte  resides in bits 8-15 and the high byte resides in bits 0-7. The Raspberry Pi stores data in ‘Big Endian’ format where the high byte resides in bits 8-15 and the low byte resides in bits 0-7. This requires some manipulation of the DATA bytes before sending.

 

The struct module, which allows strings to be interpreted as packed binary data, offers a simple way to do this.

struct.pack('<h', self.Data)

 

where ‘<’ represents encoding in Little Endian format and the ‘h’ represents a signed integer (16 bits)


Response Packet

The handshaking that goes on requires the PLC to 'ACK'nowledge that it has received the command and a short while later it will send the response message. The response message looks very similar to the command message:

image

 

The SRC and DEST bytes are reversed in the response packet - because the PC is now the source and the Pi is the destination. The STS byte will be 0 unless there is an error and then it will hold the error byte. The TNS word will match to the TNS of the command packet. Read commands will have a response with the data requested. Again this data is encoded in Little Endian format.

 

After the message has been successfully received by the Pi, the Pi needs to 'ACK'nowledge the response. This completes the message transfer process.

 

Strings

ST file types took me a little while to figure out. When you read a ST file type, the data returned is 82 bytes, regardless of the length of the actual string. If the string is less than 82 bytes, the string is padded with \x00’s. What took me a while to figure out was that the string data is returned in Little Endian format (every 2 characters are swapped). So if you had a string ‘HELLO WORLD’ it would come across as ‘EHLL OOWLR D’. Once I figured this out, I was able to write code to account for this. It turns out that the first 2 bytes have the length of the string (in Little Endian format of course), so this can be used to eliminate the \x00’s.

 

I imagine that other non purely numeric files (BCD, MSG, PD) are similar, but I have not tested these yet.

 

Code

The code for this portion of the project will be posted at  https://github.com/frellwan/SciFy-Pi.git in the Serial/df1 folder.

 

As with the RS422 communications, I have borrowed heavily from the pymodbus code. This package is well proven and seemed like a good starting point. The serialport instantiating is very simple:

 

options = utilities.Options()
config = SafeConfigParser()
config.read([options['config']])

reader  = LoggingLineWriter(config.get('FTP', 'localLogDir'))

# Create the FTP client
FTPhost = config.get('FTP', 'host')
FTPport = config.getint('FTP', 'port')
ftpEndpoint = TCP4ClientEndpoint(reactor, FTPhost, FTPport)


factory = DF1Factory(reader, ftpEndpoint)
RS232port = config.get('RS-232', 'host')
RS232baud = config.getint('RS-232', 'baudrate')

SerialDF1Client(factory, RS232port, reactor, baudrate = RS232baud)

serialLog.debug("Starting the client")

reactor.run()

 

 

 

At this point the DF1Protocol is now responsible for encoding and sending the packets to the PLC. While I have coded both the encode and decode methods, I am only showing the encode method here. This is the only method I will be using. The decode would be for the case that the PLC was sending a command request to the Pi - I suppose if you needed the Pi to emulate a PLC or to act as a bridge between multiple PLC's.

 

class protectedReadRequest(PDU):
    '''
    Base class for reading a PLC register
    '''
    _frame_size = 18
    cmd = 0x0F
    function = 0xA2

    def __init__(self, dest, parameter, size=1, packet='', src=0, **kwargs):
        ''' Initializes a new instance

        :param address: The destination PLC address
        :param parameter: The PLC address to read
        :param size: The number of elements to read
        :param packet: Used when we already have an encoded packet
        '''
        PDU.__init__(self, **kwargs)
        self.src = src
        self.dest = dest
        self.parameter = parameter
        self.sts = 0x00
        self.size = size
        self.Address = utilities.calcAddress(parameter)
        self.packet = packet
    
    def encode(self):
        ''' Encodes the request packet

        :return: The encoded packet
        '''
        if (not self.skip_encode):
            ############################
            # Packet Start Sequence
            ############################
            self.packet = struct.pack('>BB', 0x10, 0x02)        #DLE STX

            ############################
            # Packet Header Information
            ############################
            data = struct.pack('>BBBBHB', self.dest,
                                          self.src,
                                          self.cmd,
                                          self.sts,
                                          self.transaction_id,
                                          self.function)
            if (self.Address.subElement > 0):
                elementSize = SUBELEMENT_SIZE[self.Address.fileType]*self.size
            else:
                elementSize = ELEMENT_SIZE[self.Address.fileType]*self.size
            
            data += struct.pack('>B', elementSize)

            ###################################################
            # Packet Address Information
            # Note: Use Little Endian format if using 2 bytes
            ###################################################
            if (self.Address.fileNumber > 254):
                data += struct.pack('>B', 0xFF)
                data += struct.pack('<H', self.Address.fileNumber)

            else:
                data += struct.pack('>B', self.Address.fileNumber)

            data += struct.pack('>B', self.Address.fileType)

            if (self.Address.eleNumber > 254):
                data += struct.pack('>B', 0xFF)
                data += struct.pack('<H', self.Address.eleNumber)
            else:
                data += struct.pack('>B', self.Address.eleNumber)

            if (self.Address.subElement > 254):
                data += struct.pack('>B', 0xFF)
                data += struct.pack('<H', self.Address.subElement)
            else:
                data += struct.pack('>B', self.Address.subElement)

            #######################################
            # Calculate CRC before escaping DLE's
            #######################################
            crc = utilities.computeCRC(data)

            ####################################
            # Escape any DLE's ('\x10') in data
            ####################################
            start = 0
            while (data.find('\x10', start, len(data)) != -1):
                i = data.find('\x10', start, len(data))
                data = data[:i] + '\x10' + data[i:]
                start = i+2

            self.packet += data

            ###################################
            # Packet End
            ###################################
            self.packet += struct.pack('>BB', 0x10,0x03)        #DLE ETX
            self.packet += struct.pack('>H', crc)
        else:
            self.packet = packet

        return self.packet


    def __str__(self):
        ''' Returns a string representation of the instance

        :returns: A string representation of the instance
        '''
        return "DataInquiryRequest (%s,%s)" % (self.dest, self.parameter)

 

More to come ....

  • Sign in to reply
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube