OpenPLC 源代码分析
一、matiec中的bison ST语法定义规则
matiec:IEC 61131-3中定义的程序设计语言的编译器
示例程序:
FUNCTION 函数名称 : 返回值类型
VAR_INPUT
输入变量名称 : 变量类型;
END_VAR
VAR
本地变量名称 : 变量类型 := 变量值;
END_VAR
返回值变量名称 := 某种运算; //示例:返回值变量 := INT_TO_REAL(输入变量1+输入变量2)/本地变量;
END_FUNCTION
FUNCTION_BLOCK 功能块名称
VAR_INPUT
输入变量名称 : 变量类型;
END_VAR
VAR
本地变量名称 : 变量类型;
END_VAR
VAR_OUTPUT
输出变量名称 : 变量类型;
END_VAR
VAR_EXTERNAL CONSTANT
外部常量名称 : 常量类型;
END_VAR
IF Reset THEN
本地变量 := 外部常量;
ELSE
本地变量 := 本地变量 + 1;
END_IF;
输出变量 := 本地变量;
END_FUNCTION_BLOCK
PROGRAM 程序名称
VAR_OUTPUT
输出变量名称 : 变量类型;
END_VAR
VAR_INPUT
输入变量名称 : 变量类型;
END_VAR
VAR
本地变量名称 : 变量类型
END_VAR
INITIAL_STEP 初始步名称:
动作1名称(限定符);
END_STEP
ACTION 动作1名称:
动作1行为;
END_ACTION
TRANSITION FROM 初始步名称 TO 第1步名称
:= 转移条件;
END_TRANSITION
STEP 第1步名称:
动作2名称(限定符);
END_STEP
ACTION 动作2名称:
动作2行为;
END_ACTION
END_PROGRAM
CONFIGURATION config
VAR_GLOBAL CONSTANT
外部常量 : 常量类型 := 常量值;
END_VAR
RESOURCE Res0 ON PLC
TASK plc_task(INTERVAL := T#100ms,PRIORITY := 1);
PROGRAM plc_task_instance WITH plc_task : 程序名;
END_RESOURCE
END_CONFIGURATION
* TYPE
* d_s: STRUCT d1: int; d2: int;
* d_a: ARRAY [1..3] OF d_s;
* c_s: STRUCT c1: d_a; c2: d_a;
* b_s: STRUCT b1: c_s; b2: c_s;
* b_a: ARRAY [1..3] OF b_s;
* a_s: STRUCT a1: b_a; a2: b_a;
* END_TYPE
* VAR
* structvar: a_s;
* END_VAR
二、matiec源码分析
1. stage1
2. stage2
3. stage3
4. stage4
程序结构的说明:
- stage4函数调用new_code_generator函数
- new_code_generator函数的返回值为类generate_c_c的实例化对象
- 类generate_c_c派生于类iterator_visitor_c
- 类iterator_visitor_c派生于类visitor_c
- 类visitor_c引入absyntax.def和析构虚函数
类的说明:
1.主程序结构中的类
2.其他类
-
类print_function_parameter_data_types_c
-
类analyse_variable_c
-
类calculate_common_ticktime_c
-
类generate_c_SFC_IL_ST_c
-
类generate_c_pous_c
-
类generate_c_config_c
-
类generate_c_resources_c
其他:
visitor_c *generate_code = new_code_generator(&s4o, builddir);
:生成POUS.c、POUS.h、LOCATED_VARIABLES.h和VARIABLES.csv,但是文件内容为空tree_root->accept(*generate_code);
:生成Config0.c、Config0.h和Res0.c,但是文件内容不为空
delete_code_generator(generate_code);
:生成POUS.c、POUS.h、LOCATED_VARIABLES.h和VARIABLES.csv的内容
SYM_LIST(library_c, enumvalue_symtable_t enumvalue_symtable;)
宏替换的程序:
class library_c: public list_c {
public:
__VA_ARGS__
public:
class_name_c(
int fl = 0, int fc = 0, const char *ffile = NULL /* filename */, long int forder=0,
int ll = 0, int lc = 0, const char *lfile = NULL /* filename */, long int lorder=0);
class_name_c(symbol_c *elem,
int fl = 0, int fc = 0, const char *ffile = NULL /* filename */, long int forder=0,
int ll = 0, int lc = 0, const char *lfile = NULL /* filename */, long int lorder=0);
virtual void *accept(visitor_c &visitor);
/* WARNING: only use this method for debugging purposes!! */
virtual const char *absyntax_cname(void) {return #class_name_c;};
};
SYM_REF4(function_declaration_c, derived_function_name, type_name, var_declarations_list, function_body, enumvalue_symtable_t enumvalue_symtable;)
宏替换的程序:
class function_declaration_c: public symbol_c {
public:
symbol_c *ref1;
symbol_c *ref2;
symbol_c *ref3;
symbol_c *ref4;
__VA_ARGS__
public:
function_declaration_c(symbol_c *ref1,
symbol_c *ref2,
symbol_c *ref3,
symbol_c *ref4 = NULL,
int fl = 0, int fc = 0, const char *ffile = NULL /* filename */, long int forder=0,
int ll = 0, int lc = 0, const char *lfile = NULL /* filename */, long int lorder=0);
virtual void *accept(visitor_c &visitor);
/* WARNING: only use this method for debugging purposes!! */
virtual const char *absyntax_cname(void) {return #function_declaration_c;};
};
//configure.c
symbol->accept(generate_c_config); //起始代码
/* (A.2) Global variables */
vardecl = new generate_c_vardecl_c(&s4o,
generate_c_vardecl_c::local_vf,
generate_c_vardecl_c::global_vt,
symbol->configuration_name);
vardecl->print(symbol);
delete vardecl;
s4o.print("\n");
/* (B.3) Global variables initializations... */
s4o.print(s4o.indent_spaces);
vardecl = new generate_c_vardecl_c(&s4o,
generate_c_vardecl_c::constructorinit_vf,
generate_c_vardecl_c::global_vt,
symbol->configuration_name);
vardecl->print(symbol);
delete vardecl;
s4o.print("\n");
//POUS.c
/* (B.2) Member initializations... */
s4o.print(s4o.indent_spaces);
vardecl = new generate_c_vardecl_c(&s4o,
generate_c_vardecl_c::constructorinit_vf,
generate_c_vardecl_c::input_vt |
generate_c_vardecl_c::output_vt |
generate_c_vardecl_c::inoutput_vt |
generate_c_vardecl_c::private_vt |
generate_c_vardecl_c::located_vt |
generate_c_vardecl_c::external_vt);
vardecl->print(symbol->var_declarations, NULL, FB_FUNCTION_PARAM"->");
delete vardecl;
s4o.print("\n");
/* (C.4) Initialize TEMP variables */
/* function body */
s4o.print(s4o.indent_spaces + "// Initialise TEMP variables\n");
vardecl = new generate_c_vardecl_c(&s4o,
generate_c_vardecl_c::init_vf,
generate_c_vardecl_c::temp_vt);
vardecl->print(symbol->var_declarations, NULL, FB_FUNCTION_PARAM"->");
delete vardecl;
s4o.print("\n");
POUS.h的变量声明
POUS.c的变量初始化
Config0.c的变量初始化
三、matiec生成C代码的规则
原始ST代码
1.handle_program函数:
PROGRAM PLC_PRG
VAR_INPUT
local1 : INT := 11;
local2 : INT := 21;
END_VAR
VAR_OUTPUT
local3 : INT;
END_VAR
local3 := local1 + local2;
END_PROGRAM
matiec编译后的C语言代码:
void PLC_PRG_init__(PLC_PRG *data__, BOOL retain) {
__INIT_VAR(data__->LOCAL1,11,retain)
__INIT_VAR(data__->LOCAL2,21,retain)
__INIT_VAR(data__->LOCAL3,0,retain)
}
// Code part
void PLC_PRG_body__(PLC_PRG *data__) {
// Initialise TEMP variables
__SET_VAR(data__->,LOCAL3,,(__GET_VAR(data__->LOCAL1,) + __GET_VAR(data__->LOCAL2,)));
goto __end;
__end:
return;
} // PLC_PRG_body__()
总结规则
void 程序名_init__(程序名 *data__, BOOL retain) {
__变量类型_VAR(data__->变量名,变量值,retain)
}
//Code part
void 程序名_body__(程序名 *data__) {
// Initialise TEMP variables
__SET_VAR(data__->,变量3,,(__GET_VAR(data__->变量1,) 运算符 __GET_VAR(data__->变量2,)));
goto __end;
__end:
return;
} //程序名_body__()
matiec handle_program核心代码
if (wanted_varformat == constructorinit_vf) {
for(int i = 0; i < list->n; i++) {
if (is_fb) {
s4o.print(nv->get());
this->current_var_type_symbol->accept(*this);
s4o.print(FB_INIT_SUFFIX);
s4o.print("(&");
this->print_variable_prefix();
list->elements[i]->accept(*this);
print_retain();
s4o.print(");");
}
else if (this->current_var_init_symbol != NULL) {
s4o.print(nv->get());
s4o.print(INIT_VAR);
s4o.print("(");
this->print_variable_prefix();
list->elements[i]->accept(*this);
s4o.print(",");
this->current_var_init_symbol->accept(*this);
print_retain();
s4o.print(")");
}
}
}
else if (wanted_varformat == init_vf) {
s4o.print(SET_VAR);
s4o.print("(");
print_variable_prefix();
s4o.print(",");
}
list->elements[i]->accept(*this);
else if (wanted_varformat == init_vf) {
s4o.print(",,");
this->current_var_init_symbol->accept(*this);
s4o.print(");\n");
void print(symbol_c *symbol, symbol_c *scope = NULL, const char *variable_prefix = NULL) {
this->set_variable_prefix(variable_prefix);
if (globalinit_vf == wanted_varformat)
globalnamespace = scope;
finterface_var_count = 0;
switch (wanted_varformat) {
case constructorinit_vf: nv = new next_var_c("", "\n"+s4o.indent_spaces); break;
case finterface_vf: /* fall through... */
case localinit_vf: /* fall through... */
case local_vf: nv = new next_var_c("", ", "); break;
default: nv = NULL;
} /* switch() */
symbol->accept(*this);
delete nv;
nv = NULL;
globalnamespace = NULL;
}
2.handle_function函数:
原始ST代码
FUNCTION AverageVal : REAL
VAR_INPUT
Cnt1 : INT;
Cnt2 : INT;
END_VAR
VAR
InputsNumber : REAL := 2.0;
END_VAR
AverageVal := INT_TO_REAL(Cnt1+Cnt2)/InputsNumber;
END_FUNCTION
matiec编译后的C语言代码:
// FUNCTION
REAL AVERAGEVAL(
BOOL EN,
BOOL *__ENO,
INT CNT1,
INT CNT2)
{
BOOL ENO = __BOOL_LITERAL(TRUE);
REAL INPUTSNUMBER = 2.0;
REAL AVERAGEVAL = 0;
// Control execution
if (!EN) {
if (__ENO != NULL) {
*__ENO = __BOOL_LITERAL(FALSE);
}
return AVERAGEVAL;
}
AVERAGEVAL = (INT_TO_REAL(
(BOOL)__BOOL_LITERAL(TRUE),
NULL,
(INT)(CNT1 + CNT2)) / INPUTSNUMBER);
goto __end;
__end:
if (__ENO != NULL) {
*__ENO = ENO;
}
return AVERAGEVAL;
}
3.handle_function_block
原始ST代码
FUNCTION_BLOCK CounterST
VAR_INPUT
Reset : BOOL;
END_VAR
VAR
Cnt : INT;
END_VAR
VAR_OUTPUT
OUT : INT;
END_VAR
VAR_EXTERNAL CONSTANT
ResetCounterValue : INT;
END_VAR
IF Reset THEN
Cnt := ResetCounterValue;
ELSE
Cnt := Cnt + 1;
END_IF;
Out := Cnt;
END_FUNCTION_BLOCK
matiec编译后的C语言代码:
void COUNTERST_init__(COUNTERST *data__, BOOL retain) {
__INIT_VAR(data__->EN,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->ENO,__BOOL_LITERAL(TRUE),retain)
__INIT_VAR(data__->RESET,__BOOL_LITERAL(FALSE),retain)
__INIT_VAR(data__->CNT,0,retain)
__INIT_VAR(data__->OUT,0,retain)
__INIT_EXTERNAL(INT,RESETCOUNTERVALUE,data__->RESETCOUNTERVALUE,retain)
}
// Code part
void COUNTERST_body__(COUNTERST *data__) {
// Control execution
if (!__GET_VAR(data__->EN)) {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(FALSE));
return;
}
else {
__SET_VAR(data__->,ENO,,__BOOL_LITERAL(TRUE));
}
// Initialise TEMP variables
if (__GET_VAR(data__->RESET,)) {
__SET_VAR(data__->,CNT,,__GET_EXTERNAL(data__->RESETCOUNTERVALUE,));
} else {
__SET_VAR(data__->,CNT,,(__GET_VAR(data__->CNT,) + 1));
};
__SET_VAR(data__->,OUT,,__GET_VAR(data__->CNT,));
goto __end;
__end:
return;
} // COUNTERST_body__()
其他
#define SYM_REF3(class_name_c, ref1, ref2, ref3, ...) \
class class_name_c: public symbol_c { \
public: \
symbol_c *ref1; \
symbol_c *ref2; \
symbol_c *ref3; \
__VA_ARGS__ \
public: \
class_name_c(symbol_c *ref1, \
symbol_c *ref2, \
symbol_c *ref3, \
int fl = 0, int fc = 0, const char *ffile = NULL /* filename */, long int forder=0, \
int ll = 0, int lc = 0, const char *lfile = NULL /* filename */, long int lorder=0); \
virtual void *accept(visitor_c &visitor); \
/* WARNING: only use this method for debugging purposes!! */ \
virtual const char *absyntax_cname(void) {return #class_name_c;}; \
};
SYM_REF3(function_block_declaration_c, fblock_name, var_declarations, fblock_body, enumvalue_symtable_t enumvalue_symtable;)
目前支持的数据类型
/* B 1.3.1 - Elementary Data Types */
/***********************************/
void *visit(time_type_name_c *symbol) {return (void *)"TIME"; };
void *visit(bool_type_name_c *symbol) {return (void *)"BOOL"; };
void *visit(sint_type_name_c *symbol) {return (void *)"SINT"; };
void *visit(int_type_name_c *symbol) {return (void *)"INT"; };
void *visit(dint_type_name_c *symbol) {return (void *)"DINT"; };
void *visit(lint_type_name_c *symbol) {return (void *)"LINT"; };
void *visit(usint_type_name_c *symbol) {return (void *)"USINT"; };
void *visit(uint_type_name_c *symbol) {return (void *)"UINT"; };
void *visit(udint_type_name_c *symbol) {return (void *)"UDINT"; };
void *visit(ulint_type_name_c *symbol) {return (void *)"ULINT"; };
void *visit(real_type_name_c *symbol) {return (void *)"REAL"; };
void *visit(lreal_type_name_c *symbol) {return (void *)"LREAL"; };
void *visit(date_type_name_c *symbol) {return (void *)"DATE"; };
void *visit(tod_type_name_c *symbol) {return (void *)"TOD"; };
void *visit(dt_type_name_c *symbol) {return (void *)"DT"; };
void *visit(byte_type_name_c *symbol) {return (void *)"BYTE"; };
void *visit(word_type_name_c *symbol) {return (void *)"WORD"; };
void *visit(lword_type_name_c *symbol) {return (void *)"LWORD"; };
void *visit(dword_type_name_c *symbol) {return (void *)"DWORD"; };
void *visit(string_type_name_c *symbol) {return (void *)"STRING"; };
void *visit(wstring_type_name_c *symbol) {return (void *)"WSTRING"; };
目前支持的不同数据类型之间可以进行运算的操作符
void *print_datatypes_error_c::visit( or_expression_c *symbol) {return print_binary_expression_errors( "OR", symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit( xor_expression_c *symbol) {return print_binary_expression_errors("XOR", symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit( and_expression_c *symbol) {return print_binary_expression_errors("AND", symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit( equ_expression_c *symbol) {return print_binary_expression_errors( "=" , symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit(notequ_expression_c *symbol) {return print_binary_expression_errors( "<>", symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit( lt_expression_c *symbol) {return print_binary_expression_errors( "<" , symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit( gt_expression_c *symbol) {return print_binary_expression_errors( ">" , symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit( le_expression_c *symbol) {return print_binary_expression_errors( "<=", symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit( ge_expression_c *symbol) {return print_binary_expression_errors( ">=", symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit( add_expression_c *symbol) {return print_binary_expression_errors( "+" , symbol, symbol->l_exp, symbol->r_exp, symbol->deprecated_operation);}
void *print_datatypes_error_c::visit( sub_expression_c *symbol) {return print_binary_expression_errors( "-" , symbol, symbol->l_exp, symbol->r_exp, symbol->deprecated_operation);}
void *print_datatypes_error_c::visit( mul_expression_c *symbol) {return print_binary_expression_errors( "*" , symbol, symbol->l_exp, symbol->r_exp, symbol->deprecated_operation);}
void *print_datatypes_error_c::visit( div_expression_c *symbol) {return print_binary_expression_errors( "/" , symbol, symbol->l_exp, symbol->r_exp, symbol->deprecated_operation);}
void *print_datatypes_error_c::visit( mod_expression_c *symbol) {return print_binary_expression_errors("MOD", symbol, symbol->l_exp, symbol->r_exp);}
void *print_datatypes_error_c::visit( power_expression_c *symbol) {return print_binary_expression_errors( "**", symbol, symbol->l_exp, symbol->r_exp);}
bool remove_from_candidate_datatype_list(symbol_c *datatype, std::vector <symbol_c *> &candidate_datatypes) {
int pos = search_in_candidate_datatype_list(datatype, candidate_datatypes);
if (pos < 0)
return false;
candidate_datatypes.erase(candidate_datatypes.begin() + pos);
return true;
}
void *fill_candidate_datatypes_c::handle_binary_operator(const struct widen_entry widen_table[], symbol_c *symbol, symbol_c *l_expr, symbol_c *r_expr) {
if (NULL == l_expr) return NULL; /* if no prev_il_instruction */
if (NULL == r_expr) return NULL; /* if no IL operand!! */
for(unsigned int i = 0; i < l_expr->candidate_datatypes.size(); i++)
for(unsigned int j = 0; j < r_expr->candidate_datatypes.size(); j++)
/* NOTE: add_datatype_to_candidate_list() will only really add the datatype if it is != NULL !!! */
add_datatype_to_candidate_list(symbol, widening_conversion(l_expr->candidate_datatypes[i], r_expr->candidate_datatypes[j], widen_table));
remove_incompatible_datatypes(symbol);
if (debug) std::cout << "[" << l_expr->candidate_datatypes.size() << "," << r_expr->candidate_datatypes.size() << "] ==> " << symbol->candidate_datatypes.size() << " result.\n";
return NULL;
}
bool fill_candidate_datatypes_c::add_datatype_to_candidate_list(symbol_c *symbol, symbol_c *datatype) {
/* If it is an invalid data type, do not insert!
* NOTE: it reduces overall code size to do this test here, instead of doing every time before calling the add_datatype_to_candidate_list() function.
*/
if (!get_datatype_info_c::is_type_valid(datatype)) /* checks for NULL and invalid_type_name_c */
return false;
if (search_in_candidate_datatype_list(datatype, symbol->candidate_datatypes) >= 0)
/* already in the list, Just return! */
return false;
/* not yet in the candidate data type list, so we insert it now! */
symbol->candidate_datatypes.push_back(datatype);
return true;
}
void fill_candidate_datatypes_c::remove_incompatible_datatypes(symbol_c *symbol) {
#ifdef __REMOVE__
#error __REMOVE__ macro already exists. Choose another name!
#endif
#define __REMOVE__(datatype)\
remove_from_candidate_datatype_list(&get_datatype_info_c::datatype, symbol->candidate_datatypes);\
remove_from_candidate_datatype_list(&get_datatype_info_c::safe##datatype, symbol->candidate_datatypes);
{/* Remove unsigned data types */
uint64_t value = 0;
if (VALID_CVALUE( uint64, symbol)) value = GET_CVALUE(uint64, symbol);
if (IS_OVERFLOW ( uint64, symbol)) value = (uint64_t)UINT32_MAX + (uint64_t)1;
if (value > 1 ) {__REMOVE__(bool_type_name);}
if (value > UINT8_MAX ) {__REMOVE__(usint_type_name); __REMOVE__( byte_type_name);}
if (value > UINT16_MAX ) {__REMOVE__( uint_type_name); __REMOVE__( word_type_name);}
if (value > UINT32_MAX ) {__REMOVE__(udint_type_name); __REMOVE__(dword_type_name);}
if (IS_OVERFLOW( uint64, symbol)) {__REMOVE__(ulint_type_name); __REMOVE__(lword_type_name);}
}
{/* Remove signed data types */
int64_t value = 0;
if (VALID_CVALUE( int64, symbol)) value = GET_CVALUE(int64, symbol);
if (IS_OVERFLOW ( int64, symbol)) value = (int64_t)INT32_MAX + (int64_t)1;
if ((value < INT8_MIN) || (value > INT8_MAX)) {__REMOVE__(sint_type_name);}
if ((value < INT16_MIN) || (value > INT16_MAX)) {__REMOVE__( int_type_name);}
if ((value < INT32_MIN) || (value > INT32_MAX)) {__REMOVE__(dint_type_name);}
if (IS_OVERFLOW( int64, symbol)) {__REMOVE__(lint_type_name);}
}
{/* Remove floating point data types */
real64_t value = 0;
if (VALID_CVALUE( real64, symbol)) value = GET_CVALUE(real64, symbol);
if (IS_OVERFLOW ( real64, symbol)) value = (real64_t)REAL32_MAX + (real64_t)1;
if (value > REAL32_MAX ) {__REMOVE__( real_type_name);}
if (value < -REAL32_MAX ) {__REMOVE__( real_type_name);}
if (IS_OVERFLOW( real64, symbol)) {__REMOVE__(lreal_type_name);}
}
#undef __REMOVE__
}
void *print_datatypes_error_c::visit( ADD_operator_c *symbol) {return print_binary_operator_errors("ADD" , symbol, symbol->deprecated_operation);}
int search_in_candidate_datatype_list(symbol_c *datatype, const std::vector <symbol_c *> &candidate_datatypes) {
if (NULL == datatype)
return -1;
for(unsigned int i = 0; i < candidate_datatypes.size(); i++)
if (get_datatype_info_c::is_type_equal(datatype, candidate_datatypes[i]))
return i;
/* Not found ! */
return -1;
}
目前支持的变量类型
/* the types of variables that need to be processed... */
static const unsigned int none_vt = 0x0000;
static const unsigned int input_vt = 0x0001; // VAR_INPUT
static const unsigned int output_vt = 0x0002; // VAR_OUTPUT
static const unsigned int inoutput_vt = 0x0004; // VAR_IN_OUT
static const unsigned int private_vt = 0x0008; // VAR
static const unsigned int temp_vt = 0x0010; // VAR_TEMP
static const unsigned int external_vt = 0x0020; // VAR_EXTERNAL
static const unsigned int global_vt = 0x0040; // VAR_GLOBAL
// Globals declared inside a resource will not be declared
// unless global_vt is acompanied by resource_vt
static const unsigned int located_vt = 0x0080; // VAR <var_name> AT <location>
static const unsigned int program_vt = 0x0100; // PROGRAM (inside a configuration!)
// Programs declared inside a resource will not be declared
// unless program_vt is acompanied by resource_vt
static const unsigned int en_vt = 0x0200; // EN declaration
static const unsigned int eno_vt = 0x0400; // ENO declaration
static const unsigned int resource_vt = 0x8000; // RESOURCE (inside a configuration!)
// This, just of itself, will not print out any declarations!!
// It must be acompanied by either program_vt and/or global_vt
/* the qualifier of variables that need to be processed... */
static const unsigned int none_vq = 0x0000;
static const unsigned int constant_vq = 0x0001; // CONSTANT
static const unsigned int retain_vq = 0x0002; // RETAIN
static const unsigned int non_retain_vq = 0x0004; // NON_RETAIN
目前支持的变量格式
typedef enum {finterface_vf,
foutputassign_vf,
local_vf,
localinit_vf,
init_vf,
constructorinit_vf,
globalinit_vf,
globalprototype_vf
} varformat_t;
IEC 61131-3数据类型结构
上一篇: Windows 日常计划任务
下一篇: 编译器开发