C:\geb\ => main GEB directory C:\geb\lib\ C:\geb\lib\mylib1.zip => global library "mylib1", packed as zip C:\geb\lib\mylib2\ => global library "mylib2", unpacked C:\geb\lib\mylib2\lib.properties => library properties (mandatory) C:\geb\lib\mylib2\myfunc.st => ST code C:\geb\lib\mylib2\... => more files (POUS, C code, C libraries, etc) .... C:\geb\devices\DEV1\ => some device C:\geb\devices\DEV1\lib\lib3.zip => device specific library, packed C:\geb\devices\DEV1\lib\lib4\ => device specific library, unpackedEach library gets its name from its directory or zip filename.
nativeType=20 # 10, 20 see codes below charset=UTF-8 # charset of textual source files, optional, defaults to UTF-8 libName=My library # user friendly name - optional - should be uniqueIf lib.properties is absent, type 10 ("normal") is assumed.
# .categories: suggested/prefered order for categories .categories=Functions,Function Blocks # .helpurl : if this key exists, a F1 contextual help will be offered # for functions (POUS) and categories # The help for functions (POUS) will be browsed at [helpurl]#f[POUNAME] for pous # and [helpurl]#cat[CATNAME] for categories (spaces replaced by _) .helpurl=http://www.gebautomation.com/help/topic/com.gebautomation.help/html/tech/stdlib.html # POUS properties follow # For POU X , these properties can be specified: # X.desc : function description (one line) # X.cat : a category to place the POU inside this library - names are arbitrary NNF1.desc=Sample function, sums two inputs NNF1.cat=Functions NNFB2.desc=Sample FB, CONT starts at 100, increments at the end of each execution, O1 = I1 + CONT ,O2 = I2 + 0.5 NNFB2.cat=Function Blocks
1. Normal external libraries (nativeType=10)
A "normal" library is just a bunch of IEC 61131-3 code in ST language, typically one file for each POU.
The effect of including this kind of library is roughly equivalent to including the code in the
project. No more files are needed.
2. External libraries with C linking (nativeType=20)
The body of the POUS is coded in C, available in linking time (typically as a static library).
This is provided by the OEM (see implementation details below).
The simulator is not aware if this, and it relies only the IEC 61131-3 code.
Hence, the library should either include also the correct ST body for each POU,
or document that the POU does not give meaningful results in simulation.
1. Normal external library (nativeType=10)
In this case it's enough to code the pous in one (or more) ST files
which is included in the library folder/zip. For example:
========== func1.st: ==========
(* sums inputs *) FUNCTION NNF1 : INT VAR_INPUT i,j : INT ; END_VAR NNF1 := i + j; END_FUNCTION (* Function Block : CONT starts at 100, increments by 1 at the end of each execution O1 = I1 + CONT O2 = I2 + 0.5 *) FUNCTION_BLOCK NNFB2 VAR_INPUT I1 : INT; I2 : REAL; END_VAR VAR CONT : INT := 100; END_VAR VAR_OUTPUT O1 : INT; O2 : REAL; END_VAR O1 := I1 +CONT; O2 := I2 +0.5; CONT:=CONT+1; END_FUNCTION_BLOCK2. Native C implementation We also need func1.st the above, but also we need a C implementation, perhaps a wrapper/adapter to an already existing function. For example:
========== func1.h: ==========
#include <geblib.h> #include <geb.h> void init_lib1(void); /* library initialization ; name suffix should coincide with library name ; invoked by device specific (main) code */ void destroy_lib1(void); /* FUNCTION CALL: inputs , in declaration order */ INT_t geb_native_call_f_NF1(INT_t v_i ,INT_t v_j ); /* FB: should not include the EN/ENO check */ /* About the arguments and order: see below */ void geb_native_fb_NFB2( int mode, REAL_t *v_I2, REAL_t *v_O2, INT_t *v_CONT, INT_t *v_I1, INT_t *v_O1, BOOL_t *v_EN, BOOL_t *v_ENO);========== func1.c: ==========
#include <func1.h> void init_lib1(void) { } void destroy_lib1(void) { } int mysum(int x,int y) { return x+y; } INT_t geb_native_call_f_NF1(INT_t v_i ,INT_t v_j ) { /* NF1 := i + j; */ return (INT_t)mysum((int)v_i,(int)v_j); } void geb_native_fb_NFB2( int mode, REAL_t *v_I2, REAL_t *v_O2, INT_t *v_CONT, INT_t *v_I1, INT_t *v_O1, BOOL_t *v_EN, BOOL_t *v_ENO) { if(mode==0) { /* init */ *v_EN = true; *v_ENO = true; *v_I1=0; *v_O1=0; *v_I2=0.0; *v_O2=0.0; *v_CONT = 100; /* starts at 100! */ } else if(mode==1) { /* exec */ /* O1 := I1 +CONT; O2 := I2 +0.5; CONT:=CONT+1; */ *v_O1 = *v_I1 + *v_CONT; *v_O2 = *v_I2 + 0.5; *v_CONT =(*v_CONT+1); } else if(mode==2) { /* destroy : not used */ } }
This C code would typically be compiled into a static library func1.a
and included in the linked libraries when generating the final executable (this
can be accomplished either by adding them to the device compiler command line,
or by adding them to the compiler).
The init/destroy functions (init_lib1() destroy_lib1()) should
be invoked by the device code (eg, inside main.c).
Also, the func1.h header file should be made available in the compilation
by including it inside gebud.h
Notice that, in this scenario, the POU body coded in ST will be only executed by the simulator, while debugging in the IDE.
A recipe to help developers to write the C functions: code only the ST file for the library, as it were a non-native library. Code some Program in the IDE that calls all the functions/FBs in the library. Set the library type to native. In the IDE, compile and generate the C code for your program. Now, the program generated C code will not compile, but it will include for each native call a commented C line that tells you the expected function declaration: search for the comments /* FCALL_NATIVE_C_LIB */ and /* FB_NATIVE_C_LIB */ and copy those lines to your header and C source to start programming.
Our rules are:
STRING_t_MAXLEN(s) : returns allocated space = maximum length in characters (you cannot modify this) STRING_t_CURLEN(s) : returns current string length in characters STRING_t_SETLEN(s) : set current length (must be not greater than STRING_t_MAXLEN) STRING_t_BUF(s) : returns underlying char* bufferYou can also use the following utility functions from geblib.c:
void STRING_t_set2(STRING_t_P self, const char * p,int len); void STRING_t_setsz(STRING_t_P self, const char * p); /* this is actually a macro */ void STRING_t_copy(STRING_t_P to, STRING_t_P from); void STRING_t_appendbuf2(STRING_t_P self, const char * p,int len); void STRING_t_appendbufsz(STRING_t_P self, const char * p); /* this is actually a macro */ void STRING_t_append(STRING_t_P self, STRING_t_P other); int STRING_t_cmpbuf(STRING_t_P in1, const char *p, int plen); int STRING_t_cmpbufsz(STRING_t_P in1, const char *p); /* this is actually a macro */ int STRING_t_cmp(STRING_t_P in1, STRING_t_P in2); int STRING_t_eq(STRING_t_P in1, STRING_t_P in2);
The same applies to Function blocks. But here, additionally, the code must initialize the strings (in init mode) by using the macro STRING_t_init0
An example, that extends the above func1.h library examples.
======== func1.st: ========== (* function NF6STR: joins input strings with a '-' inbetween *) FUNCTION NF6STR : STRING VAR_INPUT s1,s2 : STRING ; END_VAR NF6STR := CONCAT(s1,CONCAT('-',s2)); END_FUNCTION (* Function block NFB3, with strings CONT starts at 400, increments by 1 at the end of each execution sx toggles at the end of each execution between YES and NO (starts at YES) O1 := I1 + CONT so := CONCAT(si,INT_TO_STRING(CONT),sx); *) FUNCTION_BLOCK NFB3 VAR_INPUT I1 : INT; si:STRING; END_VAR VAR CONT : INT := 400; sx : STRING := 'YES'; END_VAR VAR_OUTPUT O1 : INT; so: STRING := '?' ; END_VAR O1 := I1 + CONT; so := CONCAT(CONCAT(si,INT_TO_STRING(CONT)),sx); CONT := CONT+1; IF sx = 'YES' THEN sx := 'NO'; ELSE sx := 'YES'; END_IF; END_FUNCTION_BLOCK === func1.h === /* FUNCTION CALL with STRINGS - as with any "non value" datatype, a pointer to the return value is passed as first argument */ void geb_native_call_f_NF6STR(STRING_t_P out, STRING_t_P v_s1 ,STRING_t_P v_s2 ); /* FB with strings */ void geb_native_fb_NFB3( int mode, STRING_t_P v_si, STRING_t_P v_so, STRING_t_P v_sx, INT_t *v_CONT, INT_t *v_I1, INT_t *v_O1, BOOL_t *v_EN, BOOL_t *v_ENO); === func1.c === /* For functions that returns a string, the output is passed as pointer by caller. This function is not thread-safe nor reentrant - but this OK in IEC 61131-3 environment */ void geb_native_call_f_NF6STR(STRING_t_P out, STRING_t_P v_s1 ,STRING_t_P v_s2 ) { if(VERBOSE) printf("executing geb_native_call_f_NF6STR function call\n"); /* NF6STR := CONCAT(s1,CONCAT('-',s2)); */ STRING_t_copy(out,v_s1); STRING_t_appendbufsz(out,"-"); STRING_t_append(out,v_s2); } /* FB with strings */ void geb_native_fb_NFB3( int mode, STRING_t_P v_Si, STRING_t_P v_So, STRING_t_P v_Sx, INT_t *v_CONT, INT_t *v_I1, INT_t *v_O1, BOOL_t *v_EN, BOOL_t *v_ENO) { char buf[17]; if(VERBOSE) printf("executing geb_native_fb_NFB3 mode=%d\n",mode); if(mode==0) { /* init */ STRING_t_init0_default(v_Si); STRING_t_init0_default(v_So); *v_CONT = 400; *v_I1 = 0; *v_O1 = 0; *v_EN = true; *v_ENO =true; STRING_t_setsz(v_Sx,"YES"); } else if(mode==1) { /* exec */ /* O1 := I1 + CONT; so := CONCAT(CONCAT(si,INT_TO_STRING(CONT)),sx); CONT := CONT+1; IF sx = 'YES' THEN sx := 'NO'; ELSE sx := 'YES'; END_IF; */ *v_O1 = *v_I1 + *v_CONT+1; STRING_t_copy(v_So,v_Si); sprintf(buf,"%d",(int)(*v_CONT)); STRING_t_appendbufsz(v_So,buf); STRING_t_append(v_So,v_Sx); *v_CONT =(*v_CONT+1); if(STRING_t_cmpbufsz(v_Sx,"YES") == 0 ) { STRING_t_setsz(v_Sx,"NO"); } else { STRING_t_setsz(v_Sx,"YES"); } } else if(mode==2) { /* destroy */ /* not used anymore */ } }