├── .gitignore ├── .gitmodules ├── .travis.yml ├── Makefile ├── README.md ├── include ├── Config0.h ├── LOCATED_VARIABLES.h ├── POUS.c ├── POUS.h └── README ├── lib ├── README └── openplc │ └── src ├── platformio.ini ├── programs ├── blink.st └── blink2.st ├── src ├── Config0.cpp ├── Res0.cpp ├── hal.cpp ├── hal.h ├── main.cpp └── vars.cpp └── test └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pioenvs 3 | .piolibdeps 4 | .vscode/.browse.c_cpp.db* 5 | .vscode/c_cpp_properties.json 6 | .vscode/launch.json 7 | 8 | /plc-src -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "OpenPLC_v3"] 2 | path = OpenPLC_v3 3 | url = https://github.com/thiagoralves/OpenPLC_v3.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PLC_SRC_FILE ?= programs/blink.st 2 | 3 | OPENPLC_DIR = OpenPLC_v3 4 | SRC_GEN_DIR = plc-src 5 | 6 | REAL_PLC_SRC_FILE = $(realpath $(PLC_SRC_FILE)) 7 | MATIEC_DIR = $(realpath $(OPENPLC_DIR)/utils/matiec_src) 8 | 9 | .PHONY: all 10 | all: generate 11 | 12 | .PHONY: clean 13 | clean: 14 | rm -rf $(SRC_GEN_DIR) 15 | 16 | .PHONY: generate 17 | generate: clean 18 | mkdir $(SRC_GEN_DIR) 19 | cd $(SRC_GEN_DIR) && $(MATIEC_DIR)/iec2c -I $(MATIEC_DIR)/lib $(REAL_PLC_SRC_FILE) 20 | 21 | run: 22 | build/plc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCU-PLC 2 | 3 | MCU-PLC is a proof-of-concept port of [OpenPLC](https://openplcproject.com) core 4 | to microcontrollers (currently ESP32 only). You can run standard Structured 5 | Text programs right on the MCU rather than using the MCU as an external 6 | slave device (standard OpenPLC way). 7 | 8 | The core does not contain any communication protocols support for now, 9 | just GPIO-in/out and ADC. 10 | 11 | **BEWARE: This is really just a proof of concept. DO NOT USE IT FOR ANYTHING 12 | OTHER THAN RESEARCH!** 13 | 14 | ## Principle 15 | 16 | Similarly to OpenPLC, The Structured Text program is translated into C++ source 17 | files using [Matiec compiler](https://bitbucket.org/mjsousa/matiec). These 18 | files are linked with [OpenPLC 19 | lib](https://github.com/thiagoralves/OpenPLC_v3/tree/master/webserver/core/lib) files 20 | and a very thin MCU-specific layer (also mostly an OpenPLC rip-off). 21 | The resulting binary can be uploaded straight into the MCU. I.e. there's no 22 | API for uploading the ST program, starting and stopping the PLC or anything 23 | like that. Your ST program just runs on your MCU, that's it. 24 | 25 | I'm using [the Arduino framework](https://docs.platformio.org/en/latest/frameworks/arduino.html#framework-arduino) 26 | but the code is very easily portable to any other PlatformIO-supported framework 27 | (see `hal.cpp` file). 28 | 29 | ## Requirements 30 | 31 | You need: 32 | - standard `build-essential` or equivalent packages to build Matiec 33 | - working [PlatformIO](https://platformio.org/) installation 34 | 35 | The code is tested on the `NodeMCU ESP-32S` board but it should work 36 | on every ESP32 board with small or no modifications. You can change 37 | the board type by changing `board` option in the platformio.ini file 38 | (see [PlatfrmIO docs](https://docs.platformio.org/en/latest/platforms/espressif32.html#boards)). 39 | Pin mapping is defined in the [src/hal.cpp](src/hal.cpp) file (`..._PINS[]` arrays). 40 | 41 | ## Preparation 42 | 43 | Get OpenPLC sources: 44 | ```shell 45 | $ git submodule init 46 | $ git submodule update 47 | ``` 48 | 49 | Compile `matiec` (IEC 61131-3 compiler): 50 | ```shell 51 | $ cd OpenPLC_v3/utils/matiec_src/ 52 | $ autoreconf -i 53 | $ ./configure 54 | $ make 55 | $ cd ../../.. 56 | ``` 57 | 58 | ## PLC Program Compilation 59 | 60 | At first, generate PLC sources (using `matiec`) using: 61 | ```shell 62 | $ make 63 | ``` 64 | 65 | This will generate files from the default `blink.st` program. You can compile 66 | different program using: 67 | ```shell 68 | $ make PLC_SRC_FILE=programs/blink2.st 69 | ``` 70 | 71 | All PLC C sources are ready now, you can connect your ESP32 to your PC and build 72 | and upload the program using PlatformIO: 73 | ```shell 74 | $ pio run -t upload 75 | ``` 76 | 77 | If everything went well, you should see blinking LED on your ESP32 now. 78 | 79 | ## Running on Host (Linux only) 80 | 81 | To make debugging more convenient, you can run your PLC straight on the host 82 | computer, if it's a Linux machine: 83 | 84 | ```shell 85 | $ pio run -e linux_x86_64 86 | $ .pio/build/linux_x86_64/program 87 | ``` -------------------------------------------------------------------------------- /include/Config0.h: -------------------------------------------------------------------------------- 1 | ../plc-src/Config0.h -------------------------------------------------------------------------------- /include/LOCATED_VARIABLES.h: -------------------------------------------------------------------------------- 1 | ../plc-src/LOCATED_VARIABLES.h -------------------------------------------------------------------------------- /include/POUS.c: -------------------------------------------------------------------------------- 1 | ../plc-src/POUS.c -------------------------------------------------------------------------------- /include/POUS.h: -------------------------------------------------------------------------------- 1 | ../plc-src/POUS.h -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /lib/openplc/src: -------------------------------------------------------------------------------- 1 | ../../OpenPLC_v3/webserver/core/lib -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = esp32dev 13 | 14 | [env:esp32dev] 15 | platform = espressif32 16 | board = esp32dev 17 | framework = arduino 18 | 19 | build_flags = 20 | -Wno-unused-function 21 | -Wno-unused-variable 22 | -std=gnu++11 23 | -DHW_ESP32 24 | -DBUFFER_SIZE=32 25 | -DDEBUG 26 | -DTIMER_DEBUG 27 | 28 | upload_port = /dev/ttyUSB0 29 | 30 | [env:linux_x86_64] 31 | platform = linux_x86_64 32 | 33 | build_flags = 34 | -Wno-unused-function 35 | -Wno-unused-variable 36 | -std=gnu++11 37 | -DHW_LINUX 38 | -DBUFFER_SIZE=1024 39 | -DDEBUG 40 | -DTIMER_DEBUG 41 | -------------------------------------------------------------------------------- /programs/blink.st: -------------------------------------------------------------------------------- 1 | PROGRAM Blink 2 | VAR 3 | lamp_off : BOOL; 4 | END_VAR 5 | VAR 6 | lamp AT %QX0.0 : BOOL; 7 | END_VAR 8 | VAR 9 | TON0 : TON; 10 | TON1 : TON; 11 | END_VAR 12 | 13 | lamp := NOT(lamp_off); 14 | TON0(IN := lamp, PT := T#1s); 15 | IF TON0.Q THEN 16 | lamp_off := TRUE; (*set*) 17 | END_IF; 18 | TON1(IN := lamp_off, PT := T#1s); 19 | IF TON1.Q THEN 20 | lamp_off := FALSE; (*reset*) 21 | END_IF; 22 | END_PROGRAM 23 | 24 | 25 | CONFIGURATION Config0 26 | 27 | RESOURCE Res0 ON PLC 28 | TASK task0(INTERVAL := T#200ms,PRIORITY := 0); 29 | PROGRAM instance0 WITH task0 : Blink; 30 | END_RESOURCE 31 | END_CONFIGURATION 32 | -------------------------------------------------------------------------------- /programs/blink2.st: -------------------------------------------------------------------------------- 1 | PROGRAM Blink2 2 | VAR 3 | lamp_off : BOOL; 4 | END_VAR 5 | VAR 6 | kill_switch AT %IX0.0 : BOOL; 7 | lamp1 AT %QX0.0 : BOOL; 8 | lamp2 AT %QX0.1 : BOOL; 9 | END_VAR 10 | VAR 11 | TON0 : TON; 12 | TON1 : TON; 13 | END_VAR 14 | 15 | lamp1 := NOT(kill_switch) AND NOT(lamp_off); 16 | lamp2 := NOT(kill_switch) AND lamp_off; 17 | TON0(IN := lamp1, PT := T#1000ms); 18 | IF TON0.Q THEN 19 | lamp_off := TRUE; (*set*) 20 | END_IF; 21 | TON1(IN := lamp_off, PT := T#1000ms); 22 | IF TON1.Q THEN 23 | lamp_off := FALSE; (*reset*) 24 | END_IF; 25 | END_PROGRAM 26 | 27 | 28 | CONFIGURATION Config0 29 | 30 | RESOURCE Res0 ON PLC 31 | TASK task0(INTERVAL := T#200ms,PRIORITY := 0); 32 | PROGRAM instance0 WITH task0 : Blink2; 33 | END_RESOURCE 34 | END_CONFIGURATION 35 | -------------------------------------------------------------------------------- /src/Config0.cpp: -------------------------------------------------------------------------------- 1 | ../plc-src/Config0.c -------------------------------------------------------------------------------- /src/Res0.cpp: -------------------------------------------------------------------------------- 1 | ../plc-src/Res0.c -------------------------------------------------------------------------------- /src/hal.cpp: -------------------------------------------------------------------------------- 1 | #ifdef HW_LINUX 2 | #endif 3 | 4 | #include "iec_types.h" 5 | 6 | #include "hal.h" 7 | 8 | //Booleans 9 | extern IEC_BOOL *bool_input[BUFFER_SIZE][8]; 10 | extern IEC_BOOL *bool_output[BUFFER_SIZE][8]; 11 | 12 | //Bytes 13 | extern IEC_BYTE *byte_input[BUFFER_SIZE]; 14 | extern IEC_BYTE *byte_output[BUFFER_SIZE]; 15 | 16 | //Analog I/O 17 | extern IEC_UINT *int_input[BUFFER_SIZE]; 18 | extern IEC_UINT *int_output[BUFFER_SIZE]; 19 | 20 | //Memory 21 | extern IEC_UINT *int_memory[BUFFER_SIZE]; 22 | extern IEC_DINT *dint_memory[BUFFER_SIZE]; 23 | extern IEC_LINT *lint_memory[BUFFER_SIZE]; 24 | 25 | //Special Functions 26 | extern IEC_LINT *special_functions[BUFFER_SIZE]; 27 | 28 | extern IEC_TIME __CURRENT_TIME; 29 | extern unsigned long long common_ticktime__; 30 | 31 | #define MILLION 1000000 32 | #define BILLION 1000000000 33 | 34 | void update_time() 35 | { 36 | __CURRENT_TIME.tv_nsec += common_ticktime__; 37 | if (__CURRENT_TIME.tv_nsec >= BILLION) 38 | { 39 | __CURRENT_TIME.tv_nsec -= BILLION; 40 | __CURRENT_TIME.tv_sec++; 41 | } 42 | } 43 | 44 | /* 45 | * Macros black magic to debug print only variables which are really used in the PLC program. 46 | */ 47 | 48 | #define POOL_BOOL_I bool_input 49 | #define POOL_BOOL_Q bool_output 50 | #define POOL_UINT_I int_input 51 | #define INDEX_BOOL(a, b) [a][b] 52 | #define INDEX_UINT(a, b) [a] 53 | 54 | #define print_in_Q(...) 55 | inline void print_in_I(const char *name, IEC_BOOL *val) 56 | { 57 | logf("%s = %u\n", name, *val); 58 | } 59 | 60 | void print_inputs() 61 | { 62 | #define __LOCATED_VAR(type, name, inout, type_sym, a, b) \ 63 | print_in_##inout(#inout #type_sym #a "." #b, POOL_##type##_##inout INDEX_##type(a, b)); 64 | #include "LOCATED_VARIABLES.h" 65 | #undef __LOCATED_VAR 66 | } 67 | 68 | #define print_out_I(...) 69 | inline void print_out_Q(const char *name, IEC_BOOL *val) 70 | { 71 | logf("%s = %u\n", name, *val); 72 | } 73 | 74 | void print_outputs() 75 | { 76 | #define __LOCATED_VAR(type, name, inout, type_sym, a, b) \ 77 | print_out_##inout(#inout #type_sym #a "." #b, POOL_##type##_##inout INDEX_##type(a, b)); 78 | #include "LOCATED_VARIABLES.h" 79 | #undef __LOCATED_VAR 80 | } 81 | 82 | /* 83 | * Linux 84 | */ 85 | 86 | #ifdef HW_LINUX 87 | #include 88 | 89 | static void ts_add(struct timespec *ts, int delay) 90 | { 91 | ts->tv_nsec += delay; 92 | if (ts->tv_nsec >= BILLION) 93 | { 94 | ts->tv_nsec -= BILLION; 95 | ts->tv_sec++; 96 | } 97 | } 98 | 99 | void hw_init() 100 | { 101 | logf("initializing hw\n"); 102 | } 103 | 104 | void hw_close() 105 | { 106 | logf("closing hw\n"); 107 | } 108 | 109 | void update_buffers_in() 110 | { 111 | DEBUGF("updating input buffers\n"); 112 | print_inputs(); 113 | } 114 | 115 | void update_buffers_out() 116 | { 117 | DEBUGF("updating output buffers\n"); 118 | print_outputs(); 119 | } 120 | 121 | static struct timespec timer; 122 | void timer_init() 123 | { 124 | clock_gettime(CLOCK_MONOTONIC, &timer); 125 | } 126 | 127 | void timer_sleep_until(unsigned long long delay_ns) 128 | { 129 | ts_add(&timer, delay_ns); 130 | // NOTE: If the PLC is too slow (real time is > timer), clock_nanosleep 131 | // will sleep for no time but `timer` will be 132 | // behind wall clock. This error could get corrected in the 133 | // subsequent ticks, or can be accumulated. 134 | clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &timer, NULL); 135 | 136 | #ifdef TIMER_DEBUG 137 | static float time_old = 0; 138 | struct timespec timer2; 139 | clock_gettime(CLOCK_MONOTONIC, &timer2); 140 | float time = timer2.tv_sec + timer2.tv_nsec / (float)BILLION; 141 | DEBUGF("wall time = %.6fs\n", time); 142 | if (time_old) 143 | { 144 | float time_error = (time - time_old) * MILLION - common_ticktime__ / 1000; 145 | DEBUGF("time_error = %.0fus\n", time_error); 146 | } 147 | time_old = time; 148 | #endif 149 | } 150 | #endif 151 | 152 | /* 153 | * ESP32 154 | */ 155 | 156 | #ifdef HW_ESP32 157 | #include 158 | 159 | // see https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ 160 | // for PIN choice hints 161 | 162 | // digital inputs 163 | const uint8_t DIS_NUM = 4; 164 | const uint8_t DIS_PINS[DIS_NUM] = {16, 17, 5, 18}; 165 | // digital outputs 166 | const uint8_t DOS_NUM = 5; 167 | const uint8_t DOS_PINS[DOS_NUM] = {2, 13, 12, 14, 27}; 168 | // analog inputs 169 | const uint8_t AIS_NUM = 4; 170 | const uint8_t AIS_PINS[AIS_NUM] = {33, 32, 35, 34}; // 26, 25 does not work?! 171 | 172 | void hw_init() 173 | { 174 | Serial.begin(115200); 175 | logf("initializing hw\n"); 176 | for (uint8_t i = 0; i < AIS_NUM; i++) 177 | { 178 | pinMode(AIS_PINS[i], INPUT); 179 | } 180 | for (uint8_t i = 0; i < DIS_NUM; i++) 181 | { 182 | pinMode(DIS_PINS[i], INPUT_PULLUP); 183 | } 184 | for (uint8_t i = 0; i < DOS_NUM; i++) 185 | { 186 | pinMode(DOS_PINS[i], OUTPUT); 187 | } 188 | } 189 | 190 | void hw_close() 191 | { 192 | logf("closing hw\n"); 193 | } 194 | 195 | void update_buffers_in() 196 | { 197 | DEBUGF("updating input buffers\n"); 198 | for (uint8_t i = 0; i < DIS_NUM; i++) 199 | { 200 | uint8_t a = i / 8; 201 | uint8_t b = i % 8; 202 | IEC_BOOL *val = bool_input[a][b]; 203 | if (val) 204 | { 205 | *val = !digitalRead(DIS_PINS[i]); 206 | DEBUGF("IX%u.%u = %u\n", a, b, *val); 207 | } 208 | } 209 | for (uint8_t i = 0; i < AIS_NUM; i++) 210 | { 211 | uint8_t a = i / 8; 212 | uint8_t b = i % 8; 213 | IEC_UINT *val = int_input[i]; 214 | if (val) 215 | { 216 | *val = analogRead(AIS_PINS[i]); 217 | DEBUGF("IW%u.%u = %u\n", a, b, *val); 218 | } 219 | } 220 | } 221 | 222 | void update_buffers_out() 223 | { 224 | DEBUGF("updating output buffers\n"); 225 | for (uint8_t i = 0; i < DOS_NUM; i++) 226 | { 227 | uint8_t a = i / 8; 228 | uint8_t b = i % 8; 229 | IEC_BOOL *val = bool_output[a][b]; 230 | if (val) 231 | { 232 | DEBUGF("QX%u.%u = %u\n", a, b, *val); 233 | digitalWrite(DOS_PINS[i], *val); 234 | } 235 | } 236 | } 237 | 238 | static unsigned long timer; 239 | void timer_init() 240 | { 241 | timer = micros(); 242 | } 243 | 244 | void timer_sleep_until(unsigned long long delay_ns) 245 | { 246 | unsigned long now = micros(); 247 | 248 | #ifdef TIMER_DEBUG 249 | float time = now / (float)MILLION; 250 | DEBUGF("wall time = %.6fs\n", time); 251 | #endif 252 | // NOTE: We must use subtraction of timers only because micros() will 253 | // overflow often and subtraction after overflow gives correct results. 254 | // e.g. 0 - 0xFFFF = 1 255 | unsigned long elapsed_ns = now - timer; 256 | long delay_us = (delay_ns - elapsed_ns) / 1000; 257 | if (delay_us < 0) 258 | { 259 | logf("WARNING: timer miss by %ldus", -delay_us); 260 | } 261 | else 262 | { 263 | DEBUGF("timer margin = %ldus\n", delay_us); 264 | delayMicroseconds(delay_us); 265 | } 266 | timer = now; 267 | } 268 | #endif -------------------------------------------------------------------------------- /src/hal.h: -------------------------------------------------------------------------------- 1 | void hw_init(); 2 | void hw_close(); 3 | void update_buffers_in(); 4 | void update_buffers_out(); 5 | 6 | void update_time(); 7 | void timer_init(); 8 | void timer_sleep_until(unsigned long long delay_ns); 9 | 10 | #ifdef HW_LINUX 11 | #include 12 | #define logf printf 13 | #endif 14 | 15 | #ifdef HW_ESP32 16 | #include 17 | #define logf Serial.printf 18 | #endif 19 | 20 | #ifdef DEBUG 21 | #define DEBUGF logf 22 | #else 23 | #define DEBUGF(...) 24 | #endif -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef HW_LINUX 4 | #include // exit(...) 5 | #endif 6 | 7 | #include "iec_types.h" 8 | 9 | #include "hal.h" 10 | 11 | //MatIEC Compiler 12 | void config_run__(unsigned long tick); 13 | void config_init__(void); 14 | 15 | //vars.cpp 16 | void glue_vars(); 17 | 18 | //main.cpp 19 | void sleep_until(struct timespec *ts, int delay); 20 | 21 | //Common task timer 22 | extern unsigned long long common_ticktime__; 23 | 24 | static int tick = 0; 25 | static uint8_t run_openplc = 1; 26 | 27 | extern IEC_TIME __CURRENT_TIME; 28 | 29 | /* 30 | * INITIALIZATION 31 | */ 32 | void setup() 33 | { 34 | logf("PLC initialization\n"); 35 | 36 | logf("\tPLC ...\n"); 37 | config_init__(); 38 | glue_vars(); 39 | logf("\t\t\tOK\n"); 40 | 41 | // HARDWARE INITIALIZATION 42 | logf("\tHW ...\n"); 43 | hw_init(); 44 | update_buffers_in(); 45 | update_buffers_out(); 46 | logf("\t\t\tOK\n"); 47 | 48 | // gets the starting point for the clock 49 | logf("\ttimer ...\n"); 50 | timer_init(); 51 | logf("\t\t\tOK\n"); 52 | 53 | logf("initialization DONE\n"); 54 | } 55 | 56 | /* 57 | * MAIN LOOP 58 | */ 59 | void loop() 60 | { 61 | while (run_openplc) 62 | { 63 | #ifdef DEBUG 64 | float time = __CURRENT_TIME.tv_sec + __CURRENT_TIME.tv_nsec / (float)1000000000; 65 | DEBUGF("\ntick = %d, time = %.3fs\n", tick, time); 66 | #endif 67 | update_buffers_in(); 68 | config_run__(tick++); // execute plc program logic 69 | update_buffers_out(); 70 | update_time(); 71 | timer_sleep_until(common_ticktime__); 72 | } 73 | 74 | // SHUT DOWN 75 | update_buffers_out(); 76 | hw_close(); 77 | logf("PLC shutting down\n"); 78 | #ifdef HW_LINUX 79 | exit(0); 80 | #endif 81 | #ifdef HW_ESP32 82 | for (;;) 83 | { 84 | delay(10000); 85 | } 86 | #endif 87 | } 88 | 89 | #ifdef HW_LINUX 90 | int main(int argc, char **argv) 91 | { 92 | setup(); 93 | loop(); 94 | } 95 | #endif -------------------------------------------------------------------------------- /src/vars.cpp: -------------------------------------------------------------------------------- 1 | #include "iec_std_lib.h" 2 | 3 | //Booleans 4 | IEC_BOOL *bool_input[BUFFER_SIZE][8]; 5 | IEC_BOOL *bool_output[BUFFER_SIZE][8]; 6 | 7 | //Bytes 8 | IEC_BYTE *byte_input[BUFFER_SIZE]; 9 | IEC_BYTE *byte_output[BUFFER_SIZE]; 10 | 11 | //Analog I/O 12 | IEC_UINT *int_input[BUFFER_SIZE]; 13 | IEC_UINT *int_output[BUFFER_SIZE]; 14 | 15 | //Memory 16 | IEC_UINT *int_memory[BUFFER_SIZE]; 17 | IEC_DINT *dint_memory[BUFFER_SIZE]; 18 | IEC_LINT *lint_memory[BUFFER_SIZE]; 19 | 20 | //Special Functions 21 | IEC_LINT *special_functions[BUFFER_SIZE]; 22 | 23 | IEC_TIME __CURRENT_TIME; 24 | 25 | #define __LOCATED_VAR(type, name, ...) type __##name; 26 | #include "LOCATED_VARIABLES.h" 27 | #undef __LOCATED_VAR 28 | #define __LOCATED_VAR(type, name, ...) type *name = &__##name; 29 | #include "LOCATED_VARIABLES.h" 30 | #undef __LOCATED_VAR 31 | 32 | void glue_vars() 33 | { 34 | #define POOL_BOOL_I bool_input 35 | #define POOL_BOOL_Q bool_output 36 | #define POOL_UINT_I int_input 37 | #define INDEX_BOOL(a, b) [a][b] 38 | #define INDEX_UINT(a, b) [a] 39 | 40 | #define __LOCATED_VAR(type, name, inout, type_sym, a, b) POOL_##type##_##inout INDEX_##type(a, b) = name; 41 | #include "LOCATED_VARIABLES.h" 42 | #undef __LOCATED_VAR 43 | } 44 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------