1 Architectural Overview



Table of Contents

1 Architectural Overview 1

1.1 CODE Architecture 2

1.2 Profibus Fieldbus Architecture 3

1.2.1 Profibus-DP Card 4

1.3 Introduction to WAGO I/O Modules 4

1.4 CODE to Profibus Interface 6

1.5 User Procedures 10

1.5.1 Step 1 - Configure Profibus-DP Card 10

1.5.2 Step 2 – Design and Develop CODE Signal Table 12

1.5.3 Step 3 - Develop Client Control Processes 12

1.5.4 Step 4 - Develop Customized Device Driver 12

2 Driver Functional Capabilities 13

2.1 CODE Functional I/O Capabilities 13

2.2 Profibus Functional Capabilities 14

2.2.1 Profibus-DP Communication Modes 14

2.2.2 Profibus-DP Functional I/O Capabilities and API Functions Supported 14

2.3 Matching CODE and Profibus-DP I/O Signal Tables 20

3 Device Driver Organization 23

3.1 Software Organization 23

3.1.1 Visual C++ Project Structure 23

3.1.2 Building a Custom Executable 24

3.2 Interface Organization 24

3.2.1 Interface Procedures 25

3.2.2 CODE user_cntrl.c Functions 26

3.2.3 CODE Signal Table 28

3.2.4 CODE Client process 32

3.2.5 Interrupt Device Driver 33

3.2.6 Device Driver Threading 34

4 Implementation Tasks 35

4.1 Header Files, Constants and Parameters 36

4.2 profibus_signal_drvr_func() Implementation 39

4.3 profibus_signal_func() Implementation 41

4.3.1 profibus_signal_func Private Functions 45

4.3.1.1 Private Function: GetInputBit() 45

4.3.1.2 Private Function: ReadGroupInput() 46

4.3.1.3 Private Function: ClearOutputBit() 48

4.3.1.4 Private Function: SetOutputBit() 50

4.3.1.5 Private Function: WriteGroupOutput() 51

4.4 profibus_send_cmd() Implementation 54

4.5 Other Private Functions 58

4.6 Verification Tests 62

Architectural Overview

In this project CODE (Cimetrix Open Development Environment) is customized to integrate a Profibus-DP I/O card software device driver. CODE’s software architecture is designed to control distributed mechanisms and devices. This document provides specifications that will be implemented in the device driver and also reviews the system architecture.

Chapter 1 provides an architectural overview of the device driver interface to the Profibus-DP card. Chapter 2 describes which functional I/O capabilities are implemented on both the CODE and Profibus sides. Chapter 3 describes the PB driver implementation details, including how the project is organized in Visual C++ 4.2, the API (Application Programming Interface) functions that are implemented, and the necessary resources that are used, such as header files, constants, parameters, etc. Chapter 4 is dedicated to further defining the architecture, data structures, and pseudo-code that will be implemented in the driver. In addition, a set of tests will be defined to test the developed driver.

The card is provided by Synergetic - Model SMS-CIF30-DPM. The device driver acts as a software command interface between CODE and the DP card operating system (which runs independently on the DP card). By reading from and writing to dual-port memory (DPM) on the DP card, the device driver can configure, set, and get I/O values from various I/O devices which are distributed over the Profibus network. Since we are implementing the Profibus-DP I/O card only, these modules are primarily connected to sensors, valves, etc. The system topology is shown in Figure 1.1.

In Figure 1.1, the CODE system is customized to “attach” a Profibus-DP device driver to the CIMServer. Client control processes in CODE communicate with the Profibus-DP card by calling Profibus-specific I/O using CODE I/O API (Application Programming Interface) library functions. Figure 1.1 shows two CODE API functions (CxGetSignal() and CxSetSignal()) commonly used in client processes for I/O data exchange. Once these functions are called, the client process redirects the data exchange request to the CODE CIMServer, which then passes the request through the device driver. The device driver then calls Profibus API functions to read from or write to the card DPM.

The Profibus API set is a set of C/C++ functions provided with the Synergetic DP card software that can be embedded into a user-customizable software interface provided with CIMServer. Once compiled and linked to make a customized CIMServer version, CODE client processes can directly communicate with the Profibus-DP card for I/O data exchange. The following sections provide more detail concerning the device driver organization.

1 CODE Architecture

CODE is an acronym that stands for the Cimetrix Open Development Environment. CODE consists of the following software products:

• CIMulation - product that allows you to simulate and control manufacturing tasks using a graphical engineering workstation, such as a PC. It differs from CIMControl by providing an animated simulation of the workcell being controlled.

• CIMControl - on-line counterpart to CIMulation. It allows the equipment in an automated workcell to be controlled using a standard PC.

• CODE API Libraries - consists of approximately 400 functions, which can be used to develop automated manufacturing applications. Applications developed with these libraries will function in either CIMulation or CIMControl without modification. The API set is accessible in standard C/C++, Microsoft Visual C++, Microsoft Visual Basic, Borland Delphi, and through CIMBuilder-Tcl/Tk.

• CIMTools - Software application that allows an automated workcell to be modeled and controlled. It provides interfaces for creating geometrical models, interference checking, motion monitoring, software teach pendant, application project control, cycle time prediction, and I/O signal panel.

CODE software is based on client-server architecture – see Figure 1.1. CIMServer is CODE’s central server control program. Each CODE client program is organized as a set of function calls to monitor and control simulated or actual physical processes. These may range from the control of mechanisms, vision systems, or distributed I/O. Each client function call encapsulates its data (send data or request data) into message packets that communicate with CIMServer using Inter-Process Communication (IPC) methods.

CIMServer’s open architecture provides software "hooks" whereby users can integrate custom software interfaces to mechanism and I/O controllers. The user-modifiable interface that can be customized for mechanism control and I/O is included in a project workspace file. This software interface will be described in full detail later.

2 Profibus Fieldbus Architecture

The purpose of this document is to help the user understand the development process of creating a device driver for an I/O interface card of type Profibus-DP, manufactured by Hilscher Ggesellschaft fur Systemautomation mbH.

Profibus is one of the new generations of fieldbus I/O bus networks. Fieldbus is a generic term that describes a new digital communications network that is being used in industry to replace the existing 4-20 mA analog signal standard. The network is a digital, bi-directional, multidrop, serial-bus communication network used to link isolated field devices, such as controllers, transducers, actuators and sensors. Bi-directional means it is a duplex port; the data can be transmitted in two directions at the same time. Multidrop is also referred to as multi-access and it can be interpreted as a single bus with many nodes connected to it. Serial-bus means the data is transmitted serially according to RS232 or RS485 protocol. Profibus uses RS485 protocol.

The fieldbus has a multitude of advantages that end-users will benefit from. The major advantage is that it is a bus network and not a star network, which will reduce both configuration time and maintenance. It also improves systems performance, since fieldbus networks perform at higher baud rates.

Profibus is a vendor-independent, open fieldbus standard for a wide range of applications in manufacturing, and process automation. Devices configured by different manufacturers can communicate without special interface adjustments. Profibus can be used for both high-speed, time-critical data transmission and extensive complex communication tasks - see Figure 1.2. The Profibus family consists of three compatible versions: Profibus-DP, Profibus-PA and Profibus-FMS.

• Profibus-DP - Optimized for high speed and inexpensive hookup. DP version is designed especially for communication between automation control systems and distributed I/O at the device level. Can be used to replace parallel signal transmission with 24 V or 0 to 20 mA. This is the only card that will be supported in this specification document.

• Profibus-PA - Designed especially for process automation. It permits sensors and actuators to be connected on one common bus line even in intrinsically safe areas. Permits data communication and power over the bus using 2-wire technology according to the international standard IEC 1158-2. Will not be implemented in this spec because only I/O will be controlled.

• Profibus-FMS - General-purpose solution for communication tasks at the cell level. Powerful FMS services open up a wide range of applications and provide great flexibility. Can also be used for extensive and complex communication tasks. Will not be implemented in this spec because CODE provides much of the same function.

1 Profibus-DP Card

Profibus-DP is designed for high-speed data communication at the device level. The main purpose of this project is to provide reliable high-speed communication between the CODE system and I/O modules. Profibus DP meets these objectives, so the device driver is developed only for the Profibus-DP card. The DP API function overview is given in Section 1.4.

3 Introduction to WAGO I/O Modules

The I/O modules used in this project are the products of WAGO Electronics. Figure 1.3 shows a simple Wago I/O module connecting two proximity sensors. Wago modules are intelligent, fieldbus-independent terminal blocks designed for the decentralized automation of manufacturing and production equipment.

Figure 1.4 shows a 2-channel digital input module. The digital input module receives signals from digital field devices (sensors, etc). The physical connection of the WAGO module is simple as shown in Figures 1.3 and 1.4. The middle two wires are for the sensor power supply. The other two wires are signal input/output and signal ground.

The fieldbus coupler for Profibus-DP connects the WAGO I/O modules as a slave to the Profibus-DP card – see Figure 1.5. The bus coupler is capable of supporting all bus modules and automatically transfers the data to/from mixed I/O modules to the process image.

4 CODE to Profibus Interface

Control processes developed under the architecture of Figure 1.1 require the development of client process applications, which interact with the CODE server. The server processes the messages and requests from the client process. The Profibus-DP environment provides a set of API functions that are integrated into the compiled device driver that provide for the communication with the Profibus –DP card. These API functions allow for direct access to the card function. They are responsible for initializing and configuring the card, as well as the writing and reading of data to the I/O modules (setting and getting individual I/O bits/bytes).

The CODE I/O interface has been implemented using CODE’s open architecture interface for implementing custom device drivers. To see how CODE communicates with the Profibus-DP card, review the basic flow chart in Figure 1.6 and the following brief description bullets:

• CODE client control processes are application programs developed by the user. When these processes are executed (run), I/O functions will be called to set/get signal values. Some of these signals may be software signals used for coordinating several client processes and some may be hardware signals which are be directed to the WAGO I/O modules. The CIMServer controls the communication to these processes and provides the interpretation services for the client processes.

• The user customizes the device-driver by modifying the user_cntrl.c software interface provided by CODE. A customized CIMServer is made when the custom software interface is compiled and linked. This device driver then establishes the interface between the client processes and the Profibus-DP card.

• A set of C/C++ API functions comes with the Profibus-DP card, providing the functionality necessary to initialize and monitor the card and to exchange data through the dual-port memory (DPM) on the card.

• The DP card uses an embedded, real-time operating systems to cyclically scan the I/O modules, read the DPM, and write to the DPM. Thus, the device driver purpose is to write to these memory addresses and read from these memory addresses. There is some configuration initialization required, both for the DP card and for the CODE signal table. These will be described in greater detail later.

The Profibus API’s are defined in a source file that came with the DP card, called interface.cpp. This file contains function definitions. Table 1.1 classifies these functions; some support the PA and FMS function and will not be implemented in this device driver.

Table 1.1 – Profibus DP API Functions

|Profibus API’s |Function |

|DevOpenDriver() |Implemented – check if the board device driver is available and opens |

| |a link to it. |

|DevInitBoard() |Implemented – used for board initialization. |

|DevCloseDriver |Implemented –application calls this function to close an opened link |

| |to card device driver. |

|DevExitBoard() |Implemented – to end the communication, the application program calls |

| |this function for each board opened previously by DevInitBoard(). |

|DevExchangeIO() |Implemented – used for data I/O exchange. |

|DevSetHostState() |Implemented – used to signal card that CODE application is not |

| |running. |

|DevGetInfo() |Implemented – used to access board parameters and state in one large |

| |request. |

|DevPutMessage() |Not implemented – used for PA and FMS capabilities; not implemented in|

| |this driver. |

|DevGetMessage() |Not implemented – used for PA and FMS capabilities; not implemented in|

| |this driver. |

|DevGetBoardInfo() |Not implemented – used to get board information such as IRQ number, |

| |physical memory address. This information can be requested in CODE |

| |client process using the send command capabilities. |

|DevReadSendData() |Not implemented – used to read back data which has been set to the |

| |card using DevExchangeIO(). It can be used to verify that data has |

| |been written correctly and also used to detect errors on the Profibus |

| |card. |

|DevReset() |Not implemented – resets the communication board when called. |

|DevPutTaskParameter() |Not implemented – used for PA and FMS capabilities; not implemented in|

| |this driver. |

|DevGetTaskState() |Not implemented – used for PA and FMS capabilities; not implemented in|

| |this driver. |

|DevTriggerWatchDog() |Not implemented – not using a watchdog timer in device driver. |

|DevGetMBXState() |Not implemented – used for PA and FMS capabilities; not implemented in|

| |this driver. |

The CODE signal table is a user-defined file. It has specific formats and it contains the detailed data configuration for each signal that will be used in the CODE system, and then translated to the correct DP format. A signal is valid only after it is defined. Once the server locates the signal type in the signal table, it will determine if the signal is an external (hardware) I/O signal. If so, the CODE server will redirect the message data from the client process to the Profibus-DP card through the device driver. The driver is developed to manage the communications between the Profibus-DP card and the I/O modules and is based on two API function sets: CODE API’s and Profibus-DP API’s.

The way the card is configured is straightforward. The card scans the bus network and thus reads the input and writes the output on I/O devices connected to the network, in this case the WAGO modules. Since the DPM read and write is in the scanning loop, the card operating system (OS) will read the DPM or write to the DPM the current I/O state. The CODE device driver determines the current signal state by using DevExchangeIO(), a Profibus-DP API function provided with the Profibus-DP card. DevExchangeIO() is compiled into the device driver that forms the customized CIMServer, and then is used to write I/O to the card DPM and read I/O from the card DPM.

The I/O devices/modules are assigned addresses at specified offsets from the base address of the card. For example, the base address of a card might be set to 000H (H stands for hexadecimal format), which becomes the beginning address for the 512 bytes of DPM that stores the data sent from the CODE application to the Profibus-DP card – see Figure 1.7.

Although an I/O module typically provides for either 2 or 4 bit access at a particular offset location, we will implement 8 bit (1 byte) offsets to simplify the device driver architecture. There are 512 bytes for output data, where SndPd refers to the space reserved for Send Process Data, which is the output data sent from the host application to the Profibus-DP card. Figure 1.7 also shows that in the 2K Profibus-DP DPM, there are 512 bytes reserved for input data, where RecvPd refers to Receive Process Data, which is the input data sent from the Profibus-DP card to the host application. Using 1 byte offsets, there is enough memory for 512 inputs and 512 outputs, if only single byte I/O is used.

5 User Procedures

There are four individually defined steps that will link a Profibus DP card to a CODE process application:

• Step 1 - Configure Profibus DP card

• Step 2 – Design and develop CODE signal table.

• Step 3 - Develop client control processes.

• Step 4 - Develop customized device driver to link CODE and the Profibus DP card.

1 Step 1 - Configure Profibus-DP Card

This step is accomplished by using a system configurator that comes with the Profibus card. The user adds master and slave modules using an add-device option. The master module is one or more Profibus-DP cards, while the slave devices are the Wago fieldbus coupler modules (WAGO I/O SYSTEM DP/FMS – 750-303) which can be interfaced to A/D and I/O modular devices. Other options enable the user to select the type of the device and the number of each device that is needed.

Other options enable the user to select the device type and the number of each device that is needed. Devices can be configured for 8, 16, 24, and up to 128 bits input or output when the I/O requires more complex data exchange. Our application requires simple 2 and 4 bit modules (which will be mapped to 1 byte or 2 byte address space). We will also use some analog modules (requiring 4 bytes) for RS232 communications.

The modules can be selected from a list by the user double clicking on a slave symbol (such as WAGO I/O SYSTEM DP/FMS 12MBaud) and then selecting the Configuration data button. Individual modules are added to the device list by selecting the module and next clicking on the Add module button. The I Addr column is where the input modules (RECEIVE Data) are assigned memory locations. The O Addr column is where the output modules (SEND Data) are assigned memory locations.

The Profibus configuration file defines the memory assignment for each physical I/O module. Similarly, the physical signals are also described in the CODE signal table. Obviously, the memory assignment in the configuration file must be compatible with the corresponding signal definition in the CODE signal table.

The CODE signal table contains fields that are used by the user to match the offsets specified in the Profibus configurator tables. As an example, Figure 1.8 shows the InBase and OutBase fields used to specify the address offsets in the CODE signal table for simple single bit input and output signals. In the Profibus configurator the A/D modules that require 4 bytes each (or 2 words as shown in the table) must be ordered first, followed by simple I/O modules. The field ”Type” has the value -1 for simple digital I/O and a value equal to the A/D module number of a Wago interface module when an analog module such as 75060 is used for RS232 interfacing. Fields 6 – 10 are not used in the CODE signal table.

There are constraints on the way the memory is configured. The first part of memory is reserved for analog I/O followed by digital I/O. Each WAGO I/O module uses a certain number of bits, regardless of whether each bit is physically wired to a terminal. The number of bits varies for each module type.

Additional system parameters need to be set as described in the system configuration manual. Once the configuration setup is completed, the configuration is downloaded to the card where it can be debugged and tested.

2 Step 2 – Design and Develop CODE Signal Table

The CODE signal table represents the logical mapping of CODE process control signals to the physical I/O module hardware signals processed by the Profibus-DP card. These signals are actually individual bits that are turned on and off as the Profibus operating system scans the I/O modules and the DPM. Of significance are the send process data and receive process data memory areas, each of which is 512 bytes. The signals defined in the signal table are assigned to specific offset locations within the dual port memory (DPM), which in turn represent individual bits on the I/O modules. The signal table also identifies if the signal is input and/or output, if the signal is to be initialized by the server, how the specific data relates to the hardware configuration, etc.

3 Step 3 - Develop Client Control Processes

To control the numerous workcell devices, including the cell I/O, the user must develop source code client programs (client processes) that implement CODE API function calls. These control programs are developed under Visual C++, Version 4.2. The source code includes I/O events, mechanism motion functions, and other control functions as described in Volume 1 of the CODE documentation in Events, States and I/O.

After the client programs have been developed, compiled and linked, each client program (also referred to as a client process) can be started and then connected to a CODE server that is also running concurrently. After connecting to the server, the clients can then exchange control commands in the form of data packets with the server. The server processes I/O messages through the Profibus device driver interface, which uses the supplied Profibus-DP API functions to read from and write to the DP card DPM.

4 Step 4 - Develop Customized Device Driver

Once the device driver is completed, the user will not need to apply this step again. It will only be necessary to configure the CODE signal table and the Profibus DP card consistently. The purpose of this document is to develop and document such a device driver for the Profibus-DP I/O card.

Driver Functional Capabilities

These sections are used to describe which functional I/O capabilities are implemented on both the CODE and Profibus sides, and also to specify which I/O capabilities are not supported and why.

1 CODE Functional I/O Capabilities

This section describes which CODE functional I/O capabilities are implemented and also specify which are not supported and why.

Table 2.1 lists the CODE I/O cases that will be supported by the Profibus-DP driver. These cases are passed as a constant (long) argument through a CODE interface function call in the device driver. These values are also defined by the constant names shown in the left column of the table. The specific signal data information is passed through data structures in these same interface functions and will be discussed in later sections.

Table 2.2 lists the cases that will not be supported. Although the Profibus-DP card is not set up to monitor signals and provide interrupts, we will implement signal monitoring by internal polling.

Table 2.1 – CODE I/O Cases Supported

|Cases Supported |Explanation |

|CX_CNTRL_OPEN_SIGNAL_TABLE |Used to initialize the signal table on an I/O controller, the Profibus|

| |DP card in this case (set signal values in DPM). |

|CX_CNTRL_CLOSE_SIGNAL_TABLE |Used to close the connection to a signal table on an I/O controller, |

| |the Profibus DP card in this case. |

|CX_CNTRL_INITIALIZE_SIGNAL |Used to initialize the state of a hardware signal in the Profibus card|

| |signal table (set or write a signal value in DPM). |

|CX_CNTRL_GET_SIGNAL_VALUE |Used to get the value of a hardware signal from the Profibus card |

| |signal table (read DPM to get a signal value). |

|CX_CNTRL_SET_SIGNAL_VALUE |Used to set the value of a hardware signal in the Profibus card signal|

| |table (set or write a signal value in DPM). |

Table 2.2 – CODE I/O Cases Not Supported

|Cases not Supported |Explanation |

|CX_CNTRL_SETUP_SIGNAL_MONITORING |Not implemented - Defines how a controller or I/O card will monitor |

| |the state of a given signal, and how the I/O card will return the |

| |state when the signal changes value. Will use the polling mode for |

| |signal monitoring.. |

|CX_CNTRL_MONITOR_SIGNALS |Not implemented - CIMServer’s polling mode will be used since the |

| |Profibus driver software cannot provide an interrupt upon a signal |

| |attaining a certain value. |

|CX_CNTRL_GET_SIGNAL_CNTRL |Not implemented - Used to get specific I/O information that is |

| |implemented by the Profibus-DP card, but not implemented in CODE. Uses|

| |a CODE pass-through data structure to pass card-specific information |

| |through to the client process. |

|CX_CNTRL_SET_LATCH |Not implemented since Profibus-DP card does not support latching of |

| |joint encoder values. |

2 Profibus Functional Capabilities

The following sub-sections describe the supported communication mode with the Profibus-DP card, and the I/O capabilities and API functions that will be implemented.

1 Profibus-DP Communication Modes

Profibus-DP supports five communication modes for exchanging data between a host application (CODE client process) and the interface modules (WAGO) that are networked (linked) to the RS485 bus network. The four not implemented are identified in Table 2.3. We will support the user controlled, buffered data transfer communication mode. This mode allows the user application to directly control the data exchange by implementing a corresponding Profibus DP API function and by using a locally defined data buffer. In addition, the user can use event objects to synchronize the communication process when writer and reader threads are executing concurrently. This mode permits the CIMServer to communicate with the CIF card when requested by a CODE client process, rather than have the card assume control. We will not support the other four modes for the reasons listed in Table 2.3.

2 Profibus-DP Functional I/O Capabilities and API Functions Supported

The Profibus card is supported by a set of API functions that ease the burden of communicating with the board, and assist in data exchange. Some of these functions are not applicable for Profibus-DP data exchange and will not be implemented – see Table 1.1.

The implemented functions are defined in interface.cpp (source code for the Profibus-DP driver) and listed in Table 2.4. Table 2.5 describes some functions that will probably be implemented in the future. The Profibus card has the capability to support interrupt communications by properly setting the board IRQ. The setting of the IRQ for the board is achieved through editing of the configuration data for the board done in the registry (i.e., setting of the base address and the interrupt request for each board).

Table 2.3 –Communication Modes not Implemented

|Communication Modes |Explanation |

|Bus synchronous, device controlled |In this mode the CIF card starts a data exchange cycle by itself if it is master or |

|communication with a process image |receives a data exchange cycle if it is slave. In each valid bus cycle the device |

| |exchanges data with the DPM. The end of the data exchange is indicated when the |

| |operating system on the CIF card inverts a bit in a memory cell. In response to an |

| |interrupt, the user program complements a bit in a different memory cell. When these |

| |handshake bits are not equal at the end of each bus cycle (CIMServer device driver |

| |interface to Profibus-DP card), the CIF card grants access to both DPM data buffers, |

| |allowing reading of input data or writing of output data to the DPM. In the next bus |

| |cycle the CIMServer device driver must perform a data-exchange cycle and invert a bit|

| |in a memory cell, so both handshaking bits from the communication memory cells are |

| |equal. |

| |In this mode the CIF controls the communication and the user application (device |

| |driver) must be able to handle the handshake in interrupt and not in polling mode. We|

| |have decide to let CIMServer assume on-demand communication control rather than the |

| |card. |

|CIF controlled, buffered data transfer |This mode is similar to the previous mode except internal buffers are used to store |

| |the DPM data in the DPM in bus cycle. Again, we choose to have CIMServer control the |

| |I/O process. |

|Uncontrolled direct data transfer |In this mode the user application (CIMServer device driver) and the Profibus-DP |

| |operating system all read and write to the DPM simultaneously. Since there is no |

| |synchronization capability, this mode is not implemented. |

|User controlled, direct data transfer bus |The start point of each bus cycle is fixed by the user application. After the |

|synchronous |initialization of both buffer areas (send and receive process data areas), the user |

| |application starts each bus cycle by inverting a bit in a memory cell and obtaining |

| |access to the DPM. It then sets another bit to relinquish control back to the CIF |

| |card. This mode guarantees the fastest exchange of process data, because a bus cycle |

| |is activated after each data delivery in the DPM by the user application. So the |

| |faster the user application works, the faster the bus cycles are triggered. |

| |This mode requires user control over the timing of the bus cycle. The input/output |

| |data needs to be processed continuously with low-level bit-wise manipulation |

| |capabilities. Therefore, it will not be implemented. |

Table 2.4 – Profibus API Implemented Functions

|Implemented PB Functions |Description |

| |This function must be called first. It checks if the device driver is available and |

|short DevOpenDriver( |opens a link to it. Once this link is established, all other functions can be used. |

|unsigned short usDevNumber ) |usDevNumber represents the board number 0,..,3. This function is designed for |

| |interrupt mode. |

| |Function arguments: |

| |usDevNumber – board number 0,..,3 |

| |Return Values: |

| |DRV_NO_ERROR - No error occurred, device driver is available. |

| |DRV_USR_OPEN_ERROR - Device driver is not available. |

| |After an application has opened a link to the device driver, it must call that |

|short DevInitBoard( |function, before it can start with the communication. The function tells the device |

|unsigned short usDevNumber, |driver that an application wants to communicate with a defined board. The device |

|void pDevAddress ) |driver checks to see if the board is physically available, if the board works |

| |properly and setup all the internal state flags for the addressed board. pDevAddress |

| |is a pointer to the physical board address. |

| |Function arguments: |

| |usDevNumber – board number 0,..,3 |

| |pDevAddress – pointer to the board’s address |

| |Return Values: |

| |DRV_NO_ERROR - no error occurred, device driver is available. |

| |DRV_USR_NOT_INITIALIZED - device driver is not available. |

| |DRV_USR_DEV_NUMBER_INVALID - board number is not allowed and miscellaneous driver |

| |errors - see error description. |

| |The application (CODE device driver interface) calls this function to close an opened|

|short DevCloseDriver( |link to a card device driver. The function is designed for interrupt mode where |

|unsigned short usDevNumber ) |multiple cards are present. |

| |Function arguments: |

| |usDevNumber – board number 0,..,3 |

| |Return Values: |

| |DRV_NO_ERROR - no error occurred, device driver is available. |

| |DRV_USR_OPEN_ERROR - device driver is not available. |

| |If an application wants to end communication it calls DevExitBoard() for each board |

|short DevExitBoard( |which has been opened by a previous call to DevInitBoard(). |

|unsigned short usDevNumber ) |Function arguments: |

| |usDevNumber – board number 0,..,3 |

| |Return Values: |

| |DRV_NO_ERROR - no error occurred, device driver is available. |

| |DRV_USR_NOT_INITIALIZED - device driver is not available. |

| |DRV_USR_DEV_NUMBER_INVALID - board number is not allowed and miscellaneous driver |

| |errors: See chapter error numbers. |

| |Used to signal the DP board that a user application is running or not. |

|short DevSetHostState( |Function arguments: |

|unsigned short usDevNumber, |usDevNumber – board number 0,..,3 |

|unsigned short usMode, |usMode – 0 – HOST_NOT_READY |

|unsigned long ulTimeout ) |1 – HOST_READY |

| | |

| |usTimeout – timeout in milliseconds |

| |Return Values: |

| |DRV_NO_ERROR - no error occurred, device driver is available. |

| |DRV_USR_NOT_INITIALIZED - device driver is not available. |

| |DRV_USR_DEV_NUMBER_INVALID - board number is not allowed and miscellaneous driver |

| |errors: See chapter error numbers. |

| |DRV_USR_MODE_INVALID – mode parameter not allowed. |

| |DevExchangeIO() function is used to send IO data to and read IO data from a |

|short DevExchangeIO( |communication board. The function is able to send and read IO data at once. If one of|

|unsigned short usDevNumber, unsigned short |the size parameter is set to zero, no action will be taken for the corresponding |

|usSendOffset, unsigned short usSendSize, |function. This means, if usSendSize is set to zero, send data will not be written to |

|void *pvSendData, |the board ( the data will not be written to the 512- byte memory space – send process|

|unsigned short usReceiveOffset, unsigned |data, SndPd ). If usReceiveSize is set to zero, receive data will not be read from |

|short usReceiveSize, |the board. The input data from the bus will not be written to the 512-byte memory |

|void *pvReceiveData, |space – receive process data, RecvPd. |

|unsigned long ulTimeout ) |Function arguments: |

| |usDevNumber – board number 0..3 |

| |usSendOffset – byte offset in the send IO data area of the communication board |

| |usSendSize – length of the send IO data |

| |pvSendData - pointer to the user send data buffer |

| |usReceiveOffset – byte offset in ther receive IO data area of the communication board|

| |usReceiveSize – length of the received IO data |

| |pvReceiveData – pointer to the user read data buffer |

| |ulTimeout – timeout in milliseconds, 0=no timeout |

| |Return Values: |

| |DRV_NO_ERROR - No error occurred, device driver is available. |

| |DRV_USR_NOT_INITIALIZED - device driver is not available. |

| |DRV_USR_DEV_NUMBER_INVALID – invalid board number. |

| |DRV_USR_SENDBUF_PTR_NULL - pointer to buffer is a null pointer. |

| |DRV_USR_RECVBUF_PTR_NULL - pointer to buffer is a null pointer. |

| |DRV_USR_SENDSIZE_TOO_LONG - SendSize parameter too long. |

| |DRV_USR_RECVSIZE_TOO_LONG - ReceiveSize parameter too long or miscellaneous driver |

| |error. |

| |With DevGetInfo() function the user can read the DPM by information area. The area of|

|short DevGetInfo( |interest here is GET_IO_SEND_DATA. |

|unsigned short usDevNumber, unsigned short |Function arguments: |

|usSize, |usDevNumber – board number 0..3 |

|void* pvData) |usInfoArea – information area specified by a logical name |

| |usSize – size of the user’s data buffer and length of data to be read |

| |pvData – pointer to the user’s data buffer |

| |Return Values: |

| |DRV_NO_ERROR - no error occurred, device driver is available. |

| |DRV_USR_INFO_AREA_INVALID – Parameter InfoArea invalid |

| |DRV_USR_NOT_INITIALIZED - device driver not available. |

| |DRV_USR_MSG_BUF_NULL_PTR - pointer to the user’s data buffer is a NULL pointer. |

| |DRV_USR_SIZE_TOO_LONG - size of user data buffer exceeds 512 K |

Table 2.5 – Profibus API Functions for Future Implementation

|Future Implemention |Description |

| |With DevGetBoardInfo() function the user can read global information of all |

|short DevGetBoardInfo( |communication boards that the device driver is aware of. The user interface offers |

|unsigned short usDevNumber, unsigned short |to the user a data structure, which describes the board information data. The |

|usSize, |function copies the number of data, given in the parameter usSize. |

|void* pvData) |Function arguments: |

| |usDevNumber – board number 0..3 |

| |usSize – size of the user’s data buffer and length of data to be read |

| |pvData – pointer to the user’s data buffer |

| |Return Values: |

| |DRV_NO_ERROR - no error occurred, device driver is available. |

| |DRV_USR_NOT_INITIALIZED - device driver not available. |

| |DRV_USR_MSG_BUF_NULL_PTR - pointer to the user’s data buffer is a NULL pointer. |

| |DRV_USR_SIZE_INVALID - size of user data buffer does not correspond to the expected |

| |size or miscellaneous driver error. |

| |The function causes a reset of a communication board. The function argument usMode |

|short DevReset( |toggles the reset between a COLDSTART or WARMSTART. The amount of the timeout |

|unsigned short usDevNumber, unsigned short |ulTimeout depends on the protocol and reset mode. A COLDSTART needs a longer time |

|usMode, unsigned long ulTimeout) |than a WARMSTART, because a complete hardware check will be made by the device |

| |operating system. The time for a COLDSTART is between 3 and 10 seconds, a WARMSTART |

| |needs between 2 and 8 seconds. The reset of the board will occur whenever there is a |

| |malfunction in the system’s I/O. The bit statuses on the WAGO modules will be |

| |initialized after reset with signal values obtained from the process’s signal table. |

| |Function arguments: |

| |UsDevNumber – board number 0..3 |

| |usMode – 2 = COLDSTART, |

| |3 = WARMSTART |

| |ulTimeout – timeout value for the reset to complete |

| |Return Values: |

| |DRV_NO_ERROR - no error occurred, device driver is available. |

| |DRV_USR_NOT_INITIALIZED - device driver is not available. |

| |DRV_USR_DEV_NUMBER_INVALID – invalid board number. |

| |DRV_USR_MODE_INVALID – invalid mode parameter or miscellaneous driver error. |

| |Function is used for verification and validation of the data which are written to the|

|short DevReadSendData( |send data area with the function DevExchangeIO(). It reads back the sent data in |

|unsigned short usDevNumber, unsigned short |order to perform the verification. The function can be used by the application |

|usOffset, |program to update the user input, after the data are successfully written to the |

|unsigned short usSize, |communication board. |

|void* pvSendData) |Function arguments: |

| |usDevNumber – board number 0..3 |

| |usOffset – byte offset in the send I/O data area of the communication board |

| |usSize – length of the send I/O data to be read. |

| |pvData – pointer to the user data buffer. |

| |Return Values: |

| |DRV_NO_ERROR - no error occurred, device driver is available. |

| |DRV_USR_NOT_INITIALIZED - device driver not available. |

| |DRV_USR_DEV_NUMBER_INVALID – invalid board number. |

| |DRV_USR_BUF_PTR_NULL – pointer to buffer NULL pointer |

| |DRV_USR_SIZE_TOO_LONG – SendSize parameter too long or miscellaneous driver error. |

3 Matching CODE and Profibus-DP I/O Signal Tables

The DPM on the Profibus-DP card is configured initially to support a library of I/O devices. The selection of I/O modules that are being implemented in the current I/O configuration is done using system configurator software. By double clicking on the added I/O slave device and selecting the configuration data button, the user can add a number of input and/or output modules which can extend over an address space of from 8 bits to 128 bits relative to an address offset. These offsets are specified for both the input signals and the output signals and represent a memory offset relative to the base addressof the DPM. The physical hardware signals are then mapped into these addresses.

Since the configurator only permits the specifying of relative address space of 8 bits to 128 bits, 2 and 4-bit hardware I/O modules must fit within the relative address space specified in the configurator table. Figure 2.1 shows how a 4-bit input module and two 2-bit modules would be configured at an offset (I Addr) of 16B (B for bytes), when a length of 8 bits is specified in the configurator, Figure 2.2.

Figure 2.1 shows how the 8 physical input bits map to the memory address bits in the PB DPM. Figure 2.2 shows an excerpt of the CODE signal table in which three input signals (GRIPPER_ATTACHED, PART_HERE, PART_GONE) map to the first three hardware bits of the 4-bit hardware module.

In this case, byte 17 is not used. The Type is specified as W for word (2 bytes) or as B for byte. IW stands for input word type and QW stands for output word type, with similar notation for input and output byte type. The 4B A/D modules shown in the configurator are not shown in the CODE signal table excerpt.

Each hardware signal defined in the signal table contains fields that access hardware bits in the current I/O configuration. The DPM locations are specified at offsets and at relative bits within the signal table file as defined in Table 2.6.

Table 2.6 – Relationship between CODE Signal Table and Profibus Configurator

|Signal Table Field Definitions |Description/Relation to Profibus Signals |

|use_init |Indicates whether or not CIMServer will initialize a hardware output signal to |

| |the value specified in the adjacent field: |

| |0 = CIMServer does not initialize the value, |

| |1 = CIMServer initializes the hardware signal with the value |

| |specified in the adjacent field. |

| |Initialization takes place through a Profibus API function to write the initial |

| |value to the I/O modules which are configured to match the defined hardware |

| |signal. |

|init_value |Represents the integer value which will be written to the I/O modules configured |

| |in PB-DP to match the defined CODE hardware signal. This value must not exceed |

| |what is possible by the number of bits specified for the signal (see Width). |

|InBase |Field 1 - This field represents the DPM base address for an input signal. IOnum |

| |and Width are then used to move to the correct bits assigned to the signal. |

|OutBase |Field 2 - This field represents the DPM base address for an output signal. IOnum |

| |and Width are then used to move to the correct bits assigned to the signal. |

|Type |Field 3 – This field will have a –1 value if simple I/O, but will have a value of|

| |750650 if analog signal used for RS232 communications. |

|IOnum |Field 4 – Represents the offset I/O location for simple I/O relative to the base |

| |address. Will have value of –1 if analog I/O of Type 750650. |

|Width |Field 5 - This field specifies the number of bits allocated to this signal. For |

| |example, if 4 bits are specified, the signal value must not exceed 24 = 16. |

|Fields 6 -10 |Not used |

Device Driver Organization

These sections describe the details of the PB driver implementation, how the project is organized in Visual C++ 4.2, the functions that are implemented, the necessary resources that are used, header files, constants, parameters, etc.

1 Software Organization

This section identifies the project organization and resources used.

1 Visual C++ Project Structure

Visual C++ 4.2 is the basic development environment used to create client processes. A customized version of CIMServer will be created that provides a software interface to the Profibus-DP I/O card. CODE supports compatibility with this version of Visual C++ and with Microsoft Developer’s Studio. The main project workspace is CIMControl (runtime control version). Adding software function that supports device drivers for different controllers expands this project workspace. In this particular case we reconfigure the project workspace by linking the file CIFNTDLL.LIB provided with the Profibus-DP card into the project workspace. In addition, the dynamic link library CIFNTDLL.DLL must be placed in a path where the application can reach it.

The files in Table 3.1, including the one Profibus-DP source file developed by the user under the name profibus.cpp, now represent the total project workspace. Once compiled and linked, the new custom CIMControl (or CIMulation if the user wishes to see screen animation of the cell) executable will include the code sufficient to read/write I/O to the Profibus-DP card.

The include files and header files are located in the folder Dependencies and are shown in Table 3.2. A number of these include header files related to other device drivers, and one provided with the Profibus card, CIFUSER.H

| |Table 3.2 - Include |and Header Files | |

|autodh.h |matx_defs.h |rob_to_pcm.h |tree_node.h |

|basic_msg_const.h |motion.h |rob_to_pdx.h |TYPES.H |

|basic_msg_defs.h |msg_defs.h |rob_to_pmac.h |user_cntrl.h |

|CIFUSER.H |msg_types.h |rob_to_yscxm.h robconst.h |user_defs.h |

|cimulation.ico |OacErrno.h |roberrno.h |user_kin.CK |

|cntr_const.h |profibus.h |robot.h |user_kin.FK |

|const.h |ProfibusErrors.h |robsignal.h |user_kin.IK |

|error.h |rob_globs.h |robstruct.h |user_kin.NK |

|fb_const.h |rob_to_ac28.h |RobToOac.h |user_kin_funcs.h |

|force_traj.h |rob_todt2815.h |s_func.h |user_motion.AJ |

|fwrdkin.h |rob_to_dt2821.h |SendStruct.h |user_motion.FG |

|invkin.h |rob_to_ibs.h |server.h |user_motion.h |

|ipc.h |rob_to_icb104.h |sigcalls.h |user_motion.JA |

|ipc_io.h |rob_to_ipc.h |software_drvr.h |user_motion.JM |

|joint.h |rob_to_mx100.h |starttree.h |user_motion.VT |

|matrix.h |rob_to_pcl722.h |tnt.h | |

2 Building a Custom Executable

To build an initial executable CIMControl (or CIMulation), the user needs to perform the following steps. The character sequence Option1 → Option2 refers to selecting from the menu Option1 to get Option2. Option1 → Option2 → Option3 refers to a multiple selection sequence.

Step 1 - Select File → Open Workspace → cimcontrol.mak or cimulation.mak to choose the appropriate make file. The make file automatically creates and opens a workspace, establishes initial dependencies, etc.

Step 2 - When new drivers are added to the server, if there are any #include statements in the *.cpp or *.c files added to the project, the user must select Build → Update All Dependencies to automatically update the include dependencies.

Step 3 - In addition, the user must select Tool → Options → Directories so that the user can specify the directory paths where the header files are located. This useful option saves the user the trouble of specifying the path of each header file. For example the file profibus.cpp includes the following include file:

#include

The path to the folder where include files are located may be different from the path where the executable was originally located. By specifying: these paths in Tool → Options → Directories → Directories, the compiler and the preprocessor knows how to build the *.obj, *.pdb and *.sbr files. Files ending in sbr extension contain browsing information which keep track of the functions, variables, pointers, data structure and etc. When right clicking the mouse on any identifier and selecting Go To Definition of ‘identifier’, the user can view its definition. Files ending in pdb contain debugging information, created by selecting Debug → Settings → Link → Generate debug info. For detailed information, see Visual C++ manuals.

2 Interface Organization

This section describes the user_cntrl.c interface provided by CODE, and how it is to be modified for the device driver to the Profibus-DP card that incorporates the Profibus API functions. The CODE signal table provides the data formatting and signal initialization required for CODE I/O. CIMServer then stores this information in data structures which are passed to user developed functions in user_cntrl.c. These functions must be written to reformat the data for I/O exchange with the Profibus card through the Profibus API functions provided with the card.

1 Interface Procedures

The procedures basically include the following steps. These are described in the Customizing CODE reference manual, in Chapter 3 – Controller Interface Functions. The following steps do not describe the detailed organization of each user-defined routine. These steps describe how these routines are integrated into user_cntrl.c, without providing all the directory paths and other details. Again, see the CODE manuals for the exact details.

Step 1 – Identify a unique defined constant and logical name for the Profibus driver. This number must be greater than 1000 (numbers less than 1000 are reserved by Cimetrix). We will select

#define profibus 1006

Add this defined constant to the supplied file cntr_const.h.

Step 2 – Identify the prefix name for the user-defined interface functions to be described further in Section 3.2.2 and shown in Table 3.3. These functions provide the function names under which the driver will be built.

Step 3 – Identify the number of controller types. Note that user_cntrl.c integrates a data structure controller_instance into its organization so that an array of these controller instances can be expanded (using array enumeration) to incorporate (and point to) the correct user-supplied function through the generic function calls provided in user_cntrl.c. The user must identify the number of controller types that will be enumerated in the controller_instance array and place the following statement before the array enumeration:

#define CX_NUM_CONTROLLER “place number here”

The number of controllers must include the software signals in this number. The user simply must then add to the controller_instance array the names of the user-defined functions at the top of the user_cntrl.c file under the unique defined constant profibus. The user must identify a command line argument for an # ifdef include directive so that only those drivers that are active in the cell will be included at compile time. We will use the directive USE_profibus and add the user functions under the # ifdef, e.g.,

# ifdef USE_profibus

“specify user defined functions”

# else

“specify NULL”

# endif

The array listing should now assume the format (the first two NULL’s outside the ifdef mean that this I/O controller does not support any motion commands):

#define CX_NUM_CONTROLLER “place number here”

controller_instance controller_funclist[CX_NUM_CONTROLLER] ={

.

.

{

profibus,

NULL,

NULL,

#ifdef USE_profibus

profibus _signal_func,

profibus _signal_drvr_func,

profibus _send_cmd

#else

NULL,

NULL,

NULL

#endif

},

.

.

}

Step 4 – The user will usually build one or more include/header files that are unique to his/her interface functions. These include files should be unique and also added to the top of user_cntrl.c, again using an #ifdef and the command line argument USE_profibus .

Step 5 – The user now develops a source file expanded in the user functions defined in Section 3.1.2. This interface file constitutes the driver interface that processes the various CIMServer cases, reformats the data that is being passed back and forth between the CIMServer and the Profibus card through the CIMServer data structures and the Profibus API calls. This file is integrated to make a custom CIMServer by compiling and linking to make a new executable.

NOTE: If the user chooses to use the MFC (Microsoft Foundation Class) library, the interface functions provided must be compiled as a C++ executable. This means that the extensions to software_drvr.c, threadmain.c, user_cntrl.c, user_kin.c, and user_motion.c must be changed to “cpp”, i.e., software_drvr.cpp, threadmain.cpp, user_cntrl.cpp, user_kin.cpp, and user_motion.cpp.

2 CODE user_cntrl.c Functions

The interface file user_cntrl.c contains several routines (or functions) which the user can modify to customize a device driver. Some of these are applicable to motion control, and some to I/O control. We refer to the three that are applicable to the Profibus I/O driver in Table 3.3. Note that the xxxx refers to a user chosen prefix for these functions for which we have chosen the prefix “profibus”.

Table 3.3 – I/O user_cntrl.c Functions

|Function |Description |

|xxxx_signal_func() |Implements specific I/O signals. |

|xxxx_signal_drvr_func() |Implements specific I/O interface drivers. |

|xxxx_send_cmd() |Used to send controller specific commands to the controller. This|

| |function is used by several CODE API’s to pass through |

| |Profibus-DP I/O capabilities and commands not supported in CODE. |

The custom functions used in the driver are:

extern long profibus_signal_func (signals_t* sigPtr, long function, void* arg,

CxErrorMsg* this_error);

extern long profibus_signal_drvr_func (signal_drivers_t* drvrPtr, long function,

void* arg, CxErrorMsg* this_error);

extern long profibus_send_cmd (cmd_msg *msg, CxErrorMsg* this_error);

These functions are designed to return the value CX_OK if no error occurs inside the function, or return CX_ERROR if an error occurs. If CX_ERROR is returned, then the this_error argument of type CxErrorMsg data structure should be loaded with the correct error information and returned through the argument. The error return procedures are described in the CODE documentation section Handling Driver Errors in Chapter 3 – Controller Interface Functions.

The prototype arguments are defined in Table 3.4.

Table 3.4 – Function Arguments

|Argument |Description |

|function |This argument passes the particular I/O case from a client |

| |process to the user function. It will have values like |

| |CX_CNTRL_OPEN_SIGNAL_TABLE (see Table 2.1) which can be used in a|

| |case switch statement to call a user supplied routine. |

|arg |The pointer void* to arg means that this argument is supplied as |

| |a pointer address to a value (or data type) that depends on the |

| |I/O case defined by function. In some cases arg is of type long |

| |and in other cases it might be of data structure type signal_info|

| |- see Chapter 3 – Controller Interface Functions. |

|this_error |this_error is a data structure of type CxErrorMsg which the user |

| |uses to pass error information back to the client process when an|

| |error occurs in one of the interface routines. |

|sigPtr |A pointer of data structure type signals_t which contains the |

| |specific entry information for the CIMServer signals – again see |

| |Chapter 3 – Controller Interface Functions. |

|drvrPtr |A pointer of data structure type signal_drivers_t which contains |

| |the specific parameter information for the CIMServer signals – |

| |again see Chapter 3 – Controller Interface Functions. |

|msg |A pointer of data structure type cmd_msg which contains the |

| |specific parameters used to pass information between a client |

| |process and the profibus driver; see Chapter 3 – Controller |

| |Interface Functions. |

The user developed functions must simply operate with these data structures to get and set the signal information required by the CODE client processes, reformatting the data as required by the Profibus card.

3 CODE Signal Table

Before any signals can be used with CODE software, the signals must be defined in a file called a signal table. When the –sigtable option is used in the CIMulation or CIMControl command line argument list, the signal table is read by CIMServer. If the signal table contains an error, CIMServer will fail to start. The command line that needs to be entered in the Settings → Debug → Project settings → Project arguments edit box is:

cimcontrol –sigtable “path where the signal table file is located”

When the server reads in the signal table it initializes all signals, and internally sets them to the values specified by the user in the signal table. If the signal table contains hardware signals, it will initialize the Profibus I/O card. This allows the client process application to access and manipulate signals or set/read values for the hardware/software signals.

When CIMServer is started, it immediately reads the signal table as defined in the command line arguments. There is a header file created by a makeheader utility, associated with the signal table. It contains the logical names of the signals along with ordering numbers. The header file is included in the client process project workspace as well as in the client source code file. This way the server knows that the signals exist and are defined, so it can access and use those signals to map them to physical hardware equipment – in this case I/O interface modules.

The signal table will normally include software signal definitions as the first entry. The CIMServer manages all signals declared in the signal table. Any CODE client process may interact with the defined signals using the API functions defined in the CODE API library: Events, States, and I/O. The client process may not reference any signals in a CODE client application process that are not defined in the signal table.

|typedef struct signals_t { /* Data on physical signal instances */ |

|char name[CX_NAME_LEN]; /* ascii name of logical signal */ |

|long input; /* Hardware Input toggle */ |

|long output; /* Hardware Output toggle */ |

|long use_init; /* (T/F) use init_value to initialize signal */ |

|long init_value; /* Value to set on startup */ |

|long field1; /* Driver specific field 1 */ |

|long field2; /* Driver specific field 2 */ |

|long field3; /* Driver specific field 3 */ |

|long field4; /* Driver specific field */ |

|long field5; /* Driver specific field 5 */ |

|long field6; /* Driver specific field 6 */ |

|long field7; /* Driver specific field 7 */ |

|long field8; /* Driver specific field */ |

|long field9; /* Driver specific field 9 */ |

|long field10; /* Driver specific field 10 */ |

|long value; /* Current signal value */ |

|long sim; /* Simulation toggle */ |

|caddr_t sigInfo ; /* Generic pointer to driver specific sig str */ |

|signal_drivers_t *sdrvr; /* Pointer to corresponding signal driver. */ |

|struct table_entry first_entry; / Pointer for internal CIMServer use */ |

|long table_index; /* Array index in signal table array */ |

|char *expression; /* Logical expression, etc */ |

|char *description; /* Brief signal description */ |

|char *sig_class; /* Signal class */ |

|} |

|Figure 3.1 - signals_t Data Structure |

The signal table contains hardware specific information, such as memory offsets that will be mapped relative to the base address specified for the card DPM, etc. When CIMServer is started, the signal table file is read. The field names and values are copied to two CODE defined data structures: signals_t and signal_drivers_t. The structure members are shown in Figures 3.1 and 3.2.

The copied data from the signal table correspond directly to the member variable names, as can be seen from Figure 3.1 and Figure 3.2. The pointer variables are defined to reference local instances of these data structures. The member variables are used to perform I/O operations and access the Profibus-DP card. Table 3.5 explains the correspondence between signal table definitions and the CODE data structure member variables. In addition, CIMServer data parameters are also mapped to the arguments of the appropriate Profibus API functions and the related data structure members.

|typedef struct signal_drivers_t { /* Signal device driver struct */ |

|long driver_type ; /* Type of device driver */ |

|long driver_instance; /* Which instance of the driver */ |

|char device_name[CX_MAX_PATH_LEN]; /* Logical device name */ |

|long start_index ; /* Starting signal index */ |

|long end_index ; /* Ending signal index */ |

|long poll_flag ; /* T/F indicating polled signals */ |

|long intr_flag ; /* T/F indicating intr signals */ |

|char labels[10][CX_NAME_LEN]; /* Optional signal parm labels */ |

|caddr_t drvrInfo; /* Generic pntr to drvr specific info */ long initialized; /* T/F indicating driver inited. */ |

|long num_polled_table_entries; /* Num polled signals in driver */ |

|long controller_index ; /* Index into CxController funclist */ |

|} |

|Figure 3.2 - signal_drivers_t Data Structure |

Table 3.5 – Mapping of Signal Table Fields to CIMServer and PB-DP Parameters

|Signal Table Field |Map to CIMServer Data Parameter |Map to Profibus-DP Data Parameter |

|Definitions | | |

|Driver type |signal_drivers_t |This field is passed through the CODE function long |

| |long driver_type |get_controller_index( long controller_type, CxErrorMsg|

| | |*this_error) by the CODE server, corresponding to |

| |Defines signal controller type, with value |argument controller_type. This function is defined in |

| |defined in cntr_const.h (we use 1003 for PB-DP |user_cntrl.c. It does not map to any PB API parameter.|

| |driver. (READ ONLY) | |

|Driver instance |signal_drivers_t |If several signal table blocks are of the same driver |

| |long driver_instance |type value, e.g., 1003, then a different number is |

| | |entered for the driver instance. The PB-DP driver |

| |Defines an instance of a specific driver when |permits up to 4 boards in the same bus, as specified |

| |user connected to several different controllers|by the parameter usDevNumber passed as an argument in |

| |of the same type. (READ ONLY) |several PB API functions. |

|NT device name |signal_drivers_t |The field is used during initialization in |

| |char device_name[PATH_LN] |DevOpenDriver(). Within the function CreateFile() is |

| | |called to open a communication resource. The field |

| |Defines a logical device name used to |value is used as an argument for CreateFile(). Also, |

| |communicate with the device (READ ONLY) |the field value is referenced in PB_DP_init_card() |

| | |which contains a local pointer to signal_drivers_t |

| | |data structure. A member variable of that structure is|

| | |assigned the value of the field. The character string |

| | |in this field is copied to sdrvr->device_name. That |

| | |character string is used subsequently in |

| | |PB_DP_init_card() to open a communication resource ( |

| | |in this case serial communication resource RS485) |

|logical signal name |signals_t |The character string in this field does not map to |

| |char name[NAME_LEN] |Profibus DP data parameter, but it can be accessed |

| | |indirectly through locally defined pointers to |

| |Contains the characters of the logical signal. |signals_t data structure. |

| |(READ ONLY) | |

|input |signals_t |The value in the field is mapped indirectly in |

| |long input |PB_DP_initialize_signal() to a local pointer to |

| |Defines whether the signal is available as a |signals_t data structure named sigPtr. sigPtr->input |

| |hardware input. A value of 0 indicates that the|is used in a case statement to perform an assignment |

| |signal is not readable in hardware. A value of |of a CODE defined constant to siginfo-> sig_type |

| |1 means the signal must be polled. A value of 2|member variable. |

| |means that the corresponding driver can | |

| |generate interrupts for this signal, and does | |

| |not require polling. (READ ONLY) | |

|output |signals_t |The value in the field is mapped indirectly in |

| |long output |PB_DP_initialize_signal() to a local pointer to |

| | |signals_t data structure named sigPtr. sigPtr->output |

| |Defines whether the signal is available as a |is used in a case statement to perform an assignment |

| |hardware output. A value of 0 indicates the |of a CODE defined constant to siginfo-> sig_type |

| |signal does not have output capability and the |member variable. |

| |CIMServer does not permit a CODE application | |

| |process to set the value. A value of 1 | |

| |indicates that it does. The CIMServer saves the| |

| |value in its internal table and writes the | |

| |value to the driver. (READ ONLY) | |

|use_init |signals_t |The long value in this field does not map to Profibus |

| |long use_init |DP data parameter, but it can be accessed indirectly |

| | |through locally defined pointers to signals_t data |

| |Used only when the output field is 1, and it is|structure. |

| |a flag indicating whether or not to initialize | |

| |the signal’s state when the CIMServer is | |

| |started (1), or to leave the signal in its | |

| |present condition (0). (READ ONLY). | |

|init_value |signals_t |The long value in this field does not map to Profibus |

| |long init_value |DP data parameter, but it can be accessed indirectly |

| | |through locally defined pointers to signals_t data |

| |Used only when the output field is 1 and the |structure. |

| |use_init field is 1. When these conditions are | |

| |satisfied, the init_value defines the desired | |

| |initial value of the signal when the CIMServer | |

| |starts. (READ ONLY). | |

|InBase |signals_t |Mapped to the argument usReceiveOffset in |

| |long field1 |DevExchangeIO(). |

| | | |

| |This field represents the DPM base address for | |

| |an input signal. IOnum and Width are then used | |

| |to move to the correct bits assigned to the | |

| |signal. | |

|OutBase |signals_t |Mapped to the argument usSendOffset in |

| |long field2 |DevExchangeIO(). |

| | | |

| |This field represents the DPM base address for | |

| |an input signal. IOnum and Width are then used | |

| |to move to the correct bits assigned to the | |

| |signal. | |

|Type |signals_t |Not mapped to a PB-DP API parameter. Used in driver to|

| |long field3 |call RS232 interface routines. These routines then |

| | |exchange ASCII data by A/D emulation. |

| |This field will have a –1 value if simple I/O, | |

| |but will have a value such as 750650 denoting | |

| |the A/D module type if signal used for RS232 | |

| |communications. | |

|IOnum |signals_t |Used in driver to get to correct byte location in DPM,|

| |long field4 |before exchanging the byte and bit values between the |

| | |PB_DP card DPM and CODE server. Indirectly maps to the|

| |Represents the offset bit I/O location for |DevExchangeIO() arguments *pvSendData (if output |

| |simple I/O relative to the base address. Will |signal) and *pvReceiveData (if input signal). |

| |have value of –1 if analog I/O of Type 750650. | |

|Width |signals_t |Not mapped to a PB-DP API parameter. Used in driver to|

| |long field5 |convert signal value to correct bit values passed to |

| | |and from PB card. |

| |This field specifies the number of bits | |

| |allocated to this signal. For example, if 4 | |

| |bits are specified, the signal value must not | |

| |exceed 24 = 16. | |

|field6 –field10 |Not used |Not used |

4 CODE Client process

Figure 3.4 shows a simplified fragment of a client process, whose sole purpose is to set/get signal values through the Profibus-DP I/O interface card. When all of the user-defined interface functions and API functions have been included in the project, the next step is to create a header file from the signal table. This header file must be included in client processes using the logical signal names and also in the user interface files developed for the Profibus driver.

The CIMServer automatically assigns each signal a number based on the order in which the signals are defined in the signal table, starting from 0. In a CODE client process, signals are referenced using this numerical index into the signal table. CODE includes a makeheader utility that automatically generates a header file in which the logical name is mapped into the corresponding numerical index in the signal table. By including this header file in a client process and using the defined constant corresponding to the logical name of the signal, the readability of the source code can be improved.

The command to invoke the utility is:

makeheader sigtable_file filename.h

The command is invoked in a Command Prompt Window. The utility is located in a folder cimetrix/bin. In the command line the file containing the signal table is specified along with the target name of the header file that will be created. This command creates a header file in the same folder or in a different path location, if specified by the user. After the header file has been generated, such as the sample file in Figure 3.5, it should be included as an include file in all CODE client processes and also in the device driver interface file which interact with the signals.

5 Interrupt Device Driver

Profibus-DP supports both a polling mode and an interrupt mode. Initially, the interrupt mode will be implemented. Interrupt mode makes it possible for the user interface to access more than one board installed into the PC. This interrupt mode is not configured to interrupt when a signal input attains a certain value; thus, polling will have to be used here.

The default setting of the Profibus-DP board is interrupt mode. In interrupt mode, the default IRQ is 7. All other settings are the same for both modes. Some Profibus-DP API functions are primarily used in interrupt mode, for example DevOpenDriver() and DevCloseDriver(). In interrupt mode, up to four interface-boards can be used. Each board is configured to have its own IRQ.

6 Device Driver Threading

CIMServer will initiate a thread for each device driver (different driver or another instance of same driver) it reads from the signal table blocks. For I/O drivers it will also spawn threads for signals that will be monitored (wait for signal to reach certain value, etc.). If the monitoring uses interrupts, CIMServer will spawn a thread to monitor these types of signals. If there are signals that are polled for some value change, then a thread will be spawned for these types of signals. If some are interrupt and some are polled, then it will spawn two threads for monitoring the two different types of signals.

Since CIMServer is threaded, it will not be necessary to thread the device driver user interface.

Implementation Tasks

This section describes the implementation tasks that are necessary to complete the device driver. Most of this section is dedicated to further defining the architecture, data structures, and pseudo-code that will be implemented in the driver. In addition, a set of tests will be defined to test the developed driver.

The driver will use three operational modes to define the communication sequence with the Profibus card:

• Init mode - Mode whereby user starts the server, establishes a link with the Profibus card, and initializes I/O module states according to predefined signal table values.

Table 4.1 – Initialization Mode Case Statements and Implementation Function

|Init Mode Cases |Description |

|profibus_signal_drvr_func() |This case initializes the signal table on an I/O controller. A |

| |function makes the calls required to initialize the I/O controller |

|CX_CNTRL_OPEN_SIGNAL_TABLE |(DevOpenDriver(), DevInitBoard(), DevSetHostState()), and then |

| |initializes the member variable “initialized” in signal_drivers_t |

| |data structure to CX_TRUE. |

|profibus_signal_func() |This case initializes the state of a hardware signal. A function is |

| |called when the CIMServer reads the signal table and initializes each|

|CX_CNTRL_INITIALIZE_SIGNAL |signal. |

• Normal mode - Normal mode whereby client processes interact with the Profibus card by calling signal related API functions.

Table 4.2 – Normal Mode Case Statements and Function Implementation

|Normal Mode Cases |Description |

|profibus_signal_func() |DevExchangeIO() is called to pass signal parameters such as |

| |offset, signal value, width, etc. The signal value is then |

|CX_CNTRL_SET_SIGNAL_VALUE |written to DPM and corresponding hardware I/O then |

| |initialized with the specified value. |

|profibus_signal_func() |Call to DevExchangeIO() is made to pass signal parameters |

| |such as offset, signal value, width, etc. The signal value is|

|CX_CNTRL_GET_SIGNAL_VALUE |read from DPM and the corresponding hardware I/O signal value|

| |is loaded into a locally defined buffer which is updated in |

| |the signal table offset field for the specified input module.|

• Shutdown mode - Client processes and CIMServer closes the communication link to the Profibus card.

Table 4.3 – Shutdown Mode Case Statements and Function Implementation

|Shutdown Mode Case Statements |Description |

|profibus_signal_drvr_func() |Calls a function to close the Profibus card. The terminating of the|

| |connection with the card is accomplished by sequential function |

|CX_CNTRL_CLOSE_SIGNAL_TABLE |calls to DevSetHostState(), DevExitBoard(), and DevCloseDriver(). |

| |The application then relinquishes the memory resources allocated |

| |previously for establishing the connection with the driver and the |

| |board. |

Unfortunately, the CIMServer interface functions described in the next section are not defined as an operational mode sequence. Thus, elements of these three modes pervade all three functions.

Three interface routines will be implemented in the file named profibus.cpp:

profibus_signal_drvr_func()

profibus_signal_func()

profibus_send_cmd()

The pseudo-code for each signal case defined within these three functions is listed in the following sections.

1 Header Files, Constants and Parameters

This section defines the header files, constants, parameters and data structures that are required to implement the device driver and which are used in the interface functions.

Header files required:

The required header files are listed in Table 4.4.

Table 4.4 – Header Files Associated with profibus_signal_drvr_func()

|Header file – Description |Contents |

|profibus.h |extern long profibus_signal_func ( signals_t *sigPtr, |

|Contains all of the function prototype |long function, void *arg, CxErrorMsg * this_error ) |

|definitions that represent the function | |

|interface between CIMServer and Profibus-DP |Implemented - function described in following sections. |

|card. |extern long profibus_signal_drvr_func ( signal_drivers_t *drvrPtr, long function, |

| |void *arg, CxErrorMsg *this_error ) |

| | |

| |Implemented - function described in following sections. |

| | |

| |extern long profibus_send_cmd( signal_drivers_t *drvrPtr, long function, void |

| |*arg, CxErrorMsg *this_error ) |

| | |

| |Implemented - function described in following sections. |

|profibus_signal.h |/*---------------profibus_signal.h “sample” ----------------*/ |

|File is generated when the makeheader utility | |

|is run. It contains constant definitions of |#define OPEN_GRIPPER 0 |

|signals defined in the signal table. |#define CLOSE_GRIPPER 1 |

| |#define VALVE_1_OPEN 2 |

| |#define VALVE_1_CLOSE 3 |

| |#define VALVE_2_OPEN 4 |

| |#define VALVE_2_CLOSE 5 |

| |#define SENSOR_1_ON 6 |

| |#define SENSOR_2_ON 7 |

| | |

| |/*-------------END of the Header File-----------------*/ |

|ProfibusErrors.h |Error constants to be defined under the section Defined constants. |

|Contains error handling macro definitions | |

|associated with the signal table, i.e. signal | |

|table full, signal table not initialized, | |

|driver and device I/O errors. | |

|cifuser.h |Contents of these files can be found under the folder Dependencies in project |

|gldtypes.h |cimcontrol. These include files are provided with the Profibus driver software and|

|cifdev_i.h |must be included to call the Profibus API functions. |

Defined constants (placed in ProfibusErrors.h):

Errors related to signal definitions in signal table:

#define CX_PB_DP_INVALID_SIGNAL_INBASE_ADDRESS 2000

#define CX_PB_DP_INVALID_SIGNAL_OUTBASE_ADDRESS 2001

#define CX_PB_DP_INVALID_SIGNAL_WIDTH 2002

#define CX_PB_DP_INVALID_SIGNAL_VALUE 2004

#define CX_PB_DP_INVALID_SIGNAL_TYPE 2005

#define CX_PB_DP_INVALID_SIGNAL_IONUM(BER) 2006

Errors related to client process argument errors:

#define CX_PB_DP_INVALID_FUNCTION_TYPE 2007

#define CX_PB_DP_INVALID_BOARD_INSTANCE 2008

#define CX_PB_DP_CANNOT_MONITOR_SIGNALS 2009

Errors related to Profibus-DP API interface errors:

#define CX_PB_DP_DRIVER_ERROR 2010

#define CX_PB_DP_BOARD_NOT_INITIALIZED 2011

#define CX_PB_DP_DRIVER_NOT_OPENED 2012

#define CX_PB_DP_DRV_BOARD_NOT_INITIALIZED 2013

#define CX_PB_DP_DRV_INIT_STATE_ERROR 2014

#define CX_PB_DP_DRV_READ_STATE_ERROR 2015

#define CX_PB_DP_DRV_CMD_ACTIVE 2016

#define CX_PB_DP_DRV_PARAMETER_UNKNOWN 2017

#define CX_PB_DP_DRV_DEV_DPM_ACCESS_ERROR 2018

#define CX_PB_DP_DRV_DEV_NOT_READY 2019

#define CX_PB_DP_DRV_DEV_NOT_RUNNING 2020

#define CX_PB_DP_DRV_DEV_WATCHDOG_FAILED 2021

#define CX_PB_DP_DRV_DEV_OS_VERSION_ERROR 2022

#define CX_PB_DP_DRV_DEV_SYSERR 2023

#define CX_PB_DP_DRV_DEV_MAILBOX_FULL 2024

#define CX_PB_DP_DRV_DEV_PUT_TIMEOUT 2025

#define CX_PB_DP_DRV_DEV_GET_TIMEOUT 2026

#define CX_PB_DP_DRV_DEV_GET_NO_MESSAGE 2027

#define CX_PB_DP_DRV_DEV_RESET_TIMEOUT 2028

#define CX_PB_DP_DRV_DEV_NO_COM_FLAG 2029

#define CX_PB_DP_DRV_DEV_EXCHANGE_FAILED 2030

#define CX_PB_DP_DRV_DEV_EXCHANGE_TIMEOUT 2031

#define CX_PB_DP_DRV_DEV_COM_MODE_UNKNOWN 2032

#define CX_PB_DP_DRV_USR_OPEN_ERROR 2033

#define CX_PB_DP_DRV_USR_INIT_DRV_ERROR 2034

#define CX_PB_DP_DRV_USR_NOT_INITIALIZED 2035

#define CX_PB_DP_DRV_USR_COMM_ERR 2036

#define CX_PB_DP_DRV_USR_DEV_NUMBER_INVALID 2037

#define CX_PB_DP_DRV_USR_INFO_AREA_INVALID 2038

#define CX_PB_DP_DRV_USR_NUMBER_INVALID 2039

#define CX_PB_DP_DRV_USR_MODE_INVALID 2040

#define CX_PB_DP_DRV_USR_MSG_BUF_NULL_PTR 2041

#define CX_PB_DP_DRV_USR_MSG_BUF_TOO_SHORT 2042

#define CX_PB_DP_DRV_USR_SIZE_INVALID 2043

#define CX_PB_DP_DRV_USR_SIZE_ZERO 2044

#define CX_PB_DP_DRV_USR_SIZE_TOO_LONG 2045

#define CX_PB_DP_DRV_USR_DEV_PTR_NULL 2046

#define CX_PB_DP_DRV_USR_BUF_PTR_NULL 2047

#define CX_PB_DP_DRV_USR_SENDSIZE_TOO_LONG 2048

#define CX_PB_DP_DRV_USR_RECVSIZE_TOO_LONG 2049

#define CX_PB_DP_DRV_USR_SENDBUF_PTR_NULL 2050

#define CX_PB_DP_DRV_USR_RECVBUF_PTR_NULL 2051

#define CX_PB_DP_RCS_ERROR 2052

#define CX_PB_DP_MISC_ERROR 2053

Errors related to failure to initialize WAGO modules:

#define CX_PB_DP_WAGO_MODULE_INIT_FAILURE 2054

#define CX_PB_DP_WAGO_MODULE_COM_FAILURE 2055

Global parameters:

None

Global data structures:

None

2 profibus_signal_drvr_func() Implementation

Function:

long profibus_signal_drvr_func( signal_drivers_t *drvrPtr, long function, void *arg,

CxErrorMsg *this_error)

Return value:

Returns error condition CX_OK or CX_ERROR

Arguments:

*drvrPtr – Pointer to CODE defined data structure (see Figure 3.3)

function – The signal case/function to be handled

*arg – Different data type depending on function (case) type

*this_error – Pointer to CODE defined error data structure (see CODE

documentation.

Local constants:

None

Local parameters:

err - Returns error condition

ReturnFlag - Store return values from functions that are called

outputbuffer - Pointer to server side buffer that maps to DPM

loop - For loop counter

Error handling:

Errors returned from calls to the Profibus functions are stored in ReturnFlag. If a Profibus error is returned then the local routine ReportProfibusErrorMessages() is called to report the error type. Errors are returned to the server by the long value CX_ERROR. Error conditions are specified by type and error text in the macros SetErrorMsg() and SetErrorText(), as shown by the following examples:

SetErrorMsg(this_error, CX_CASE_NOT_IMPLEMENTED);

SetErrorText(this_error,” CX_CNTRL_MONITOR_SIGNALS not supported for Profibus”);

Pseudo-code - profibus_signal_drvr_func():

long profibus_signal_drvr_func(signal_drivers_t *drvrPtr, long function, void *arg,

CxErrorMsg *this_error)

{

local variables:

int loop

long err = CX_OK

long ReturnFlag

char *outputbuffer

Initialize ReturnFlag to DRV_NO_ERROR

Switch based on the value of “function” argument

case CX_CNTRL_CLOSE_SIGNAL_TABLE: // close driver signal table

Call DevSetHostState() with argument HOST_NOT_READY and no timeout

Call DevExitBoard() to end communication with board

Call DevCloseDriver() to close Profibus driver application

Break if any of these calls do not return DRV_NO_ERROR

Free the memory allocated to drvrPtr->drvrInfo

break

case CX_CNTRL_MONITOR_SIGNALS:

/* monitors signal status when driver generates asynchronous interrupts */

Set err to CX_ERROR

Set error msg CX_CASE_NOT_IMPLEMENTED

Set error text “CX_CNTRL_MONITOR_SIGNALS not supported for profibus“

break

case CX_CNTRL_OPEN_SIGNAL_TABLE

/* opens signal table associated with a device driver. */

Check to see if Profibus card already open by checking drvrPtr->initialized

If set to one, then already initialized and can return

If not initialized, then

Call DevOpenDriver() to open Profibus driver application

Call DevInitBoard() to initialize board

Call DevSetHostState() with argument HOST_READY

Check return values in each previous call to ensure that no errors are returned

Allocate 512 Bytes as output buffer and assign to drvrPtr->drvrInfo

Then assign all 512 Bytes zero values and pass to card using

DevExchangeIO() to zero out DPM 512B output buffer

Initialize drvrPtr->initialized to one

break

default

/* handle an error message in case CIMServer passes invalid function argument */

Set err to CX_ERROR

Set error msg CX _INVALID_FUNCTION_TYPE

Set error text “ Invalid function type for Profibus device driver“

break

End switch

If ReturnFlag is DRV_NO_ERROR then

return CX_OK

Else call ReportProfibusErrorMessages() to report Profibus error encountered and

return CX_ERROR

}

3 profibus_signal_func() Implementation

Function:

long profibus_signal_func(signals_t* sigPtr, long function, void *arg, CxErrorMsg *this_error)

Return value:

Returns error condition

Arguments:

*sigPtr – Pointer to CODE defined data structure (see Figure 3.3)

function – The signal case/function to be handled

*arg – Different data type depending on function (case) type

*this_error – Pointer to CODE error data struct (see CODE documentation)

Local constants:

None

Local parameters:

err - Return error condition

DesiredValue - Stores signal value to be passed to Profibus card DPM

ReturnFlag - Stores returned value from function call

InBase - Base address for input signal offset

OutBase - Base address for output signal offset

Type - Type of I/O (digital bit, analog, RS232)

IOnum - Number assigned to the I/O signal

Width - Number of bits allocated to the signal

output_to_rs232[4] - Control bytes used for rs232 communications

input_to_rs232[4] - Control bytes used for rs232 communications

loop - For loop counter

start_time - Used for timing-out functions

present_time - Used for timing-out functions

longptr - A pointer to arg

Error handling:

Errors are indicated when a function returns the long value CX_ERROR. Error conditions are specified by type and error text in the macros SetErrorMsg() and SetErrorText(), as shown earlier in Section 4.2.

Pseudo-code - profibus_signal _func():

long profibus_signal_func(signals_t* sigPtr, long function, void* arg, CxErrorMsg* this_error)

{

local variables:

long err = CX_OK

int loop

int DesiredValue

short ReturnFlag

short InBase

short OutBase

int Type

int IOnum

int Width

unsigned char output_to_rs232[4]

unsigned char input_to_rs232[4]

time_t start_time

time_t present_time

Initialize ReturnFlag to DRV_NO_ERROR

Use local parameters to store the field values in the sigPtr data structure

InBase = (short) sigPtr ->Field1

OutBase = (short) sigPtr ->Field2

Type = (int) sigPtr ->Field3

IOnum = (int) sigPtr ->Field4

Width = (int) sigPtr ->Field5

arg is used by some cases as a data value, and in others as a pointer.

Cast arg as int and load into DesiredValue and also as a long pointer and store in longptr.

Switch based on value of “function” argument

case CX_CNTRL_INITIALIZE_SIGNAL

Check to see that sigPtr fields have valid data; return CX_ERROR if invalid

Only 16 bit signal width is supported

Switch on module type from Type field

case –1

This is generic bit-based output so break since

CX_CNTRL_SET_SIGNAL_VALUE will be called to set signals

break

case 750650

This is 4 Byte analog module (Wago 750650) used for RS232 communications

Initialize each of the 4 bytes in output_to rs232 buffer and

input_to_rs232 buffers to zero

Assign the initialization bit (bit 3) for the first control byte (byte 1 which is

output_to rs232[1]) to the value 1 (hex 0x04) and pass it through

DevExchangeIO to init the correct DPM bit at the correct OutBase

address offset and with 1000 ms timeout. OutBase is cast to

unsigned short in the DevExchangeIO argument as required.

Receive DPM acknowledgement by calling DevExchangeIO with timeout

of 1000 ms in a while loop by reading input_to_rs232[StatusByte] in

DevExchangeIO until the status bit (bit 3) is returned as one (or 0x04).

The InBase address offset is cast to unsigned short. The status byte

is the second input byte (byte 1)

Next, the control byte is cleared to zero by setting

output_to rs232[ControlByte] = 0 and passing it back through

DevExchangeIO.

Again acknowledge by calling DevExchangeIO with timeout of 1000 ms in a

` while loop reading input_to_rs232[StatusByte] until it returns a zero value.

default

Set error msg CX_INVALID_MODULE_TYPE

Set error text “Invalid profibus module type”

Return CX_ERROR

End switch on module type

Check return values in each previous call to ensure that no errors are returned

Break from case CX_CNTRL_INITIALIZE_SIGNAL

case CX_CNTRL_GET_SIGNAL_VALUE

Switch on module type

case –1

Check signal width. If width is one (simple bit IO), then call GetInputBit

with arguments InBase and IOnum and return value in the argument

&ReturnData of function GetInputBit. Store value in longptr.

else if width is greater than 1, then call ReadGroupInput with arguments

InBase, IOnum, and Width and return value in the argument

&ReturnData. Store value in longptr.

beak case –1

case 750650

Set error msg CX_INVALID_SIGNAL_TYPE

Set error text “Profibus module type 750650 not supported as

CxGetSignalFunction“

return CX_ERROR

default

Set error msg CX_INVALID_MODULE_TYPE

Set error text “Invalid profibus module type”

return CX_ERROR

End switch on module type

break of case CX_CNTRL_GET_SIGNAL_VALUE

case CX_CNTRL_SET_SIGNAL_VALUE

Switch on module type

case –1

Check signal width. If width is one (simple bit IO), but desired value

is not 0 or 1, then return error.

If width is one and desired value is 0, then call ClearOutputBit,

passing arguments of OutBase, IOnum, and drvrInfo,

a pointer set to the address of the input buffer that was

initialized when the signal table was opened.

If width is one and desired value is 1, then call SetOutputBit,

passing arguments of OutBase, IOnum, and drvrInfo.

If width is greater than 1, then check value to ensure that it can be

stored in 16 bits. If so, call WriteGroupOutput passing arguments

of OutBase, IOnum, DesiredValue, and drvrInfo.

Check return values for potential errors from the previous calls

break

case 750650

Set error msg CX_INVALID_SIGNAL_TYPE

Set error text “Profibus module type 750650 not supported as

CxSetSignalFunction“

return CX_ERROR

DEFAULT

Set error msg CX_INVALID_MODULE_TYPE

Set error text “Invalid profibus module type”

return CX_ERROR

End switch on module type

break of case CX_CNTRL_SET_SIGNAL_VALUE

case CX_CNTRL_SETUP_SIGNAL_MONITORING

Set error msg CX_PB_DP_CANNOT_MONITOR_SIGNALS

Set error text “CX_CNTRL_SETUP_SIGNAL_MONITORING not supported for profibus “

return CX_ERROR

case CX_CNTRL_SET_LATCH

Set error msg CX_CASE_NOT_IMPLEMENTED

Set error text “Case CX_CNTRL_SET_LATCH not supported for profibus“

return CX_ERROR

case CX_CNTRL_GET_SIGNAL_CNTRL

Set error msg CX_CASE_NOT_IMPLEMENTED

Set error text “Case CX_CNTRL_GET_SIGNAL_CNTRL not supported for profibus“

return CX_ERROR

case CX_CNTRL_PULSE_SIGNAL

Set error msg CX_CASE_NOT_IMPLEMENTED

Set error text “Case CX_CNTRL_PULSE_SIGNAL not supported for profibus“

return CX_ERROR

DEFAULT

/* handle an error message in case CIMServer passes invalid function argument */

Set error msg CX_INVALID_FUNC_TYPE

Set error text “ Invalid function type for profibus_signal_func in profibus driver“

return CX_ERROR

End switch

If ReturnFlag is DRV_NO_ERROR then

return CX_OK

Else call ReportProfibusErrorMessages() to report Profibus error encountered and

return CX_ERROR

}

1 profibus_signal_func Private Functions

The following private functions are required to implement profibus_signal_func():

static long GetInputBit(long OutBase, long InBase, CxErrorMsg *this_error)

static long ReadGroupInput(long InBase, long IOnum, long Width, CxErrorMsg *this_error)

static long SetOutputBit(long OutBase, long IOnum, unsigned char *OutputBuffer,

CxErrorMsg *this_error)

static long ClearOutputBit(long OutBase, long IOnum, unsigned char *OutputBuffer,

CxErrorMsg *this_error)

static long WriteGroupOutput(long OutBase, long IOnum, long Width, long DesiredValue,

unsigned char *OutputBuffer, CxErrorMsg *this_error)

1 Private Function: GetInputBit()

This function simply gets a single bit , specified by the OutBase address offset and the IOnum.

Function:

static long GetInputBit (unsigned short InBase, int IOnum, long *ReturnData)

Return value:

Returns error condition in ReturnFlag

Arguments:

InBase - DPM input signal address offset

IOnum - Input signal number relative to InBase

*ReturnData - To return single bit value (0 or 1)

Local constants:

None

Local parameters:

div_result - Find byte number for bit to be set to zero

ReturnFlag - Stores return value from call to DevExchangeIO()

ReceiveOffset - The DPM address offset passed through DevExchangeIO()

byte_width - Byte width is set to 8 bits

databyte - Local parameter used to store the byte of interest

BitWithinByte - A byte with all bits set to 0 except the bit of interest

DPM_BitNum - IOnum reduced by 1

Error handling:

Shown in pseudo-code

Pseudo-code:

static long GetInputBit (unsigned short InBase, int IOnum, long * ReturnData)

{

local variables:

short ReturnFlag

int ReceiveOffset=0

int byte_width = 8

int databyte

int BitWithinByte

int DPM_BitNum

div_t div_result

Since IOnum numbered from 1, reduce value by one and store in DPM_BitNum

Find byte that contains specified input bit using div() operator

div_result = div(DPM_BitNum, byte_width)

Set ReceiveOffset to InBase and then apply the div_result byte shift by adding

div_result quotient to ReceiveOffset

Now create a byte that has the remainder (rem) bit set to one and the other bits

in the byte set to 0 by applying BitWithinByte = 2rem

Now call DevExchangeIO() with ReceiveOffset and read back 1 byte into databyte.

Check the return value for errors and return the error state in ReturnFlag

Do a bitwise & between BitWithinByte and databyte and if true

Set * ReturnData to 1

else

Set * ReturnData to 0

return ReturnFlag

}

2 Private Function: ReadGroupInput()

This function simply sets a single bit to zero.

Function:

static long ReadGroupInput ( unsigned short InBase, int IOnum, int Width,

long * ReturnData)

Return value:

Returns error condition in ReturnFlag

Arguments:

InBase - DPM input signal address offset

IOnum - Input signal number relative to InBase

Width - Number of bits used for signal value

Local constants:

None

Local parameters:

div_result - Find byte number for bit to be set to zero

ReturnFlag - Stores return value from call to DevExchangeIO()

ReceiveOffset - The DPM address offset passed through DevExchangeIO()

byte_width - Byte width is set to 8 bits

databyte - Local parameter used to store the byte of interest

currentbit - Bit determined from remainder

BitWithinByte - Byte with all bits set to 0 except the bit of interest

Groupdata - Used for local group bit manipulations

bitdataoffset - Stores byte which has some or all of needed bits

DPMdata[3] - Stores 3 received bytes returned from DPM

Error handling:

Shown in pseudo-code

Pseudo-code:

static long ReadGroupInput (unsigned short InBase, int IOnum, int Width, long * ReturnData)

{

local variables:

char DPMdata[3]

int DPM_BitNum

int ReceiveOffset=0

int BitWithinByte;

int databyte

unsigned short ReturnFlag

long Groupdata = 0

int byte_width = 8

int bitdataoffset

int currentbit;

div_t div_result

Since IOnum numbered from 1, reduce value by one and store in DPM_BitNum

Find byte that contains specified output bit using div() operator

div_result = div(DPM_BitNum, byte_width)

Set ReceiveOffset to InBase and then apply the div_result byte shift by adding

div_result quotient to ReceiveOffset. This quotient is the byte offset from InBase.

Use the remainder from this operation to determine the bit number from within

the byte found previously and set the remainder to currentbit. This bit,

within this byte, is the DPM bit mapped to the desired Hardware (Physical) I/O.

This remainder is also the beginning bit offset into DPMdata for Groupdata.

Now call DevExchangeIO() at ReceiveOffset and request three DPM bytes

be loaded into DPMdata. Use timeout of 1000 ms. Check the return value for

errors and return the error state if error occurs. Driver only supports bit Width

of 16 or less (signal values of 0 to 65536)

Now use a while loop to extract the value of each bit, counting down Width until

all bit values have been determined. Within this while loop:

Use the div() operator to find byte (0, 1, or 2) for the currentbit and

store in bitdataoffset

Load the byte numbered bitdataoffset into databyte using the local buffer DPMdata

Now create a byte that has the remainder (rem) bit set to one and the other bits

in the byte set to 0 by applying BitWithinByte = 2rem

Now do a bitwise & of BitWithinByte and databyte.

If the & result is positive then the current bit in DPMdata is a one. To load these

bits recursively, we start with the empty byte GroupData, do a bitwise shift shift left

and then increment the byte by GroupData++ to place a 1 in the

least significant bit.

else if the result is 0 we simply do the shift.

Now count down the Width and increment up currentbit by one and continue the loop

until Width is zero.

End while loop

Load Groupdata into * ReturnData which will be returned as function argument

Return the error state in ReturnFlag

}

3 Private Function: ClearOutputBit()

This function simply sets a single bit to zero.

Function:

static long ClearOutputBit ( unsigned short OutBase, int IOnum, char *OutputBuffer)

Return value:

Returns error condition ReturnFlag

Arguments:

OutBase - DPM output signal address offset

IOnum - Output signal number

*OutputBuffer - Output buffer on server side

Local constants:

None

Local parameters:

div_result - Find byte number for bit to be set to zero

ReturnFlag - Stores return value from call to DevExchangeIO()

SendOffset - The DPM address offset passed through DevExchangeIO()

byte_width - Byte width is set to 8 bits

databyte - Local parameter used to store the byte of interest

BitWithinByte - A byte with all bits set to 0 except the bit of interest

DPM_BitNum - IOnum reduced by 1

Error handling:

Shown in pseudo-code

Pseudo-code:

static long ClearOutputBit (unsigned short usOutBase, long IOnum, unsigned char *OutputBuffer, CxErrorMsg *this_error)

{

local variables:

short ReturnFlag

int SendOffset=0

int byte_width = 8

int databyte

int BitWithinByte

int DPM_BitNum

div_t div_result

Since IOnum numbered from 1, reduce value by one and store in DPM_BitNum

Find byte that contains specified output bit using div() operator

div_result = div(DPM_BitNum, byte_width)

Set SendOffset to OutBase and then apply the div_result byte shift by adding

div_result quotient to SendOffset

Now create a byte that has the remainder (rem) bit set to one and the other bits

in the byte set to 0 by applying BitWithinByte = 2rem

Read the local output buffer at byte offset SendOffset and and store value in databyte

Complement BitWithinByte so it can be used as a mask and then & it with databyte , storing

the final value in databyte and also updating the local output buffer with this same byte.

This is the byte of interest with all its original bit values except the bit of interest, now 0.

Now call DevExchangeIO() to pass this single byte to the DPM which will set the bit

of interest to 0. Check the return value for errors and return the error state if error

return ReturnFlag

}

4 Private Function: SetOutputBit()

This function simply sets a single bit to one.

Function:

static long SetOutputBit(unsigned short OutBase, int IOnum, char *OutputBuffer)

Return value:

Returns error condition in ReturnFlag

Arguments:

OutBase - DPM output signal address offset

IOnum - Output signal number

*OutputBuffer - Output buffer on server side

Local constants:

None

Local parameters:

div_result - Find byte number for bit to be set to zero

ReturnFlag - Stores return value from call to DevExchangeIO()

SendOffset - The DPM address offset passed through DevExchangeIO()

byte_width - Byte width is set to 8 bits

databyte - Local parameter used to store the byte of interest

BitWithinByte - A byte with all bits set to 0 except the bit of interest

DPM_BitNum - IOnum reduced by 1

Error handling:

Shown in pseudo-code

Pseudo-code:

static long SetOutputBit (unsigned short OutBase, int IOnum, char *OutputBuffer)

{

local variables:

short ReturnFlag

int byte_width = 8

int SendOffset=0

int databyte

int DPM_BitNum

int BitWithinByte

div_t div_result

Since IOnum numbered from 1, reduce value by one and store in DPM_BitNum

Find byte that contains specified output bit using div() operator

div_result = div(IOnum, byte_width)

Set SendOffset to OutBase and then apply the div_result byte shift by adding

div_result quotient to SendOffset

Now create a byte that has the remainder (rem) bit set to one and the other bits

in the byte set to 0 by applying BitWithinByte = 2rem

Read the local output buffer at byte offset SendOffset and and store value in databyte

Do a bitwise “or” of databyte and BitWithinByte to set the bit of interest to 1 and store

result in databyte . Also update the output buffer with this byte.

Now call DevExchangeIO() to pass this single byte to the DPM which will set the bit

of interest to 1. Check the return value for errors and return the error state in this_error

return ReturnFlag

}

5 Private Function: WriteGroupOutput()

This function simply sets writes the value of a group of bits to the DPM.

Function:

static long WriteGroupOutput(unsigned short OutBase, int IOnum, int Width,

int DesiredValue, char *OutputBuffer)

Return value:

Returns error condition in ReturnFlag

Arguments:

OutBase - DPM output signal address offset

IOnum - First bit of output signal

Width - Number of bits for output signal

DesiredValue - Set to value received in arg

*OutputBuffer - Output buffer on server side

Local constants:

None

Local parameters:

ReturnFlag - Return value from PB library call

bitposition - Used as a bit mask

BitWithinByte - Holds value when one bit is set to 1

Beginning_IOnum - Holds initial value of IOnum

Current_IOnum - Holds current DPM bit to be modified

DPM_BitNum - Local bit number counter

databyte - Load byte of current bit

SendOffset - DPM offset adjusted by bit number

byte_width - 8 bits per byte

div_result - Determines byte holding bit of interest

Error handling:

Shown in pseudo-code

Pseudo-code:

static long WriteGroupOutput(unsigned short OutBase, int IOnum, int Width, int DesiredValue,

char *OutputBuffer)

{

local variables:

short ReturnFlag

int bitposition

int BitWithinByte

int DPM_BitNum

int databyte

unsigned short SendOffset=0

int byte_width=8

int Beginning_IOnum

int Current_IOnum

div_t div_result

Store IOnum in Beginning_IOnum and Current_IOnum as starting bit

Add Width to IOnum to get ending bit number but must decrement by 1.

Store result in Current_IOnum

Set bitposition equal to value of 1 which also turns the first bit on

Do a while loop over all group bits by decrementing down from IOnum + Width (now stored

as Current_IOnum) until all bits have been considered. Inside the loop do the following:

Do an bitwise & of bitposition and DesiredValue to see if the bit in bitposition

of DesiredValue is a 1. If so, then

Set DPM_BitNum to Current_IOnum and Decrement DPM_BitNum by 1.

First, find the byte that contains the specified beginning output bit by applying the

div() operator and storing the result in div_result

Set SendOffset to OutBase and then increment up by the quotient from the div_result

quotient.

The remainder from div_result is the bit number within the byte found previously.

Form a mask using the div_result remainder as the exponent of 2 to set the desired bit

in the rem position. BitWithinByte = 2rem

Read the buffer at the current offset byte (SendOffset) and store in databyte .

Do a bitwise or between databyte and BitWithinByte to turn bit on and store result

in databyte.

Now store the modified byte in OutputBuffer [SendOffset]

In anticipation of next loop decrement apply left shift operator to bitposition

to shift bit one place (to next bit).

End if

Else if the bit in bitposition of DesiredValue is not a 1, then

Set DPM_BitNum to Current_IOnum and decrement DPM_BitNum by 1.

First, find the byte that contains the specified beginning output bit by applying the

div() operator and storing the result in div_result

Set SendOffset to OutBase and then increment up by the quotient from the div_result

quotient.

The remainder from div_result is the bit number within the byte found previously.

Form a mask using the div_result remainder as the exponent of 2 to set the desired bit

in the rem position. BitWithinByte = 2rem

Read the buffer at the current offset byte (SendOffset) and store in databyte .

Do a complement of BitWithinByte and store in BitWithinByte as a bit mask.

Do an bitwise & of databyte and BitWithinByte to mask the bit off

Store modified byte in OutputBuffer[SendOffset] as databyte

In anticipation of next loop decrement apply left shift operator to bitposition

to shift bit one place (to next bit).

End else if

Decrement Current_IOnum to move to next bit

end while loop

Write data in buffer to DPM by calling DevExchangeIO at SendOffset and writing 3 bytes

from the OutputBuffer beginning at byte SendOffset. Timeout of 1000 ms

Error check on ReturnFlag from call

}

4 profibus_send_cmd() Implementation

This function is used to enable RS232 communications between a CODE client process and a physical device connected through a Wago 750650 RS232 module. In this way, it is not necessary to develop a specific device driver for simple devices connected to the Profibus fieldbus. String commands can be sent directly from the client process.

Devices of interest to the EAAL lab include a reflow oven, barcode device, PLC interface, solder dispenser, etc. These devices only require periodic communications, in contrast to the intensive communications required for the device drivers to the Profibus-DP card and the Seiko controllers.

Function:

long profibus_send_cmd (cmd_msg *msg, CxErrorMsg *this_error)

Return value:

Returns error condition

Arguments:

*msg – Pointer to CODE message data structure (see Figure 3.3)

*this_error – Pointer to CODE error data struct (see CODE documentation)

Local constants:

None

Local parameters:

err - Return error condition

ReturnFlag - Stores returned value from function call

InBase - Base address for input signal offset

OutBase - Base address for output signal offset

SendDataBuffer - Server side data buffer

NumberBytesToTransmit - Bytes to be sent to rs232 module

NumBytesSent - Local counter for bytes sent

EndOfHeader - Offset at beginning of rs232 data

*commandstring - Command string sent to device

output_to_rs232[4] - Control bytes used for rs232 communications

input_to_rs232[4] - Control bytes used for rs232 communications

start_time - Used for timing-out functions

present_time - Used for timing-out functions

Error handling:

Errors are indicated when a function returns the long value CX_ERROR. Error conditions are specified by type and error text in the macros SetErrorMsg() and SetErrorText(), as shown earlier in Section 4.2.

Pseudo-code - profibus_send_cmd():

long profibus_send_cmd(cmd_msg *msg, CxErrorMsg* this_error)

{

local variables:

long err = CX_OK

short ReturnFlag

unsigned short InBase

unsigned short OutBase

unsigned char SendDataBuffer[512]

unsigned int NumBytesToTransmit

unsigned char NumBytesSent

unsigned char EndOfHeader=7

unsigned char *commandstring

unsigned char output_to_rs232[4]

unsigned char input_to_rs232[4]

time_t start_time

time_t present_time

The command message is first loaded into commandstring as an unsigned char pointer.

ReturnFlag is initialized to DRV_NO_ERROR

Note: The first byte of the command string will be a command type. This function will read the

first byte, and switch to a case for handling that command type. The first byte is an

unsigned Hexadecimal byte. Wago 750-650 RS-232 module data transmissions

will be handled by case 1. Wago 750-650 RS-232 module data receptions will be

handled by case 2.

Do a case switch on the first byte in commandstring (byte 0)

Case 1 is the Wago 750-650 RS-232 module data transmission case.

The next 2 bytes of the string will be the InBase value from

the signal table, and the following 2 bytes will be the

OutBase value from the signal table. These bases are the

addresses assigned in the signal table to the Wago 750-650

module that you wish to transmit data to (PC->WAGO->field).

Byte 1 (numbered from 0) is the upper byte of InBase. Byte 2 is the lower byte of

InBase. Byte 3 is the upper byte of OutBase, and Byte 4 is

the lower byte of OutBase. Thus,

Load commandstring[1] as an unsigned short into InBase and then

shift into the upper byte using ................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download