Generic

codesys.com Ingo

A very common way to integrate your own I/O drivers is to use an FB interface inside of a library. A much better way, and much more convenient from the usability is the integration in form of a real I/O driver. This approach should always been choosen when you decide to share your driver with others.

To implement an I/O driver in CODESYS you need mainly two things:

  • A device description
  • An FB, implementing the I/O driver interface

In the following you will find some general rules to follow while implementing your driver. Beside this guide, you should check out the different templates for different kinds of drivers to start from. Because in detail the implementation will be diffferent if you implement an SPI driver or you read a value from a sensor using a REST API.

But independent of the kind of driver, which you plan to implement. If you are not routined in writing such software, it is a good idea to prove the access of the hardware directly in an application.

Device Description

Every device, which you can add to your CODESYS project, is described in an XML format inside of a Device Description.

For finetuning and to get a deeper understanding of the format, please check out the schema file. In the following we will just explain the basics to enable you to write your first description.

XML Schema

The XML schema can be downloaded under the URI, which is defined in every XML file:
http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd

Device identification

That the device can be uniquely identified in the device repository on every computer in the world, we need to maintain a few IDs. Please register the ID, which you will be using in the Device Database.

  • Device Type: Either use the matching one from the templates, or use 8000.
  • VendorID: Use 0004, as this is reserved for all public domain drivers
  • DeviceID: Use a unique one, which you added to the Device Database
  • Version: You are free with that, but you should always increase it when you make changes to the device description

Note: The combination of all IDs, described above has to be globally unique. Official versions of a driver with the same IDs, but different content shall never exist.

Connector

A device or module, which you can add to the device tree in CODESYS contains at least one connector. This connector is used to attach the module to a parent device.

    <Connector moduleType="8023" interface="Common.PCI" role="child" explicit="false" connectorId="1" hostpath="-1">
      <InterfaceName name="local:PCI">PCI-Bus</InterfaceName>
      <Slot count="1" allowEmpty="false"></Slot>
    </Connector>
  • moduletype: use 8000 + the DeviceID. This way you make sure, that you have no conflicts with other public domain drivers.
  • interface: For submodules, like SPI, I2C, or similar, use the interface from the template. For all others, you can use Common.PCI. This interface can be easily attached to most PLCs.
  • ConnectorID: simply enumerate the connectors within your modules. This ID is only used to specify the so called "host path"

Interfaces

Interfaces are symbolic names to match compatible connectors, so to find compatible devices or modules. We have some different concepts to configure our device tree in CODESYS.

  • Variable Connectors
    You don't see anything in the device tree. But when you click on "Add Device" you get a list of all devices, which are providing an interface, that is compatible to the interfaces of the parent. The number of allowed devices which can be added to a variable connector can be varied.
    <Connector moduleType="8023" interface="OpenSource:Internal" role="parent" explicit="false" connectorId="2" hostpath="1">
      <InterfaceName name="local:internal">Internal</InterfaceName>
      <Var max="4"></Var>
  • Fix connectors
    Fix connectors are very similar to Variable connectors, but the devices are added implicitely and can't be changed.
    <Connector moduleType="8023" interface="OpenSource:FixConnectorInterface" role="parent" explicit="false" connectorId="2" hostpath="1">
      <InterfaceName name="local:internal">Internal</InterfaceName>
      <Fixed>
        <!-- This is an example of a fixed module specified in the same file -->
        <Module>
          <LocalModuleId>8023</LocalModuleId>
        </Module>
        <!-- This is an example of a fixed module specified by DeviceIdentification. The defininition of this module is in another *.devdesc.xml -->
        <Module>
          <DeviceIdentification deviceType="40107" deviceId="0001 bcde" version="3.5.9.0"/>
        </Module>
      </Fixed>
  • Slots
    Slots are again similar to variable slots. But the user can see the slot in the device tree. Because every slot has a fix place in the tree, and because the user can plug and remove the devices, the specialty for the driver is, that there can be empty slots.

This was a huge preamble for an easy topic. But it was important that you get an idea of how the result will look like.

Interfaces are easy. You define one interface per child connector and one for every parent connector. There are no fix restrictions for the interface names. But it is an unofficial rule, that they should be prefixed with a short or full name of the vendor.

      <Connector ConnectorId="2" HostPath="-1" interface="MyCompany.A" moduleType="40101"
                 role="parent">
         <Slot allowEmpty="false" count="16"/>
      </Connector>
      <Connector ConnectorId="3" HostPath="-1" interface="MyCompany.B" moduleType="40102"
                 role="parent">
         <Slot allowEmpty="false" count="16"/>
      </Connector>
...
   <Modules>
      <Module>
         <ModuleId>1701</ModuleId>
         <DeviceInfo>
            <Name name="localStrings:Name1704">Digital Input</Name>
            <Description name="localStrings:Desc1704"/>
            <Vendor name="localStrings:3S">3S-Smart Software Solutions</Vendor>
            <OrderNumber/>
         </DeviceInfo>
         <Connector ConnectorId="2" HostPath="-1" interface="MyCompany.A" moduleType="41101"
                    role="child">
            <Slot allowEmpty="false" count="1"/>
            <HostParameterSet>
               <Parameter ParameterId="1000" type="std:BIT">
                  <Attributes channel="input" download="true" functional="false" offlineaccess="readwrite"
                              onlineaccess="readwrite"/>
                  <Default>0</Default>
                  <Name name="local:in1">in1</Name>
               </Parameter>
            </HostParameterSet>
         </Connector>
      </Module>
      <Module>
         <ModuleId>1702</ModuleId>
         <DeviceInfo>
            <Name name="localStrings:Name1705">Digital Output</Name>
            <Description name="localStrings:Desc1705"/>
            <Vendor name="localStrings:3S">3S-Smart Software Solutions</Vendor>
            <OrderNumber/>
         </DeviceInfo>
         <Connector ConnectorId="3" HostPath="-1" interface="MyCompany.A" moduleType="41102"
                    role="child">
            <Slot allowEmpty="false" count="1"/>
            <HostParameterSet>
               <Parameter ParameterId="1000" type="std:BIT">
                  <Attributes channel="output" download="true" functional="false" offlineaccess="readwrite"
                              onlineaccess="readwrite"/>
                  <Default>0</Default>
                  <Name name="local:in1">in1</Name>
               </Parameter>
            </HostParameterSet>
         </Connector>
      </Module>
      <Module>
         <ModuleId>1703</ModuleId>
         <DeviceInfo>
            <Name name="localStrings:Name1705">Digital Output</Name>
            <Description name="localStrings:Desc1705"/>
            <Vendor name="localStrings:3S">3S-Smart Software Solutions</Vendor>
            <OrderNumber/>
         </DeviceInfo>
         <Connector ConnectorId="4" HostPath="-1" interface="MyCompany.B" moduleType="41103"
                    role="child">
            <Slot allowEmpty="false" count="1"/>
            <HostParameterSet>
               <Parameter ParameterId="1000" type="std:BIT">
                  <Attributes channel="output" download="true" functional="false" offlineaccess="readwrite"
                              onlineaccess="readwrite"/>
                  <Default>0</Default>
                  <Name name="local:in1">in1</Name>
               </Parameter>
            </HostParameterSet>
         </Connector>
      </Module>
      <Module>
         <ModuleId>1704</ModuleId>
         <DeviceInfo>
            <Name name="localStrings:Name1706">PWM Output</Name>
            <Description name="localStrings:Desc1706"/>
            <Vendor name="localStrings:3S">3S-Smart Software Solutions</Vendor>
            <OrderNumber/>
         </DeviceInfo>
         <Connector ConnectorId="5" HostPath="-1" interface="MyCompany.B" moduleType="41104"
                    role="child">
            <Slot allowEmpty="false" count="1"/>
         </Connector>
      </Module>
   </Modules>

Parameter Set

A device can roughly hold two kinds of parameters:

  • configuration parameters, which are important to initialize the I/O system
  • I/O channels, which can be mapped to variables

I/O Channels

Data types, default values, etc. are defined equally for all kinds of parameters. But there is a significant difference in how the different kinds of parameters are processed.

I/O channels are processed in the same way as configuration parameters, thus they have additional mapping information assigned to it, so that they are also passed on to IoDrvReadInputs/-WriteOutputs.

I/O channels are marked with the "channel" attribute.

<Attributes channel="input" download="true" functional="false" offlineaccess="write" onlineaccess="readwrite" />

Other parameter attributes

  • Download specifies if the parameter is downloaded as part of the connectorlist, that is passed to IoDrvUpdateConfiguration
  • Functional specifies if reading or writing the parameter results in a function call to IoDrvReadParamet/-WriteParameter
  • Online-/OfflineAccess defines the allowed permissions in online or offline mode.

Datatypes

Both can be configured with the same structured datatypes.

They can be located globally on device level, then they are called DeviceParameters. If they are part of the device that the user adds to the tree, then, they are called HostParameters and attached to one of its connectors.

You can use virtually every datatype for a parameter, which can be used in IEC61131. This includes especially arrays and structures.
But you have to be aware that you need to define those datatypes consistently twice (in your driver library and your device description). There is no extra check. If they don't match between your device description and your code, it will just not work, crash, or whatever...

Some examples...

Basic datatypes

Basic datatypes don't need a declaration. They can be used with the namespace prefix "std". So "std:BOOL" is the equivalent of a BOOL datatype in IEC.

DevDesc:

        <Parameter ParameterId="1000" type="std:DWORD">
          <Attributes channel="input" download="true" functional="false" offlineaccess="write" onlineaccess="readwrite" />
          <Default>0</Default>
          <Name name="local:DWIN">DWORD Input</Name>
          <Description name="local:DWIN.Desc">DWORD Input</Description>
        </Parameter>

Structures

IEC:

TYPE DUT :
STRUCT
    BYTE1 : BYTE;
    BYTE2 : BYTE;
    BYTE3 : BYTE;
    BYTE4 : BYTE;
END_STRUCT
END_TYPE

DevDesc:

  <Types namespace="local">
   <StructType name="Channel4Byte">
      <Component identifier="Byte0" type="std:BYTE">
        <Default />
        <VisibleName name="local:Byte0">Byte0</VisibleName>
      </Component>
      <Component identifier="Byte1" type="std:BYTE">
        <Default />
        <VisibleName name="local:Byte1">Byte1</VisibleName>
      </Component>
      <Component identifier="Byte2" type="std:BYTE">
        <Default />
        <VisibleName name="local:Byte2">Byte2</VisibleName>
      </Component>
      <Component identifier="Byte3" type="std:BYTE">
        <Default />
        <VisibleName name="local:Byte3">Byte3</VisibleName>
      </Component>
    </StructType>
    </Types>
    ...
        <Parameter ParameterId="1000" type="local:Channel4Byte">
          <Attributes channel="input" download="true" functional="false" offlineaccess="write" onlineaccess="readwrite" />
          <Default>0</Default>
          <Name name="local:DWIN">DWORD Input</Name>
          <Description name="local:DWIN.Desc">DWORD Input</Description>
        </Parameter>

Bit Channels

IEC:

TYPE DUT :
STRUCT
    BYTE1 : BYTE;
    BYTE2 : BYTE;
    BYTE3 : BYTE;
    BYTE4 : BYTE;
END_STRUCT
END_TYPE

DevDesc:

    <BitfieldType basetype="std:BYTE" name="Bitfield">
      <Component identifier="Bit0" type="std:BOOL">
        <Default />
        <VisibleName name="local:Bitfield.Bit0">Bit0</VisibleName>
      </Component>
      <Component identifier="Bit1" type="std:BOOL">
        <Default />
        <VisibleName name="local:Bitfield.Bit1">Bit1</VisibleName>
      </Component>
      <Component identifier="Bit2" type="std:BOOL">
        <Default />
        <VisibleName name="local:Bitfield.Bit2">Bit2</VisibleName>
      </Component>
      <Component identifier="Bit3" type="std:BOOL">
        <Default />
        <VisibleName name="local:Bitfield.Bit3">Bit3</VisibleName>
      </Component>
      <Component identifier="Bit4" type="std:BOOL">
        <Default />
        <VisibleName name="local:Bitfield.Bit4">Bit4</VisibleName>
      </Component>
      <Component identifier="Bit5" type="std:BOOL">
        <Default />
        <VisibleName name="local:Bitfield.Bit5">Bit5</VisibleName>
      </Component>
      <Component identifier="Bit6" type="std:BOOL">
        <Default />
        <VisibleName name="local:Bitfield.Bit6">Bit6</VisibleName>
      </Component>
      <Component identifier="Bit7" type="std:BOOL">
        <Default />
        <VisibleName name="local:Bitfield.Bit7">Bit7</VisibleName>
      </Component>
    </BitfieldType>
   <StructType name="local:Channel4Byte">
      <Component identifier="Byte0" type="local:Bitfield">
        <Default />
        <VisibleName name="local:Byte0">Byte0</VisibleName>
      </Component>
      <Component identifier="Byte1" type="local:Bitfield">
        <Default />
        <VisibleName name="local:Byte1">Byte1</VisibleName>
      </Component>
      <Component identifier="Byte2" type="local:Bitfield">
        <Default />
        <VisibleName name="local:Byte2">Byte2</VisibleName>
      </Component>
      <Component identifier="Byte3" type="local:Bitfield">
        <Default />
        <VisibleName name="local:Byte3">Byte3</VisibleName>
      </Component>
    </StructType>
    ...
        <Parameter ParameterId="1000" type="local:Channel4Byte">
          <Attributes channel="input" download="true" functional="false" offlineaccess="write" onlineaccess="readwrite" />
          <Default>0</Default>
          <Name name="local:DWIN">DWORD Input</Name>
          <Description name="local:DWIN.Desc">DWORD Input</Description>
        </Parameter>

Driver Interface

Please always start with a template, when you start writing a driver. This will give you a good skeleton of your driver. Anyway, this chapter contains a few basic informations about specific interface functions.

IoDrvUpdateConfiguration

This is always the first entry point when you start writing a driver. It is called when a program is loaded, and gives all drivers the chance to register itself for specific connectors of the device tree.

Additionally it gives the driver the chance to prepare itself and to configure the I/O system.

This function is called with pConnectorList set to 0, when the application is deleted or reseted. So all drivers need to handle that.

IF (pConnectorList = 0) THEN
    RETURN;
END_IF

Registering for a connector

Check the template for details. In general you search for a connector by its "Module ID" . Then you register your base interface pointer at this connector.

This is enough, that you are called by the I/O manager for every I/O update.

IF m_pConnector^.hIoDrv = 0 THEN
    m_pConnector^.hIoDrv := m_hInterface;

Reading Configuration

You get the whole built time configuration of the application passed to this function. Read those values with IoMgrConfigReadParameter to configure your subsystem.

Preparing I/O channels

The I/O channels are also just a special type of parameter, and can be read in this early stage. You can use the value "dwDriverSpecific" to prepare your I/Os in a way that you can quickly access them later on.

The best way is to store a pointer to the I/O data there. Those can be perfectly used with IoMgrCopy... in IoDrvReadInputs/-WriteOutputs.

    FOR i:=0 TO 7 DO
        pParameter := IoMgrConfigGetParameter(m_pConnector, 1000 + i);
        IF (pParameter <> 0) THEN
            pParameter^.dwDriverSpecific := ADR(_MCP3008.auiValue[i]);
        END_IF
    END_FOR

IoDrvReadInputs / Iodrvwriteoutputs

CODESYS reduces the updates of the I/O to the bare minimum. The compiler detects already which I/O is used in which task, and calls the drivers from those task contexts in which they are used.

To allow the driver to update only the necessary I/O variables, you get a list of mappings passes to those functions.

FOR i:=0 TO nCount - 1 DO
    IF (pConnectorMapList[i].dwNumOfChannels = 0) THEN
        CONTINUE;
    END_IF
    FOR j:= 0 TO UDINT_TO_UINT(pConnectorMapList[i].dwNumOfChannels) - 1 DO 
        IoMgrCopyInputLE(ADR(pConnectorMapList[i].pChannelMapList[j]), pConnectorMapList[i].pChannelMapList[j].pParameter^.dwDriverSpecific);
    END_FOR
END_FOR

Related

Documentation: IndexMain

Discussion

  • aliazzz

    aliazzz - 2018-09-30

    <attributes download="true" offlineaccess="write" functional="false" channel="input" onlineaccess="readwrite"></attributes>

    => Please explain these meta tags, as they are important ;-)

     
    • Ingo

      Ingo - 2018-10-06

      I tried to explain that by reworking parts of this chapter. Hope it helps...

       
      • aliazzz

        aliazzz - 2018-10-20
         

        Last edit: aliazzz 2019-01-18
  • aliazzz

    aliazzz - 2018-10-20

    ...

     

    Last edit: aliazzz 2018-10-20
  • BG_Automation

    BG_Automation - 2019-12-09

    I have a question about the structures. I will start with the BIT structure. It looks

    Should the code read
    TYPE Bitfield :
    STRUCT
    Bit0 : Bit;
    Bit1 : Bit;
    Bit2 : Bit;
    Bit3 : Bit;
    END_STRUCT
    END_TYPE

    Device Description

    <BitfieldType basetype="std:BYTE" name="Bitfield">
      <Component identifier="Bit0" type="std:BOOL">
        <Default /> ....
    
     
    • aliazzz

      aliazzz - 2019-12-09

      No,

      You should declare a BYTE in the PLC Code

      byBitfield : BYTE;
      

      Then you can address each bit as follows;

      byBitfield.0 := xMyBool0;
      ..
      byBitfield.7 := xMyBool7;
      

      Try to avoid own DUT's (enums, structs) in the Devdesc.xml and stick to basic primitives like BYTE, DINT, REAL, WORD etc. This will make your life easier in editing the devdesc.xml.

      Offcourse if you want to use them, no problem, but you have to declare them in your code AND the devdes.xml file, so in two seperate places!

      Good luck

       

      Last edit: aliazzz 2019-12-09
  • BG_Automation

    BG_Automation - 2019-12-24

    If I want to create a module file for a SPI bus device, what module filter do I use.

    For example, an ethercat fieldbus module uses a description file like this:
    <ethercatmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:nonamespaceschemalocation="EtherCATModule.xsd"></ethercatmodule>

    Is there a SPI module?

    Since the device I am using has multiple types of devices, I thought plugging modules would be the best.

     

    Last edit: BG_Automation 2019-12-25
  • Ingo

    Ingo - 2019-12-25

    Did you already check the [SPI] page?

    Additionally you can find an SPI Template driver in the code repository.

     

    Related

    Documentation: SPI

    • BG_Automation

      BG_Automation - 2019-12-29

      Yes, I did locate the SPI template titled SPI_Template.devdesc.xml, I probably did not explain myself well enough based on your response. I am looking for a template to create a plug in SPI module to plug into a connector slot. I did not see a template for a SPI_Modules_V1.0.xml file. I have successfully created 8 slots, now I want to create a module to plug into one of the slots. I also would like to see how to iterate through each module and and perform an action based on the module type.

      For example, I have a relay card and a DAC card on a SPI bus with addresses 0 and 1 respectively. I plug the modules into my device slots. I have 8 slots available for devices. I put the device type as host parameter and use this parameter as a key to process the information. As I go through a loop in the FB, I will send data in a format to turn relays on, the change the data to send to the DAC module. The data will be formated for the module type and sent through the SPI bus.

      1.) How do I create a module file?
      2.) How do I iterate through each slot and read the host parameters?
      3.) How do I link the inputs and outputs to the function blocks?

      While I am researching this, I am working on interfacing a relay module using SPI. I have this somewhat working but needs more work.

      BG

       

      Last edit: BG_Automation 2019-12-29
      • Ingo

        Ingo - 2019-12-29

        Hi Brian,

        I think I understand your situation a bit better now.

        1) module file
        I would recommend to stay with one file. Modules are just additional devices, defined in the same device description file. They share virtually every possible tags with the devices, except those, which are identifying the device in the device repository.

        So, you can define a module parallel to the device in your device description:

        ...
        </Device>
        <Modules>
              <Module>
                 <ModuleId>1704</ModuleId>
                 <DeviceInfo>
                    <Name name="localStrings:Name1704>Digital Input</Name>
                    <Description name="localStrings:Desc1704"/>
                    <Vendor name="localStrings:3S">3S-Smart Software Solutions</Vendor>
                    <OrderNumber/>
                 </DeviceInfo>
                 <Connector ConnectorId="2" HostPath="-1" interface="MyCompany.A" moduleType="41101"
                            role="child">
                    <Slot allowEmpty="false" count="1"/>
                    <HostParameterSet>
                    ...
        

        2) iterating over slots
        As I don't know your exact connector layout, I can give you only a vague answer. There are functions to iterate over the connectors and parameters, when you write an I/O driver. Those functions are located in the library IoStandard, and called:

        • IoMgrGetFirstConnector
        • IoMgrGetNextConnector
        • IoMgrGetFirstChildConnector
        • IoMgrGetNextChildConnector

        And you guess it, you can iterate over a connector tree with those functions. So I guess, that independent of your exact connector layout, it should be no rocket science.
        The only tricky part is, that you need to keep track over the remaining connectors in the list.

        You get the number of connectors passed to IoDrvUpdateConfiguration, and this count is reduced when you call one of the functions above. But when you return from iterating through the childs, you need the previous count again.

        So as a design pattern, you should simply use a copy of the connector count variable for iterating over the childs.

        3) linking inputs and outputs to function blocks
        I personally always try to set the driver specific value of the parameters, corresponding to my I/O channels to the pointer of the I/Os of the function block.

        Then I can use the functions IoMgrCopyInputs/Outputs in IoDrvReadInputs/WriteOutputs. This makes live a bit easier in those higher frequently called functions.

        Hope I could help a bit

         
  • BG_Automation

    BG_Automation - 2020-01-03

    Your comments above helped out a lot but I am a bit confused on how to create the methods for IoDrvReadInputs/WriteOutputs. I added these two methods to my library and they don't seem to be called.

    So lets say I have my Output to Relay1. I added this to my host parameter section as with an attribute of being an output. It shows up in my IO Map channel list in my project and I map a value to Relay1. All of this works for me so far, but I am lost on how the data moves from my project to the library and how to link the data in the library. I have tried getting the pointer to this object using pParameter := IoMgrConfigGetParameter(m_pConnector, 1000 + i); and the pointer does not seem to do anything.

    Is there a good driver project that illustrates this method to us? I think the last two sections of preparing I/O channels is vague and needs a little more explanation. This page has been an excellent guide on how to develop drivers.

    Thanks for the help!

    BG

     
    • Ingo

      Ingo - 2020-01-03

      The MCP3008 driver is doing it this way.

      Step 1) Implement the I/O Driver Interface.

      For this, the driver defines a new FB, implenting this interface:

      FUNCTION_BLOCK IoDrvMCP3008 EXTENDS IoDrvBase
      

      Step 2) Instantiate the driver FB in your devdesc:

              <RequiredLib libname="Linux SPI MCP3008" vendor="Open Source" version="1.0.0.0" identifier="deviceLib">
                <FBInstance basename="$(DeviceName)" fbname="IoDrvMCP3008">
                  <Initialize methodName="Initialize" />
                </FBInstance>
              </RequiredLib>
      

      Step 3) instantiate your SPI FB inside of the drivrt FB:

      _MCP3008: MCP3008;
      

      Step 4) Map the I/O channels to the inputs and outputs of your SPI FB

          //Setup I/O area
          FOR i:=0 TO 7 DO
              pParameter := IoMgrConfigGetParameter(m_pConnector, 1000 + i);
              IF (pParameter <> 0) THEN
                  pParameter^.dwDriverSpecific := ADR(_MCP3008.auiValue[i]);
              END_IF
          END_FOR
      

      Step 5) Call IoMgrCopyXXX in ReadInputs / WriteOutputs

      FOR i:=0 TO nCount - 1 DO
          IF (pConnectorMapList[i].dwNumOfChannels = 0) THEN
              CONTINUE;
          END_IF
          FOR j:= 0 TO UDINT_TO_UINT(pConnectorMapList[i].dwNumOfChannels) - 1 DO 
              IoMgrCopyInputLE(ADR(pConnectorMapList[i].pChannelMapList[j]), pConnectorMapList[i].pChannelMapList[j].pParameter^.dwDriverSpecific);
          END_FOR
      END_FOR
      
       
      • BG_Automation

        BG_Automation - 2020-01-05

        I have this partially working, but I seem to be missing something. My driver copies the first Channel Output which is a BYTE, but does not copy the boolean. It looks like the channel map stops at the first byte. I have read through the xml file over and over again, it pretty much matches what you have in the

        Can you please explain how this line works?
        pParameter^.dwDriverSpecific := ADR(_MCP3008.auiValue[i]);

        I don't understand how the channel map number of channels increments, looking at the line of code above, it looks like the dwDriverSpecific value keeps getting overwritten by the 8 addresses of the auiValue and just the last address is in the pParameter^.dwDriverSpecific.

        So this is what I have:

        I can see the value in the first byte named Relays, but the LED value never changes.

            //Setup I/O area
        
                pParameter := IoMgrConfigGetParameter(m_pConnector, 1000);
                IF (pParameter <> 0) THEN
                    pParameter^.dwDriverSpecific := ADR(_RelayPlate.Relays);
                END_IF
                pParameter := IoMgrConfigGetParameter(m_pConnector, 1001);
                IF (pParameter <> 0) THEN
                    pParameter^.dwDriverSpecific := ADR(_RelayPlate.LED);
                END_IF
                pParameter := IoMgrConfigGetParameter(m_pConnector, 1002);
                IF (pParameter <> 0) THEN
                    pParameter^.dwDriverSpecific := ADR(_RelayPlate.ppFrame);
                END_IF
                pParameter := IoMgrConfigGetParameter(m_pConnector, 1003);
                IF (pParameter <> 0) THEN
                    pParameter^.dwDriverSpecific := ADR(_RelayPlate.FirmwareVersion);
                END_IF
                pParameter := IoMgrConfigGetParameter(m_pConnector, 1004);
                IF (pParameter <> 0) THEN
                    pParameter^.dwDriverSpecific := ADR(_RelayPlate.HardwareVersion);
                END_IF
        
        
          XML
        
              <HostParameterSet>
                <Parameter ParameterId="1000" type="std:BYTE">
                  <Attributes channel="output" download="true" functional="false" onlineaccess="readwrite" />
                  <Default/>
                  <Name name="Relays">Relays</Name>
                  <Description name="Relays_Desc">Relays on Relay Plate</Description>
                </Parameter>
                <Parameter ParameterId="1001" type="std:BOOL">
                  <Attributes channel="output" download="true" functional="false" onlineaccess="readwrite" />
                  <Default/>
                  <Name name="LED">Led</Name>
                  <Description name="LED_Desc-">LED CONTROL</Description>
                </Parameter>
                <Parameter ParameterId="1002" type="std:BIT">
                  <Attributes channel="input" download="true" functional="false" onlineaccess="readwrite" />
                  <Default>0</Default>
                  <Name name="Control_IO">Control IO Port</Name>
                  <Description name="Control_IO_Desc-">Control IO Port</Description>
                </Parameter>
                <Parameter ParameterId="1003" type="std:BYTE">
                  <Attributes channel="input" download="true" functional="false" onlineaccess="readwrite" />
                  <Default>0</Default>
                  <Name name="FW_Version">Firmware Version</Name>
                  <Description name="FW_VersionDesc-">Firmware Version of Relay Plate</Description>
                </Parameter>
                <Parameter ParameterId="1004" type="std:BYTE">
                  <Attributes channel="input" download="true" functional="false" onlineaccess="readwrite" />
                  <Default>0</Default>
                  <Name name="HW_Version">Hardware Version</Name>
                  <Description name="HW_VersionDesc-">Hardware Version of Relay Plate</Description>
                </Parameter>
              </HostParameterSet>
        
         
        • Ingo

          Ingo - 2020-01-05

          Looks good!
          Which data type is the vqriable LED? You know, that for a boolean channel, just one bit is copied.

          The line:

          pParameter^.dwDriverSpecific := ...
          

          ... is storing the address of the variable of your driver FB in the structure of the parameter. This address is then used in ReadInputs/WriteOutputs to copy the I/O data quickly there.

           
          • BG_Automation

            BG_Automation - 2020-01-05

            The led is a bool, all I can do with the LED is turn it on or off. The odd part to me is the ppFrame is a also a bool and it works good, but the output LED does not. I set the LED to true in my program and the value does not pass through into the library.

            *** Update, after playing with the code for a little bit, I found out I had to add it to my program. I was just trying to force the value to make it work. Once I added a LED coil to my program as a coil it started to work.

            Thanks again for your help!

             

            Last edit: BG_Automation 2020-01-05
  • i-campbell

    i-campbell - 2020-01-21

    Can you please clarify if we should use 0004 or 0003 as the vendor ID? I see in the documentation and some of the projects, these vendor IDs are used interchangeably.

     
    • Ingo

      Ingo - 2020-01-22

      Hi Ian,

      this is what counts for open source drivers:
      https://forge.codesys.com/drv/io-drivers/database/

      If you use another range, or you don't register your ID, you are not safe against conflicts with deivers from others.

       
  • FabioPD

    FabioPD - 2020-02-26

    Hello can I create a driver for the I2C AM2315 (https://learn.adafruit.com/am2315-encased-i2c-temperature-humidity-sensor/arduino-code) ?
    or there are some packages to download? Thanks a lot!

     
    • i-campbell

      i-campbell - 2020-02-26

      Hi Fabio,
      you've found the driver section already! Yes you will need to create your own driver. I look forward to seeing it, and or lending pointers where needed.

       
      • FabioPD

        FabioPD - 2020-02-26

        If I create it with this tutorial , after that can the moderator add the driver in the list?

         

        Last edit: FabioPD 2020-02-28
        • Ingo

          Ingo - 2020-02-28

          Hi Fabio,

          no moderation necessary. Just create your own driver project by clicking on "register project" in driver neighborhood. Then describe your work in the wiki of the project, upload all code and devdescs to SVN, and you are done.

          To register your driver ID, just go to database and add it there.

           

          Last edit: Ingo 2020-02-28
          • FabioPD

            FabioPD - 2020-03-09

            Hello Ingo I can't undestand how to merge the io-drivers-code-r13-trunk folder in my codesys project.... Have I to use SVN and install CODESYS SVN or I can do without? The process how to build a driver is not clear!

             
            • Ingo

              Ingo - 2020-03-10

              Yes, you need to use CODESYS SVN. But don't worry about the license.
              Whenyou use it with CODESYS Forge, you can use it w/o a license.

              Indeed you are right. The workflow with CODESYS SVN is not well known, yet.
              So a tutorial might be good.

               

Log in to post a comment.