├── examples ├── mux.fbd ├── pid.fbd ├── hvac.fbd ├── lights.fbd ├── logic.fbd ├── readme.txt ├── counter.fbd ├── twopump.fbd ├── generator.fbd └── waterheating.fbd ├── .gitignore ├── LICENSE ├── demo_modbus.c ├── demo.c ├── fbdrt.h ├── README.md └── fbdrt.c /examples/mux.fbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/mux.fbd -------------------------------------------------------------------------------- /examples/pid.fbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/pid.fbd -------------------------------------------------------------------------------- /examples/hvac.fbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/hvac.fbd -------------------------------------------------------------------------------- /examples/lights.fbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/lights.fbd -------------------------------------------------------------------------------- /examples/logic.fbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/logic.fbd -------------------------------------------------------------------------------- /examples/readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/readme.txt -------------------------------------------------------------------------------- /examples/counter.fbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/counter.fbd -------------------------------------------------------------------------------- /examples/twopump.fbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/twopump.fbd -------------------------------------------------------------------------------- /examples/generator.fbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/generator.fbd -------------------------------------------------------------------------------- /examples/waterheating.fbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crossrw/fbd-runtime/HEAD/examples/waterheating.fbd -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | 5 | # Libraries 6 | *.lib 7 | *.a 8 | 9 | # Shared objects (inc. Windows DLLs) 10 | *.dll 11 | *.so 12 | *.so.* 13 | *.dylib 14 | 15 | # Executables 16 | *.exe 17 | *.out 18 | *.app 19 | 20 | # preprocessor 21 | *.j 22 | 23 | # vscode files 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .vscode/launch.json 30 | .vscode/tasks.json 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Alexey Lutovinin (crossrw1@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /demo_modbus.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "fbdrt.h" 6 | 7 | // #define SILENT 8 | #define CICLCOUNT 1000000 9 | 10 | const unsigned char description[] = {0x16, 0x16, 0x21, 0x21, 0x94, 0x02, 0x00, 0x03, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0x0C, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x90, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC8, 0x40, 0x0A, 0x80, 0x00, 0x00, 0x6E, 0x6F, 0x6E, 0x61, 0x6D, 0x65, 0x00, 0x31, 0x00, 0x32, 0x30, 0x2D, 0x31, 0x31, 0x2D, 0x32, 0x31, 0x20, 0x31, 13 | 0x38, 0x3A, 0x32, 0x37, 0x00, 0x28, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 0x00, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x37, 0x00, 0x1F, 0x00, 0x00, 14 | 0x00, 0x02, 0x00, 0x01, 0x02, 0x54, 0x3D, 0x25, 0x2E, 0x32, 0x66, 0x00, 0x00, 0x88, 0x39, 0x42, 0x22}; 15 | 16 | #define MEM_SIZE 2048 17 | 18 | char memory[MEM_SIZE]; 19 | 20 | void msleep(int ms) 21 | { 22 | struct timespec ts; 23 | ts.tv_sec = ms / 1000; 24 | ts.tv_nsec = (ms % 1000) * 1000000; 25 | nanosleep(&ts, NULL); 26 | } 27 | 28 | tSignal FBDgetProc(char type, tSignal index) 29 | { 30 | time_t my_time; 31 | struct tm * timeinfo; 32 | // 33 | time(&my_time); 34 | timeinfo = localtime (&my_time); 35 | #ifndef SILENT 36 | switch(type) { 37 | case 0: 38 | printf(" request InputPin(%ld)\n", index); 39 | // имитация RTC 40 | switch(index) { 41 | case GP_RTC_HOUR: 42 | return timeinfo->tm_hour; 43 | case GP_RTC_MINUTE: 44 | return timeinfo->tm_min; 45 | case GP_RTC_SECOND: 46 | return timeinfo->tm_sec; 47 | case GP_RTC_DAY: 48 | return timeinfo->tm_mday; 49 | case GP_RTC_MONTH: 50 | return timeinfo->tm_mon+1; 51 | case GP_RTC_YEAR: 52 | return timeinfo->tm_year+1900; 53 | default: 54 | return 0; 55 | } 56 | case 1: 57 | printf(" request NVRAM(%ld)\n", index); 58 | return 0; 59 | } 60 | #endif 61 | return 0; 62 | } 63 | 64 | void FBDsetProc(char type, tSignal index, tSignal *value) 65 | { 66 | #ifndef SILENT 67 | switch(type) 68 | { 69 | case 0: 70 | printf(" set OutputPin(%ld) to value %ld\n", index, *value); 71 | break; 72 | case 1: 73 | printf(" set NVRAM(%ld) to value %ld\n", index, *value); 74 | break; 75 | } 76 | #endif 77 | } 78 | 79 | #ifdef USE_HMI 80 | // рисование графических примитивов 81 | // 82 | // рисование залитого прямоугольника 83 | void FBDdrawRectangle(tScreenDim x1, tScreenDim y1, tScreenDim x2, tScreenDim y2, tColor color) 84 | { 85 | printf("draw rectangle: x1:%d y1:%d x2:%d y2:%d color:%d\n", x1, y1, x2, y2, color); 86 | } 87 | // рисование текста 88 | void FBDdrawText(tScreenDim x1, tScreenDim y1, unsigned char font, tColor color, tColor bkcolor, bool transparent, char *text) 89 | { 90 | printf("draw text: x1:%d y1:%d font:%d color:%d bkcolor:%d text:'%s'\n", x1, y1, font, color, bkcolor, text); 91 | } 92 | // рисование линии 93 | void FBDdrawLine(tScreenDim x1, tScreenDim y1, tScreenDim x2, tScreenDim y2, tColor color) 94 | { 95 | printf("draw line: x1:%d y1:%d x2:%d y2:%d color:%d\n", x1, y1, x2, y2, color); 96 | } 97 | // рисование залитого эллипса 98 | void FBDdrawEllipse(tScreenDim x1, tScreenDim y1, tScreenDim x2, tScreenDim y2, tColor color) 99 | { 100 | printf("draw ellipse: x1:%d y1:%d x2:%d y2:%d color:%d\n", x1, y1, x2, y2, color); 101 | } 102 | // рисование картинки 103 | void FBDdrawImage(tScreenDim x1, tScreenDim y1, tScreenDim image) 104 | { 105 | printf("draw image: x1:%d y1:%d index:%d\n", x1, y1, image); 106 | } 107 | // завершение рисования экрана (копирование видеообласти) 108 | void FBDdrawEnd(void) 109 | { 110 | printf("draw end -------------------------------------\n"); 111 | } 112 | #endif 113 | 114 | int main(void) 115 | { 116 | int res,i; 117 | tModbusReq mbrequest; 118 | tModbusRTUsettings modbusSettings; 119 | // 120 | res = fbdInit(description); 121 | if(res <= 0) { 122 | printf("result = %d\n", res); 123 | return 0; 124 | } 125 | if(res > MEM_SIZE) { 126 | printf("not enough memory\n"); 127 | return 0; 128 | } 129 | // 130 | fbdSetMemory(memory, true); 131 | printf("memory request size = %d\n", res); 132 | // 133 | printf("FBD_OPT_REQ_VERSION: %ld\n", FBD_REQ_VERSION); 134 | printf("FBD_NETVAR_USE: %ld\n", FBD_NETVAR_USE); 135 | printf("FBD_NETVAR_PORT: %ld\n", FBD_NETVAR_PORT); 136 | printf("FBD_NETVAR_GROUP: %ld\n", FBD_NETVAR_GROUP); 137 | printf("FBD_SCREEN_COUNT: %ld\n", FBD_SCREEN_COUNT); 138 | printf("FBD_SCHEMA_SIZE: %ld\n", FBD_SCHEMA_SIZE); 139 | printf("FBD_HINTS_COUNT: %ld\n", FBD_HINTS_COUNT); 140 | // 141 | printf("fbdModbusUsage(void): %d\n", fbdModbusUsage()); 142 | printf("FBD_MODBUS_RETRYCOUNT: %ld\n", FBD_MODBUS_RETRYCOUNT); 143 | 144 | // 145 | if(fbdModbusGetSerialSettings(&modbusSettings)) { 146 | printf("Modbus RTU settings:\n"); 147 | printf(" baudRate: %d\n", modbusSettings.baudRate); 148 | printf(" parity: %d\n", modbusSettings.parity); 149 | printf(" stopBits: %d\n", modbusSettings.stopBits); 150 | printf(" timeout: %d\n", modbusSettings.timeout); 151 | } else { 152 | printf("Modbus RTU settings not present\n"); 153 | } 154 | 155 | 156 | // main loop 157 | i=0; 158 | while(i < CICLCOUNT) { 159 | i++; 160 | 161 | fbdDoStepEx(100, 0); 162 | // 163 | if(fbdGetNextModbusRTURequest(&mbrequest)) { 164 | printf("New Modbus request:\n"); 165 | printf(" IP:%ld\n", mbrequest.ip); 166 | printf(" SA:%d\n", mbrequest.slaveAddr); 167 | printf(" FC:%d\n", mbrequest.funcCode); 168 | printf(" RA:%d\n", mbrequest.regAddr); 169 | printf(" RC:%d\n", mbrequest.regCount); 170 | printf(" DATA:%ld\n", mbrequest.data.intData); 171 | // 172 | 173 | if(mbrequest.slaveAddr == 1) { 174 | fbdSetModbusRTUResponse(i); 175 | } else { 176 | fbdSetModbusRTUNoResponse(0); 177 | } 178 | 179 | } else { 180 | printf("no Modbus requests\n"); 181 | } 182 | 183 | 184 | msleep(10); 185 | } 186 | // 187 | return 0; 188 | } 189 | -------------------------------------------------------------------------------- /demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "fbdrt.h" 6 | 7 | // #define SILENT 8 | #define CICLCOUNT 1000000 9 | 10 | // date: 19-01-2022 17:52:44 11 | // file name: C:\Users\al\Documents\fbd2Projects\examples\Светофор.fbdbin 12 | // title: Управление светофором 13 | // required RTL version: 7 14 | // HMI: used 15 | // netvar: not used 16 | // elements count: 24 17 | // sizeof(tSignal) = 4 18 | // sizeof(tElemIndex) = 2 19 | const unsigned char description[] = {0x17, 0x17, 0x17, 0x03, 0x03, 0x03, 0x0F, 0x20, 0x00, 0x04, 0x44, 0x04, 0x04, 0x44, 0x00, 0x06, 0x06, 0x06, 0x01, 0x0C, 0x0C, 0x0C, 0x00, 0x01, 0x94, 0x06, 0x00, 0x0F, 0x00, 0x06, 0x00, 20 | 0x11, 0x00, 0x06, 0x00, 0x10, 0x00, 0x17, 0x00, 0x12, 0x00, 0x04, 0x00, 0x10, 0x00, 0x0F, 0x00, 0x09, 0x00, 0x11, 0x00, 0x0D, 0x00, 0x05, 0x00, 0x15, 0x00, 0x0A, 0x00, 0x07, 0x00, 0x06, 0x00, 21 | 0x03, 0x00, 0x11, 0x00, 0x13, 0x00, 0x0F, 0x00, 0x14, 0x00, 0x10, 0x00, 0x0C, 0x00, 0x10, 0x00, 0x02, 0x00, 0x11, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x0B, 0x00, 0xD0, 0x07, 0x00, 0x00, 22 | 0x98, 0x3A, 0x00, 0x00, 0x40, 0x1F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x98, 0x3A, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 23 | 0xE8, 0x03, 0x00, 0x00, 0xE8, 0x03, 0x00, 0x00, 0xA0, 0x0F, 0x00, 0x00, 0xD0, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 24 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xE8, 0x03, 0x00, 0x00, 0x08, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE5, 0x0C, 0x00, 25 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC8, 0x40, 0x00, 0x80, 0xC2, 0xF0, 0xE5, 0xEC, 0xFF, 0x20, 0xE7, 0xE5, 0xEB, 0xE5, 0xED, 26 | 0xEE, 0xE3, 0xEE, 0x00, 0xC2, 0xF0, 0xE5, 0xEC, 0xFF, 0x20, 0xEA, 0xF0, 0xE0, 0xF1, 0xED, 0xEE, 0xE3, 0xEE, 0x00, 0xC2, 0xF0, 0xE5, 0xEC, 0xFF, 0x20, 0xE6, 0xE5, 0xEB, 0xF2, 0xEE, 0xE3, 0xEE, 27 | 0x00, 0xD3, 0xEF, 0xF0, 0xE0, 0xE2, 0xEB, 0xE5, 0xED, 0xE8, 0xE5, 0x20, 0xF1, 0xE2, 0xE5, 0xF2, 0xEE, 0xF4, 0xEE, 0xF0, 0xEE, 0xEC, 0x00, 0x31, 0x00, 0x31, 0x39, 0x2D, 0x30, 0x31, 0x2D, 0x32, 28 | 0x32, 0x20, 0x31, 0x37, 0x3A, 0x35, 0x32, 0x00, 0xFF, 0x94, 0x01, 0x00, 0x00, 0x64, 0x00, 0x0F, 0x00, 0x24, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x73, 0x00, 0x05, 29 | 0x00, 0xE0, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xD1, 0xE2, 0xE5, 0xF2, 0xEE, 0xF4, 0xEE, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 30 | 0x00, 0x7D, 0x00, 0x23, 0x00, 0xC2, 0x00, 0xD1, 0x00, 0x86, 0x31, 0x00, 0x00, 0x18, 0x00, 0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x96, 0x00, 0xB8, 0x00, 0xC7, 31 | 0x00, 0x49, 0x4A, 0x00, 0x00, 0x18, 0x00, 0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x5F, 0x00, 0xB8, 0x00, 0x90, 0x00, 0x49, 0x4A, 0x00, 0x00, 0x18, 0x00, 0x05, 32 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x28, 0x00, 0xB8, 0x00, 0x59, 0x00, 0x49, 0x4A, 0x00, 0x00, 0x18, 0x00, 0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 33 | 0x00, 0x87, 0x00, 0x9B, 0x00, 0xB8, 0x00, 0xCC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x2D, 0x00, 0xB8, 0x00, 0x5E, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x05, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x01, 0x00, 0x00, 0x00, 0x87, 0x00, 0x2D, 0x00, 0xB8, 0x00, 0x5E, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x18, 0x00, 0x05, 35 | 0x00, 0x01, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x87, 0x00, 0x9B, 0x00, 0xB8, 0x00, 0xCC, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x18, 0x00, 0x05, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 36 | 0x00, 0x87, 0x00, 0x64, 0x00, 0xB8, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x05, 0x00, 0x02, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x64, 0x00, 0xB8, 0x00, 0x95, 37 | 0x00, 0xE0, 0xFF, 0x00, 0x00, 0x28, 0x00, 0x02, 0x00, 0x01, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0xDC, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xD0, 0xE5, 0xE6, 38 | 0xE8, 0xEC, 0x20, 0x22, 0xC4, 0xE5, 0xED, 0xFC, 0x22, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x02, 0x00, 0x02, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0xDC, 0x00, 0x00, 0xFC, 0x00, 39 | 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xD0, 0xE5, 0xE6, 0xE8, 0xEC, 0x20, 0x22, 0xCD, 0xEE, 0xF7, 0xFC, 0x22, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x03, 0x00, 0x02, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 40 | 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x03, 0x00, 0x01, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA1, 0x9F, 0xBC, 41 | 0x9B}; 42 | 43 | #define MEM_SIZE 4096 44 | 45 | char memory[MEM_SIZE]; 46 | 47 | void msleep(int ms) 48 | { 49 | struct timespec ts; 50 | ts.tv_sec = ms / 1000; 51 | ts.tv_nsec = (ms % 1000) * 1000000; 52 | nanosleep(&ts, NULL); 53 | } 54 | 55 | tSignal FBDgetProc(char type, tSignal index) 56 | { 57 | time_t my_time; 58 | struct tm * timeinfo; 59 | // 60 | time(&my_time); 61 | timeinfo = localtime (&my_time); 62 | #ifndef SILENT 63 | switch(type) { 64 | case 0: 65 | printf(" request InputPin(%d)\n", index); 66 | // имитация RTC 67 | switch(index) { 68 | case GP_RTC_HOUR: 69 | return timeinfo->tm_hour; 70 | case GP_RTC_MINUTE: 71 | return timeinfo->tm_min; 72 | case GP_RTC_SECOND: 73 | return timeinfo->tm_sec; 74 | case GP_RTC_DAY: 75 | return timeinfo->tm_mday; 76 | case GP_RTC_MONTH: 77 | return timeinfo->tm_mon+1; 78 | case GP_RTC_YEAR: 79 | return timeinfo->tm_year+1900; 80 | default: 81 | return 0; 82 | } 83 | case 1: 84 | printf(" request NVRAM(%d)\n", index); 85 | return 0; 86 | } 87 | #endif 88 | return 0; 89 | } 90 | 91 | void FBDsetProc(char type, tSignal index, tSignal *value) 92 | { 93 | #ifndef SILENT 94 | switch(type) 95 | { 96 | case 0: 97 | printf(" set OutputPin(%d) to value %d\n", index, *value); 98 | break; 99 | case 1: 100 | printf(" set NVRAM(%d) to value %d\n", index, *value); 101 | break; 102 | } 103 | #endif 104 | } 105 | 106 | #ifdef USE_HMI 107 | // рисование графических примитивов 108 | // 109 | // рисование залитого прямоугольника 110 | void FBDdrawRectangle(tScreenDim x1, tScreenDim y1, tScreenDim x2, tScreenDim y2, tColor color) 111 | { 112 | printf("draw rectangle: x1:%d y1:%d x2:%d y2:%d color:%d\n", x1, y1, x2, y2, color); 113 | } 114 | // рисование текста 115 | void FBDdrawText(tScreenDim x1, tScreenDim y1, unsigned char font, tColor color, tColor bkcolor, bool transparent, char *text) 116 | { 117 | printf("draw text: x1:%d y1:%d font:%d color:%d bkcolor:%d text:'%s'\n", x1, y1, font, color, bkcolor, text); 118 | } 119 | // рисование линии 120 | void FBDdrawLine(tScreenDim x1, tScreenDim y1, tScreenDim x2, tScreenDim y2, tColor color) 121 | { 122 | printf("draw line: x1:%d y1:%d x2:%d y2:%d color:%d\n", x1, y1, x2, y2, color); 123 | } 124 | // рисование залитого эллипса 125 | void FBDdrawEllipse(tScreenDim x1, tScreenDim y1, tScreenDim x2, tScreenDim y2, tColor color) 126 | { 127 | printf("draw ellipse: x1:%d y1:%d x2:%d y2:%d color:%d\n", x1, y1, x2, y2, color); 128 | } 129 | // рисование картинки 130 | void FBDdrawImage(tScreenDim x1, tScreenDim y1, tScreenDim image) 131 | { 132 | printf("draw image: x1:%d y1:%d index:%d\n", x1, y1, image); 133 | } 134 | // завершение рисования экрана (копирование видеообласти) 135 | void FBDdrawEnd(void) 136 | { 137 | printf("draw end -------------------------------------\n"); 138 | } 139 | 140 | #endif 141 | 142 | 143 | int main(void) 144 | { 145 | 146 | int res,i; 147 | 148 | // DWORD start, end; 149 | // 150 | res = fbdInit(description); 151 | if(res <= 0) { 152 | printf("result = %d\n", res); 153 | return 0; 154 | } 155 | if(res > MEM_SIZE) { 156 | printf("not enough memory\n"); 157 | return 0; 158 | } 159 | // 160 | fbdSetMemory(memory, true); 161 | printf("memory request size = %d\n", res); 162 | // 163 | printf("FBD_OPT_REQ_VERSION: %d\n", FBD_REQ_VERSION); 164 | printf("FBD_NETVAR_USE: %d\n", FBD_NETVAR_USE); 165 | printf("FBD_NETVAR_PORT: %d\n", FBD_NETVAR_PORT); 166 | printf("FBD_NETVAR_GROUP: %d\n", FBD_NETVAR_GROUP); 167 | printf("FBD_SCREEN_COUNT: %d\n", FBD_SCREEN_COUNT); 168 | printf("FBD_SCHEMA_SIZE: %d\n", FBD_SCHEMA_SIZE); 169 | printf("FBD_HINTS_COUNT: %d\n", FBD_HINTS_COUNT); 170 | 171 | // main loop 172 | // start = GetTickCount(); 173 | 174 | // printf("IO hints:\n"); 175 | // for(i=0; i<16; i++) { 176 | // printf(" type=0 index=%d text:'%s'\n", i, fbdHMIgetIOhint(0, i)); 177 | // printf(" type=1 index=%d text:'%s'\n", i, fbdHMIgetIOhint(1, i)); 178 | // printf("\n"); 179 | // } 180 | 181 | i=0; 182 | while(i < CICLCOUNT) { 183 | i++; 184 | #ifndef SILENT 185 | printf("step %d\n", i); 186 | #endif 187 | 188 | fbdDoStepEx(100, 0); 189 | msleep(10); 190 | 191 | } 192 | 193 | // end = GetTickCount(); 194 | // printf("%lu ms\n", end-start); 195 | // printf("cicle duration %f ms\n", 1.0*(end-start)/CICLCOUNT); 196 | 197 | 198 | 199 | return 0; 200 | } 201 | -------------------------------------------------------------------------------- /fbdrt.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fbdrt.h 3 | * @author crossrw1@gmail.com 4 | * @brief FBD-Runtime Library Headers 5 | * @version 9.0 6 | * @date 2021-11-17 7 | */ 8 | 9 | #ifndef FBDRT_H 10 | #define FBDRT_H 11 | 12 | #include // for true/false definition 13 | #include // type definition 14 | 15 | // 7 - базовая 16 | // 8 - поддержка экранов 17 | // 9 - Modbus 18 | #define FBD_LIB_VERSION 9 19 | 20 | // описание упакованной структуры 21 | #if defined ( __CC_ARM ) 22 | #define __packed_struct __packed struct 23 | #elif defined ( __GNUC__ ) 24 | #define __packed_struct struct __attribute__ ((__packed__)) 25 | #elif defined ( __TASKING__ ) 26 | #define __packed_struct struct __packed 27 | #endif 28 | 29 | // 30 | // 31 | // ========================================================================= 32 | // = начало настроек ======================================================= 33 | // 34 | // определение порядка байт, по умолчанию используется LOW_ENDIAN 35 | //#define BIG_ENDIAN 36 | // 37 | // размер памяти для сигнала схемы (1, 2 или 4) 38 | #define SIGNAL_SIZE 4 39 | // 40 | // размер пямяти для индекса элемента (1 или 2) 41 | #define INDEX_SIZE 2 42 | // 43 | // префикс и суфикс используемые для определения памяти в ROM или FLASH 44 | #define ROM_CONST const 45 | #define ROM_CONST_SUFX 46 | // 47 | // префикс и суфикс используемые для определения памяти в которой хранится описание схемы 48 | #define DESCR_MEM const 49 | #define DESCR_MEM_SUFX 50 | // 51 | // нобходимо определить символ USE_HMI если планируете использовать функции HMI 52 | #define USE_HMI 53 | // 54 | // нобходимо определить символ USE_MATH если планируете использовать генератор sin 55 | #define USE_MATH 56 | // 57 | // SPEED_OPT - оптимизация скорости выполнения за счет увеличения размера необходимого RAM 58 | // Чем больше схема, тем больше увеличение скорости. 59 | // На примере waterheating.fbd: 60 | // - уменьшение времени цикла в 4.9 раза; 61 | // - увеличение размера RAM c 374 до 870 байт (2.3 раза) 62 | #define SPEED_OPT 63 | // 64 | // максимальный размер стека, используемый при расчете схемы 65 | // один элемент стека занимает (sizeof(tElemIndex)+1) байт 66 | #define FBDSTACKSIZE 128 67 | // 68 | // data type for stack pointer 69 | typedef uint8_t tFBDStackPnt; 70 | // 71 | typedef int32_t tLongSignal; 72 | // 73 | // = конец настроек ======================================================== 74 | // ========================================================================= 75 | // 76 | // типы элементов: 77 | #define ELEMMASK 0x3F 78 | #define INVERTFLAG 0x40 79 | // 80 | typedef enum { 81 | ELEM_OUT_PIN = 0, 82 | ELEM_CONST = 1, 83 | ELEM_NOT = 2, 84 | ELEM_AND = 3, 85 | ELEM_OR = 4, 86 | ELEM_XOR = 5, 87 | ELEM_RSTRG = 6, 88 | ELEM_DTRG = 7, 89 | ELEM_ADD = 8, 90 | ELEM_SUB = 9, 91 | ELEM_MUL = 10, 92 | ELEM_DIV = 11, 93 | ELEM_TON = 12, 94 | ELEM_CMP = 13, 95 | ELEM_OUT_VAR = 14, 96 | ELEM_INP_PIN = 15, 97 | ELEM_INP_VAR = 16, 98 | ELEM_PID = 17, 99 | ELEM_SUM = 18, 100 | ELEM_COUNTER = 19, 101 | ELEM_MUX = 20, 102 | ELEM_ABS = 21, 103 | ELEM_WP = 22, 104 | ELEM_SP = 23, 105 | ELEM_TP = 24, 106 | ELEM_MIN = 25, 107 | ELEM_MAX = 26, 108 | ELEM_LIM = 27, 109 | ELEM_EQ = 28, 110 | ELEM_BAND = 29, 111 | ELEM_BOR = 30, 112 | ELEM_BXOR = 31, 113 | ELEM_GEN = 32, 114 | ELEM_INP_MDBS= 33, 115 | ELEM_OUT_MDBS= 34, 116 | // 117 | ELEM_TYPE_COUNT 118 | } tFBD_ELEMENT_TYPE; 119 | // 120 | #ifdef USE_MATH 121 | #include 122 | #ifndef M_PI 123 | #define M_PI 3.14159265358979323846 124 | #endif // M_PI 125 | #endif // USE_MATH 126 | 127 | typedef uint16_t tOffset; 128 | // 129 | #if defined(BIG_ENDIAN) && (SIGNAL_SIZE > 1) 130 | #define SIGNAL_BYTE_ORDER(x) lotobigsign(x) 131 | #else 132 | #define SIGNAL_BYTE_ORDER(x) (x) 133 | #endif // defined 134 | 135 | #if defined(BIG_ENDIAN) && (INDEX_SIZE > 1) 136 | #define ELEMINDEX_BYTE_ORDER(x) lotobigidx(x) 137 | #else 138 | #define ELEMINDEX_BYTE_ORDER(x) (x) 139 | #endif // defined 140 | 141 | #if (SIGNAL_SIZE == 1) 142 | typedef int8_t tSignal; 143 | #define MAX_SIGNAL INT8_MAX 144 | #define MIN_SIGNAL INT8_MIN 145 | // 146 | #elif (SIGNAL_SIZE == 2) 147 | typedef int16_t tSignal; 148 | #define MAX_SIGNAL INT16_MAX 149 | #define MIN_SIGNAL INT16_MIN 150 | // 151 | #elif (SIGNAL_SIZE == 4) 152 | typedef int32_t tSignal; 153 | #define MAX_SIGNAL INT32_MAX 154 | #define MIN_SIGNAL INT32_MIN 155 | #else 156 | #error Invalid value of SIGNAL_SIZE 157 | #endif // SIGNAL_SIZE 158 | // 159 | #if INDEX_SIZE == 1 160 | typedef uint8_t tElemIndex; 161 | #define MAX_INDEX UINT8_MAX 162 | #elif INDEX_SIZE == 2 163 | typedef uint16_t tElemIndex; 164 | #define MAX_INDEX UINT16_MAX 165 | #else 166 | #error Invalid value of INDEX_SIZE 167 | #endif // INDEX_SIZE 168 | // 169 | // end element description flag 170 | #define END_MARK (unsigned char)((sizeof(tSignal)|(sizeof(tElemIndex)<<3))|0x80) 171 | // END_MARK: 172 | // bit 0-2: sizeof(tSignal) 173 | // bit 3-4: sizeof(tElemIndex) 174 | // bit 5: reserved 175 | // bit 6: reserved 176 | // bit 7: 1 177 | 178 | // коды ошибок, возвращаемые fbdInit() 179 | typedef enum { 180 | ERR_INVALID_ELEMENT_TYPE = -1, 181 | ERR_INVALID_SIZE_TYPE = -2, 182 | ERR_INVALID_LIB_VERSION = -3, 183 | ERR_INVALID_CHECK_SUM = -4 184 | } FBD_INIT_RESULT; 185 | 186 | // ------------------------------------------------------------------------------------------------------- 187 | // Инициализация схемы 188 | // ------------------------------------------------------------------------------------------------------- 189 | 190 | int fbdInit(DESCR_MEM unsigned char *descr); 191 | 192 | void fbdSetMemory(char *buf, bool needReset); 193 | 194 | void fbdDoStep(tSignal period); 195 | 196 | // ------------------------------------------------------------------------------------------------------- 197 | // Экраны 198 | // ------------------------------------------------------------------------------------------------------- 199 | #ifdef USE_HMI 200 | 201 | #define SCREEN_WIDTH 320 202 | #define SCREEN_HEIGHT 240 203 | 204 | typedef uint16_t tScreenDim; 205 | typedef uint16_t tColor; 206 | 207 | // описание структуры экрана 208 | typedef __packed_struct Screen_t { 209 | uint16_t len; // размер экрана 2 210 | tColor bkcolor; // цвет фона 2 211 | uint16_t period; // период обновления 2 212 | uint16_t elemCount; // количество элементов экрана 2 213 | // тут элементы экрана 214 | } tScreen; 215 | 216 | // описание элементов экрана 217 | // базовый элемент с видимостью 218 | typedef __packed_struct ScrElemBase_t { 219 | uint16_t len; // размер структуры 2 220 | uint16_t type; // тип элемента 2 221 | // 222 | uint16_t visibleCond; // условие видимости 2 223 | tElemIndex visibleElem; // индекс элемента 2 224 | tSignal visibleValue; // константа - значение сигнала 4 225 | // 226 | tScreenDim x1; // координата x 2 227 | tScreenDim y1; // координата y 2 228 | } tScrElemBase; 229 | 230 | // элемент прямоугольник 231 | typedef __packed_struct ScrElemRect_t { 232 | tScrElemBase parent; // 16 233 | // 234 | tScreenDim x2; // координата x2 2 235 | tScreenDim y2; // координата y2 2 236 | tColor color; // цвет 2 237 | uint16_t reserve; // 2 238 | } tScrElemRect; 239 | 240 | // элемент эллипс 241 | typedef __packed_struct ScrElemCircle_t { 242 | tScrElemBase parent; // 16 243 | // 244 | tScreenDim x2; // координата x2 2 245 | tScreenDim y2; // координата y2 2 246 | tColor color; // цвет 2 247 | uint16_t reserve; // 2 248 | } tScrElemCircle; 249 | 250 | // элемент линия 251 | typedef __packed_struct ScrElemLine_t { 252 | tScrElemBase parent; // 16 253 | // 254 | tScreenDim x2; // координата x2 2 255 | tScreenDim y2; // координата y2 2 256 | tColor color; // цвет 2 257 | tScreenDim width; // толщина линии 2 258 | float sine; // 4 259 | float cosinus; // 4 260 | } tScrElemLine; 261 | 262 | // элемент картинка 263 | typedef __packed_struct ScrElemImage_t { 264 | tScrElemBase parent; // 16 265 | // 266 | uint16_t index; // индекс картинки 2 267 | uint16_t reserve; // 2 !!! 268 | } tScrElemImage; 269 | 270 | // элемент текст 271 | typedef __packed_struct ScrElemText_t { 272 | tScrElemBase parent; // 16 273 | // 274 | tColor color; // цвет 2 275 | tColor bkcolor; // цвет фона 2 276 | // 277 | tElemIndex valueElem; // индекс элемента 2 278 | uint8_t font; // индекс шрифта, старший бит прозрачность 1 279 | uint8_t divider; // делитель 1 280 | char text[]; // сам текст, заканчивается 0 длинна должны быть кратна 4 ! 281 | } tScrElemText; 282 | 283 | // элемент шкала 284 | typedef __packed_struct ScrElemGauge_t { 285 | tScrElemBase parent; // 16 286 | // 287 | tScreenDim x2; // координата x2 2 288 | tScreenDim y2; // координата y2 2 289 | // 290 | tColor color; // цвет 2 291 | tColor bkcolor; // цвет фона 2 292 | // 293 | tSignal maxvalue; // максимальное значение шкалы 4 294 | tElemIndex valueElem; // индекс элемента 2 295 | uint16_t orientation; // ориентация: 0 - гор, 1 - вер 2 296 | } tScrElemGauge; 297 | 298 | // один шаг вычисления схемы с последующим рисованием (при необходимости) экрана screen 299 | void fbdDoStepEx(tSignal period, short screenIndex); 300 | 301 | // перечисление используется в процедуре отрисовки тектовых сообщений на экране для получения текущего времени 302 | enum GP_RTC_PARAMS { 303 | GP_RTC_HOUR = 20, 304 | GP_RTC_MINUTE = 21, 305 | GP_RTC_SECOND = 22, 306 | GP_RTC_DAY = 23, 307 | GP_RTC_MONTH = 24, 308 | GP_RTC_YEAR = 25 309 | }; 310 | #endif 311 | 312 | // ------------------------------------------------------------------------------------------------------- 313 | // Сетевые переменные 314 | // ------------------------------------------------------------------------------------------------------- 315 | // 316 | // структура описания сетевой переменной 317 | typedef struct netvar_t { 318 | tSignal index; // номер сетевой переменной 319 | tSignal value; // значение сетевой переменной 320 | } tNetVar; 321 | // 322 | void fbdSetNetVar(tNetVar *netvar); 323 | // 324 | bool fbdGetNetVar(tNetVar *netvar); 325 | // 326 | void fbdChangeAllNetVars(void); 327 | 328 | // ------------------------------------------------------------------------------------------------------- 329 | // MODBUS 330 | // ------------------------------------------------------------------------------------------------------- 331 | // 332 | // настройки последовательного порта для Modbus RTU 333 | // 334 | // скорость обмена: 335 | typedef enum { 336 | FBD_BR_1200 = 0, 337 | FBD_BR_2400 = 1, 338 | FBD_BR_4800 = 2, 339 | FBD_BR_9600 = 3, 340 | FBD_BR_19200 = 4, 341 | FBD_BR_38400 = 5, 342 | FBD_BR_57600 = 6, 343 | FBD_BR_115200 = 7 344 | } tFBD_BAUDRATE; 345 | // 346 | // контроль чётности: 347 | typedef enum { 348 | FBD_PAR_NONE = 0, 349 | FBD_PAR_ODD = 1, 350 | FBD_PAR_EVEN = 2 351 | } tFBD_PARITY; 352 | // 353 | // стоп-биты: 354 | typedef enum { 355 | FBD_SB_1 = 0, 356 | FBD_SB_2 = 1 357 | } tFBD_STOPB; 358 | // 359 | // статус использования Modbus 360 | typedef enum { 361 | FBD_MODBUS_NONE = 0, 362 | FBD_MODBUS_RTU = 1, 363 | FBD_MODBUS_TCP = 2, 364 | FBD_MODBUS_BOTH = 3 365 | } tFBD_MODBUS_USAGE; 366 | // 367 | // функция Modbus 368 | typedef enum { 369 | FBD_MODBUS_READ_COILS = 1, 370 | FBD_MODBUS_READ_DISCRETE_INPUTS = 2, 371 | FBD_MODBUS_READ_HOLDING_REGISTERS = 3, 372 | FBD_MODBUS_READ_INPUT_REGISTERS = 4, 373 | // 374 | FBD_MODBUS_WRITE_SINGLE_COIL = 5, 375 | FBD_MODBUS_WRITE_SINGLE_REGISTER = 6, 376 | FBD_MODBUS_WRITE_MULTIPLE_COILS = 15, 377 | FBD_MODBUS_WRITE_MULTIPLE_REGISTERS = 16 378 | } tFBD_MODBUS_FUNCTION; 379 | 380 | // структура описания настроек последовательного порта Modbus RTU 381 | typedef struct modbusrtusettings_t { 382 | unsigned int timeout; // время одидания ответа в мс (0..4095) 383 | tFBD_BAUDRATE baudRate; // скорость обмена 384 | tFBD_PARITY parity; // контроль чётности: 0-None, 1-Odd, 2-Even 385 | tFBD_STOPB stopBits; // количество стоп-бит: 0-1, 1-2 386 | } tModbusRTUsettings; 387 | 388 | // данные Modbus 389 | typedef union { 390 | tSignal intData; 391 | float floatData; 392 | uint16_t ushortData[2]; 393 | int16_t shortData[2]; 394 | uint8_t byteData[4]; 395 | 396 | } tModbusData; 397 | 398 | // структура описания запроса MODBUS 399 | typedef struct modbusreq_t { 400 | tSignal ip; // ip адрес устройства, если ==0, то использовать протокол RTU 401 | uint8_t slaveAddr; // адрес устройства 402 | tFBD_MODBUS_FUNCTION funcCode; // код функции 403 | uint16_t regAddr; // адрес регистра ModBus 404 | uint16_t regCount; // количество регистров 405 | tModbusData data; // данные, только для запросов записи 406 | } tModbusReq; 407 | 408 | // modbus bytes order 409 | #define FBD_MODBUS_OPT_BO 0x04000000 410 | // modbus words order 411 | #define FBD_MODBUS_OPT_WO 0x08000000 412 | 413 | // Получить статус использования Modbus заруженной схемой 414 | // Вызывать только после выполнения "fbdSetMemory()" 415 | // Результат выполнения: 416 | // FBD_MODBUS_NONE - Modbus не используется 417 | // FBD_MODBUS_RTU - Используется только Modbus RTU 418 | // FBD_MODBUS_TCP - Используется только Modbus TCP 419 | // FBD_MODBUS_BOTH - Используется только Modbus RTU и TCP 420 | tFBD_MODBUS_USAGE fbdModbusUsage(void); 421 | 422 | // Получить значения настроек Modbus RTU 423 | // Вызывать только после выполнения "fbdSetMemory()" 424 | // Результат выполнения: 425 | // false - менять настройки последовательного порта не надо (использовать текущие) 426 | // true - необходимо установить значения настроек последовательного порта, настройки помещены в структуру *pnt 427 | bool fbdModbusGetSerialSettings(tModbusRTUsettings *pnt); 428 | 429 | // Modbus RTU 430 | 431 | // Получить очередной запрос ModBus RTU для выполнения 432 | // Результат выполнения: 433 | // false - запросов больше нет 434 | // true - запрос есть, он помещен в структуру mbrequest 435 | bool fbdGetNextModbusRTURequest(tModbusReq *mbrequest); 436 | // 437 | // Установка результата успешного выполнения предыдущего запроса Modbus RTU. 438 | // Функция должна быть вызвана после успешного получения ответа на запрос Modbus RTU. 439 | // Параметр response - данные возвращённые запросом. 440 | void fbdSetModbusRTUResponse(tSignal response); 441 | // 442 | // Установить признак неуспешного результата полученного ранее запроса ModBus RTU 443 | // Функция должна быть вызвана после неудачного выполнения запроса Modbus RTU 444 | void fbdSetModbusRTUNoResponse(int errCode); 445 | 446 | // Modbus TCP 447 | 448 | // Получить очередной запрос ModBus TCP для выполнения 449 | // Результат выполнения: 450 | // false - запросов больше нет 451 | // true - запрос есть, он помещен в структуру mbrequest 452 | bool fbdGetNextModbusTCPRequest(tModbusReq *mbrequest); 453 | // 454 | // Установка результата успешного выполнения предыдущего запроса Modbus TCP. 455 | // Функция должна быть вызвана после успешного получения ответа на запрос Modbus TCP. 456 | // Параметр response - данные возвращённые запросом. 457 | void fbdSetModbusTCPResponse(tSignal response); 458 | // 459 | // Установить признак неуспешного результата полученного ранее запроса ModBus TCP 460 | // Функция должна быть вызвана после неудачного выполнения запроса Modbus TCP 461 | void fbdSetModbusTCPNoResponse(int errCode); 462 | 463 | // ------------------------------------------------------------------------------------------------------- 464 | // Получение значений глобальных настроек схемы 465 | // ------------------------------------------------------------------------------------------------------- 466 | // 467 | enum FBD_OPTIONS { 468 | FBD_OPT_REQ_VERSION = 0, 469 | FBD_OPT_NETVAR_USE = 1, 470 | FBD_OPT_NETVAR_PORT = 2, 471 | FBD_OPT_NETVAR_GROUP = 3, 472 | FBD_OPT_SCREEN_COUNT = 4, 473 | FBD_OPT_SCHEMA_SIZE = 5, 474 | FBD_OPT_HINTS_COUNT = 6, 475 | FBD_OPT_MODBUSRTU_OPT = 7 476 | }; 477 | 478 | extern DESCR_MEM unsigned char DESCR_MEM_SUFX *fbdGlobalOptionsCount; 479 | extern DESCR_MEM tSignal DESCR_MEM_SUFX *fbdGlobalOptions; 480 | #define FBD_REQ_VERSION fbdGlobalOptions[FBD_OPT_REQ_VERSION] 481 | #define FBD_NETVAR_USE fbdGlobalOptions[FBD_OPT_NETVAR_USE] 482 | #define FBD_NETVAR_PORT fbdGlobalOptions[FBD_OPT_NETVAR_PORT] 483 | #define FBD_NETVAR_GROUP fbdGlobalOptions[FBD_OPT_NETVAR_GROUP] 484 | #define FBD_SCREEN_COUNT ((*fbdGlobalOptionsCount>FBD_OPT_SCREEN_COUNT)?fbdGlobalOptions[FBD_OPT_SCREEN_COUNT]:0) 485 | #define FBD_SCHEMA_SIZE ((*fbdGlobalOptionsCount>FBD_OPT_SCHEMA_SIZE)?fbdGlobalOptions[FBD_OPT_SCHEMA_SIZE]:0) 486 | #define FBD_HINTS_COUNT ((*fbdGlobalOptionsCount>FBD_OPT_HINTS_COUNT)?fbdGlobalOptions[FBD_OPT_HINTS_COUNT]:0) 487 | #define FBD_MODBUSRTU_OPT ((*fbdGlobalOptionsCount>FBD_OPT_MODBUSRTU_OPT)?fbdGlobalOptions[FBD_OPT_MODBUSRTU_OPT]:0) 488 | #define FBD_MODBUS_RETRYCOUNT ((FBD_MODBUSRTU_OPT >> 19) & 3) 489 | #define FBD_MODBUS_PAUSE ((FBD_MODBUSRTU_OPT >> 21) & 1023) 490 | 491 | // FBD_MODBUS_OPT 492 | // |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| 493 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 494 | // |1 | Pause | RC |SB| Par | Baud Rate | Таймаут ответа | 495 | // 496 | // 00..11: Таймаут ответа - время одидания ответа (0..4095), мс 497 | // 12..15: Baud Rate: 498 | // 0000 - 9600 499 | // 0001 - 19200 500 | // 0010 - 38400 501 | // 0011 - 57600 502 | // 0100 - 115200 503 | // 16..17: Check Parity: 504 | // 00 - None 505 | // 01 - Odd 506 | // 10 - Even 507 | // 18: StopBits: 508 | // 0 - 1 509 | // 1 - 2 510 | // 19..20: RC - количество повторных попыток при ошибке чтения 511 | // 00 - 0 512 | // 01 - 1 513 | // 10 - 2 514 | // 11 - 3 515 | // 21..30: Pause - пауза между запросами (0..1023), мс 516 | // 31: 517 | // 0 - не менять настройки последовательного порта 518 | // 1 - менять настройки последовательного порта 519 | 520 | #ifdef USE_HMI 521 | // HMI 522 | // ------------------------------------------------------------------------------------------------------- 523 | // структура описания точки контроля или регулирования 524 | typedef struct hmidata_t { 525 | tSignal value; // текущее значение точки 526 | tSignal lowlimit; // нижний предел значения (только для точек регулирования) 527 | tSignal upperLimit; // верхний предел значения (только для точек регулирования) 528 | tSignal defValue; // значение "по умолчанию" (только для точек регулирования) 529 | tSignal divider; // делитель для отображения на индикаторе 530 | tSignal step; // шаг регулирования (только для точек регулирования) 531 | DESCR_MEM char *caption; // текстовая надпись 532 | } tHMIdata; 533 | // 534 | // получить значение точки регулирования 535 | bool fbdHMIgetSP(tSignal index, tHMIdata *pnt); 536 | // 537 | // установить значение точки регулирования 538 | void fbdHMIsetSP(tSignal index, tSignal value); 539 | // 540 | // получить значение точки контроля 541 | bool fbdHMIgetWP(tSignal index, tHMIdata *pnt); 542 | // 543 | // структура описания проекта 544 | typedef struct hmidescription_t { 545 | DESCR_MEM char *name; // наименование проекта 546 | DESCR_MEM char *version; // версия проекта 547 | DESCR_MEM char *btime; // дата и время компиляции 548 | } tHMIdescription; 549 | // 550 | // получить структуру с описанием проекта 551 | void fbdHMIgetDescription(tHMIdescription *pnt); 552 | // 553 | // возвращает указатель на текстовое описание (хинт) входа или выхода, если такого описание не найдено, то возвращает NULL 554 | DESCR_MEM char DESCR_MEM_SUFX *fbdHMIgetIOhint(char type, char index); 555 | // 556 | #endif // USE_HMI 557 | 558 | #endif // FBDRT_H 559 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![GitHub top language](https://img.shields.io/github/languages/top/crossrw/fbd-runtime.svg) 2 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/crossrw/fbd-runtime.svg) 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/719831e80f6f4a609722804dac73bd09)](https://www.codacy.com/app/crossrw/fbd-runtime?utm_source=github.com&utm_medium=referral&utm_content=crossrw/fbd-runtime&utm_campaign=Badge_Grade) 4 | ![GitHub](https://img.shields.io/github/license/crossrw/fbd-runtime.svg) 5 | 6 | # FBD-runtime 7 | 8 | ![fbd logo](https://www.mnppsaturn.ru/fbd2/images/fbd2.png) 9 | 10 | Библиотека предназначена для выполнения программ на языке FBD (*Язык функциональных диаграмм*) для ПЛК (*Программируемых Логических Контроллеров*). 11 | 12 | Подробнее о языке FBD можно прочитать на сайте [Wikipedia](http://en.wikipedia.org/wiki/Function_block_diagram). 13 | 14 | FBD - один из языков программирования описанных в международном стандарте IEC 61131-3. Данная реализация не полностью совместима с указанным стандартом, однако большинство его требований реализовано. 15 | 16 | Редактор и симулятор выполнения программ FBD вы можете скачать по [ссылке](https://www.mnppsaturn.ru/fbd2/fbd2setup.exe). 17 | 18 | На YoTube создан [плейлист](https://www.youtube.com/playlist?list=PL-NR9fQZQS9kNCQq-ceBZPDU_Iv5p9PwC), посвященный контроллеру САТ-500, использующем бибилиотеку. 19 | 20 | Библиотека использована при разработке универсального контроллера САТ-500, подробная информация о контроллере на доступна на сайте [www.sat500.ru](https://www.sat500.ru). 21 | 22 | ## Оглавление 23 | 24 | * [**Введение**](#a1) 25 | * [**Возможности**](#a2) 26 | * [**Основные понятия**](#a2) 27 | + [Контроллер](#a2.0) 28 | + [Схема](#a2.00) 29 | + [Элемент](#a2.1) 30 | + [Цепь](#a2.2) 31 | + [Сигнал](#a2.3) 32 | + [Точки регулирования и контроля](#a2.4) 33 | + [Сетевая переменная](#a2.6) 34 | + [Пример схемы](#a2.7) 35 | * [**Ограничения**](#a3) 36 | * [**Производительность**](#a4) 37 | * [**Описания элементов**](#a5) 38 | + [Входы/Выходы/Константы/Переменные](#a5.1) 39 | - [Вход](#a5.1.1) 40 | - [Связь с входным контактом контроллера](#a5.1.2) 41 | - [Константа](#a5.1.3) 42 | - [Точка регулирования](#a5.1.4) 43 | - [Входная сетевая переменная](#a5.1.5) 44 | - [Выход](#a5.1.6) 45 | - [Связь с выходным контактом контроллера](#a5.1.7) 46 | - [Выходная сетевая переменная](#a5.1.8) 47 | + [Логические функции](#a5.2) 48 | - [Логическое "НЕ"](#a5.2.1) 49 | - [Логическое "И"](#a5.2.2) 50 | - [Логическое "ИЛИ"](#a5.2.3) 51 | - [Логическое "Исключающее ИЛИ"](#a5.2.4) 52 | + [Триггеры/счётчики](#a5.3) 53 | - [RS - триггер](#a5.3.1) 54 | - [D - триггер](#a5.3.2) 55 | - [Счётчик](#a5.3.3) 56 | + [Арифметические функции](#a5.4) 57 | - [Сложение](#a5.4.1) 58 | - [Вычитание](#a5.4.2) 59 | - [Умножение](#a5.4.3) 60 | - [Деление](#a5.4.4) 61 | - [Абсолютное значение](#a5.4.5) 62 | + [Битовые операции](#a5.41) 63 | - [Побитовое логическое "И"](#a5.41.1) 64 | - [Побитовое логическое "ИЛИ"](#a5.41.2) 65 | - [Побитовое логическое "Исключающее ИЛИ"](#a5.41.3) 66 | + [Функции регулирования](#a5.5) 67 | - [Регулятор](#a5.5.1) 68 | - [Интегрирование](#a5.5.2) 69 | + [Таймеры](#a5.6) 70 | - [Таймер TON](#a5.6.1) 71 | - [Таймер TP](#a5.6.2) 72 | - [Генератор](#a5.6.3) 73 | + [Остальные элементы](#a5.7) 74 | - [Функция сравнения](#a5.7.1) 75 | - [Вычисление максимума](#a5.7.2) 76 | - [Вычисление минимума](#a5.7.3) 77 | - [Мультиплексор](#a5.7.4) 78 | - [Ограничитель](#a5.7.5) 79 | * [**Установка библиотеки**](#a6) 80 | * [**Выполнение**](#a8) 81 | 82 | 83 | 84 | ## Введение 85 | 86 | Язык функциональных диаграмм создан для описания функционирования одного или нескольких (соединенных через информационную сеть) логических контроллеров. Язык описывает входящие в состав схемы элементы и соединяющие их цепи. 87 | 88 | 89 | 90 | ## Возможности 91 | 92 | * Нет зависимости от аппаратуры, возможность применения на любой платформе где есть компилятор языка C 93 | * Алгоритм оптимизирован для встроенных контроллеров: PIC, AVR, ARM и т.п. Вычисление не использует рекурсию, стек данных используется очень экономно 94 | * Поддержка архитектур Big-Endian и Litle-Endian 95 | * Экономное использование RAM: схема из 400 элементов использует только около 1 kb ОЗУ 96 | * Широкий диапазон поддерживаемых элементов: логические, арифметические, сравнение, таймеры, триггеры, регулятор. Набор элементов может быть легко расширен 97 | * Сохранение промежуточных результатов в NVRAM (для триггеров, таймеров, точек регулирования и т.п.) 98 | * Поддержка работы по сети (Ethernet, ModBus или подобное) 99 | * Базовая поддержка интерфейса с оператором (HMI) 100 | 101 | 102 | 103 | ## Основные понятия 104 | 105 | 106 | 107 | ### Контроллер 108 | 109 | ПЛК – программируемый логический контроллер, представляют собой микропроцессорное устройство, предназначенное для сбора, преобразования, обработки, хранения информации и выработки команд управления, имеющий конечное количество входов и выходов, подключенных к ним датчиков, ключей, исполнительных механизмов к объекту управления, и предназначенный для работы в режимах реального времени с ограниченным вмешательством человека. Наиболее часто ПЛК используются для автоматизации технологических процессов. 110 | 111 | Типовая структура ПЛК показана на рисунке: 112 | 113 | ![fbd struct](https://www.mnppsaturn.ru/fbd2/images/struct.png) 114 | 115 | Обычно ПЛК предусматривает выполнение следующих функций: 116 | 117 | * Выполнение управляющей программы реализующей алгоритм управления 118 | * Подключение входных сигналов (input pins) внешних датчиков (температура, давление, дискретные входы, напряжение и т.п.) 119 | * Подключение выходных сигналов (output pins) для управления внешним оборудованием (дискретные выходы, сигналы тока или напряжения) 120 | * Подключение к информационной сети (network), объединяющей несколько ПЛК и обеспечивающей их совместное функционирование 121 | * Содержит средства реализации интерфейса с человеком (HMI) с помощью индикатора, дисплея, клавиатуры и т.п. 122 | 123 | Рабочий цикл ПЛК включает 4 фазы: 124 | 125 | 1. Чтения значений входных сигналов 126 | 2. Выполнение программы обработки 127 | 3. Установку значений выходных сигналов 128 | 4. Вспомогательные операции (обмен данными по информационной сети, реализация HMI и т.п.). 129 | 130 | 131 | 132 | ### Схема 133 | 134 | Схема на языке FBD это набор элементов, значений их параметров и соединений (цепей) между элементами. 135 | 136 | 137 | 138 | ### Элемент 139 | 140 | Элемент схемы это минимальный функциональный блок, выполняющий определенную операцию (логическую, арифметическую, задержку и т.п.) или обеспечивающий подключение схемы к входным или выходным цепям программируемого контроллера. 141 | 142 | * Каждый элемент должен иметь уникальное в рамках схемы идентифицирующее его имя. 143 | * Элемент может иметь некоторое количество входов (а может и не иметь) и один или ни одного выхода. Количество входов и выходов зависит от типа элемента. На графическом изображении элементов входы всегда расположены слева, а выходы справа от элемента. Каждый вход элемента должен быть подключен к цепи, которая соединяет его с выходом другого элемента. Выход элемента может быть подключен к одному или нескольким входам других элементов (в том числе и его собственным входам). Выход элемента может быть никуда не подключен, в этом случае элемент не имеет смысла. 144 | * Элементы, которые не имеют выхода обычно используются для обозначения аппаратных выходов программируемого контроллера, выходных сетевых переменных или точек отображения. 145 | * Элементы, которые не имеют входа обычно используются для обозначения аппаратных входов программируемого контроллера, входных сетевых переменных или точек регулирования. 146 | * Элемент может иметь (а может и не иметь: это зависит от типа элемента) параметры, влияющие на его функционирование. Параметры имеют фиксированное значение и задаются при разработке схемы. 147 | 148 | 149 | 150 | ### Цепь 151 | 152 | Цепь это логическая связь между входами и выходами элементов предназначенная для передачи состояния (значения) сигнала. Каждая цепь должна быть подключена строго к одному выходу и одному или нескольким входам элементов схемы. Состояние цепи к которой не подключен ни один выход элемента является неопределенным. Цепь, которая не подключена ни к одному входу элемента, является не используемой и ее состояние не вычисляется. Аналогично элементам, цепи имеют уникальные в рамках схемы имена. Цепи служат для передачи значений сигналов между элементами схемы. На графическом изображении схемы цепи изображаются в виде линий, которые соединяют входы и выходы элементов. 153 | 154 | 155 | 156 | ### Сигнал 157 | 158 | Сигнал представляет собой целое число со знаком. В зависимости от параметров, указанных при сборке библиотеки, сигнал может занимать 1, 2 или 4 байта. Сигналы можно использовать для логических операций. При этом значение сигнала равное 0 интерпретируется как логический "0" (False), а значение сигнала не равное 0 - как логическая "1" (True). Для входов некоторых элементов важно не само значение сигнала, а факт его нарастания (передний фронт). Такие входы на графическом изображении элемента обозначены наклонной чертой. Факт наличия переднего фронта фиксируется при любом изменении сигнала в большую сторону (Si > Si-1). Элементы, выполняющие логические функции, формируют значение сигнала "1" для логической "1" и значение сигнала "0" для логического "0". 159 | 160 | 161 | 162 | ### Точки регулирования и контроля 163 | 164 | Точка регулирования является составной частью организации интерфейса контроллера с оператором (HMI). При помощи точек регулирования оператор может установить значения параметров, используемых при работе схемы. 165 | 166 | Точка контроля является составной частью организации интерфейса контроллера с оператором (HMI). При помощи точек контроля оператор может просматривать значения сигналов в различных цепях схемы. 167 | 168 | 169 | 170 | ### Сетевая переменная 171 | 172 | Сетевые переменные служат для обмена данными между контроллерами по информационной сети. В схеме могут быть определены входные и выходные сетевые переменные. Каждая сетевая переменная идентифицируется номером. Значение сигнала поданное на выходную сетевую переменную становится доступным через имеющие тот же номер входные сетевые переменные, описанные в схемах на других контроллерах, подключенных к единой информационной сети. 173 | 174 | 175 | 176 | ### Пример схемы 177 | 178 | Пример простой схемы показан на рисунке: 179 | 180 | ![fbd demo](https://www.mnppsaturn.ru/fbd2/images/fbddemo.png) 181 | 182 | 183 | 184 | ## Ограничения 185 | 186 | Используемый алгоритм расчета содержит определенные ограничения: 187 | 188 | * Элемент не может иметь более одного выхода 189 | * Поддерживается только один тип данных (`tSignal`). Это относится ко всем входам и выходам всех элементов, значениям сигналов цепей, констант. Размерность типа данных определяется при компиляции проекта. Обычно используется тип целое число со знаком. 190 | 191 | 192 | 193 | ## Производительность 194 | 195 | Один цикл вычисления схемы производится в ходе выполнения функции `fbdDoStep()`. В ходе одного цикла производится расчет значений всех элементов схемы и установка значений всех сетевых переменных и выходных контактов. Время расчета зависит от множества факторов, в первую очередь от количества и типов используемых элементов в схеме. Результаты тестирования времени выполнения небольшой [схемы из 10 элементов](https://www.mnppsaturn.ru/fbd2/images/generator.zip) показаны в таблице: 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |
CPU@FreqКомпиляторДлительность циклаРасчет одного элемента (среднее)
PIC18@9.83x4MHzxc8 v1.21~5ms~500µs
Si8051@48MHzKeil C51~2.5ms~250µs
ARM920T@180MHzgcc v4.1.2~61µs~6.1µs
STMF437@168MHzKeil~31µs~3.1µs
i7-3770K@3.5GHzgcc v4.4.1~279ns~27.9ns
204 | 205 | 206 | 207 | ## Описания элементов 208 | 209 | 210 | 211 | ### Входы/Выходы/Константы/Переменные 212 | 213 | 214 | 215 | #### Вход 216 | 217 | Элемент имеет один выходной контакт. Входов у элемента нет. Элемент является источником значения сигнала для других подключенных к нему элементов. В зависимости от настроек, элемент может выполнять четыре разные функции: 218 | 219 | 220 | 221 | ##### Связь с входным контактом контроллера 222 | 223 | ![inpin](https://www.mnppsaturn.ru/fbd2/images/inpin.png) 224 | 225 | В этом случае у элемента имеется один параметр: номер контакта контроллера. Выходное значение сигнала элемента зависит от номера контакта и особенностей аппаратной реализации контроллера. Обычно используется для получения значений подключенных дискретных входов, датчиков температуры, давления, АЦП и т.п. Активное состояние дискретного входа (замкнут, подано напряжение) должно соответствовать значению сигнала "1". 226 | 227 | 228 | 229 | ##### Константа 230 | 231 | ![const](https://www.mnppsaturn.ru/fbd2/images/const.png) 232 | 233 | В этом случае у элемента имеется один параметр: значение константы. Выходное значение сигнала элемента всегда соответствует значению параметра (константе). 234 | 235 | 236 | 237 | ##### Точка регулирования 238 | 239 | ![sp](https://www.mnppsaturn.ru/fbd2/images/sp.png) 240 | 241 | Точка регулирования необходима для реализации возможности корректировки значений настроек, используемых при вычислении схемы. В отличие от константы, значение выходного сигнала элемента может быть изменено эксплуатирующим персоналом (или наладчиком) контроллера. Изменение значения точки регулирование может быть организовано через средства человеко-машинного интерфейса (HMI) контроллера (клавиатуру, индикатор и т.п.). Точка регулирования имеет 4 параметра: значение по умолчанию, минимальное значение, максимальное значение, текстовая строка с описанием. 242 | 243 | 244 | 245 | ##### Входная сетевая переменная 246 | 247 | ![invar](https://www.mnppsaturn.ru/fbd2/images/invar.png) 248 | 249 | Библиотека предусматривает возможность подключения контроллера в информационную сеть, которая объединяет несколько однотипных или разнородных контроллеров. Такая сеть может быть построена с использованием интерфейсов Ethernet, RS-485, CAN и т.п. Значение выходного сигнала соответствует значению, полученному от другого контроллера по информационной сети. У элемента есть два параметра: номер сетевой переменной и значение переменной "по умолчанию". Последние полученные по сети значения переменных сохраняются в NVRAM и доступны в дальнейшем при возникшей неисправности сети, в том числе после выключения и включения контроллера. Значение сетевой переменной "по умолчанию" используется после загрузки новой программы в контроллер до получения нового значения переменной из сети. 250 | 251 | 252 | 253 | #### Выход 254 | 255 | Элемент имеет один входной контакт. Выходов у элемента нет. В зависимости от настроек, элемент может выполнять две разные функции: 256 | 257 | 258 | 259 | ##### Связь с выходным контактом контроллера 260 | 261 | ![outpin](https://www.mnppsaturn.ru/fbd2/images/outpin.png) 262 | 263 | У элемента имеется один параметр: номер выходного контакта контроллера. Интерпретация входного значения зависит от особенностей аппаратной реализации на конкретном контроллере. Обычно используется для управления дискретными и аналоговыми выходами контроллера. 264 | 265 | 266 | 267 | ##### Выходная сетевая переменная 268 | 269 | ![outvar](https://www.mnppsaturn.ru/fbd2/images/outvar.png) 270 | 271 | У элемента имеется один параметр: номер выходной сетевой переменной. Значение поданного на вход элемента сигнала передается по информационной сети и доступно на других контроллерах через элемент "Входная сетевая переменная". Не рекомендуется иметь в рамках одной схемы элементы "Входная сетевая переменная" и "Выходная сетевая переменная" с одинаковыми номерами. 272 | 273 | 274 | 275 | ### Логические функции 276 | 277 | 278 | 279 | #### Инверсия (NOT) 280 | 281 | ![not](https://www.mnppsaturn.ru/fbd2/images/not.png) 282 | 283 | Элемент выполняет функцию логической инверсии "NOT". Элемент имеет один вход и один выход. Таблица истинности: 284 | 285 | 286 | 287 | 288 | 289 |
ВходВыходной сигнал
Логический "0"1
Логическая "1"0
290 | 291 | 292 | 293 | #### Логическое "И" (AND) 294 | 295 | ![and](https://www.mnppsaturn.ru/fbd2/images/and.png) 296 | 297 | Элемент выполняет логическую функцию "И" ("AND"). Элемент имеет два входа и один выход. Элемент имеет настройку инвертирования выходного сигнала. 298 | Таблица истинности: 299 | 300 | 301 | 302 | 303 | 304 | 305 |
Вход 1Вход 2ВыходИнвертированный выход
Логический "0"Любое значение01
Any valueЛогический "0"01
Логическая "1"Логическая "1"10
306 | 307 | 308 | 309 | #### Логическое "ИЛИ" (OR) 310 | 311 | ![or](https://www.mnppsaturn.ru/fbd2/images/or.png) 312 | 313 | Элемент выполняет логическую функцию "ИЛИ" ("OR"). Элемент имеет два входа и один выход. Элемент имеет настройку инвертирования выходного сигнала. 314 | Таблица истинности: 315 | 316 | 317 | 318 | 319 | 320 | 321 |
Вход 1Вход 2ВыходИнвертированный выход
Логический "0"Логический "0"01
Логическая "1"Любое значение10
Любое значениеЛогическая "1"10
322 | 323 | 324 | 325 | #### Логическое "Исключающее ИЛИ" (XOR) 326 | 327 | ![xor](https://www.mnppsaturn.ru/fbd2/images/xor.png) 328 | 329 | Элемент выполняет логическую функцию "Исключающее ИЛИ" ("XOR"). Элемент имеет два входа и один выход. Элемент имеет настройку инвертирования выходного сигнала. Таблица истинности: 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 |
Вход 1Вход 2ВыходИнвертированный выход
Логический "0"Логический "0"01
Логический "0"Логическая "1"10
Логическая "1"Логический "0"10
Логическая "1"Логическая "1"01
338 | 339 | 340 | 341 | ### Триггеры 342 | 343 | 344 | 345 | #### RS - триггер 346 | 347 | ![rs](https://www.mnppsaturn.ru/fbd2/images/rs.png) 348 | 349 | RS - триггер имеет два входа (R-Reset и S-Set) и один выход (Q). 350 | 351 | RS - триггер, сохраняет своё предыдущее состояние при неактивном состоянии обоих входов (сигнал логического "0") и изменяет своё состояние при подаче на один из его входов сигнала логической "1". Пока на входы R и S подан сигнал логический "0", выход Q находится в неизменном состоянии. Если на вход S (Set) подать сигнал логическая "1", то сигнал на выходе Q принимает значение "1". Если на вход R (Reset) подать сигнал логическая "1", то сигнал на выходе Q принимает значение "0". При переводе сигнала на входе S или R в состояние логического "0", сигнал на выходе Q сохраняет своё предыдущее значение. При подаче на оба входа S и R сигнала логическая "1" состояние выхода Q не определено. Элемент имеет настройку инвертирования выходного сигнала. 352 | 353 | Таблица истинности: 354 | 355 | 356 | 357 | 358 | 359 | 360 |
SRQnextДействие
00QХранение
010Сброс
101Установка
11?Не разрешено
361 | 362 | Каждый раз, когда триггер изменяет своё состояние, новое значение сохраняется в энергонезависимой памяти NVRAM (путём вызова функции `FBDsetProc(1, index, *value)`). При инициализации схемы, ранее сохранённые значения триггера восстанавливаются (путём вызова функции `FBDgetProc(1, index)`). 363 | 364 | 365 | 366 | #### D - триггер 367 | 368 | ![d](https://www.mnppsaturn.ru/fbd2/images/d.png) 369 | 370 | D - триггер имеет два входа (D-Data и C-Clock) и один выход (Q). 371 | 372 | D - триггер при положительном перепаде (нарастании) сигнала на входе C запоминает состояние входа D и выдаёт его на выход Q. При отсутствии положительного перепада на входе C, состояние выхода Q сохраняется постоянным. 373 | 374 | Таблица истинности: 375 | 376 | 377 | 378 | 379 |
DCQnextДействие
Любое значениеНарастаниеDЗапоминание
Любое значениеНет нарастанияСохранённое значение DХранение
380 | 381 | В отличие от традиционных D - триггеров значения сигнала на входе D и выходе Q может принимать любое значение (не только "0" и "1"). 382 | 383 | Каждый раз, когда триггер изменяет своё состояние, новое значение сохраняется в энергонезависимой памяти NVRAM (путём вызова функции `FBDsetProc(1, index, *value)`). При инициализации схемы, ранее сохранённые значения триггера восстанавливаются (путём вызова функции `FBDgetProc(1, index)`). 384 | 385 | 386 | 387 | #### Счётчик 388 | 389 | ![cnt](https://www.mnppsaturn.ru/fbd2/images/cnt.png) 390 | 391 | Элемент имеет три входа и один выход. Если на вход R (Reset) подан сигнал логической "1", то сигнал на выходе Q принимает значение "0" (сброс счётчика). Если на вход R подан сигнал логического "0", то значение сигнала на выходе Q увеличивается на 1 при положительном перепаде на входе "+" или уменьшается на 1 при положительном перепаде на входе "-". При отсутствии положительного перепада на входах "+" и "-" значение сигнала на выходе Q не изменяется. 392 | 393 | Таблица истинности: 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 |
+-RQnextДействие
Любое значениеЛюбое значение10Сброс
НарастаниеНет нарастания0Q+1Увеличение на 1
Нет нарастанияНарастание0Q-1Уменьшение на 1
Нет нарастанияНет нарастания0QНет изменений
НарастаниеНарастание0QНет изменений
402 | 403 | Каждый раз, когда счётчик изменяет своё состояние, новое значение сохраняется в энергонезависимой памяти NVRAM (путём вызова функции `FBDsetProc(1, index, *value)`). При инициализации схемы, ранее сохранённые значения счётчика восстанавливаются (путём вызова функции `FBDgetProc(1, index)`). 404 | 405 | 406 | 407 | ### Арифметические функции 408 | 409 | 410 | 411 | #### Сложение 412 | 413 | ![add](https://www.mnppsaturn.ru/fbd2/images/add.png) 414 | 415 | Элемент имеет два входа и один выход. Значение выходного сигнала вычисляется, как арифметическая сумма значений сигналов на входах. 416 | 417 | 418 | 419 | #### Вычитание 420 | 421 | ![sub](https://www.mnppsaturn.ru/fbd2/images/sub.png) 422 | 423 | Элемент имеет два входа и один выход. Значение выходного сигнала вычисляется, как арифметическая разность сигнала на первом (уменьшаемое) и на втором (вычитаемое) входах. 424 | 425 | 426 | 427 | #### Умножение 428 | 429 | ![mul](https://www.mnppsaturn.ru/fbd2/images/mul.png) 430 | 431 | Элемент имеет два входа и один выход. Значение выходного сигнала вычисляется, как арифметическое произведение сигналов на первом и втором входах. 432 | 433 | 434 | 435 | #### Деление 436 | 437 | ![div](https://www.mnppsaturn.ru/fbd2/images/div.png) 438 | 439 | Элемент имеет два входа и один выход. Значение выходного сигнала вычисляется, как арифметическое деление значения сигнала на первом входе (делимое) на значение сигнала на втором входе (делитель). 440 | 441 | Особые случаи: 442 | 443 | 444 | 445 | 446 | 447 |
Вход 1Вход 2Выход
001
Любое положительное значение0MAX_SIGNAL
Любое отрицательное значение0MIN_SIGNAL
448 | 449 | О том, что делить на 0 нельзя я знаю. :-) 450 | 451 | 452 | 453 | #### Абсолютное значение 454 | 455 | ![abs](https://www.mnppsaturn.ru/fbd2/images/abs.png) 456 | 457 | Элемент имеет один вход и один выход. Значение выходного сигнала есть абсолютное значение сигнала на входе. 458 | 459 | 460 | 461 | ### Битовые операции 462 | 463 | 464 | 465 | #### Побитовое логическое "И" 466 | 467 | ![band](https://www.mnppsaturn.ru/fbd2/images/band.png) 468 | 469 | Побитовое «И» — это бинарная операция, действие которой эквивалентно применению логического «И» к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях значений входных сигналов. Другими словами, если оба соответствующих бита значений входных сигналов равны 1, результирующий двоичный разряд равен 1; если же хотя бы один бит из пары равен 0, результирующий двоичный разряд равен 0. 470 | 471 | 472 | 473 | #### Побитовое логическое "ИЛИ" 474 | 475 | ![bor](https://www.mnppsaturn.ru/fbd2/images/bor.png) 476 | 477 | Побитовое «ИЛИ» — это бинарная операция, действие которой эквивалентно применению логического «ИЛИ» к каждой паре битов, которые стоят на одинаковых позициях в двоичных представлениях значений входных сигналов. Другими словами, если оба соответствующих бита значений входных сигналов равны 0, двоичный разряд результата равен 0; если же хотя бы один бит из пары равен 1, двоичный разряд результата равен 1. 478 | 479 | 480 | 481 | #### Побитовое логическое "Исключающее ИЛИ" 482 | 483 | ![bxor](https://www.mnppsaturn.ru/fbd2/images/bxor.png) 484 | 485 | Исключающее« ИЛИ» (сложение по модулю 2) — это бинарная операция, результат действия которой равен 1, если число складываемых единичных битов нечётно и равен 0, если чётно. Другими словами, если оба соответствующих бита значений входных сигналов равны между собой, двоичный разряд результата равен 0; в противном случае, двоичный разряд результата равен 1. 486 | 487 | 488 | 489 | ### Функции регулирования 490 | 491 | 492 | 493 | #### Регулятор 494 | 495 | ![pid](https://www.mnppsaturn.ru/fbd2/images/pid.png) 496 | 497 | Элемент имеет четыре входа и один выход. Входы: 498 | 499 | * U - текущее значение контролируемого процесса (обратная связь) 500 | * REF - значение, которое должно быть достигнуто в процессе регулирования (уставка) 501 | * DT - время реакции регулятора (мс) 502 | * P - коэффициент пропорциональности 503 | 504 | Выход Q - сигнал влияния на регулируемый процесс. 505 | 506 | В отличие от множества других подобных реализаций, данный элемент не использует традиционный алгоритм PID регулирования. Вместо этого применен алгоритм Эйлера-Лагранжа, обеспечивающий простоту настройки, высокую скорость сходимости и отсутствие склонности к возникновению колебаний. Данный алгоритм широко используется при стыковке космических аппаратов. Подробности можно узнать из специальной литературы, на сайте проекта Wikipedia имеется [статья](https://ru.wikipedia.org/wiki/%D0%A3%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_%D0%AD%D0%B9%D0%BB%D0%B5%D1%80%D0%B0_%E2%80%94_%D0%9B%D0%B0%D0%B3%D1%80%D0%B0%D0%BD%D0%B6%D0%B0), которая поясняет некоторые моменты алгоритма. 507 | 508 | Подробности реализации можно увидеть в исходном коде и примерах. 509 | 510 | 511 | 512 | #### Интегратор 513 | 514 | ![sum](https://www.mnppsaturn.ru/fbd2/images/sum.png) 515 | 516 | Элемент используется для интегрирования (суммирования) во времени значения входного сигнала. Элемент часто используется совместно с описанным выше [регулятором](#a5.5.1). Элемент имеет три входа и один выход. Входы: 517 | 518 | * X - интегрируемый сигнал 519 | * DT - время интегрирования (мс) 520 | * Lim - ограничение значения выходного сигнала 521 | 522 | Выход Q - значение величины интеграла (суммы). 523 | 524 | Каждый интервал времени DT значение входного сигнала X складывается с текущим накопленным значением. Если новое значение больше значения Lim или меньше значения -Lim, то результат ограничивается значением Lim или -Lim. Сигнал на выходе Q принимает значение результата. 525 | 526 | 527 | 528 | ### Таймеры 529 | 530 | 531 | 532 | #### Таймер TON 533 | 534 | ![ton](https://www.mnppsaturn.ru/fbd2/images/ton.png) 535 | 536 | TON - это таймер с задержкой включения. Элемент может быть использован в генераторах периодических сигналов и счетчиках времени. Элемент имеет два входа и один выход: 537 | 538 | * D - запуск таймера 539 | * T - величина задержки (мс) 540 | * Q - выходной логический сигнал 541 | 542 | Если на входе D присутствует сигнал логического "0", то на выходе Q всегда сигнал "0". Если на вход D подать сигнал логической "1", то на выходе Q появится сигнал "1" через промежуток времени, соответствующий сигналу на входе T. Элемент имеет настройку, инвертирующую значение выходного сигнала. 543 | 544 | Таблица истинности: 545 | 546 | 547 | 548 | 549 | 550 | 551 |
DУсловиеВыходИнвертированный выход
Логический "0"Любое01
Логическая "1"Время T не истекло01
Логическая "1"Время T истекло10
552 | 553 | Временная диаграмма таймера TON: 554 | 555 | ![dton](https://www.mnppsaturn.ru/fbd2/images/dton.png) 556 | 557 | 558 | 559 | #### Таймер TP 560 | 561 | ![tp](https://www.mnppsaturn.ru/fbd2/images/tp.png) 562 | 563 | Элемент имеет два входа и выход: 564 | 565 | * D - запуск таймера 566 | * T - время задержки (мс) 567 | * Q - выход 568 | 569 | Положительный фронт сигнала на входе D запускает таймер и устанавливает на выходе Q значение сигнала "1". По истечении времени T значение выходного сигнала принимает значение "0". Элемент имеет настройку, инвертирующую значение выходного сигнала. 570 | 571 | Временная диаграмма таймера TP: 572 | 573 | ![dtp](https://www.mnppsaturn.ru/fbd2/images/dtp.png) 574 | 575 | 576 | 577 | #### Генератор 578 | 579 | ![gen](https://www.mnppsaturn.ru/fbd2/images/gen.png) 580 | 581 | Генератор может быть использован для формирования периодических сигналов различной формы. Элемент имеет два входа и выход: 582 | 583 | * A - амплитуда формируемого сигнала 584 | * T - период последовательности (мс) 585 | * Q - выход 586 | 587 | Если на входе T присутствует значение сигнала меньшее или равное "0", то генератор остановлен и на его выходе Q формируется сигнал со значением "0". При положительном значении сигнала на входе T генератор формирует на выходе Q периодический сигнал с амплитудой A и периодом T. В зависимости от настройки элемента, выполненной при проектировании схемы, форма сигнала может быть следующих видов: 588 | 589 | * Прямоугольные импульсы (меандр со значениями сигнала "0" и "A") 590 | * Пилообразный сигнал (сигнал с линейно нарастающим значением и последующим сбросом в "0") 591 | * Импульсы треугольной формы 592 | * Синусоидальный сигнал 593 | 594 | ![dgen](https://www.mnppsaturn.ru/fbd2/images/dgen.png) 595 | 596 | Значение сигнала на выходе Q лежит в диапазоне от "0" до "A" для генераторов меандра, пилообразных и треугольных импульсов. Для генератора синусоидального сигнала выход Q принимает значение от "-A" до "A". 597 | 598 | Степень достоверности формы выходного сигнала генератора зависит от соотношения заданного периода сигнала и времени выполнения расчета схемы. Рекомендуется устанавливать значение периода T большим, чем время расчета одного шага схемы умноженное на коэффициент 15-20. В противном случае форма выходного сигнала генератора может значительно отличаться от требуемой. 599 | 600 | 601 | 602 | ### Остальные элементы 603 | 604 | 605 | 606 | #### Сравнение 607 | 608 | ![cmp](https://www.mnppsaturn.ru/fbd2/images/cmp.png) 609 | 610 | Элемент имеет два входа и выход, выполняет сравнение значений сигналов на входах 1 и 2. Элемент имеет настройку, инвертирующую значение выходного сигнала. Таблица истинности: 611 | 612 | 613 | 614 | 615 | 616 |
УсловиеВыходИнвертированный выход
Input1Val > Input2Val10
Input1Val <= Input2Val01
617 | 618 | 619 | 620 | #### Вычисление максимума 621 | 622 | ![max](https://www.mnppsaturn.ru/fbd2/images/minmax.png) 623 | 624 | Элемент имеет два входа и выход. Выходное значение сигнала соответствует входному сигналу с максимальным значением. Таблица истинности: 625 | 626 | 627 | 628 | 629 | 630 |
УсловиеВыход
Input1Val > Input2ValInput1Val
Input1Val <= Input2ValInput2Val
631 | 632 | 633 | 634 | #### Вычисление минимума 635 | 636 | ![min](https://www.mnppsaturn.ru/fbd2/images/minmax.png) 637 | 638 | Элемент имеет два входа и выход. Выходное значение сигнала соответствует входному сигналу с минимальным значением. Таблица истинности: 639 | 640 | 641 | 642 | 643 | 644 |
УсловиеВыход
Input1Val > Input2ValInput2Val
Input1Val <= Input2ValInput1Val
645 | 646 | 647 | 648 | #### Мультиплексор 649 | 650 | ![ms](https://www.mnppsaturn.ru/fbd2/images/ms.png) 651 | 652 | Расширяемый мультиплексор. Элемент имеет пять входов (D0-D3, A) и один выход. Значение выходного сигнала соответствует значению сигнала на выбранном входе D0-D3. Выбор выхода производится на основании значения сигнала на входе A. Таблица истинности: 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 |
AВыход Q
0D0
1D1
2D2
3D3
661 | 662 | 663 | 664 | #### Ограничитель 665 | 666 | ![lim](https://www.mnppsaturn.ru/fbd2/images/lim.png) 667 | 668 | Ограничитель сигнала имеет 3 входа и один выход: 669 | 670 | * D - входной сигнал 671 | * MX - верхнее ограничение сигнала 672 | * MN - нижнее ограничение сигнала 673 | 674 | Выходное значение сигнала элемента соответствует входному значению ограниченному значениями сигналов на входах MX (верхнее ограничение) и MN (нижнее ограничение). 675 | 676 | 677 | 678 | ### Установка и настройка библиотеки 679 | 680 | Установка библиотеки заключается в копировании файлов `fbdrt.h` и `fbdrt.c` в свой проект. Настройка состоит в редактировании файла `fbdrt.h` в соответствии со своими требованиями. 681 | 682 | Вы можете изменить следующие определения в файле `fbdrt.h`: 683 | 684 | 1. Выберите тип данных, используемый для хранения значений сигналов. 685 | 2. Выберите тип данных используемый для индекса элементов. 686 | 3. Если необходимо измените определения `ROM_CONST` и `DESCR_MEM`. 687 | 4. Выберите необходимость использования оптимизации по скорости выполнения (определение `SPEED_OPT`). 688 | 5. Выберите необходимость использования функций HMI (определение `USE_HMI`). 689 | 6. Напишите свою реализацию функций `FBDgetProc()` и `FBDsetProc()`. 690 | 7. Если вы используете экраны HMI напишите свою реализацию функций `FBDdrawXXXXXX()`. 691 | 692 | От выбора типа данных для хранения сигналов зависит то, сигналы каких значений сможет обрабатывать ваша схема. Кроме того, от этого выбора 693 | зависят объем программы и объем используемой ею памяти и скорость расчета схемы. Для небольших встариваемых микроконтроллеров это может быть важным. В общем случае тип данных должен обеспечивать хранение целого числа со знаком. Если вы не уверены в выборе, то рекомендую остановиться на 694 | значении "по умолчанию": 32-х битное целое число со знаком. Выбор типа производится установкой значения определения `SIGNAL_SIZE`: 695 | 696 | ```c 697 | // размер памяти для сигнала схемы (1, 2 или 4) 698 | #define SIGNAL_SIZE 4 699 | ``` 700 | 701 | Выбор типа данных для индекса элемента определяет как много элементов может быть в вашей схеме. Тип данных должен обеспечивать возможность хранения целых чисел без знака. Выбор типа производится установкой значения определения `INDEX_SIZE`. Рекомендуем остановить свой выбор на значении "по умолчанию" 2, в этом случае в вашей схеме может быть до 65535 элементов. 702 | 703 | ```c 704 | // размер пямяти для индекса элемента (1 или 2) 705 | #define INDEX_SIZE 2 706 | ``` 707 | 708 | Определения `ROM_CONST` и `DESCR_MEM` используются для описания памяти в которой размещаются константы и массив описания схемы соответственно. Значения зависят от используемого вами компилятора. Для примера, при использовании компилятора xc8 (Microchip) и размещении данных в памяти FLASH необходимо использовать `const`: 709 | 710 | ```c 711 | // data in ROM/FLASH 712 | #define ROM_CONST const 713 | // schema description 714 | #define DESCR_MEM const 715 | ``` 716 | 717 | Использование определения `SPEED_OPT` уменьшает время расчета схемы примерно в 6 раз(для средних и больших схем), объем необходимой памяти RAM при этом увеличивается примерно в 3 раза. Когда вы включаете определение `SPEED_OPT`, то в момент инициализации производится предварительный расчет некоторых таблиц с указателями, использование которых уменьшает время расчета схемы. 718 | 719 | Определение `USE_HMI` необходимо в случае, когда ваш PLC содержит средства организации интрерфейса с человеком. Такими средствами могут быть например экран и кнопки. Будьте внимательны: если вы отключите HMI точки управления будут возвращать неопределенные данные! Пример: 720 | 721 | ```c 722 | // needed if you use HMI functions 723 | #define USE_HMI 724 | // speed optimization reduces the calculation time, but increases the size of memory (RAM) required 725 | #define SPEED_OPT 726 | ``` 727 | 728 | Если вы установили `USE_HMI`, то дополнительно необходимо указать разрешение экране вашего контроллера в определениях: 729 | 730 | ```c 731 | #define SCREEN_WIDTH 320 732 | #define SCREEN_HEIGHT 240 733 | ``` 734 | 735 | Функции `FBDgetProc()` и `FBDsetProc()` служат для связи схемы с реальными входами и выходами вашего PLC. 736 | 737 | Функция `FBDgetProc()` используется для чтения состояния входных сигналов (контактов) или значений, сохраненных в памяти NVRAM. 738 | 739 | Функция `FBDsetProc()` используется для записии значений выходных сигналов (контактов) или для сохранения значений в память NVRAM. 740 | 741 | Реализация этих функций зависит от специфики ваших задач. В общем случае рекомендуем придерживаться следующих правил: 742 | 743 | * Для дискретных входов и выходов использовать значения `0` и `1`. При этом значение `0` должно соответствовать пассивному состоянию: выключен, разомкнут и т.п. 744 | * Для аналоговых входов и выходов значения которых представлены в инженерных единицах измерения (градусы, бары, вольты) необходимо использовать множитель, который преобразует эти значения в целые числа. Например для входа к которому подключен датчик температуры можно использовать множитель 100, который преобразует значение температуры на датчике +10.5 C в значение сигнала схемы 1050. 745 | 746 | Не используйте прямую запись в EEPROM при каждом вызове `FBDsetProc()`: библиотека далает такие вызовы при каждом изменении состяния тригеров, таймеров и генератора. Частая запись в EEPROM может значительно сократить срок ее службы. Решением может быть отложенная запись или использование памяти RAM с питанием от батарейки. 747 | 748 | ```c 749 | tSignal FBDgetProc(char type, tSignal index) 750 | { 751 | switch(type) { 752 | case 0: 753 | printf(" request InputPin(%d)\n", index); 754 | return 0; 755 | case 1: 756 | printf(" request NVRAM(%d)\n", index); 757 | return 0; 758 | } 759 | } 760 | 761 | void FBDsetProc(char type, tSignal index, tSignal *value) 762 | { 763 | switch(type) 764 | { 765 | case 0: 766 | printf(" set OutputPin(%d) to value %d\n", index, *value); 767 | break; 768 | case 1: 769 | printf(" set NVRAM(%d) to value %d\n", index, *value); 770 | break; 771 | } 772 | } 773 | ``` 774 | 775 | 776 | 777 | ## Выполнение 778 | 779 | ![fbd alg](https://www.mnppsaturn.ru/fbd2/images/alg.png) 780 | 781 | ### Инициализация 782 | 783 | Инициализация схемы должна быть выполнена один раз в начале работы. Инициализация можут быть выполнена повторно для сброса состояния схемы. 784 | Для инициализации схемы необходимо выполнить вызов функции: 785 | 786 | ```c 787 | int fbdInit(DESCR_MEM unsigned char *descr) 788 | ``` 789 | 790 | __descr__ - указатель на массив описания схемы. 791 | 792 | Отрицательное значение результата функции означает ошибку: 793 | __-1__ - неверный код элемента в описании схемы; 794 | __-2__ - несовпадение размерности сигнала или элемента; 795 | __-3__ - массив описания содержит схему с неподдерживаемой версией; 796 | __-4__ - ошибка при проверке контрольной суммы описания схемы. 797 | 798 | Положительное значение результата функции - требуемый объем RAM для выполнения схемы. 799 | 800 | Если вызов инициализаии схемы выполнен без ошибок и вы имеете необходимый объем RAM, то следующим шагом необходимо выполнить вызов инициализации памяти: 801 | 802 | ```c 803 | void fbdSetMemory(char *buf, bool needReset) 804 | ``` 805 | 806 | __buf__ - указатель на буфер памяти, размер буфера должен быть не меньше величины, которую вернул вызов функции `fbdInit()`; 807 | __needReset__ - необходимость сброса содержимого EEPROM. 808 | 809 | Параметр __needReset__ должен быть установлен в значение `TRUE` после записи в контроллер новой схемы. При этом значения всех элементов с "пямятью" (точек регулирования, тригеров и генероаторов) сбрасываются в состояние "по умолчанию". 810 | 811 | После инициализации памяти схема готова к работе. 812 | 813 | ### Пошаговое выполнение 814 | 815 | Для вычисления схемы необходимо периодически (например в главном цикле программы) выполнять функцию: 816 | 817 | ```c 818 | void fbdDoStep(tSignal period) 819 | ``` 820 | 821 | __period__ - время, прошедшее с момента предыдущего вызова функции, при первом вызове необходимо указать значение 0. 822 | 823 | В результате каждого вызова функции выполняется вычисление всех элементов схемы и производится установка всех выходных контактов и переменных. 824 | 825 | ### Синхронизация времени 826 | 827 | Некоторые элементы схемы используют значение времени или временных интервалов. К таким элементам относятся генератор, таймер, интегратор сигнала, регулятор. Для каждого элемента библиотека организует независимый таймер, значение которого хранится в RAM. Для хранения значения таймера используется тип `tSignal`. 828 | 829 | Timer values changes by `period` every time the function `fbdDoStep(tSignal period)` is called. Time can be expressed in any units: sec, ms, µs. Unit selection depends on the time scale in which the PLC should work. In my opinion, a decent selection of milliseconds for most cases. 830 | 831 | ## Network variables 832 | 833 | ## Human machine interface (HMI) 834 | 835 | The library contains basic functions support HMI. This can be used if your PLC has a display (LCD). You can make character or graphical menu with which to perform the following functions: 836 | 837 | * View values of schema points (`Watchpoints`) 838 | * Set the values of set points (`Setpoints`) 839 | 840 | ![fbd menu](https://www.mnppsaturn.ru/fbd2/images/menu2.png) 841 | 842 | Watchpoints is used to display the values of the signals for operator. Setpoints are used to set the reference values, used in the operation of the circuit. Each watchpoint and setpoint has the associated text caption, that can be displayed on LCD. Moreover, for setpoints are stored maximum and minimum values of the signal that it can receive. 843 | 844 | ![fbd menu2](https://https://www.mnppsaturn.ru/fbd2/images/menu3.png) 845 | 846 | To get the value of the watchpoint, use the function: 847 | 848 | ```c 849 | bool fbdHMIGetWP(tSignal index, tHMIdata *pnt); 850 | // index - number of watchpoint (0..watch points count-1) 851 | // pnt - pointer to data struct: 852 | typedef struct { 853 | tSignal value; // current point value 854 | tSignal lowlimit; // low limit for value (only for setpoints) 855 | tSignal upperLimit; // upper limit for value (only for setpoints) 856 | DESCR_MEM char *caption; // pointer to text caption (asciiz) 857 | } tHMIdata; 858 | ``` 859 | 860 | If the point exists, the function returns `true` and the data in the structure of `pnt`, otherwise the function returns a value `false`. Items `lowlimit` and `upperlimit` not used. 861 | To get the value of the setpoint, use the function: 862 | 863 | ```c 864 | bool fbdHMIGetSP(tSignal index, tHMIdata *pnt); 865 | // index - number of setpoint (0..set points count-1) 866 | // pnt - pointer to data structure 867 | ``` 868 | 869 | If the point exists, the function returns `true` and the data in the structure of `pnt`, otherwise the function returns a value `false`. Watchpoints and setpoints numbering starts with 0. 870 | To set the new setpoint value, use the function: 871 | 872 | ```c 873 | void fbdHMISetSP(tSignal index, tSignal value); 874 | // index - number of setpoint (0..set points count-1) 875 | // value - new value of setpoint 876 | ``` 877 | 878 | ## Текущий статус 879 | 880 | Это работает. Библиотека используется в некоторых "боевых" проектах и не содержит известных автору проблем. 881 | 882 | Дальнейшие планы: 883 | 884 | - [x] оптимизация скорости выполнения 885 | - [ ] доработка документации 886 | - [x] Modbus-Master 887 | - [ ] Alarms 888 | - [ ] журнал событий 889 | 890 | 891 | ## Автор 892 | 893 | Алексей Лутовинин -- crossrw1@gmail.com 894 | -------------------------------------------------------------------------------- /fbdrt.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file fbdrt.c 3 | * @author crossrw1@gmail.com 4 | * @brief FBD-Runtime library 5 | * @version 9.0 6 | * @date 2021-11-17 7 | */ 8 | 9 | #include 10 | #include "fbdrt.h" 11 | 12 | #ifdef USE_HMI 13 | // для snprintf 14 | #include 15 | #endif 16 | 17 | // 06-01-2018 18 | // + добавлены битовые операции 19 | // + добавлен генератор сигналов 20 | // + для входных сетевых переменных добавлено значение "по умолчанию" 21 | // * изменен способ проверки версии RTL 22 | // * добавлено определение USE_MATH, с ним для некоторых расчетов используется плавающая точка 23 | 24 | // 12-01-2018 25 | // * починил SPEED_OPT 26 | 27 | // 14-08-2018 28 | // + добавлены экраны интерфейса для контроллера 29 | 30 | // 27-03-2019 31 | // + добавлена контрольная сумма для схемы 32 | 33 | // 29-03-2019 34 | // + добавлены хинты для входов и выходов контроллера 35 | 36 | // 14-11-2021 37 | // + Modbus 38 | 39 | // ----------------------------------------------------------------------------- 40 | // FBDgetProc() и FBDsetProc() - callback, должны быть описаны в основной программе 41 | // ----------------------------------------------------------------------------- 42 | 43 | /** 44 | * @brief Чтение значения входного сигнала или NVRAM. 45 | * Должна быть описана в основной программе. 46 | * 47 | * @param type Tип чтения: 0 - входной сигнал, 1 - значение NVRAM 48 | * @param index Индекс читаемого значения 49 | * @return tSignal Прочитанное значение сигнала 50 | */ 51 | extern tSignal FBDgetProc(char type, tSignal index); 52 | 53 | /** 54 | * @brief Запись значения выходного сигнала или NVRAM. 55 | * Должна быть описана в основной программе. 56 | * 57 | * @param type Tип записи: 0 - выходной сигнал, 1 - значение NVRAM 58 | * @param index Индекс записываемого значения 59 | * @param value Записываемое значение. 60 | */ 61 | extern void FBDsetProc(char type, tSignal index, tSignal *value); 62 | // ----------------------------------------------------------------------------- 63 | 64 | #ifdef USE_HMI 65 | // рисование графических примитивов 66 | // 67 | // рисование залитого прямоугольника 68 | extern void FBDdrawRectangle(tScreenDim x1, tScreenDim y1, tScreenDim x2, tScreenDim y2, tColor color); 69 | // рисование текста 70 | extern void FBDdrawText(tScreenDim x1, tScreenDim y1, unsigned char font, tColor color, tColor bkcolor, bool transparent, char *text); 71 | // рисование линии 72 | extern void FBDdrawLine(tScreenDim x1, tScreenDim y1, tScreenDim x2, tScreenDim y2, tColor color); 73 | // рисование залитого эллипса 74 | extern void FBDdrawEllipse(tScreenDim x1, tScreenDim y1, tScreenDim x2, tScreenDim y2, tColor color); 75 | // рисование картинки 76 | extern void FBDdrawImage(tScreenDim x1, tScreenDim y1, tScreenDim image); 77 | // завершение рисования экрана (копирование видеообласти) 78 | extern void FBDdrawEnd(void); 79 | 80 | #endif 81 | 82 | // 83 | void fbdCalcElement(tElemIndex index); 84 | void fbdSetStorage(tElemIndex element, unsigned char index, tSignal value); 85 | 86 | #ifdef USE_HMI 87 | DESCR_MEM char DESCR_MEM_SUFX * fbdGetCaptionByIndex(tElemIndex captionIndex); 88 | #ifndef SPEED_OPT 89 | DESCR_MEM char DESCR_MEM_SUFX * DESCR_MEM_SUFX fbdGetCaption(tElemIndex index); 90 | #endif 91 | #endif 92 | 93 | #if defined(BIG_ENDIAN) && (SIGNAL_SIZE > 1) 94 | tSignal lotobigsign(tSignal val); 95 | #endif // defined 96 | 97 | #if defined(BIG_ENDIAN) && (INDEX_SIZE > 1) 98 | tElemIndex lotobigidx(tElemIndex val); 99 | #endif // defined 100 | 101 | // элемент стека вычислений 102 | typedef struct fbdstackitem_t { 103 | tElemIndex index; // индекс элемента 104 | unsigned char input; // номер входа 105 | } tFBDStackItem; 106 | 107 | void setCalcFlag(tElemIndex element); 108 | void setRiseFlag(tElemIndex element); 109 | void setChangeVarFlag(tElemIndex index); 110 | void setChangeModbusFlag(tElemIndex index); 111 | bool getAndClearChangeModbusFlag(tElemIndex index); 112 | void setModbusNoResponse(tElemIndex index); 113 | void setModbusResponse(tElemIndex index, tSignal response); 114 | void fillModbusRequest(tElemIndex index, tModbusReq *mbrequest); 115 | void setModbusFloat(tElemIndex index, float data, float mul); 116 | uint32_t getCoilBitsMask(unsigned count); 117 | void swapModbusByteOrder(tModbusData *data); 118 | void swapModbusWordOrder(tModbusData *data); 119 | 120 | char getCalcFlag(tElemIndex element); 121 | char getRiseFlag(tElemIndex element); 122 | 123 | tSignal intAbs(tSignal val); 124 | 125 | // ---------------------------------------------------------- 126 | // массив описания схемы (расположен в ROM или RAM) 127 | DESCR_MEM unsigned char DESCR_MEM_SUFX *fbdDescrBuf; 128 | // формат данных: 129 | // TypeElement1 <- тип элемента 130 | // TypeElement2 131 | // ... 132 | // TypeElementN 133 | // -1 <- флаг окончания описания элементов 134 | // описания входов элемента 135 | DESCR_MEM tElemIndex DESCR_MEM_SUFX *fbdInputsBuf; 136 | // InputOfElement <- вход элемента 137 | // InputOfElement 138 | // .. 139 | // описания параметров элементов 140 | DESCR_MEM tSignal DESCR_MEM_SUFX *fbdParametersBuf; 141 | // ParameterOfElement <- параметр элемента 142 | // ParameterOfElement 143 | // ... 144 | // количество глобальных параметров схемы 145 | DESCR_MEM unsigned char DESCR_MEM_SUFX *fbdGlobalOptionsCount; 146 | // глобальные параметры схемы 147 | DESCR_MEM tSignal DESCR_MEM_SUFX *fbdGlobalOptions; 148 | // 149 | #ifdef USE_HMI 150 | // текстовые описания для HMI 151 | DESCR_MEM char DESCR_MEM_SUFX *fbdCaptionsBuf; 152 | // text, 0 <- captions 153 | // text, 0 154 | // ... 155 | // экраны (начало выровнено по границе 4-х байт) 156 | DESCR_MEM tScreen DESCR_MEM_SUFX *fbdScreensBuf; 157 | // screen0 158 | // screen1 159 | // ... 160 | // текстовые описания входов и выходов, количество указано в параметре FBD_OPT_HINTS_COUNT 161 | DESCR_MEM char DESCR_MEM_SUFX *fbdIOHints; 162 | // type(char), index(char), text, 0 163 | // type(char), index(char), text, 0 164 | // ... 165 | // контрольная сумма всей программы CRC32 166 | 167 | #endif // USE_HMI 168 | 169 | // ---------------------------------------------------------- 170 | // массив расчета схемы (расположен только в RAM) 171 | tSignal *fbdMemoryBuf; 172 | // формат данных: 173 | // OutputValue0 174 | // OutputValue1 175 | // ... 176 | // OutputValueN 177 | // 178 | // storage values 179 | tSignal *fbdStorageBuf; 180 | // Storage0 181 | // Storage1 182 | // ... 183 | // StorageN 184 | // 185 | // флаги "расчет выполнен" и "нарастающий фронт", 2 бита для каждого элемента 186 | char *fbdFlagsBuf; 187 | // Flags0 188 | // Flags1 189 | // ... 190 | // FlagsN 191 | // флаги "значение изменилось", 1 бит для каждого элемента ELEM_OUT_VAR (14) 192 | char *fbdChangeVarBuf; 193 | // ChangeVar0 194 | // ChangeVar1 195 | // ... 196 | // ChangeVarN 197 | // флаги "значение изменилось", 1 бит для каждого элемента ELEM_OUT_MDBS (34) 198 | char *fbdChangeModbusBuf; 199 | // ChangeModbus0 200 | // ChangeModbus1 201 | // ... 202 | // ChangeModbusN 203 | 204 | #ifdef SPEED_OPT 205 | // только при использовании оптимизации по скорости выполнения 206 | tOffset *inputOffsets; 207 | // Смещение входа 0 элемента 0 208 | // Смещение входа 0 элемента 1 209 | // ... 210 | // Смещение входа 0 элемента N 211 | tOffset *parameterOffsets; 212 | // Смещение параметра 0 элемента 0 213 | // Смещение параметра 0 элемента 1 214 | // ... 215 | // Смещение параметра 0 элемента N 216 | tOffset *storageOffsets; 217 | // Смещение хранимого значения 0 элемента 0 218 | // Смещение хранимого значения 1 219 | // ... 220 | // Смещение хранимого значения N 221 | 222 | #ifdef USE_HMI 223 | // Структура для быстрого доступа к текстовым описаниям 224 | typedef struct pointaccess_t { 225 | tElemIndex index; // point element index 226 | DESCR_MEM char DESCR_MEM_SUFX *caption; // указатель на текстовую строку 227 | } tPointAccess; 228 | // 229 | tPointAccess *wpOffsets; 230 | tPointAccess *spOffsets; 231 | #endif // USE_HMI 232 | // указатели для быстрого доступа к тектовым описаниям проекта 233 | DESCR_MEM char *fbdCaptionName; 234 | DESCR_MEM char *fbdCaptionVersion; 235 | DESCR_MEM char *fbdCaptionBTime; 236 | // 237 | #endif // SPEED_OPT 238 | 239 | tElemIndex fbdElementsCount; 240 | tElemIndex fbdStorageCount; 241 | tElemIndex fbdFlagsByteCount; 242 | tElemIndex fbdChangeVarByteCount; 243 | tElemIndex fbdChangeModbusByteCount; 244 | 245 | tElemIndex fbdModbusRTUCount; 246 | tElemIndex fbdModbusTCPCount; 247 | tElemIndex fbdModbusRTUIndex; 248 | tElemIndex fbdModbusTCPIndex; 249 | short fbdModbusRTUTimer; 250 | short fbdModbusTCPTimer; 251 | 252 | unsigned char fbdModbusRTUErrorCounter; 253 | unsigned char fbdModbusTCPErrorCounter; 254 | 255 | #ifdef USE_HMI 256 | tElemIndex fbdWpCount; 257 | tElemIndex fbdSpCount; 258 | short fbdCurrentScreen; // текущий экран 259 | unsigned short fbdCurrentScreenTimer; // таймер текущего экрана 260 | #endif // USE_HMI 261 | // 262 | char fbdFirstFlag; 263 | 264 | // массив с количествами входов для элементов каждого типа 265 | ROM_CONST uint8_t ROM_CONST_SUFX FBDdefInputsCount[ELEM_TYPE_COUNT] = {1,0,1,2,2,2,2,2,2,2,2,2,2,2,1,0,0,4,3,3,5,1,1,0,2,2,2,3,2,2,2,2,2,0,1}; 266 | // массив с количествами параметров для элементов каждого типа 267 | ROM_CONST uint8_t ROM_CONST_SUFX FBDdefParametersCount[ELEM_TYPE_COUNT] = {1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,0,0,0,0,0,1,5,0,0,0,0,0,0,0,0,1,3,2}; 268 | // массив с количествами хранимых данных для элементов каждого типа 269 | ROM_CONST uint8_t ROM_CONST_SUFX FBDdefStorageCount[ELEM_TYPE_COUNT] = {0,0,0,0,0,0,1,1,0,0,0,0,1,0,0,0,1,2,1,1,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0}; 270 | // 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 271 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 272 | // Параметры элемента ELEM_INP_MDBS (чтение Modbus) 273 | // 274 | // |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| 275 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 276 | // | FMT/CNT |WO|BO| FNC | Адрес устройства | Адрес регистра MODBUS | 277 | // 278 | // BO - byte order: 0 - HiLo, 1 - LoHi 279 | // WO - word order: 0 - HiLo, 1 - LoHi 280 | // FNC - Modbus function: 281 | // 00 - read coil (0x01) 282 | // 01 - read discrete input (0x02) 283 | // 10 - read holding registers (0x03) 284 | // 11 - read input registers (0x04) 285 | // FMT/CNT - формат читаемых данных (для "read input registers" и "read holding registers") или количество регистров для чтения (для "read coil" и "read discrete input") 286 | // FMT - формат читаемых данных (для "read input registers" и "read holding registers"): 287 | // 0000 - uint16 (0..65535) 1 регистр 288 | // 0001 - int16 (0..65535) 1 регистр 289 | // 0010 - int32 2 регистра 290 | // MM11 - single 2 регистра, MM - множитель 00 - 1, 01 - 10, 10 - 100, 11 - 1000 291 | // CNT - количество читаемых бит (для "read coil" и "read discrete input"): 292 | // WO = 0 293 | // 0000 - 1 294 | // ... 295 | // 1111 - 16 296 | // WO = 1 297 | // 0000 - 17 298 | // ... 299 | // 1111 - 32 300 | // 301 | // Параметры элемента ELEM_OUT_MDBS (запись Modbus) 302 | // 303 | // |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| 304 | // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 305 | // | FMT/CNT |WO|BO| FNC | Адрес устройства | Адрес регистра MODBUS | 306 | // 307 | // BO - byte order: 0 - HiLo, 1 - LoHi 308 | // WO - word order: 0 - HiLo, 1 - LoHi 309 | // FNC - Modbus function: 310 | // 00 - Write Single Coil (0x05) 311 | // 01 - Write Multiple Coils (0x0F) 312 | // 10 - Write Single Register (0x06) 313 | // 11 - Write Multiple registers (0x10) 314 | // FMT/CNT - формат записываемых данных (для "Write Single Register" и "Write Multiple registers") или количество регистров для записи (для "Write Multiple Coils") 315 | // FMT - формат данных (для "Write Single Register" и "Write Multiple registers"): 316 | // 0000 - uint16 (0..65535) 1 регистр 317 | // 0001 - int16 (0..65535) 1 регистр 318 | // 0010 - int32 2 регистра 319 | // MM11 - single 2 регистра, MM - делитель 00 - 1, 01 - 10, 10 - 100, 11 - 1000 (только Write Multiple registers) 320 | // CNT - количество записываемых бит (для "Write Multiple Coils"): 321 | // WO = 0 322 | // 0000 - 1 323 | // ... 324 | // 1111 - 16 325 | // WO = 1 326 | // 0000 - 17 327 | // ... 328 | // 1111 - 32 329 | // 330 | 331 | #ifdef SPEED_OPT 332 | // описания "быстрых" вариантов функций 333 | // -------------------------------------------------------------------------------------------- 334 | #define FBDINPUTOFFSET(index) *(inputOffsets + (index)) 335 | #define FBDGETPARAMETER(element,index) SIGNAL_BYTE_ORDER(fbdParametersBuf[*(parameterOffsets+element)+index]) 336 | #define FBDGETSTORAGE(element,index) fbdStorageBuf[*(storageOffsets+element)+index] 337 | 338 | #else // SPEED_OPT 339 | // описания "медленных" вариантов функций 340 | // -------------------------------------------------------------------------------------------- 341 | // расчет смещения на первый вход элемента 342 | #define FBDINPUTOFFSET(index) _fbdInputOffset(index) 343 | tOffset _fbdInputOffset(tElemIndex index) 344 | { 345 | tElemIndex i = 0; 346 | tOffset offset = 0; 347 | // 348 | while (i < index) offset += FBDdefInputsCount[fbdDescrBuf[i++] & ELEMMASK]; 349 | return offset; 350 | } 351 | // получить значение параметра элемента 352 | #define FBDGETPARAMETER(element,index) _fbdGetParameter(element,index) 353 | tSignal _fbdGetParameter(tElemIndex element, unsigned char index) 354 | { 355 | tElemIndex elem = 0; 356 | tOffset offset = 0; 357 | // 358 | while (elem < element) offset += FBDdefParametersCount[fbdDescrBuf[elem++] & ELEMMASK]; 359 | return SIGNAL_BYTE_ORDER(fbdParametersBuf[offset + index]); 360 | } 361 | // получить хранимое значение для элемента 362 | #define FBDGETSTORAGE(element,index) _fbdGetStorage(element,index) 363 | tSignal _fbdGetStorage(tElemIndex element, unsigned char index) 364 | { 365 | tElemIndex elem = 0; 366 | tOffset offset = 0; 367 | // 368 | while (elem>1) ^ (0xEDB88320 & t); 389 | } 390 | } 391 | return crc; 392 | } 393 | 394 | // ------------------------------------------------------------------------------------------------------- 395 | 396 | /** 397 | * @brief Инициализация схемы 398 | * Функция должна быть вызвана один раз в самом начале работы. 399 | * Результат: количество ОЗУ, необходимое для выполнения схемы (значение больше 0) или (в случае ошибки) отрицательное значение: 400 | * ERR_INVALID_ELEMENT_TYPE - неверный код элемента в описании схемы 401 | * ERR_INVALID_SIZE_TYPE - неверный размер tSignal или tElementIndex 402 | * ERR_INVALID_LIB_VERSION - несовпадает версия программы и библиотеки 403 | * ERR_INVALID_CHECK_SUM - неверная контрольная сумма программы 404 | 405 | * @param buf Указатель на массив описания схемы 406 | * @return Количество байт RAM, необходимое для выполнения схемы или код ошибки 407 | */ 408 | int fbdInit(DESCR_MEM unsigned char DESCR_MEM_SUFX *buf) 409 | { 410 | tOffset inputs = 0; 411 | tOffset parameters = 0; 412 | unsigned char elem; 413 | // 414 | fbdElementsCount = 0; 415 | fbdStorageCount = 0; 416 | fbdChangeVarByteCount = 0; 417 | fbdChangeModbusByteCount = 0; 418 | // 419 | fbdModbusRTUIndex = MAX_INDEX; 420 | fbdModbusTCPIndex = MAX_INDEX; 421 | fbdModbusRTUErrorCounter = 0; 422 | fbdModbusTCPErrorCounter = 0; 423 | fbdModbusRTUCount = 0; 424 | fbdModbusTCPCount = 0; 425 | #ifdef USE_HMI 426 | fbdWpCount = 0; 427 | fbdSpCount = 0; 428 | DESCR_MEM char DESCR_MEM_SUFX *curCap; 429 | tElemIndex i; 430 | #endif // USE_HMI 431 | // 432 | if(!buf) return ERR_INVALID_ELEMENT_TYPE; 433 | fbdDescrBuf = buf; 434 | // цикл по всем элементам 435 | while(1) { 436 | elem = fbdDescrBuf[fbdElementsCount]; 437 | if(elem & 0x80) break; 438 | elem &= ELEMMASK; 439 | if(elem >= ELEM_TYPE_COUNT) return ERR_INVALID_ELEMENT_TYPE; 440 | // подсчет всех входов 441 | inputs += FBDdefInputsCount[elem]; 442 | // подсчет всех параметров 443 | parameters += FBDdefParametersCount[elem]; 444 | // подсчет всех хранимых параметров 445 | fbdStorageCount += FBDdefStorageCount[elem]; 446 | // подсчёт элементов некоторых типов 447 | switch (elem) { 448 | case ELEM_OUT_VAR: 449 | fbdChangeVarByteCount++; // подсчёт выходных переменных 450 | break; 451 | case ELEM_OUT_MDBS: 452 | fbdChangeModbusByteCount++; // подсчёт элементов записи в Modbus 453 | break; 454 | #ifdef USE_HMI 455 | case ELEM_WP: 456 | fbdWpCount++; // точки контроля 457 | break; 458 | case ELEM_SP: 459 | fbdSpCount++; // точки регулирования 460 | break; 461 | #endif // USE_HMI 462 | } 463 | // общий подсчёт элементов 464 | fbdElementsCount++; 465 | } 466 | // проверка правильности флага завершения 467 | if(elem != END_MARK) return ERR_INVALID_SIZE_TYPE; 468 | // расчет указателей 469 | fbdInputsBuf = (DESCR_MEM tElemIndex DESCR_MEM_SUFX *)(fbdDescrBuf + fbdElementsCount + 1); 470 | fbdParametersBuf = (DESCR_MEM tSignal DESCR_MEM_SUFX *)(fbdInputsBuf + inputs); 471 | fbdGlobalOptionsCount = (DESCR_MEM unsigned char DESCR_MEM_SUFX *)(fbdParametersBuf + parameters); 472 | fbdGlobalOptions = (DESCR_MEM tSignal DESCR_MEM_SUFX *)(fbdGlobalOptionsCount + 1); 473 | // проверка версии программы 474 | if(*fbdGlobalOptions > FBD_LIB_VERSION) return ERR_INVALID_LIB_VERSION; 475 | #ifdef USE_HMI 476 | // указатель на начало текстовых строк точек контроля и регулирования 477 | fbdCaptionsBuf = (DESCR_MEM char DESCR_MEM_SUFX *)(fbdGlobalOptions + *fbdGlobalOptionsCount); 478 | // после текстовых строк точек контроля и регулирования идут еще 3 строки: имя проекта, версия проекта, дата создания проекта 479 | // расчет указателя на первый экран 480 | // первый экран идет после текстовых описаний в количестве (fbdWpCount + fbdSpCount), причем его начало выровнено на 4 байта 481 | curCap = fbdCaptionsBuf; 482 | // перебираем все строки текстовых описаний 483 | // 3 - это имя проекта, версия проекта, дата создания проекта 484 | for(i=0; i < (fbdWpCount + fbdSpCount + 3); i++) { 485 | while(*(curCap++)); 486 | } 487 | // выравнивание curCap по границе 32 бита 488 | while((int)curCap % 4) curCap++; 489 | // ставим указатель на начало экранов 490 | fbdScreensBuf = (DESCR_MEM tScreen DESCR_MEM_SUFX *)curCap; 491 | // хинты входов и выходов идут после экранов 492 | if(FBD_HINTS_COUNT > 0) { 493 | // перебираем все экраны (если экраны есть) в поисках окончания 494 | DESCR_MEM tScreen DESCR_MEM_SUFX *screen = fbdScreensBuf; 495 | i = 0; 496 | while (i < FBD_SCREEN_COUNT) { 497 | screen = (tScreen *)((char *)screen + screen->len); 498 | i++; 499 | } 500 | fbdIOHints = (DESCR_MEM char DESCR_MEM_SUFX *)screen; 501 | } 502 | #endif // USE_HMI 503 | // расчёт и проверка CRC, если указан параметр FBD_SCHEMA_SIZE 504 | if(FBD_SCHEMA_SIZE) { 505 | // если результат 0, то CRC не используется (старая версия редактора) 506 | if(fbdCRC32(fbdDescrBuf, FBD_SCHEMA_SIZE)) return ERR_INVALID_CHECK_SUM; 507 | } 508 | // память для флагов расчета и фронта 509 | fbdFlagsByteCount = (fbdElementsCount>>2) + ((fbdElementsCount&3)?1:0); 510 | // память для флагов изменений значения выходной переменной 511 | fbdChangeVarByteCount = (fbdChangeVarByteCount>>3) + ((fbdChangeVarByteCount&7)?1:0); 512 | // память для флагов изменений значения записи в Modbus 513 | fbdChangeModbusByteCount = (fbdChangeModbusByteCount>>3) + ((fbdChangeModbusByteCount&7)?1:0); 514 | // 515 | #ifdef SPEED_OPT 516 | #ifdef USE_HMI 517 | return (fbdElementsCount + fbdStorageCount)*sizeof(tSignal) + fbdFlagsByteCount + fbdChangeVarByteCount + fbdChangeModbusByteCount + fbdElementsCount*3*sizeof(tOffset) + (fbdWpCount+fbdSpCount)*sizeof(tPointAccess); 518 | #else // USE_HMI 519 | return (fbdElementsCount + fbdStorageCount)*sizeof(tSignal) + fbdFlagsByteCount + fbdChangeVarByteCount + fbdChangeModbusByteCount + fbdElementsCount*3*sizeof(tOffset); 520 | #endif // USE_HMI 521 | #else // SPEED_OPT 522 | return (fbdElementsCount + fbdStorageCount)*sizeof(tSignal) + fbdFlagsByteCount + fbdChangeVarByteCount + fbdChangeModbusByteCount; 523 | #endif // SPEED_OPT 524 | } 525 | 526 | /** 527 | * @brief Инициализация памяти схемы 528 | * Функция должна быть вызвана после fbdInit(). 529 | * 530 | * @param buf Указатель на буфер памяти (размер возвращается fbdInit()), используемой при расчёте схемы 531 | * @param needReset Признак необходимости сброса NVRAM после загрузки новой (ранее не выполнявщейся) схемы 532 | */ 533 | void fbdSetMemory(char *buf, bool needReset) 534 | { 535 | tElemIndex i; 536 | #ifdef USE_HMI 537 | tHMIdata hmiData; 538 | #endif // USE_HMI 539 | #ifdef SPEED_OPT 540 | tOffset curInputOffset = 0; 541 | tOffset curParameterOffset = 0; 542 | tOffset curStorageOffset = 0; 543 | #ifdef USE_HMI 544 | tOffset curWP = 0; 545 | tOffset curSP = 0; 546 | DESCR_MEM char DESCR_MEM_SUFX *curCap; 547 | #endif // USE_HMI 548 | #endif // SPEED_OPT 549 | fbdModbusRTUIndex = MAX_INDEX; 550 | fbdModbusTCPIndex = MAX_INDEX; 551 | // 552 | fbdModbusRTUErrorCounter = 0; 553 | fbdModbusTCPErrorCounter = 0; 554 | // 555 | fbdModbusRTUTimer = 0; 556 | fbdModbusTCPTimer = 0; 557 | // 558 | fbdModbusRTUCount = 0; 559 | fbdModbusTCPCount = 0; 560 | // 561 | fbdMemoryBuf = (tSignal *)buf; 562 | // инициализация указателей 563 | fbdStorageBuf = fbdMemoryBuf + fbdElementsCount; 564 | fbdFlagsBuf = (char *)(fbdStorageBuf + fbdStorageCount); 565 | fbdChangeVarBuf = (char *)(fbdFlagsBuf + fbdFlagsByteCount); 566 | fbdChangeModbusBuf = (char *)(fbdChangeVarBuf + fbdChangeVarByteCount); 567 | // инициализация памяти (выходы элементов) 568 | memset(fbdMemoryBuf, 0, sizeof(tSignal)*fbdElementsCount); 569 | // инициализация памяти (установка всех флагов изменения значения) 570 | fbdChangeAllNetVars(); 571 | // установка всех флагов изменения значений записи в Modbus 572 | if(fbdChangeModbusByteCount > 0) memset(fbdChangeModbusBuf, 255, fbdChangeModbusByteCount); 573 | // восстановление значений триггеров из nvram 574 | for(i = 0; i < fbdStorageCount; i++) { 575 | if(needReset) { 576 | fbdStorageBuf[i] = 0; 577 | FBDsetProc(1, i, &fbdStorageBuf[i]); 578 | } else { 579 | fbdStorageBuf[i] = FBDgetProc(1, i); 580 | } 581 | } 582 | #ifdef SPEED_OPT 583 | // инициализация буферов быстрого доступа 584 | inputOffsets = (tOffset *)(fbdChangeModbusBuf + fbdChangeModbusByteCount); 585 | parameterOffsets = inputOffsets + fbdElementsCount; 586 | storageOffsets = parameterOffsets + fbdElementsCount; 587 | // 588 | fbdCaptionName = 0; 589 | fbdCaptionVersion = 0; 590 | fbdCaptionBTime = 0; 591 | #ifdef USE_HMI 592 | // инициализация буферов для быстрого доступа к watch- и set- points 593 | wpOffsets = (tPointAccess *)(storageOffsets + fbdElementsCount); 594 | spOffsets = wpOffsets + fbdWpCount; 595 | curCap = fbdCaptionsBuf; 596 | #endif // USE_HMI 597 | for(i=0; i < fbdElementsCount; i++) { 598 | *(inputOffsets + i) = curInputOffset; 599 | *(parameterOffsets + i) = curParameterOffset; 600 | *(storageOffsets + i) = curStorageOffset; 601 | // 602 | unsigned char elem = fbdDescrBuf[i] & ELEMMASK; 603 | curInputOffset += FBDdefInputsCount[elem]; 604 | curParameterOffset += FBDdefParametersCount[elem]; 605 | curStorageOffset += FBDdefStorageCount[elem]; 606 | // 607 | #ifdef USE_HMI 608 | switch(elem) { 609 | case ELEM_WP: 610 | (wpOffsets + curWP)->index = i; 611 | (wpOffsets + curWP)->caption = curCap; 612 | curWP++; 613 | while(*(curCap++)); 614 | break; 615 | case ELEM_SP: 616 | (spOffsets + curSP)->index = i; 617 | (spOffsets + curSP)->caption = curCap; 618 | curSP++; 619 | while(*(curCap++)); 620 | break; 621 | } 622 | #endif // USE_HMI 623 | } 624 | #endif // SPEED_OPT 625 | // подсчёт количества элементов Modbus и инициализация значений чтения Modbus 626 | // цикл по всем элементам 627 | for(i=0; i < fbdElementsCount; i++) { 628 | switch (fbdDescrBuf[i] & ELEMMASK) { 629 | case ELEM_INP_MDBS: 630 | // установка значения элемента на значение по умолчанию 631 | // параметр 2: значение по умолчанию 632 | fbdMemoryBuf[i] = FBDGETPARAMETER(i, 2); 633 | case ELEM_OUT_MDBS: 634 | // параметр 0: IP-адрес 635 | if(FBDGETPARAMETER(i, 0)) fbdModbusTCPCount++; else fbdModbusRTUCount++; 636 | } 637 | } 638 | // инициализация входных переменных 639 | if(needReset) { 640 | // при загрузке новой схемы значение входных сетевых переменных (до того, как они будут получены) равны 0, это не совсем хорошо... 641 | // инициализируем их значениями "по умолчанию" из программы 642 | for(i = 0; i < fbdElementsCount; i++) { 643 | if(fbdDescrBuf[i] == ELEM_INP_VAR) { 644 | fbdSetStorage(i, 0, FBDGETPARAMETER(i, 1)); 645 | } 646 | } 647 | } 648 | #ifdef USE_HMI 649 | // инициализация значений точек регулирования (HMI setpoints) 650 | i = 0; 651 | while(fbdHMIgetSP(i, &hmiData)) { 652 | // если значение точки регулирования не корректное, то устанавливаем значение по умолчанию 653 | if(needReset||(hmiData.value > hmiData.upperLimit)||(hmiData.value < hmiData.lowlimit)) fbdHMIsetSP(i, hmiData.defValue); 654 | i++; 655 | } 656 | // 657 | fbdCurrentScreen = -1; 658 | fbdCurrentScreenTimer = 0; 659 | #endif // USE_HMI 660 | // 661 | fbdFirstFlag = 1; 662 | } 663 | 664 | // ------------------------------------------------------------------------------------------------------- 665 | #ifdef USE_HMI 666 | 667 | /** 668 | * @brief Проверка видимости экранного элемента 669 | * 670 | * @param elem Указатель на описание экранного элемента 671 | * @return true Элемент видим 672 | * @return false Элемент не видим 673 | */ 674 | bool isScrElemVisible(tScrElemBase *elem) 675 | { 676 | if(elem->visibleElem == 0xffff) return true; // не выбран элемент видимости 677 | switch(elem->visibleCond) { 678 | case 0: 679 | return true; // условие "всегда" 680 | case 1: // условие "равно" 681 | return fbdMemoryBuf[elem->visibleElem] == elem->visibleValue; 682 | case 2: // условие "не равно" 683 | return fbdMemoryBuf[elem->visibleElem] != elem->visibleValue; 684 | case 3: // условие "больше" 685 | return fbdMemoryBuf[elem->visibleElem] > elem->visibleValue; 686 | case 4: // условие "меньше" 687 | return fbdMemoryBuf[elem->visibleElem] < elem->visibleValue; 688 | default: 689 | return true; 690 | } 691 | } 692 | 693 | tSignal getElementOutputValue(tElemIndex index) 694 | { 695 | if(index == 0xffff) return 0; 696 | return fbdMemoryBuf[index]; 697 | } 698 | 699 | void sprintf2d(char *buf, tSignal val) 700 | { 701 | *(buf + 1) = val % 10 + '0'; 702 | val /= 10; 703 | *(buf) = val % 10 + '0'; 704 | } 705 | 706 | void sprintf4d(char *buf, tSignal val) 707 | { 708 | *(buf + 3) = val % 10 + '0'; 709 | val /= 10; 710 | *(buf + 2) = val % 10 + '0'; 711 | val /= 10; 712 | *(buf + 1) = val % 10 + '0'; 713 | val /= 10; 714 | *(buf) = val % 10 + '0'; 715 | } 716 | 717 | /** 718 | * @brief Отрисовка экрана 719 | * 720 | * @param screen Указатель на описание экрана 721 | */ 722 | void drawCurrentScreen(DESCR_MEM tScreen DESCR_MEM_SUFX *screen) 723 | { 724 | tScrElemBase *elem; 725 | char dttext[32]; 726 | char text[32]; // больше на экран не лезет 727 | tSignal v2; 728 | int i,j,k; 729 | char c,nc; 730 | float f; 731 | // очистка экрана 732 | FBDdrawRectangle(0, 0, SCREEN_WIDTH-1, SCREEN_HEIGHT-1, screen->bkcolor); 733 | // 734 | // цикл по элементам экрана 735 | i = 0; 736 | elem = (tScrElemBase *)((char *)screen + sizeof(tScreen)); 737 | while(i++ < screen->elemCount) { 738 | // проверка видимости 739 | if(isScrElemVisible(elem)) { 740 | // рисование 741 | switch(elem->type) { 742 | case 0: // линия 743 | k = ((tScrElemLine *)elem)->width; 744 | if(k > 1) { 745 | k--; 746 | for(j=-k/2; j<=k/2 + k%2; j++) { 747 | FBDdrawLine(roundf(elem->x1-j*((tScrElemLine *)elem)->sine), roundf(elem->y1+j*((tScrElemLine *)elem)->cosinus), roundf(((tScrElemLine *)elem)->x2-j*((tScrElemLine *)elem)->sine), roundf(((tScrElemLine *)elem)->y2+j*((tScrElemLine *)elem)->cosinus), ((tScrElemLine *)elem)->color); 748 | } 749 | } else { 750 | FBDdrawLine(elem->x1, elem->y1, ((tScrElemLine *)elem)->x2, ((tScrElemLine *)elem)->y2, ((tScrElemLine *)elem)->color); 751 | } 752 | break; 753 | case 1: // прямоугольник 754 | FBDdrawRectangle(elem->x1, elem->y1, ((tScrElemRect *)elem)->x2, ((tScrElemRect *)elem)->y2, ((tScrElemRect *)elem)->color); 755 | break; 756 | case 2: // текст 757 | f = getElementOutputValue(((tScrElemText *)elem)->valueElem) * (float)1.0; 758 | switch (((tScrElemText *)elem)->divider) { 759 | case 1: 760 | f = f / (float)10.0; 761 | break; 762 | case 2: 763 | f = f / (float)100.0; 764 | break; 765 | case 3: 766 | f = f / (float)1000.0; 767 | } 768 | // форматирование даты и времени 769 | j = 0; 770 | k = 0; 771 | do { 772 | c = ((tScrElemText *)elem)->text[j++]; 773 | if(c == '%') { 774 | nc = ((tScrElemText *)elem)->text[j]; 775 | switch(nc) { 776 | case 'd': 777 | if(k < (sizeof(dttext)-2)) { 778 | sprintf2d(&dttext[k], FBDgetProc(0, GP_RTC_DAY)); 779 | k += 2; 780 | } 781 | j++; 782 | break; 783 | case 'm': 784 | if(k < (sizeof(dttext)-2)) { 785 | sprintf2d(&dttext[k], FBDgetProc(0, GP_RTC_MONTH)); 786 | k += 2; 787 | } 788 | j++; 789 | break; 790 | case 'y': 791 | if(k < (sizeof(dttext)-2)) { 792 | sprintf2d(&dttext[k], FBDgetProc(0, GP_RTC_YEAR)-2000); 793 | k += 2; 794 | } 795 | j++; 796 | break; 797 | case 'h': 798 | if(k < (sizeof(dttext)-2)) { 799 | sprintf2d(&dttext[k], FBDgetProc(0, GP_RTC_HOUR)); 800 | k += 2; 801 | } 802 | j++; 803 | break; 804 | case 'n': 805 | if(k < (sizeof(dttext)-2)) { 806 | sprintf2d(&dttext[k], FBDgetProc(0, GP_RTC_MINUTE)); 807 | k += 2; 808 | } 809 | j++; 810 | break; 811 | case 's': 812 | if(k < (sizeof(dttext)-2)) { 813 | sprintf2d(&dttext[k], FBDgetProc(0, GP_RTC_SECOND)); 814 | k += 2; 815 | } 816 | j++; 817 | break; 818 | case 'Y': 819 | if(k < (sizeof(dttext)-4)) { 820 | sprintf4d(&dttext[k], FBDgetProc(0, GP_RTC_YEAR)); 821 | k += 4; 822 | } 823 | j++; 824 | break; 825 | default: 826 | dttext[k++] = c; 827 | } 828 | } else { 829 | dttext[k++] = c; 830 | } 831 | } while (c); 832 | // 833 | snprintf(text, sizeof(text), dttext, f); 834 | FBDdrawText(elem->x1, elem->y1, ((tScrElemText *)elem)->font & 0x7f, ((tScrElemText *)elem)->color, ((tScrElemText *)elem)->bkcolor, (((tScrElemText *)elem)->font & 0x80) != 0, text); 835 | break; 836 | case 3: // картинка 837 | FBDdrawImage(elem->x1, elem->y1, ((tScrElemImage *)elem)->index); 838 | break; 839 | case 4: // шкала 840 | v2 = getElementOutputValue(((tScrElemGauge *)elem)->valueElem); 841 | // нормирование значения 842 | if(v2 < 0) v2 = 0; else 843 | if(v2 > ((tScrElemGauge *)elem)->maxvalue) v2 = ((tScrElemGauge *)elem)->maxvalue; 844 | // 845 | if(((tScrElemGauge *)elem)->orientation == 0) { 846 | // горизонтальная ориентация 847 | v2 = elem->x1 + v2*(((tScrElemGauge *)elem)->x2 - elem->x1) / ((tScrElemGauge *)elem)->maxvalue; 848 | // рисуем прямоугольник значения 849 | FBDdrawRectangle(elem->x1, elem->y1, v2, ((tScrElemGauge *)elem)->y2, ((tScrElemGauge *)elem)->color); 850 | if(v2 < ((tScrElemGauge *)elem)->x2) { 851 | // рисуем прямоугольник остатка 852 | FBDdrawRectangle(v2, elem->y1, ((tScrElemGauge *)elem)->x2, ((tScrElemGauge *)elem)->y2, ((tScrElemGauge *)elem)->bkcolor); 853 | } 854 | } else { 855 | // вертикальная ориентация 856 | v2 = ((tScrElemGauge *)elem)->y2 - v2*(((tScrElemGauge *)elem)->y2 - elem->y1) / ((tScrElemGauge *)elem)->maxvalue; 857 | // рисуем прямоугольник значения 858 | FBDdrawRectangle(elem->x1, v2, ((tScrElemGauge *)elem)->x2, ((tScrElemGauge *)elem)->y2, ((tScrElemGauge *)elem)->color); 859 | if(elem->y1 < v2) { 860 | // рисуем прямоугольник остатка 861 | FBDdrawRectangle(elem->x1, elem->y1, ((tScrElemGauge *)elem)->x2, v2, ((tScrElemGauge *)elem)->bkcolor); 862 | } 863 | } 864 | break; 865 | case 5: // эллипс 866 | FBDdrawEllipse(elem->x1, elem->y1, ((tScrElemCircle *)elem)->x2, ((tScrElemCircle *)elem)->y2, ((tScrElemCircle *)elem)->color); 867 | break; 868 | } 869 | } 870 | // переход к следующему элементу 871 | elem = (tScrElemBase *)((char *)elem + elem->len); 872 | } 873 | // 874 | FBDdrawEnd(); 875 | } 876 | 877 | /** 878 | * @brief Выполнение одного шага вычисления схемы. 879 | * Функция выполняет один шаг вычисления схемы и перерисовку указанноего экрана при необходимости. 880 | * 881 | * @param period Время с момента предыдущего вызова fbdDoStepEx(), например в мс. 882 | * @param screenIndex Индекс текущего отображаемого экрана. 883 | */ 884 | void fbdDoStepEx(tSignal period, short screenIndex) 885 | { 886 | DESCR_MEM tScreen DESCR_MEM_SUFX *screen; 887 | int i; 888 | // расчет схемы 889 | fbdDoStep(period); 890 | // проверка корректности номера экрана 891 | if(screenIndex >= FBD_SCREEN_COUNT) return; 892 | // не показывать экран 893 | if(screenIndex < 0) { 894 | fbdCurrentScreen = screenIndex; 895 | return; 896 | } 897 | // расчет указателя на экран 898 | i = 0; 899 | screen = fbdScreensBuf; 900 | while (i < screenIndex) { 901 | screen = (tScreen *)((char *)screen + screen->len); 902 | i++; 903 | } 904 | // проверка необходимости перерисовки 905 | if(screenIndex != fbdCurrentScreen) { 906 | // сменился экран 907 | fbdCurrentScreen = screenIndex; 908 | fbdCurrentScreenTimer = 0; 909 | drawCurrentScreen(screen); 910 | } else { 911 | fbdCurrentScreenTimer += period; 912 | if(fbdCurrentScreenTimer >= screen->period) { 913 | fbdCurrentScreenTimer = 0; 914 | drawCurrentScreen(screen); 915 | } 916 | } 917 | } 918 | #endif // USE_HMI 919 | 920 | /** 921 | * @brief Выполнение одного шага вычисления схемы 922 | * Функция выполняет один шаг вычисления схемы 923 | * 924 | * @param period время с момента предыдущего вызова fbdDoStep(), например в милисекундах 925 | */ 926 | void fbdDoStep(tSignal period) 927 | { 928 | tSignal value, param; 929 | tElemIndex index; 930 | // сброс признаков расчета и нарастающего фронта 931 | memset(fbdFlagsBuf, 0, fbdFlagsByteCount); 932 | // модификация таймеров Modbus 933 | if(period) { 934 | if(period <= fbdModbusRTUTimer) fbdModbusRTUTimer -= period; else fbdModbusRTUTimer = 0; 935 | if(period <= fbdModbusTCPTimer) fbdModbusTCPTimer -= period; else fbdModbusTCPTimer = 0; 936 | } 937 | // основной цикл расчета 938 | for(index=0; index < fbdElementsCount; index++) { 939 | unsigned char element = fbdDescrBuf[index] & ELEMMASK; 940 | switch(element) { 941 | // элементы с таймером 942 | case ELEM_TON: // timer TON 943 | case ELEM_PID: // PID 944 | case ELEM_SUM: // SUM 945 | case ELEM_TP: // timer TP 946 | case ELEM_GEN: // GEN 947 | if(!period) break; 948 | value = FBDGETSTORAGE(index, 0); 949 | if(value) { 950 | value -= period; 951 | if(value < 0) value = 0; 952 | fbdSetStorage(index, 0, value); 953 | } 954 | break; 955 | case ELEM_OUT_PIN: // output PIN 956 | fbdCalcElement(index); 957 | param = FBDGETPARAMETER(index, 0); 958 | FBDsetProc(0, param, &fbdMemoryBuf[index]); // установка значения выходного контакта 959 | break; 960 | case ELEM_OUT_VAR: // выходная переменная 961 | case ELEM_OUT_MDBS: // запись Modbus 962 | #ifdef USE_HMI 963 | case ELEM_WP: // точка контроля 964 | #endif // USE_HMI 965 | fbdCalcElement(index); 966 | break; 967 | } 968 | } 969 | fbdFirstFlag = 0; 970 | } 971 | 972 | // ------------------------------------------------------------------------------------------------------- 973 | 974 | /** 975 | * @brief Установить принятое по сети значение переменной 976 | * 977 | * @param netvar Указатель на структуру описания сетевой переменной 978 | */ 979 | void fbdSetNetVar(tNetVar *netvar) 980 | { 981 | tElemIndex i; 982 | // 983 | for(i=0; i < fbdElementsCount; i++) { 984 | if(fbdDescrBuf[i] == ELEM_INP_VAR) { 985 | if(FBDGETPARAMETER(i, 0) == netvar->index) { 986 | fbdSetStorage(i, 0, netvar->value); 987 | return; 988 | } 989 | } 990 | } 991 | return; 992 | } 993 | 994 | /** 995 | * @brief Получить значение переменной для отправки по сети 996 | * Функцию необходимо вызывать до тех пор, пока она не вернет false. 997 | * 998 | * @param netvar Указатель на структуру описания сетевой переменной 999 | * @return true Переменная для отправки есть, она помещена в структуру netvar 1000 | * @return false Переменных для отправки больше нет 1001 | */ 1002 | bool fbdGetNetVar(tNetVar *netvar) 1003 | { 1004 | tElemIndex i, varindex; 1005 | // 1006 | varindex = 0; 1007 | for(i=0; i < fbdElementsCount; i++) { 1008 | if(fbdDescrBuf[i] == ELEM_OUT_VAR) { 1009 | // проверяем установку флага изменений 1010 | if(fbdChangeVarBuf[varindex>>3]&(1u<<(varindex&7))) { 1011 | // флаг установлен, сбрасываем его 1012 | fbdChangeVarBuf[varindex>>3] &= ~(1u<<(varindex&7)); 1013 | // номер переменной 1014 | netvar->index = FBDGETPARAMETER(i, 0); 1015 | netvar->value = fbdMemoryBuf[i]; 1016 | return true; 1017 | } 1018 | varindex++; 1019 | } 1020 | } 1021 | return false; 1022 | } 1023 | 1024 | /** 1025 | * @brief Установить для всех выходных сетевых переменных признак изменения 1026 | * Функцию можно вызывать периодически для принудительной отправки всех переменных. 1027 | */ 1028 | void fbdChangeAllNetVars(void) 1029 | { 1030 | if(fbdChangeVarByteCount > 0) memset(fbdChangeVarBuf, 255, fbdChangeVarByteCount); 1031 | } 1032 | 1033 | /** 1034 | * @brief Получение статуса использования Modbus схемой 1035 | * 1036 | * @return tFBD_MODBUS_USAGE Статус использования Modbus 1037 | */ 1038 | tFBD_MODBUS_USAGE fbdModbusUsage(void) 1039 | { 1040 | tFBD_MODBUS_USAGE modbusUsage = FBD_MODBUS_NONE; 1041 | // 1042 | if(fbdModbusRTUCount) modbusUsage |= FBD_MODBUS_RTU; 1043 | if(fbdModbusTCPCount) modbusUsage |= FBD_MODBUS_TCP; 1044 | // 1045 | return modbusUsage; 1046 | } 1047 | 1048 | /** 1049 | * @brief Получение значений настроек Modbus RTU. 1050 | * Возвращает набор настроек последовательного порта RS485 для использования обмена по протоколу Modbus RTU 1051 | * 1052 | * @param pnt Указатель на структуру описания настроек 1053 | * @return true Необходимо применить возвращённый набор настроек 1054 | * @return false Необходимо оставить текущие настройки контроллера 1055 | */ 1056 | bool fbdModbusGetSerialSettings(tModbusRTUsettings *pnt) 1057 | { 1058 | // проверка количества элементов работы с ModbusRTU 1059 | if(!fbdModbusRTUCount) return false; 1060 | // 1061 | tSignal serialSettings = FBD_MODBUSRTU_OPT; 1062 | // проверка бита необходимости установки настроек 1063 | if(!(serialSettings & 0x80000000)) return false; 1064 | // результат положительный 1065 | if(!pnt) return true; 1066 | // заполняем структуру 1067 | pnt->timeout = serialSettings & 0x0fff; 1068 | pnt->baudRate = (tFBD_BAUDRATE)((serialSettings >> 12) & 15); 1069 | pnt->parity = (tFBD_PARITY)((serialSettings >> 16) & 3); 1070 | pnt->stopBits = (tFBD_STOPB)((serialSettings >> 18) & 1); 1071 | // 1072 | return true; 1073 | } 1074 | 1075 | /** 1076 | * @brief Получение следующего запроса Modbus RTU для выполнения. 1077 | * 1078 | * @param mbrequest Указатель на структуру описания запроса Modbus RTU 1079 | * @return true Возвращён очередной запрос Modbus RTU 1080 | * @return false Запросов Modbus RTU пока больше нет 1081 | */ 1082 | bool fbdGetNextModbusRTURequest(tModbusReq *mbrequest) 1083 | { 1084 | if(!fbdModbusRTUCount) return false; 1085 | // поиск следующего элемента Modbus RTU 1086 | tElemIndex index, i; 1087 | unsigned char elem; 1088 | // проверка необходимости повторного запроса 1089 | if(fbdModbusRTUErrorCounter) { 1090 | // необходим повторный запрос 1091 | fbdModbusRTUErrorCounter--; 1092 | fillModbusRequest(fbdModbusRTUIndex, mbrequest); 1093 | // 1094 | return true; 1095 | } else { 1096 | // поиск следующего элемента 1097 | index = fbdModbusRTUIndex; 1098 | for(i=0; i < fbdElementsCount; i++) { 1099 | // переходим к следующему элементу 1100 | index++; 1101 | if(index >= fbdElementsCount) { 1102 | // проверка таймера минимального периода опроса 1103 | if(fbdModbusRTUTimer) return false; 1104 | fbdModbusRTUTimer = FBD_MODBUS_PAUSE; 1105 | index = 0; 1106 | } 1107 | elem = fbdDescrBuf[index] & ELEMMASK; 1108 | if((elem == ELEM_INP_MDBS)||(elem == ELEM_OUT_MDBS)) { 1109 | // проверяем тип Modbus 1110 | if(!FBDGETPARAMETER(index, 0)) { 1111 | // это Modbus RTU 1112 | if(elem == ELEM_OUT_MDBS) { 1113 | // если это элемент записи, то проверяем флаг изменений 1114 | // если флаг не установлен, то переходим к следующему элементу 1115 | // если установлен, то формируем команду записи 1116 | if(!getAndClearChangeModbusFlag(index)) continue; 1117 | // повторные запросы при записи не используем, они и так будут работать из-за флагов изменения 1118 | fbdModbusRTUErrorCounter = 0; 1119 | } else { 1120 | // устанавливаем количество повторных запросов 1121 | fbdModbusRTUErrorCounter = FBD_MODBUS_RETRYCOUNT; 1122 | } 1123 | // нашли подходящий элемент 1124 | fbdModbusRTUIndex = index; 1125 | fillModbusRequest(index, mbrequest); 1126 | return true; 1127 | } 1128 | } 1129 | } 1130 | } 1131 | // 1132 | return false; 1133 | } 1134 | 1135 | /** 1136 | * @brief Установка результата успешного выполнения запроса Modbus RTU. 1137 | * Должна быть вызвана после успешного выполнения запроса чтения или записи Modbus RTU. 1138 | * @param response Данные, возвращённые запросом 1139 | */ 1140 | void fbdSetModbusRTUResponse(tSignal response) 1141 | { 1142 | fbdModbusRTUErrorCounter = 0; 1143 | setModbusResponse(fbdModbusRTUIndex, response); 1144 | } 1145 | 1146 | /** 1147 | * @brief Установить признак не успешного результата выполнения запроса ModBus RTU. 1148 | * Устанавливается признак не успешного чтения или записи Modbus для ранее полученного описания запроса. 1149 | * Должна вызываться при любой ошибке: нет ответа, ошибка CRC, получение кода исключения и т.п. 1150 | * 1151 | * @param errCode Код ошибки: 0 - нет ответа или ошибка CRC, !=0 - ответ с кодом исключения 1152 | */ 1153 | void fbdSetModbusRTUNoResponse(int errCode) 1154 | { 1155 | if((errCode != 0) || (fbdModbusRTUErrorCounter == 0)) { 1156 | fbdModbusRTUErrorCounter = 0; 1157 | setModbusNoResponse(fbdModbusRTUIndex); 1158 | } 1159 | } 1160 | 1161 | /** 1162 | * @brief Получение следующего запроса Modbus TCP для выполнения. 1163 | * 1164 | * @param mbrequest Указатель на структуру описания запроса Modbus TCP 1165 | * @return true Возвращён очередной запрос Modbus TCP 1166 | * @return false Запросов Modbus TCP пока больше нет 1167 | */ 1168 | bool fbdGetNextModbusTCPRequest(tModbusReq *mbrequest) 1169 | { 1170 | if(!fbdModbusTCPCount) return false; 1171 | // поиск следующего элемента Modbus TCP 1172 | tElemIndex index, i; 1173 | unsigned char elem; 1174 | // проверка необходимости повторного запроса 1175 | if(fbdModbusTCPErrorCounter) { 1176 | // необходим повторный запрос 1177 | fbdModbusTCPErrorCounter--; 1178 | fillModbusRequest(fbdModbusTCPIndex, mbrequest); 1179 | // 1180 | return true; 1181 | } else { 1182 | // поиск следующего элемента 1183 | index = fbdModbusTCPIndex; 1184 | for(i=0; i < fbdElementsCount; i++) { 1185 | // переходим к следующему элементу 1186 | index++; 1187 | if(index >= fbdElementsCount) { 1188 | // проверка таймера минимального периода опроса 1189 | if(fbdModbusTCPTimer) return false; 1190 | fbdModbusTCPTimer = FBD_MODBUS_PAUSE; 1191 | index = 0; 1192 | } 1193 | elem = fbdDescrBuf[index] & ELEMMASK; 1194 | if((elem == ELEM_INP_MDBS)||(elem == ELEM_OUT_MDBS)) { 1195 | // проверяем тип Modbus 1196 | if(FBDGETPARAMETER(index, 0)) { 1197 | // это Modbus TCP 1198 | if(elem == ELEM_OUT_MDBS) { 1199 | // если это элемент записи, то проверяем флаг изменений 1200 | // если флаг не установлен, то переходим к следующему элементу 1201 | // если установлен, то формируем команду записи 1202 | if(!getAndClearChangeModbusFlag(index)) continue; 1203 | // повторные запросы при записи не используем, они и так будут работать из-за флагов изменения 1204 | fbdModbusTCPErrorCounter = 0; 1205 | } else { 1206 | // устанавливаем количество повторных запросов 1207 | fbdModbusTCPErrorCounter = FBD_MODBUS_RETRYCOUNT; 1208 | } 1209 | // нашли подходящий элемент 1210 | fbdModbusTCPIndex = index; 1211 | fillModbusRequest(index, mbrequest); 1212 | return true; 1213 | } 1214 | } 1215 | } 1216 | } 1217 | // 1218 | return false; 1219 | } 1220 | 1221 | /** 1222 | * @brief Установка результата успешного выполнения запроса Modbus TCP. 1223 | * Должна быть вызвана после успешного выполнения запроса чтения или записи Modbus TCP. 1224 | * 1225 | * @param response Данные, возвращённые запросом 1226 | */ 1227 | void fbdSetModbusTCPResponse(tSignal response) 1228 | { 1229 | fbdModbusTCPErrorCounter = 0; 1230 | setModbusResponse(fbdModbusTCPIndex, response); 1231 | } 1232 | 1233 | /** 1234 | * @brief Установить признак не успешного результата выполнения запроса ModBus TCP. 1235 | * Устанавливается признак не успешного чтения или записи Modbus для ранее полученного описания запроса. 1236 | * 1237 | * @param errCode Код ошибки: 0 - нет ответа или ошибка CRC, !=0 - ответ с кодом исключения 1238 | */ 1239 | void fbdSetModbusTCPNoResponse(int errCode) 1240 | { 1241 | if((errCode != 0) || (fbdModbusTCPErrorCounter == 0)) { 1242 | fbdModbusTCPErrorCounter = 0; 1243 | setModbusNoResponse(fbdModbusTCPIndex); 1244 | } 1245 | } 1246 | 1247 | #ifdef USE_HMI 1248 | #ifndef SPEED_OPT 1249 | // ------------------------------------------------------------------------------------------------------- 1250 | bool fbdGetElementIndex(tSignal index, unsigned char type, tElemIndex *elemIndex) 1251 | { 1252 | unsigned char elem; 1253 | // 1254 | *elemIndex = 0; 1255 | while(1) { 1256 | elem = fbdDescrBuf[*elemIndex]; 1257 | if(elem & 0x80) return false; 1258 | if(elem == type) { 1259 | if(index) index--; else break; 1260 | } 1261 | (*elemIndex)++; 1262 | } 1263 | return true; 1264 | } 1265 | #endif // SPEED_OPT 1266 | 1267 | // ------------------------------------------------------------------------------------------------------- 1268 | // HMI functions 1269 | 1270 | /** 1271 | * @brief Получить значение точки регулирования 1272 | * 1273 | * @param index Индекс точки регулирования 1274 | * @param pnt Указатель на заполяемую структуру 1275 | * @return true Требуемая точка регулирования есть 1276 | * @return false Требуемой точки регулирования нет 1277 | */ 1278 | bool fbdHMIgetSP(tSignal index, tHMIdata *pnt) 1279 | { 1280 | tElemIndex elemIndex; 1281 | if(index >= fbdSpCount) return false; 1282 | #ifdef SPEED_OPT 1283 | elemIndex = (spOffsets + index)->index; 1284 | pnt->caption = (spOffsets + index)->caption; 1285 | #else 1286 | if(!fbdGetElementIndex(index, ELEM_SP, &elemIndex)) return false; 1287 | pnt->caption = fbdGetCaption(elemIndex); 1288 | #endif // SPEED_OPT 1289 | pnt->value = FBDGETSTORAGE(elemIndex, 0); 1290 | pnt->lowlimit = FBDGETPARAMETER(elemIndex, 0); 1291 | pnt->upperLimit = FBDGETPARAMETER(elemIndex, 1); 1292 | pnt->defValue = FBDGETPARAMETER(elemIndex, 2); 1293 | pnt->divider = FBDGETPARAMETER(elemIndex, 3); 1294 | pnt->step = FBDGETPARAMETER(elemIndex, 4); 1295 | return true; 1296 | } 1297 | 1298 | /** 1299 | * @brief Установить значение точки регулирования 1300 | * 1301 | * @param index Индекс точки регулирования 1302 | * @param value Устанавливаемое значение 1303 | */ 1304 | void fbdHMIsetSP(tSignal index, tSignal value) 1305 | { 1306 | tElemIndex elemIndex; 1307 | if(index >= fbdSpCount) return; 1308 | #ifdef SPEED_OPT 1309 | elemIndex = (spOffsets + index)->index; 1310 | #else 1311 | if(!fbdGetElementIndex(index, ELEM_SP, &elemIndex)) return; 1312 | #endif // SPEED_OPT 1313 | fbdSetStorage(elemIndex, 0, value); 1314 | } 1315 | 1316 | /** 1317 | * @brief Получить значение точки контроля. 1318 | * Заполняет описание точки контроля по адресу pnt, если указанная точка имеется 1319 | * @param index Индекс точки контроля 1320 | * @param pnt Указатель на описание точки контроля 1321 | * @return true Точка контроля присутствует 1322 | * @return false Точка контроля отсутствует 1323 | */ 1324 | bool fbdHMIgetWP(tSignal index, tHMIdata *pnt) 1325 | { 1326 | tElemIndex elemIndex = 0; 1327 | if(index >= fbdWpCount) return false; 1328 | #ifdef SPEED_OPT 1329 | elemIndex = (wpOffsets + index)->index; 1330 | pnt->caption = (wpOffsets + index)->caption; 1331 | #else 1332 | if(!fbdGetElementIndex(index, ELEM_WP, &elemIndex)) return false; 1333 | pnt->caption = fbdGetCaption(elemIndex); 1334 | #endif // SPEED_OPT 1335 | pnt->value = fbdMemoryBuf[elemIndex]; 1336 | pnt->divider = FBDGETPARAMETER(elemIndex, 0); 1337 | return true; 1338 | } 1339 | // ------------------------------------------------------------------------------------------------------- 1340 | 1341 | /** 1342 | * @brief Получение структуры с описанием проекта 1343 | * 1344 | * @param pnt Указатель на структуру описания проекта 1345 | */ 1346 | void fbdHMIgetDescription(tHMIdescription *pnt) 1347 | { 1348 | #ifdef SPEED_OPT 1349 | pnt->name = fbdCaptionName?fbdCaptionName:(fbdCaptionName = fbdGetCaptionByIndex(fbdWpCount + fbdSpCount)); 1350 | pnt->version = fbdCaptionVersion?fbdCaptionVersion:(fbdCaptionVersion = fbdGetCaptionByIndex(fbdWpCount + fbdSpCount + 1)); 1351 | pnt->btime = fbdCaptionBTime?fbdCaptionBTime:(fbdCaptionBTime = fbdGetCaptionByIndex(fbdWpCount + fbdSpCount + 2)); 1352 | #else 1353 | pnt->name = fbdGetCaptionByIndex(fbdWpCount + fbdSpCount); 1354 | pnt->version = fbdGetCaptionByIndex(fbdWpCount + fbdSpCount + 1); 1355 | pnt->btime = fbdGetCaptionByIndex(fbdWpCount + fbdSpCount + 2); 1356 | #endif 1357 | } 1358 | 1359 | /** 1360 | * @brief Получение указателя на текстовое описание (хинт) входа или выхода. 1361 | * Возвращает указатель на текстовое описание (хинт) входа или выхода, если такого описание не найдено, то возвращает NULL. 1362 | * Значение параметра index соответствует значению параметра index функций FBDgetProc(type, index) и FBDsetProc(type, index, *value). 1363 | * @param type Тип: 0 - входы, 1 - выходы. 1364 | * @param index Индекс входа или выхода 1365 | * @return DESCR_MEM* Указатель на текстовое описание входа или выхода 1366 | */ 1367 | DESCR_MEM char DESCR_MEM_SUFX *fbdHMIgetIOhint(char type, char index) 1368 | { 1369 | tSignal hintsCount = FBD_HINTS_COUNT; 1370 | if(!hintsCount) return NULL; // описаний нет 1371 | DESCR_MEM char DESCR_MEM_SUFX *curHint = fbdIOHints; 1372 | tSignal i = 0; 1373 | // цикл перебора всех описаний 1374 | while(1) { 1375 | // хинты хранятся в виде последовательности описаний: 1376 | // type(char), index(char), text, 0 1377 | // type(char), index(char), text, 0 1378 | // ... 1379 | if((*curHint == type)&&(*(curHint + 1) == index)) return curHint + 2; // описание найдено 1380 | // переходим к следующему хинту 1381 | i++; 1382 | if(i >= hintsCount) return NULL; // больше описаний нет 1383 | // расчет указателя на следующий хинт 1384 | curHint += 2; 1385 | while(*(curHint++)); 1386 | } 1387 | } 1388 | 1389 | /** 1390 | * @brief Расчет указателя на текстовое описание элемента по индексу описания 1391 | * 1392 | * @param captionIndex Индекс описания 1393 | * @return DESCR_MEM char DESCR_MEM_SUFX Указатель на текстовое описание 1394 | */ 1395 | DESCR_MEM char DESCR_MEM_SUFX * fbdGetCaptionByIndex(tElemIndex captionIndex) 1396 | { 1397 | tOffset offset = 0; 1398 | while(captionIndex) if(!fbdCaptionsBuf[offset++]) captionIndex--; 1399 | return &fbdCaptionsBuf[offset]; 1400 | } 1401 | 1402 | #ifndef SPEED_OPT 1403 | // ------------------------------------------------------------------------------------------------------- 1404 | // расчет указателя на текстовое описание элемента по индексу элемента 1405 | DESCR_MEM char DESCR_MEM_SUFX * fbdGetCaption(tElemIndex elemIndex) 1406 | { 1407 | tElemIndex captionIndex, index; 1408 | // 1409 | index = 0; 1410 | captionIndex = 0; 1411 | while(index < elemIndex) { 1412 | switch(fbdDescrBuf[index++] & ELEMMASK) { 1413 | case ELEM_WP: 1414 | case ELEM_SP: 1415 | captionIndex++; 1416 | break; 1417 | } 1418 | } 1419 | return fbdGetCaptionByIndex(captionIndex); 1420 | } 1421 | #endif // SPEED_OPT 1422 | #endif // USE_HMI 1423 | 1424 | // ------------------------------------------------------------------------------------------------------- 1425 | 1426 | /** 1427 | * @brief Расчёт выходного значения элемента 1428 | * 1429 | * @param curIndex Индекс элемента 1430 | */ 1431 | void fbdCalcElement(tElemIndex curIndex) 1432 | { 1433 | tFBDStackItem fbdStack[FBDSTACKSIZE]; // стек вычислений 1434 | tFBDStackPnt fbdStackPnt; // указатель стека 1435 | unsigned char curInput; // текущий входной контакт элемента 1436 | unsigned char inputCount; // число входов текущего элемента 1437 | tOffset baseInput; // 1438 | tElemIndex inpIndex; 1439 | tSignal s1,s2,s3,s4,v; // значения сигналов на входе 1440 | // 1441 | if(getCalcFlag(curIndex)) return; // элемент уже рассчитан? 1442 | // 1443 | fbdStackPnt = 0; 1444 | curInput = 0; 1445 | // 1446 | baseInput = FBDINPUTOFFSET(curIndex); // 1447 | inputCount = FBDdefInputsCount[fbdDescrBuf[curIndex] & ELEMMASK]; 1448 | // 1449 | do { 1450 | // если у текущего элемента ещё есть входы 1451 | if(curInput < inputCount) { 1452 | // и этот вход еще не расчитан 1453 | inpIndex = ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput + curInput]); 1454 | // проверка: вход уже рассчитан или ко входу подключен выход этого же компонента 1455 | if(getCalcFlag(inpIndex)||(curIndex == inpIndex)) curInput++; else { 1456 | // ставим признак того, что элемент как-бы уже расчитан 1457 | // это нужно в случае, если в схеме есть обратные связи 1458 | setCalcFlag(curIndex); 1459 | // вход еще не рассчитан, запихиваем текущий элемент и номер входа в стек 1460 | fbdStack[fbdStackPnt].index = curIndex; 1461 | fbdStack[fbdStackPnt++].input = curInput; 1462 | // переходим к следующему дочернему элементу 1463 | curIndex = inpIndex; 1464 | curInput = 0; 1465 | baseInput = FBDINPUTOFFSET(curIndex); // элемент сменился, расчет смещения на первый вход элемента 1466 | inputCount = FBDdefInputsCount[fbdDescrBuf[curIndex] & ELEMMASK]; 1467 | } 1468 | continue; // следующая итерация цикла 1469 | } else { 1470 | // входов больше нет, а те которые есть уже рассчитаны 1471 | // определяем значения входов (если надо) 1472 | switch(inputCount) { 1473 | case 5: 1474 | v = fbdMemoryBuf[ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput + 4])]; 1475 | case 4: 1476 | s4 = fbdMemoryBuf[ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput + 3])]; 1477 | case 3: 1478 | s3 = fbdMemoryBuf[ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput + 2])]; 1479 | case 2: 1480 | s2 = fbdMemoryBuf[ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput + 1])]; 1481 | case 1: 1482 | s1 = fbdMemoryBuf[ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput])]; 1483 | } 1484 | // 1485 | // вычисляем значение текущего элемента, результат в s1 1486 | // 1487 | switch(fbdDescrBuf[curIndex] & ELEMMASK) { 1488 | case ELEM_OUT_PIN: // OUTPUT PIN 1489 | case ELEM_WP: // HMI watchpoint 1490 | break; 1491 | case ELEM_OUT_VAR: // OUTPUT VAR 1492 | // при изменении значения сигнала ставим флаг 1493 | if(fbdMemoryBuf[curIndex] != s1) { 1494 | setChangeVarFlag(curIndex); 1495 | } 1496 | break; 1497 | case ELEM_OUT_MDBS: // ELEM_OUT_MDBS 1498 | // при изменении значения сигнала ставим флаг 1499 | if(fbdMemoryBuf[curIndex] != s1) { 1500 | setChangeModbusFlag(curIndex); 1501 | } 1502 | break; 1503 | case ELEM_CONST: // CONST 1504 | s1 = FBDGETPARAMETER(curIndex, 0); 1505 | break; 1506 | case ELEM_NOT: // NOT 1507 | s1 = s1?0:1; 1508 | break; 1509 | case ELEM_AND: // AND 1510 | s1 = s1 && s2; 1511 | break; 1512 | case ELEM_OR: // OR 1513 | s1 = s1 || s2; 1514 | break; 1515 | case ELEM_XOR: // XOR 1516 | s1 = (s1?1:0)^(s2?1:0); 1517 | break; 1518 | case ELEM_RSTRG: // RSTRG 1519 | if(s1||s2) { 1520 | s1 = s1?0:1; 1521 | fbdSetStorage(curIndex, 0, s1); 1522 | } else s1 = FBDGETSTORAGE(curIndex, 0); 1523 | break; 1524 | case ELEM_DTRG: // DTRG 1525 | // смотрим установку флага фронта на входе "С" 1526 | if(getRiseFlag(ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput+1]))) 1527 | fbdSetStorage(curIndex, 0, s1); 1528 | else 1529 | s1 = FBDGETSTORAGE(curIndex, 0); 1530 | break; 1531 | case ELEM_ADD: // ADD 1532 | s1 += s2; 1533 | break; 1534 | case ELEM_SUB: // SUB 1535 | s1 -= s2; 1536 | break; 1537 | case ELEM_MUL: // MUL 1538 | s1 *= s2; 1539 | break; 1540 | case ELEM_DIV: // DIV 1541 | if(!s2) { 1542 | if(s1 > 0) s1 = MAX_SIGNAL; else if(s1 < 0) s1 = MIN_SIGNAL; else s1 = 1; 1543 | } else s1 /= s2; 1544 | break; 1545 | case ELEM_TON: // TIMER TON 1546 | // s1 - D 1547 | // s2 - T 1548 | if(s1) { 1549 | s1 = (FBDGETSTORAGE(curIndex, 0) == 0); 1550 | } else { 1551 | fbdSetStorage(curIndex, 0, s2); 1552 | s1 = 0; 1553 | } 1554 | break; 1555 | case ELEM_CMP: // CMP 1556 | s1 = s1 > s2; 1557 | break; 1558 | case ELEM_INP_PIN: 1559 | s1 = FBDgetProc(0, FBDGETPARAMETER(curIndex, 0)); // INPUT PIN 1560 | break; 1561 | case ELEM_INP_VAR: 1562 | s1 = FBDGETSTORAGE(curIndex, 0); // INPUT VAR 1563 | break; 1564 | case ELEM_INP_MDBS: 1565 | // просто последнее значение 1566 | s1 = fbdMemoryBuf[curIndex]; // ELEM_INP_MDBS 1567 | break; 1568 | case ELEM_PID: // PID 1569 | if(!FBDGETSTORAGE(curIndex, 0)) { // проверка срабатывания таймера 1570 | fbdSetStorage(curIndex, 0, s3); // установка таймера 1571 | s2 = s1 - s2; // ошибка PID 1572 | // error limit 1573 | //v = MAX_SIGNAL/2/s4; 1574 | //if(intAbs(s2) > v) s2 = (s2>0)?v:-v; 1575 | // 1576 | if(!fbdFirstFlag) v = ((tLongSignal)(s1 - FBDGETSTORAGE(curIndex, 1)) * 128)/s3; else v = 0; // скорость изменения входной величины 1577 | fbdSetStorage(curIndex, 1, s1); // сохранение прошлого входного значения 1578 | if((v < intAbs(s2))||(v > intAbs(s2*3))) { 1579 | s1= -(tLongSignal)s4*(s2*2 + v) / 128; 1580 | } else s1 = fbdMemoryBuf[curIndex]; 1581 | } else s1 = fbdMemoryBuf[curIndex]; 1582 | break; 1583 | case ELEM_SUM: // SUM 1584 | if(!FBDGETSTORAGE(curIndex, 0)) { // проверка срабатывания таймера 1585 | fbdSetStorage(curIndex, 0, s2); // установка таймера 1586 | // 1587 | s1 += fbdMemoryBuf[curIndex]; // сложение с предыдущим значением 1588 | // ограничение 1589 | if(s1 > 0) { if(s1 > s3) s1 = s3; } else { if(s1 < -s3) s1 = -s3; } 1590 | } else s1 = fbdMemoryBuf[curIndex]; 1591 | break; 1592 | case ELEM_COUNTER: // Counter 1593 | if(s3) s1 = 0; else { 1594 | s1 = FBDGETSTORAGE(curIndex, 0); 1595 | if(getRiseFlag(ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput]))) s1++; 1596 | if(getRiseFlag(ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput+1]))) s1--; 1597 | } 1598 | fbdSetStorage(curIndex, 0, s1); 1599 | break; 1600 | case ELEM_MUX: // MUX 1601 | v &= 3; 1602 | if(v==1) s1 = s2; else 1603 | if(v==2) s1 = s3; else 1604 | if(v==3) s1 = s4; 1605 | break; 1606 | case ELEM_ABS: // ABS 1607 | if(s1 < 0) s1 = -s1; 1608 | break; 1609 | case ELEM_SP: // HMI setpoint 1610 | #ifdef USE_HMI 1611 | s1 = FBDGETSTORAGE(curIndex, 0); 1612 | #endif // USE_HMI 1613 | break; 1614 | case ELEM_TP: // TIMER TP 1615 | // s1 - D 1616 | // s2 - T 1617 | if(FBDGETSTORAGE(curIndex, 0)) { 1618 | s1 = 1; 1619 | } else { 1620 | if(getRiseFlag(ELEMINDEX_BYTE_ORDER(fbdInputsBuf[baseInput])) && s2) { 1621 | fbdSetStorage(curIndex, 0, s2); 1622 | s1 = 1; 1623 | } else s1 = 0; 1624 | } 1625 | break; 1626 | case ELEM_MIN: // MIN 1627 | if(s2 < s1) s1 = s2; 1628 | break; 1629 | case ELEM_MAX: // MAX 1630 | if(s2 > s1) s1 = s2; 1631 | break; 1632 | case ELEM_LIM: // LIM 1633 | if(s1 > s2) s1 = s2; else if(s1 < s3) s1 = s3; 1634 | break; 1635 | case ELEM_EQ: // EQ 1636 | s1 = s1 == s2; 1637 | break; 1638 | case ELEM_BAND: // побитный AND 1639 | s1 = s1 & s2; 1640 | break; 1641 | case ELEM_BOR: // побитный OR 1642 | s1 = s1 | s2; 1643 | break; 1644 | case ELEM_BXOR: // побитный XOR 1645 | s1 = s1 ^ s2; 1646 | break; 1647 | case ELEM_GEN: // генератор 1648 | if(s1 > 0) { // s1 - enable, период; s2 - амплитуда 1649 | s3 = FBDGETSTORAGE(curIndex, 0); // s3 - остаток таймера периода 1650 | if(s3) { 1651 | switch (FBDGETPARAMETER(curIndex, 0)) { // тип генератора 1652 | case 0: // меандр 1653 | s1 = (s3 > (s1>>1))?0:s2; 1654 | break; 1655 | case 1: // пила 1656 | #ifdef USE_MATH 1657 | s1 = roundl(1.0*s2*(s1-s3)/s1); 1658 | #else 1659 | s1 = (s2*(s1-s3))/s1; 1660 | #endif 1661 | break; 1662 | case 2: // треугольник 1663 | if(s3 > (s1>>1)) { 1664 | #ifdef USE_MATH 1665 | s1 = roundl(2.0*s2*(s1-s3)/s1); // нарастание 1666 | } else { 1667 | s1 = roundl(2.0*s2*s3/s1); // спад 1668 | #else 1669 | s1 = 2*s2*(s1-s3)/s1; // нарастание 1670 | } else { 1671 | s1 = 2*s2*s3/s1; // спад 1672 | #endif 1673 | } 1674 | break; 1675 | #ifdef USE_MATH 1676 | case 3: // sin 1677 | s1 = roundl(s2*sin(2.0*M_PI*(s1-s3)/s1)); 1678 | break; 1679 | #endif 1680 | } 1681 | } else { 1682 | // запуск таймера 1683 | fbdSetStorage(curIndex, 0, s1); 1684 | // старт генератора с 0 1685 | s1 = 0; 1686 | } 1687 | } else { 1688 | // генератор остановлен 1689 | s1 = 0; 1690 | } 1691 | break; 1692 | } 1693 | setCalcFlag(curIndex); // установка флага "вычисление выполнено" 1694 | if(fbdDescrBuf[curIndex] & INVERTFLAG) s1 = s1?0:1; // инверсия результата (если нужно) 1695 | if(s1 > fbdMemoryBuf[curIndex]) setRiseFlag(curIndex); // установка флага фронта (если он был) 1696 | fbdMemoryBuf[curIndex] = s1; // сохраняем результат в буфер 1697 | } 1698 | // текущий элемент вычислен, пробуем достать из стека родительский элемент 1699 | if(fbdStackPnt--) { 1700 | curIndex = fbdStack[fbdStackPnt].index; // восстанавливаем родительский элемент 1701 | curInput = fbdStack[fbdStackPnt].input + 1; // в родительском элементе сразу переходим к следующему входу 1702 | baseInput = FBDINPUTOFFSET(curIndex); // элемент сменился, расчет смещения на первый вход элемента 1703 | inputCount = FBDdefInputsCount[fbdDescrBuf[curIndex] & ELEMMASK]; 1704 | } else break; // стек пуст, вычисления завершены 1705 | } while(1); 1706 | } 1707 | 1708 | /** 1709 | * @brief Сохранить значение в EEPROM памяти элемента 1710 | * 1711 | * @param element Индекс элемента 1712 | * @param index Индекс ячейки памяти 1713 | * @param value Значение 1714 | */ 1715 | void fbdSetStorage(tElemIndex element, unsigned char index, tSignal value) 1716 | { 1717 | #ifdef SPEED_OPT 1718 | tOffset offset = *(storageOffsets + element) + index; 1719 | #else 1720 | tOffset offset = 0; 1721 | tElemIndex elem = 0; 1722 | // 1723 | while (elem < element) offset += FBDdefStorageCount[fbdDescrBuf[elem++] & ELEMMASK]; 1724 | offset += index; 1725 | #endif // SPEED_OPT 1726 | if(fbdStorageBuf[offset] != value) { 1727 | fbdStorageBuf[offset] = value; 1728 | FBDsetProc(1, offset, &fbdStorageBuf[offset]); 1729 | } 1730 | } 1731 | 1732 | /** 1733 | * @brief Установить флаг "Элемент вычислен" 1734 | * 1735 | * @param element Индекс элемента 1736 | */ 1737 | void setCalcFlag(tElemIndex element) 1738 | { 1739 | fbdFlagsBuf[element>>2] |= 1u<<((element&3)<<1); 1740 | } 1741 | 1742 | /** 1743 | * @brief Установка флага нарастания выходного значения элемента (rising flag) 1744 | * 1745 | * @param element Индекс элемента 1746 | */ 1747 | void setRiseFlag(tElemIndex element) 1748 | { 1749 | fbdFlagsBuf[element>>2] |= 1u<<(((element&3)<<1)+1); 1750 | } 1751 | 1752 | /** 1753 | * @brief Получение значения флага "Элемент вычислен" 1754 | * 1755 | * @param element Индекс элемента 1756 | * @return char 1 - установлен, 0 - сброшен 1757 | */ 1758 | char getCalcFlag(tElemIndex element) 1759 | { 1760 | return (fbdFlagsBuf[element>>2]&(1u<<((element&3)<<1)))?1:0; 1761 | } 1762 | 1763 | /** 1764 | * @brief Получение значения флага нарастания выходного значения элемента (rising flag) 1765 | * 1766 | * @param element Индекс элемента 1767 | * @return char 1 - установлен, 0 - сброшен 1768 | */ 1769 | char getRiseFlag(tElemIndex element) 1770 | { 1771 | return (fbdFlagsBuf[element>>2]&(1u<<(((element&3)<<1)+1)))?1:0; 1772 | } 1773 | 1774 | /** 1775 | * @brief Установка флага изменения выходной сетевой переменной 1776 | * 1777 | * @param index Индекс элемента ELEM_OUT_VAR 1778 | */ 1779 | void setChangeVarFlag(tElemIndex index) 1780 | { 1781 | tElemIndex varIndex = 0; 1782 | // определяем номер флага 1783 | while(index--) { 1784 | if((fbdDescrBuf[index] & ELEMMASK) == ELEM_OUT_VAR) varIndex++; 1785 | } 1786 | fbdChangeVarBuf[varIndex>>3] |= 1u<<(varIndex&7); 1787 | } 1788 | 1789 | /** 1790 | * @brief Установка флага изменения значение Modbus 1791 | * 1792 | * @param index Индекс элемента записи Modbus 1793 | */ 1794 | void setChangeModbusFlag(tElemIndex index) 1795 | { 1796 | tElemIndex varIndex = 0; 1797 | // определяем номер флага 1798 | while(index--) { 1799 | if((fbdDescrBuf[index] & ELEMMASK) == ELEM_OUT_MDBS) varIndex++; 1800 | } 1801 | fbdChangeModbusBuf[varIndex>>3] |= 1u<<(varIndex&7); 1802 | } 1803 | 1804 | /** 1805 | * @brief Возвращает и сбрасыввет, если он установлен, флаг изменения значения записи в Modbus 1806 | * 1807 | * @param index Индекс элемента записи Modbus 1808 | * @return true Флаг установлен и был сброшен 1809 | * @return false Флаг сброшен 1810 | */ 1811 | bool getAndClearChangeModbusFlag(tElemIndex index) 1812 | { 1813 | tElemIndex varIndex = 0; 1814 | // определяем номер флага 1815 | while(index--) { 1816 | if((fbdDescrBuf[index] & ELEMMASK) == ELEM_OUT_MDBS) varIndex++; 1817 | } 1818 | if(fbdChangeVarBuf[varIndex >> 3]&(1u << (varIndex & 7))) { 1819 | // флаг установлен, сбрасываем 1820 | fbdChangeVarBuf[varIndex>>3] &= ~(1u<<(varIndex&7)); 1821 | return true; 1822 | } else { 1823 | return false; 1824 | } 1825 | } 1826 | 1827 | /** 1828 | * @brief Установка признака "нет ответа" для элемента чтения или записи Modbus 1829 | * @param index Индекс элемента чтения или записи Modbus 1830 | */ 1831 | void setModbusNoResponse(tElemIndex index) 1832 | { 1833 | if(index == MAX_INDEX) return; 1834 | // 1835 | switch (fbdDescrBuf[index] & ELEMMASK) { 1836 | case ELEM_INP_MDBS: 1837 | // ошибка чтения Modbus, устанавливаем значение по умолчанию 1838 | fbdMemoryBuf[index] = FBDGETPARAMETER(index, 2); 1839 | break; 1840 | case ELEM_OUT_MDBS: 1841 | // ошибка записи Modbus, устанавливаем флаг необходимости повторной записи 1842 | setChangeModbusFlag(index); 1843 | break; 1844 | } 1845 | } 1846 | 1847 | /** 1848 | * @brief Разбор и установка принятых по Modbus данных 1849 | * 1850 | * @param index Индекс элемента чтения или записи Modbus 1851 | * @param response Принятые данные 1852 | */ 1853 | void setModbusResponse(tElemIndex index, tSignal response) 1854 | { 1855 | tSignal options; 1856 | tModbusData data; 1857 | unsigned char fmtcnt; 1858 | // 1859 | if(index == MAX_INDEX) return; 1860 | // если это не элемент чтения Modbus, то ничего не далаем 1861 | // для элементов записи Modbus флаг изменений сбрасывается при формировании запроса 1862 | if((fbdDescrBuf[index] & ELEMMASK) != ELEM_INP_MDBS) return; 1863 | // чтение выполнено успешно 1864 | options = FBDGETPARAMETER(index, 1); 1865 | fmtcnt = (options >> 28) & 15; 1866 | // 1867 | switch ((options >> 24) & 3) { 1868 | case 0: 1869 | case 1: 1870 | // FBD_MODBUS_READ_COILS 1871 | // FBD_MODBUS_READ_DISCRETE_INPUTS 1872 | fmtcnt++; 1873 | if(options & FBD_MODBUS_OPT_WO) fmtcnt += 16; 1874 | // обнуляем ненужные биты 1875 | fbdMemoryBuf[index] = response & getCoilBitsMask(fmtcnt); 1876 | // 1877 | break; 1878 | case 2: 1879 | case 3: 1880 | // FBD_MODBUS_READ_HOLDING_REGISTERS 1881 | // FBD_MODBUS_READ_INPUT_REGISTERS 1882 | data.intData = response; 1883 | // изменение порядка байт 1884 | if(!(options & FBD_MODBUS_OPT_BO)) swapModbusByteOrder(&data); 1885 | // 1886 | switch (fmtcnt & 3) { 1887 | case 0: 1888 | // uint16 (0..65535) 1 регистр 1889 | fbdMemoryBuf[index] = data.ushortData[0]; 1890 | // 1891 | break; 1892 | case 1: 1893 | // int16 (0..65535) 1 регистр 1894 | fbdMemoryBuf[index] = data.shortData[0]; 1895 | // 1896 | break; 1897 | case 2: 1898 | // int32 2 регистра 1899 | // изменение порядка слов 1900 | if(!(options & FBD_MODBUS_OPT_WO)) swapModbusWordOrder(&data); 1901 | fbdMemoryBuf[index] = data.intData; 1902 | // 1903 | break; 1904 | case 3: 1905 | // single 2 регистра, MM - множитель 00 - 1, 01 - 10, 10 - 100, 11 - 1000 1906 | // изменение порядка слов 1907 | if(!(options & FBD_MODBUS_OPT_WO)) swapModbusWordOrder(&data); 1908 | // множитель 1909 | switch ((fmtcnt >> 2) & 3) { 1910 | case 0: 1911 | setModbusFloat(index, data.floatData, (float)1.0); 1912 | break; 1913 | case 1: 1914 | setModbusFloat(index, data.floatData, (float)10.0); 1915 | break; 1916 | case 2: 1917 | setModbusFloat(index, data.floatData, (float)100.0); 1918 | break; 1919 | case 3: 1920 | setModbusFloat(index, data.floatData, (float)1000.0); 1921 | break; 1922 | } 1923 | break; 1924 | } 1925 | } 1926 | } 1927 | 1928 | /** 1929 | * @brief Заполнение структуры запроса Modbus 1930 | * 1931 | * @param index Индекс элемента чтения или записи Modbus 1932 | * @param mbrequest Указатель на структуру запроса Modbus 1933 | */ 1934 | void fillModbusRequest(tElemIndex index, tModbusReq *mbrequest) 1935 | { 1936 | unsigned char fmtcnt; 1937 | tSignal options = FBDGETPARAMETER(index, 1); 1938 | // 1939 | mbrequest->ip = FBDGETPARAMETER(index, 0); 1940 | mbrequest->regAddr = options & 0x0000ffff; 1941 | mbrequest->slaveAddr = (options >> 16) & 0xff; 1942 | fmtcnt = (options >> 28) & 15; 1943 | // 1944 | if((fbdDescrBuf[index] & ELEMMASK) == ELEM_INP_MDBS) { 1945 | // чтение Modbus 1946 | switch ((options >> 24) & 3) { 1947 | case 0: 1948 | mbrequest->funcCode = FBD_MODBUS_READ_COILS; 1949 | mbrequest->regCount = fmtcnt + 1; 1950 | if(options & FBD_MODBUS_OPT_WO) mbrequest->regCount += 16; 1951 | break; 1952 | case 1: 1953 | mbrequest->funcCode = FBD_MODBUS_READ_DISCRETE_INPUTS; 1954 | mbrequest->regCount = fmtcnt + 1; 1955 | if(options & FBD_MODBUS_OPT_WO) mbrequest->regCount += 16; 1956 | break; 1957 | case 2: 1958 | mbrequest->funcCode = FBD_MODBUS_READ_HOLDING_REGISTERS; 1959 | // FMT - формат читаемых данных (для "read input registers" и "read holding registers"): 1960 | // 0000 - uint16 (0..65535) 1 регистр 1961 | // 0001 - int16 (0..65535) 1 регистр 1962 | // 0010 - int32 2 регистра 1963 | // MM11 - single 2 регистра, MM - множитель 00 - 1, 01 - 10, 10 - 100, 11 - 1000 1964 | mbrequest->regCount = (fmtcnt & 2)?2:1; 1965 | break; 1966 | case 3: 1967 | mbrequest->funcCode = FBD_MODBUS_READ_INPUT_REGISTERS; 1968 | mbrequest->regCount = (fmtcnt & 2)?2:1; 1969 | break; 1970 | } 1971 | } else { 1972 | // запись Modbus 1973 | switch ((options >> 24) & 3) { 1974 | case 0: 1975 | // FBD_MODBUS_WRITE_SINGLE_COIL 1976 | mbrequest->funcCode = FBD_MODBUS_WRITE_SINGLE_COIL; 1977 | mbrequest->regCount = 1; 1978 | mbrequest->data.intData = fbdMemoryBuf[index]?0x00ff:0; 1979 | break; 1980 | case 1: 1981 | // FBD_MODBUS_WRITE_MULTIPLE_COILS 1982 | mbrequest->funcCode = FBD_MODBUS_WRITE_MULTIPLE_COILS; 1983 | mbrequest->regCount = fmtcnt + 1; 1984 | if(options & FBD_MODBUS_OPT_WO) mbrequest->regCount += 16; 1985 | mbrequest->data.intData = fbdMemoryBuf[index]; 1986 | break; 1987 | case 2: 1988 | // FBD_MODBUS_WRITE_SINGLE_REGISTER 1989 | mbrequest->funcCode = FBD_MODBUS_WRITE_SINGLE_REGISTER; 1990 | mbrequest->regCount = 1; 1991 | mbrequest->data.intData = fbdMemoryBuf[index]; 1992 | if(!(options & FBD_MODBUS_OPT_BO)) swapModbusByteOrder(&mbrequest->data); 1993 | break; 1994 | case 3: 1995 | mbrequest->funcCode = FBD_MODBUS_WRITE_MULTIPLE_REGISTERS; 1996 | // FBD_MODBUS_WRITE_MULTIPLE_REGISTERS 1997 | // формат данных 1998 | switch (fmtcnt & 3) { 1999 | case 0: 2000 | case 1: 2001 | // UINT16, INT16 2002 | mbrequest->regCount = 1; 2003 | mbrequest->data.intData = fbdMemoryBuf[index]; 2004 | // порядок байт 2005 | if(!(options & FBD_MODBUS_OPT_BO)) swapModbusByteOrder(&mbrequest->data); 2006 | // 2007 | break; 2008 | case 2: 2009 | // INT32 2010 | mbrequest->regCount = 2; 2011 | mbrequest->data.intData = fbdMemoryBuf[index]; 2012 | // порядок байт и слов 2013 | if(!(options & FBD_MODBUS_OPT_BO)) swapModbusByteOrder(&mbrequest->data); 2014 | if(!(options & FBD_MODBUS_OPT_WO)) swapModbusWordOrder(&mbrequest->data); 2015 | // 2016 | break; 2017 | case 3: 2018 | // float 2019 | mbrequest->regCount = 2; 2020 | // приведение к типу single precission float 2021 | mbrequest->data.floatData = fbdMemoryBuf[index]; 2022 | // делитель 2023 | switch ((fmtcnt >> 2) & 3) { 2024 | case 0: 2025 | // mbrequest->data.floatData /= 1; 2026 | break; 2027 | case 1: 2028 | mbrequest->data.floatData /= (float)10.0; 2029 | break; 2030 | case 2: 2031 | mbrequest->data.floatData /= (float)100.0; 2032 | break; 2033 | case 3: 2034 | mbrequest->data.floatData /= (float)1000.0; 2035 | break; 2036 | } 2037 | // порядок байт и слов 2038 | if(!(options & FBD_MODBUS_OPT_BO)) swapModbusByteOrder(&mbrequest->data); 2039 | if(!(options & FBD_MODBUS_OPT_WO)) swapModbusWordOrder(&mbrequest->data); 2040 | // 2041 | break; 2042 | } 2043 | // 2044 | break; 2045 | } 2046 | } 2047 | } 2048 | 2049 | /** 2050 | * @brief Проверка и установка значения чтения Modbus с типом float 2051 | * 2052 | * @param index Индекс элемента чтения Modbus 2053 | * @param data Новые данные 2054 | * @param mul Множитель 2055 | */ 2056 | void setModbusFloat(tElemIndex index, float data, float mul) 2057 | { 2058 | int fpc = fpclassify(data); 2059 | if((fpc == FP_NORMAL) || (fpc == FP_ZERO)) { 2060 | data = roundf(data * mul); 2061 | if((data >= MIN_SIGNAL)&&(data <= MAX_SIGNAL)) { 2062 | // корректное значение, устанавливаем его 2063 | fbdMemoryBuf[index] = data; 2064 | return; 2065 | } 2066 | } 2067 | // значение float некорректно, ставим значение по умолчанию 2068 | fbdMemoryBuf[index] = FBDGETPARAMETER(index, 2); 2069 | } 2070 | 2071 | /** 2072 | * @brief Меняет порядок байтов в данных Modbus 2073 | * @param data Указатель на данные 2074 | */ 2075 | void swapModbusByteOrder(tModbusData *data) 2076 | { 2077 | unsigned char t; 2078 | // 2079 | t = (*data).byteData[0]; 2080 | (*data).byteData[0] = (*data).byteData[1]; 2081 | (*data).byteData[1] = t; 2082 | // 2083 | t = (*data).byteData[2]; 2084 | (*data).byteData[2] = (*data).byteData[3]; 2085 | (*data).byteData[3] = t; 2086 | } 2087 | 2088 | /** 2089 | * @brief Меняет порядок слов в данных Modbus 2090 | * @param data Указатель на данные 2091 | */ 2092 | void swapModbusWordOrder(tModbusData *data) 2093 | { 2094 | unsigned short t; 2095 | // 2096 | t = (*data).ushortData[0]; 2097 | (*data).ushortData[0] = (*data).ushortData[1]; 2098 | (*data).ushortData[1] = t; 2099 | } 2100 | 2101 | /** 2102 | * @brief Вычисление абсолютного значения сигнала 2103 | * 2104 | * @param val Значение сигнала 2105 | * @return tSignal Абсолютное значение сигнала 2106 | */ 2107 | inline tSignal intAbs(tSignal val) 2108 | { 2109 | return (val>=0)?val:-val; 2110 | } 2111 | 2112 | /** 2113 | * @brief Возвращает значение у кторого установлены count младших бит. 2114 | * 2115 | * @param count Количество бит 2116 | * @return uint32_t Результат 2117 | */ 2118 | uint32_t getCoilBitsMask(unsigned count) 2119 | { 2120 | if(count < 32) { 2121 | return (1 << count) - 1; 2122 | } else { 2123 | return 0xffffffff; 2124 | } 2125 | } 2126 | 2127 | #if defined(BIG_ENDIAN) && (SIGNAL_SIZE > 1) 2128 | typedef union { 2129 | tSignal value; 2130 | #if SIGNAL_SIZE == 2 2131 | char B[2]; 2132 | #elif SIGNAL_SIZE == 4 2133 | char B[4]; 2134 | #endif 2135 | } teus; 2136 | // ------------------------------------------------------------------------------------------------------- 2137 | tSignal lotobigsign(tSignal val) 2138 | { 2139 | teus uval; 2140 | char t; 2141 | // 2142 | uval.value = val; 2143 | t = uval.B[0]; 2144 | #if (SIGNAL_SIZE == 2) 2145 | uval.B[0] = uval.B[1]; 2146 | uval.B[1] = t; 2147 | #else 2148 | uval.B[0] = uval.B[3]; 2149 | uval.B[3] = t; 2150 | t = uval.B[1]; 2151 | uval.B[1] = uval.B[2]; 2152 | uval.B[2] = t; 2153 | #endif // SIGNAL_SIZE 2154 | return uval.value; 2155 | } 2156 | #endif // defined 2157 | // 2158 | #if defined(BIG_ENDIAN) && (INDEX_SIZE > 1) 2159 | typedef union { 2160 | tSignal value; 2161 | char B[2]; 2162 | } teui; 2163 | // ------------------------------------------------------------------------------------------------------- 2164 | tElemIndex lotobigidx(tElemIndex val) 2165 | { 2166 | teui uval; 2167 | char t; 2168 | // 2169 | uval.value = val; 2170 | t = uval.B[0]; 2171 | uval.B[0] = uval.B[1]; 2172 | uval.B[1] = t; 2173 | return uval.value; 2174 | } 2175 | #endif // defined 2176 | --------------------------------------------------------------------------------