Creating devices

What is a 'device'?

In the GEB Automation IDE programming model, a device not only is the specification of the hardware target device, but it also includes all the elements (apart from the POUs coded by the end-user in the IEC-61131-3 languages inside a project) needed to build the binary object and (if supported) transfer and run it on hardware. This includes: One or more devices are to be provided to the end-user by the OEM. They are packed in the GEB Automation IDE, inside the devices/ directory. The end-user will typically not interact directly with the device code, but only associate it to a project, through the Project properties -> GEB tab.

Typical files

This is just an example of a typical device dir; except for files provided by GEB (flag G), the naming is conventional.

File type flags:
G: provided by GEB Automation (you should not modify these)
GO: compiled by OEM from GEB C source code library (see step 4. below)
O: provided by the OEM implementer
Os: provided by the OEM implementer, GEB includes a sample or template

filetypeexplanation
device.properties O Configuration file
libgebiec.a GO GEB IEC 61131 support binary (static) library
gebiec.h G GEB IEC 61131 standard libraries headers (implemented in libgebiec.a)
gebtime.h G GEB specific time related functions (implemented in libgebiec.a)
geblib.h G GEB IEC 61131 support functions (implemented in libgebiec.a)
gebdebug.h G GEB specific debug related functions (implemented in libgebiec.a)
gebdbgprot.h G GEB specific debug comm protocol functions (implemented in libgebiec.a)
geb.h G GEB public header files: interface to the device code.
gebarch.h Os Architecture/compiler dependent definitions (types and macros)
gebdrv.h G GEB gral mechanisms for var mapping (directly represented variables)
deviceio.c Os Device specific i/o code, variables mapping, time functions
gebud.h Os "User defined" functions: native libraries, code supplied by OEM
main.c Os Executable entry point, main loop.
build.bat Os Script for building binary (calls C compiler)
transfer.bat Os Script for transfering binary (only relevant if local build and remote run)
run.bat Os Script for local or remote running/debugging (if supported)
debugcomm.h G Low level communication for debugging (declaration)
debugcomm.c Os Low level communication for debugging (if supported)

More than one device can be declared in one device directory, in extra device_XXX.properties configuration files. The device name is specified in the configuration file, is should be unique inside an installation.

The name of OEM provided source code files are arbitrary; more device specific code can be added, as well as header files and binary libraries.

Creating a device, step by step

Note: For compilation of a custom device (step 5 below) you need to place the C source code of our internal runtime library in libsrc/c/. This is not included in the public IDE, but any license (expect trial/academic) grants access to it. See libsrc.zip file in the site (for logged users). Be aware that this file can change between versions.

1. Create a device directory

It must reside below \devices\, and must contain at least a device.properties. To begin with, the simplest way is to copy the whole win_local directory, and use it as template.

Changes in the device configuration should be reloaded automatically by the IDE, eventually they might require a restart.

2. Edit device.properties

This follows the syntax of a Java property file: UTF-8 encoding, one setting per line,
#
starts a comment.

For details about device.properties config file properties and their syntax, see any of the included files.

You should also edit the declaration of the range of directly represented (mapped) variables for this device. Eg:

# mapped addresses : format according to norms (page 37) . we allow ranges
#    
# format of keys: ioaddress.ID.range  ioaddress.ID.comment
# where ID is an arbitrary (unique) alphanumeric identifier, and 'comment' is optional

# files in filesystem - for demonstration only
ioaddress.f1.range = %IW51.1-16  
ioaddress.f1.comment = Input Word (16 bits) in file
# 
ioaddress.m1.range=%IX1.1-16   
ioaddress.m1.comment=MODBUS DIGITAL COIL TABLE

In that example, that line means the device knows how to map any AT variable with %MW10.1. as prefix. You can add more lines to add another ranges or individual addresses.

3. Customize, if necessary, gebarch.h file

This little header contains some basic architecture-dependent definitions. If your compiler is not fully C99 compatible, you might need to set here the elemental types (int8_t, bool). This is not normally needed. If the next steps gives some compilation error, then you probably need to.

4. Implement the device-specific required functions

These are declared in geb.h, and tipically implemented in deviceio.c. They include variable mappings, variable persisting and time related functions.

4.a Variable mappings: directly represented (AT) variables

In the simplest implementation, you define (typically in gebarch.h) the constant

#define USE_DRVarModuleSimple
and then you just need to code in (say) deviceio.c these functions from gebdrv.h:
void geb_drv_set_int8(char d,char w,int ad0,int ad1,int ad2,int ad3,int8_t val);
int8_t geb_drv_get_int8(char d,char w,int ad0,int ad1,int ad2,int ad3);
void geb_drv_set_int16(char d,char w,int ad0,int ad1,int ad2,int ad3,int16_t val);
int16_t geb_drv_get_int16(char d,char w,int ad0,int ad1,int ad2,int ad3);
void geb_drv_set_int32(char d,char w,int ad0,int ad1,int ad2,int ad3,int32_t val);
int32_t geb_drv_get_int32(char d,char w,int ad0,int ad1,int ad2,int ad3);
void geb_drv_set_float(char d,char w,int ad0,int ad1,int ad2,int ad3,float val);
float geb_drv_get_float(char d,char w,int ad0,int ad1,int ad2,int ad3);
This assumes a simple address scheme: any AT address is split into (up to) four numeric fields, which correspond to ad0,ad1,ad2,ad3 arguments above. The first two letters after the % character are passed as arguments d=directionand w=width; these is merely informative, most of the semantics of these characters is already taken into account on upper layers, so typically this can be ignored. For example reading a variable %MW10.2.4 it result in a calll to `geb_drv_get_int16('M', 'W', 10, 2, 4, 0)`

If not need more flexible mappings (more datatypes, or other addressing schemes) you can comment out the line #define USE_DRVarModuleSimple and provide your own implementation of the functions defined in gebdrv.h

See the deviceio.c in the included devices for examples and templates.

NOTE: the behaviour of the AT mapping has changed in version 3. Now all the AT variables in the project are allocated in a global heap, and they are read/written from/to IO devices by calling the above functions only at the start/end of the execution cycle. This implies that now IO reading/writing is not performed each time an AT var is accesed, but only before and after the cycle execution. This also implies that POUs execution works with a snapshot of the IO vars, there are no asynchronous changes due to IO inputs varying during execution - as it should be.

4.b Variable persistence: RETAIN variables

The device must implement these three functions:

/* loads data from disk, flash, etc - this will be called by Geb - returns negative if error, 0 if no data */
int readRetainData(unsigned char *buffer, int len);

/* saves retain data - returns negative if error  */
int writeRetainData(unsigned char * buffer,int len); 

/* delete all persisted data from flash/disk - to force a cold reboot - this should be called in case of fatal error */
void deleteRetainData(void);  
Typically, these will simply read and write the bytes from FLASH memory or hard disk, if available. Examples are provided. There's no need to add extra intelligence -to check for changes, for example-, the GEB generated code will call these functions only when necessary.

4.c Time related functions

Because this is highly device specific, we define a simple time API via these straightforward functions (declared in geb.h), that the device manufacturer must implement:

/*
 Gets the current time counter read, in ticks (not necessarily a real time clock) 
 The meaning of the fields is totally arbitrary, they are opaque placeholders, to be interpreted 
 by the device code itself in the  function dev_elapsed_time()
*/
void dev_time(int32_t  *hi,uint32_t *lo);

/* 
 Gets the real time elapsed since the timestamp (data stored from a previous dev_time call) till now.
 The implementation should decide on the time unit and inform it in the 'unit' argument.
      unit=  0=usec (10e-6 secs)  1=msec (10e-3 secs - RECOMMENDED)  2: seconds  unit=-1: not implemented
*/
int32_t dev_elapsed_time(int32_t  ts_hi,uint32_t ts_lo, int *unit);

/* Gets current calendar time - optional -  month 1=january - fill all with zero if not implemented */
void dev_datetime(int16_t *year,int16_t *mon,int16_t *mday, int16_t *hour, int16_t *min,int16_t * sec,int16_t *msec);

/* 
  Tries to sleep usecs micro-seconds - usually a resolution of milliseconds is enough. 
  Returns the usecs slept. 
*/
int dev_usleep(int usec);
Again, examples are provided - see the sample devices deviceio.c files.

4.d Low level communication for debugger

If the device supports on-hardware debugging, debugcomm.c should implement the functions declared in debugcomm.h. The example provided in debugcomm_sample.c should work for any environment that supports TCP/IP sockets API (Linux and Windows included).

5. Regenerate (recompile) the geb library libgebiec.a

The library must be recompiled from the GEB source code, with the compiler pertinent to this device. The code is provided by GEB to OEMs, in devices/libsrc/c/ (you should not modify this code). A sample batch script is provided (complib.bat), that does all the work; use that as a template, customize it according to the selected compiler. If the compiler is remote, this step should be also executed remotely, so that the libgebiec.a is available in the remote box.

6. Implement your entry point and main code

For this point, see main.c and common_f.c for example or template.

6.a Implement global special functions

The OEM code must implement a few functions defined and commented in geb.h.


/**
* Handle an event, for example writing to stdout or to a log file.
* There are several different event types
*       0: Fatal error     (this should probably call deleteRetainData() to force a  cold reboot)
*       1: Error (non fatal)
*       2: Warning / Info
*       3: Debug
*/
void gebEvent(const char* s, int type);

/**
*  Handler for out-of-range errors. Tyically implemented in main.c, this should trigger a fatal error.
*/
void outOfRange(long v, long vmin, long vmax, const char *msg);

6.b Implement main loop, invoking hooks

The main execution code and execution loop should call the following function at the appropiate points:

/* hook for performing actions in different points of the execution cycle */
#define GEB_WHERE_STARTING        0 /* prior to entering the main loop */
#define GEB_WHERE_LOOP            1 /* inside loop, not running pou - this could be called very often */
#define GEB_WHERE_CYCLE_START     2 /* prior to execution of group of programs (cycle start) */
#define GEB_WHERE_CYCLE_END       3 /* after execution of group of programs (cycle end) */
#define GEB_WHERE_END_OF_PROGRAM  4 /* inside program, just before ending*/
#define GEB_WHERE_END_OF_FUNC_FB  5 /* inside funciton or FB (not program) pou, just before ending */
#define GEB_WHERE_ENDING          6 /* exiting main loop */
#define GEB_WHERE_SUSPENSION     10 /* debugging: suspended (called repeteadly) */
void geb_hook(int where,bool debugmode);
See example in common_f.c

6.c Implement hook function

Typically you should copy the geb_hook in common_f.c 6.d Implement main function

It's important to call follow the pattern in example main.c at start:

gebInit(); /* should be called early, even before parsing command line args */
if(! parseArgs(argc,argv)) return 1;
The parsing of arguments should support the standard GEB arguments (see againg example in common_f.c) in order to support run/debug from the IDE.