Driver Documentation
Database
This documentation describes the process of creating an own SPI driver for CODESYS, based on the SPI Template, which can be found in the Code Repository. The driver consists of a "Device Description" as well as a CODESYS Library. All essential settings in the Library or the Device Description are repeated in this documentation. For more detailed informations about the Device Descriptions, please check the general I/O driver documentation.
You can use a copy of the Device Description SPI_Template.devdesc.xml as a starting point. You need to adapt the following sections of the device description for your needs.
The template of the device description can be downloaded with an ordinary SVN client (like Tortoise SVN) or directly from the repository browser, from the Code Repository.
<DeviceDescription>
...
<Device>
...
<DeviceIdentification>
...
<Type>501</Type>
...
</DeviceIdentification>
...
</Device>
...
</DeviceDescription>
<DeviceDescription>
...
<Device>
...
<DeviceIdentification>
...
<Id>0003 0001</Id>
...
</DeviceIdentification>
...
</Device>
...
</DeviceDescription>
<DeviceDescription>
...
<Device>
...
<DeviceIdentification>
...
<Version>1.0.0.0</Version>
...
</DeviceIdentification>
...
</Device>
...
</DeviceDescription>
The connector describes the node in the device tree, which the user sees after he added your device. The I/O driver will search for this connector.
<DeviceDescription>
...
<Device>
...
<Connector moduleType="500" interface="Raspberry.SPI" ...>
...
...
</Connector>
...
</Device>
...
</DeviceDescription>
<DeviceDescription>
...
<Device>
...
<Connector moduleType="500" interface="Raspberry.SPI" ...>
...
...
</Connector>
...
</Device>
...
</DeviceDescription>
This section specifies some parameters of the driver, and in our case, also which library should be used.
<DeviceDescription>
...
<Device>
...
<Connector moduleType="500" interface="Raspberry.SPI" ...>
...
<DriverInfo needsBusCycle="false">
...
<RequiredLib libname="SPI Template" vendor="Open Source" version="1.0.0.0" identifier="SPI Template">
...
</RequiredLib>
...
</Parameter>
...
</Connector>
...
</Device>
...
</DeviceDescription>
<DeviceDescription>
...
<Device>
...
<Connector moduleType="500" interface="Raspberry.SPI" ...>
...
<DriverInfo needsBusCycle="false">
...
<RequiredLib libname="SPI Template" vendor="Open Source" version="1.0.0.0" identifier="SPI Template">
...
<FBInstance basename="$(DeviceName)" fbname="SPITemplate">
...
<Initialize methodName="Initialize" />
<CyclicCall methodname="AfterReadInputs" task="#buscycletask" whentocall="afterReadInputs" />
<CyclicCall methodname="BeforeWriteOutputs" task="#buscycletask" whentocall="beforeWriteOutputs" />
...
</FBInstance>
...
</RequiredLib>
...
</Parameter>
...
</Connector>
...
</Device>
...
</DeviceDescription>
The host parameter set defines all your configuration parameters, as well as I/O channels. In the current SPI interface, you can use configuration parameters, but no I/O channels.
The configuration parameters can be read in the library within the function "Initialize".
<DeviceDescription>
...
<Device>
...
<Connector moduleType="500" interface="Raspberry.SPI" ...>
...
<HostParameterSet>
...
<Parameter ...>
...
</Parameter>
...
</Connector>
...
</Device>
...
</DeviceDescription>
For more information about the HostParameterSet and datatypes, please check the general I/O driver documentation.
The template library can be checked out with CODESYS SVN or alternatively with CODESYS SVN Forge. Both can be obtained from the CODESYS Store. As well as the device description, it is placed in the code repository.
It is a standard CODESYS Library, which needs the standard information to behave as expected to the user. We describe all bits, which need to be changed in the following documentation.
The function block can be renamed. But make sure, that you change the "FBInstance" parameter in the device description accordingly. It has to be extended from "SPI", which is part of the "Raspberry Pi Peripherals" library.
FUNCTION_BLOCK SPITemplate EXTENDS spi
VAR_INPUT
END_VAR
VAR_OUTPUT
dwRaw : DWORD;
rValue : REAL;
END_VAR
In this example, the output of this function block is the output of the SPI driver, and the value which will be used by the user in his application. So the value can be used like this in the application:
rMyTemperature := SPITemplate.rValue;
All methods, which are overloading in your function block, need to call the super method, so that the method of the base SPI function block is still executed.
SUPER^.AfterReadInputs();
IF _iState = 10 THEN
FOR usiChannel := 0 TO 0 DO
aby[0] := 1;
aby[1] := 16#80 + SHL(usiChannel AND 7, 4);
aby[2] := 0;
aby[3] := 0;
IF NOT transfer(pabyTxBuffer:=ADR(aby) , pabyRxBuffer:=ADR(aby) , udiLen:=3 , uiDelayus:=0) THEN
_iState := 1000;
END_IF
CASE usiChannel OF
0:
dwRaw := aby[3];
dwRaw := SHL(dwRaw,8) OR aby[2];
dwRaw := SHL(dwRaw,8) OR aby[1];
dwRaw := SHL(dwRaw,8) OR aby[0];
rValue := DWORD_TO_REAL(SHR(dwRaw, usiBitWidth));
rValue := rValue * rResolution;
END_CASE
END_FOR
END_IF
BeforeWriteOutputs can be used in a similar way to write the outputs to the SPI device. The output is read from the "input" of the function block.
Initialize is a special method which can be used to read configuration parameters. When the application is loaded, this function is called, and the corresponding "Connector" (s. Device Description documentation) is passed to it. You can then use IoStandard.ConfigGetParameter() to access the configuration parameters.
SUPER^.Initialize(wModuleType, dwInstance, pConnector);
pParam := ConfigGetParameter(_pConnector, 1000);
IF pParam <> 0 THEN
pusiBitWidth := IoStandard.ConfigGetParameterValuePointer(pParam, ADR(udiResult));
usiBitWidth := pusiBitWidth^;
END_IF
pParam := ConfigGetParameter(_pConnector, 1001);
IF pParam <> 0 THEN
prResolution := IoStandard.ConfigGetParameterValuePointer(pParam, ADR(udiResult));
rResolution := prResolution^;
END_IF
You might have noticed the few lines above:
aby[0] := 1;
aby[1] := 16#80 + SHL(usiChannel AND 7, 4);
aby[2] := 0;
aby[3] := 0;
IF NOT transfer(pabyTxBuffer:=ADR(aby) , pabyRxBuffer:=ADR(aby) , udiLen:=3 , uiDelayus:=0) THEN
_iState := 1000;
END_IF
In SPI you always have to write out the same number of bytes, which you plan to receive. The method "transfer" is actually doing the transfer on the SPI bus. The addressing code is stolen from the MCP3008 chip. There you have one start bit (in bytes 0), and then a "command", which defines the ADC we want to read.
For more informations about the MCP3008, you may check out these pages from Adafruit.
As the data is shifted out on the SPI bus, and then received, you can use the same buffer for transmit and receive (like we did in the example above).
In the "Project Information" you have to enter few names and identifiers:
As the Company, you can use "Open Source" if it is a plain open source driver (corresponds to the VendorID 0003 of the Device Description).
The Title of the library is the name, which the user selects in the library repository when he adds your library manually.
The Version can be freely defined. But it usually makes sense to keep this version in sync with the "Device Description"
Now, we switch to the more advanced properties of our library.
The property DefaultNamespace defines the namespace prefix, which one has to use to access POUs of your library. We recommend, that you use the same as the library name, but w/o spaces.
The property Placeholder is important to set. But deviations from the library name are for more advanced use cases. So just enter the same as you entered in Title
The rest of the properties can be usually ignored.
Documentation: Generic
Documentation: IndexMain
more posts ...
Suggested Edit:
SPI Transfer
You might have noticed the few lines above:
Thanks for the report!
I am having trouble writing to the SPI bus.
I have a python script I am trying to translate to codesys.
SPI Master settings in codesys:
Max Hz 500000
bits per word is 8
port mode is 1
It seems to transfer with no errors, but nothing happens.
If I run the code in python then everything works. I can toggle the relay on and off with the following code.
import piplates.RELAYplate as RELAY
import time
RELAY.relayTOGGLE(0,1)
Do I need to open the spi port? Is there something more that I need to do?
Your code looks good. I expect, that you are missing some init code. Did you already try "strace" on your python script?
I would either try strace or a python debugger to get a better understanding of the python code.
In CODESYS you can't do much wrong. Except that you might have the wrong device or frequency configured in the SPI master of your project.
Especially the frequency can be important, as on some raspbian versions, the highest possible frequency was not limited correctly. Therefore you need to configure it in the SPI master device in your CODESYS project. (Took me some hours once ;) )
But you don't need to open anything. This is done by the SPI master already.
I figured out the problem with communicating over the SPI bus.
I though this line of code in Python [spi.open(0,1) ] meant open port 0 in mode 1. I found out that this actually means open port 0, device 1. In the SPI master device I needed to replace the value in the SPI Port parameter from '/dev/spidev0.0' with '/dev/spidev0.1'. Once I did this everything started working. I am moving on to complete the driver.
Thanks for your help, confirming the code told me to look in a new direction.
Thanks for confirming the code. Knowing the code is good forced me to look in a new direction. The python line of code [spi.open(0,1)] was the area I needed to focus on. I read some website that said spi.open(0,1) meant open port 0 in mode 1. Then I found out it meant open port 0 using device 1. I changed the settings in the SPI master on the in the device parameters. I put as spidev0.1 instead of spidev0.0. Then everything started to work.
Thanks for the help!