Всем доброго времени суток. Я работаю на большом предприятии, занимаюсь автоматизацией производства. Очень захотелось с Вами поделиться наработками, которые мы используем в повседневной трудовой жизни. Не судите строго, это моя первая статья на большую аудиторию...

Если мы говорим о промышленной автоматизации, то понимаем, что программирование будет связано с языками стандарта МЭК 61131-3.

На основе данного стандарта в промышленные контроллеры устанавливают программное обеспечение от:

  • ISaGRAF, Rockwell Automation (Монреаль, Канада)

  • CoDeSYS, CODESYS GmbH (Кемптен, Германия)

  • ISPSoft, Delta Electronics, Inc (Тайбэй, Тайвань)

А как же быть, когда тебе не хочется зависеть от стороннего программного обеспечения и иметь переносимый код, который с минимальными трудозатратами можно развернуть на множестве устройств, и даже поставить на персональном компьютере?

Я нашел контроллер Icp-Das LP-8x21 на Ubuntu 12, пару модулей ввода/вывода MDS DIO-16BD и AIO-4 от КонтрАвт для тестов. Вот уже и есть с чем экспериментировать.

Тестовая сборка
Тестовая сборка

Для связи контроллера и модулей ввода/вывода будем использовать один из наиболее распространенных стандартов физического уровня связи - RS-485. Данные модули используют протокол Modbus.

Для создания исполняемых файлов под данный контроллер необходимо для его процессора установить компилятор:

sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

Устанавливаем файлы для сборки и исходники libmodbus-dev:

sudo apt install libmodbus-dev
apt source libmodbus-dev

Переходим в созданную папку с исходниками и собираем библиотеку под компилятор для arm-linux-gnueabihf:

./autogen.sh
./configure --host=arm-linux-gnueabihf --enable-shared --enable-static
make

В папке ./src/.libs/  вы найдете все собранные библиотеки чтобы подложить в проект. Нам потребуется файл libmodbus.a.

Из документации для модулей ввода/вывода находим регистры для чтения. Для DIO-16BD это регистр 258 для входа, а для AIO-4 мы прочитаем 4 аналоговых входа с адреса 207. Т.к. там располагаются 4 значения типа float, то будем читать сразу 8 регистров.  Для пробы соберем на C++ небольшой проект:

Файл main.cpp:

#include <modbus/modbus.h>
#include <iostream>
#include <errno.h>
#include "util.h"

using namespace std;

bool _stop=false;

void handle_signal(int i) {
   printf("[MAIN] Terminating\n"); 
   _stop=true;
}

int main() {
   signal(SIGINT, handle_signal);
   signal(SIGTERM, handle_signal);
   cout<<"Start..."<<endl;
   uint16_t buffer[8];
   modbus_t *ctx=modbus_new_rtu("/dev/ttyS2",115200,'N',8,1);
   if (modbus_connect(ctx) == -1)
       cout<<"Connection failed: "<<modbus_strerror(errno)<<endl;
   modbus_set_response_timeout(ctx, 1,0);
   while(!_stop) {
       modbus_set_slave(ctx,1);
       int rc=modbus_read_registers(ctx,258,1,buffer);
       if (rc == -1)
           cout<<"#1 MDS_DIO_16BD::Read() "<<modbus_strerror(errno)<<endl;
       else
           cout<<"#1 "<<buffer[0]<<endl;
       modbus_set_slave(ctx,2);
       nsleep(10);
       rc=modbus_read_registers(ctx,258,1,buffer);
       if (rc == -1)
           cout<<"#2 MDS_DIO_16BD::Read() "<<modbus_strerror(errno)<<endl;
       else
           cout<<"#2 "<<buffer[0]<<endl;
       modbus_set_slave(ctx,3);
       nsleep(10);
       rc = modbus_read_input_registers(ctx, 207, 8, buffer);
       if (rc == -1)
           cout<<"#3 MDS_AIO_4::Read() "<<modbus_strerror(errno)<<endl;
       else
           cout<<"#3 "<<modbus_get_float_badc(buffer)<<" "<<modbus_get_float_badc(buffer+2)<<" "<<modbus_get_float_badc(buffer+4)<<" "<<modbus_get_float_badc(buffer+6)<<endl;
       nsleep(10);
   }  
   modbus_close(ctx);
   modbus_free(ctx);
   cout<<"Stop..."<<endl;
}

Файл util.cpp:

int nsleep(long miliseconds) {
   struct timespec req, rem;
   if(miliseconds > 999) {
       req.tv_sec = (int)(miliseconds / 1000);
       req.tv_nsec = (miliseconds - ((long)req.tv_sec * 1000)) * 1000000;
   }
   else {
       req.tv_sec = 0;
       req.tv_nsec = miliseconds * 1000000;
   }
   return nanosleep(&req , &rem);
}

Собираем и отправляем исполняемый файл при помощи следующих команд:

arm-linux-gnueabihf-g++ -std=c++11 -I ./libmodbus -L. -o ./main ./*.cpp -lmodbus
sshpass -p "icpdas" scp main root@192.168.0.2:/root/

Все собралось и отправилось на ПЛК. Если все у нас собрано и сконфигурировано правильно, то в терминале мы получим такой результат:

#1 0
#2 0
#3 4.000000 4.000001 3.999998 4.000000

Отлично все работает, в цикле отображаются нужные данные. Вот мы и научились получать данные с “поля”.

Если сейчас при таких таймингах посмотреть загрузку процессора на ПЛК, то мы увидим не очень красивую картину, что 3 модуля загружают контроллер на 10%. В боевых условиях это очень и очень много, т.к. сейчас есть рабочие проекты где более десятка линий с количеством модулей 7-10 штук.

Посмотрим данные о загрузке системы. Вызовем команду top:

top - 10:47:15 up  5:28,  2 users,  load average: 1.00, 1.01, 1.03
Tasks: 108 total,   1 running, 107 sleeping,   0 stopped,   0 zombie
Cpu(s):  1.3%us, 14.4%sy,  0.0%ni, 84.0%id,  0.0%wa,  0.0%hi,  0.3%si,  0.0%st
Mem:	506968k total,	95152k used,   411816k free,    	0k buffers
Swap:    	0k total,    	0k used,    	0k free,	41316k cached

  PID USER  	PR  NI  VIRT  RES  SHR S %CPU %MEM	TIME+  COMMAND
 2079 root  	20   0  2660 1004  860 S  9.5  0.2 0:09.09 main…

Для уменьшения нагрузки на систему, можно увеличить задержку после опроса всех модулей ввода/вывода. Если установить задержку в 300 мс., то загрузка процессора составить 1.6% на данную задачу.

Как же нам улучшить и унифицировать работу с большим количеством разных устройств? Надо пойти в сторону паттерна строителя. Создадим базовый класс для Modbus устройств:

Файл ModbusModule.h:

class ModbusModule
{
public:
    ModbusModule(modbus_t *ctx, int addr);
    virtual ~ModbusModule();
    virtual void Read() {};
    virtual void Write() {};
    bool isError=true,isChanged=true;
    int error=0;
protected:
    bool oldIsError=false;
    int rc,olderror;
protected:
    void Set();
    modbus_t *ctx;
    int address;
};

Файл ModbusModule.cpp:

ModbusModule::ModbusModule(modbus_t *ctx, int addr) :
    ctx{ctx},address{addr} { }

ModbusModule::~ModbusModule() { }

void ModbusModule::Set() {
    modbus_flush(ctx);
    modbus_set_slave(ctx,address);
    nsleep(5);
}

А теперь давайте опишем само устройство на примере MDS DIO-16BD. Концепция заключается в том, чтобы передавать адреса переменных в которые нам надо получать значения или записывать данные в устройство. И другие потоки должны иметь доступ к этим переменным.

Файл MDS-DIO-16BD.h:

class MDS_DIO_16BD : public ModbusModule
{
public:
    MDS_DIO_16BD(modbus_t *ctx, int addr,
        volatile bool *rB1=NULL,volatile bool *rB2=NULL,volatile bool *rB3=NULL,volatile bool *rB4=NULL,
        volatile bool *rB5=NULL,volatile bool *rB6=NULL,volatile bool *rB7=NULL,volatile bool *rB8=NULL,
        volatile bool *rB9=NULL,volatile bool *rB10=NULL,volatile bool *rB11=NULL,volatile bool *rB12=NULL,
        volatile bool *rB13=NULL,volatile bool *rB14=NULL,volatile bool *rB15=NULL,volatile bool *rB16=NULL,
        volatile bool *wB1=NULL,volatile bool *wB2=NULL,volatile bool *wB3=NULL,volatile bool *wB4=NULL,
        volatile bool *wB5=NULL,volatile bool *wB6=NULL,volatile bool *wB7=NULL,volatile bool *wB8=NULL,
        volatile bool *wB9=NULL,volatile bool *wB10=NULL,volatile bool *wB11=NULL,volatile bool *wB12=NULL,
        volatile bool *wB13=NULL,volatile bool *wB14=NULL,volatile bool *wB15=NULL,volatile bool *wB16=NULL,
        volatile uint16_t *rC1=NULL,volatile uint16_t *rC2=NULL,volatile uint16_t *rC3=NULL,volatile uint16_t *rC4=NULL,
        volatile bool *wReset1=NULL,volatile bool *wReset2=NULL,volatile bool *wReset3=NULL,volatile bool *wReset4=NULL);
    ~MDS_DIO_16BD() override;
    void Write() override;
    void Read() override;
    void setRev(int index);


private:
    uint16_t *buffer;
    volatile bool *rB1,*rB2,*rB3,*rB4,*rB5,*rB6,*rB7,*rB8,*rB9,*rB10,*rB11,*rB12,*rB13,*rB14,*rB15,*rB16,*wB1,*wB2,*wB3,*wB4,*wB5,*wB6,*wB7,*wB8,*wB9,*wB10,*wB11,*wB12,*wB13,*wB14,*wB15,*wB16;
    volatile uint16_t *rC1,*rC2,*rC3,*rC4;
    volatile bool *wReset1,*wReset2,*wReset3,*wReset4;
    bool rev[16];
};

Файл MDS-DIO-16BD.cpp:

MDS_DIO_16BD::MDS_DIO_16BD(modbus_t *ctx, int addr,
    volatile bool *rB1,volatile bool *rB2,volatile bool *rB3,volatile bool *rB4,
    volatile bool *rB5,volatile bool *rB6,volatile bool *rB7,volatile bool *rB8,
    volatile bool *rB9,volatile bool *rB10,volatile bool *rB11,volatile bool *rB12,
    volatile bool *rB13,volatile bool *rB14,volatile bool *rB15,volatile bool *rB16,
    volatile bool *wB1,volatile bool *wB2,volatile bool *wB3,volatile bool *wB4,
    volatile bool *wB5,volatile bool *wB6,volatile bool *wB7,volatile bool *wB8,
    volatile bool *wB9,volatile bool *wB10,volatile bool *wB11,volatile bool *wB12,
    volatile bool *wB13,volatile bool *wB14,volatile bool *wB15,volatile bool *wB16,
    volatile uint16_t *rC1,volatile uint16_t *rC2,volatile uint16_t *rC3,volatile uint16_t *rC4,
    volatile bool *wReset1,volatile bool *wReset2,volatile bool *wReset3,volatile bool *wReset4)
    : rB1{rB1},rB2{rB2},rB3{rB3},rB4{rB4},rB5{rB5},rB6{rB6},rB7{rB7},rB8{rB8},
    rB9{rB9},rB10{rB10},rB11{rB11},rB12{rB12},rB13{rB13},rB14{rB14},rB15{rB15},rB16{rB16},
    wB1{wB1},wB2{wB2},wB3{wB3},wB4{wB4},wB5{wB5},wB6{wB6},wB7{wB7},wB8{wB8},
    wB9{wB9},wB10{wB10},wB11{wB11},wB12{wB12},wB13{wB13},wB14{wB14},wB15{wB15},wB16{wB16},
    rC1{rC1},rC2{rC2},rC3{rC3},rC4{rC4},
    wReset1{wReset1},wReset2{wReset2},wReset3{wReset3},wReset4{wReset4},ModbusModule(ctx,addr) {
    buffer = new uint16_t[16];
    memset(buffer, 0, 16 * sizeof(uint16_t));
    for(int i=0;i<16;i++) rev[i]=false;
}
MDS_DIO_16BD::~MDS_DIO_16BD() {
    delete[] buffer;
}

void MDS_DIO_16BD::Write() {
    uint16_t temp=0;
    if(wB1!=NULL||wB2!=NULL||wB3!=NULL||wB4!=NULL||wB5!=NULL||wB6!=NULL||wB7!=NULL||wB8!=NULL||
        wB9!=NULL||wB10!=NULL||wB11!=NULL||wB12!=NULL||wB13!=NULL||wB14!=NULL||wB15!=NULL||wB16!=NULL) {
        temp=((wB1==NULL)?0:rev[0]?!(*wB1<<0):(*wB1<<0))|
            ((wB2==NULL)?0:rev[1]?!(*wB2<<1):(*wB2<<1))|
            ((wB3==NULL)?0:rev[2]?!(*wB3<<2):(*wB3<<2))|
            ((wB4==NULL)?0:rev[3]?!(*wB4<<3):(*wB4<<3))|
            ((wB5==NULL)?0:rev[4]?!(*wB5<<4):(*wB5<<4))|
            ((wB6==NULL)?0:rev[5]?!(*wB6<<5):(*wB6<<5))|
            ((wB7==NULL)?0:rev[6]?!(*wB7<<6):(*wB7<<6))|
            ((wB8==NULL)?0:rev[7]?!(*wB8<<7):(*wB8<<7))|
            ((wB9==NULL)?0:rev[8]?!(*wB9<<8):(*wB9<<8))|
            ((wB10==NULL)?0:rev[9]?!(*wB10<<9):(*wB10<<9))|
            ((wB11==NULL)?0:rev[10]?!(*wB11<<10):(*wB11<<10))|
            ((wB12==NULL)?0:rev[11]?!(*wB12<<11):(*wB12<<11))|
            ((wB13==NULL)?0:rev[12]?!(*wB13<<12):(*wB13<<12))|
            ((wB14==NULL)?0:rev[13]?!(*wB14<<13):(*wB14<<13))|
            ((wB15==NULL)?0:rev[14]?!(*wB15<<14):(*wB15<<14))|
            ((wB16==NULL)?0:rev[15]?!(*wB16<<15):(*wB16<<15));
        buffer[1]=temp;
        Set();
        rc = modbus_write_registers(ctx, 267, 1, buffer+1);
        isError=(rc==-1);
        if (rc == -1)
            fprintf(stderr, "#%d MDS_DIO_16BD::Write(... %s\n", address, modbus_strerror(errno));
    }
    if(wReset1!=NULL||wReset2!=NULL||wReset3!=NULL||wReset4!=NULL) {
        temp=((wReset1==NULL)?0:(*wReset1<<0))|
            ((wReset2==NULL)?0:(*wReset2<<1))|
            ((wReset3==NULL)?0:(*wReset3<<2))|
            ((wReset4==NULL)?0:(*wReset4<<3));
        rc = modbus_write_register(ctx, 276, temp);
        isError&=(rc==-1);
        if (rc == -1)
            fprintf(stderr, "#%d MDS_DIO_16BD::Write(... %s\n",address, modbus_strerror(errno));
    }
}

void MDS_DIO_16BD::Read() {
    if(rB1!=NULL||rB2!=NULL||rB3!=NULL||rB4!=NULL||rB5!=NULL||rB6!=NULL||rB7!=NULL||rB8!=NULL||
        rB9!=NULL||rB10!=NULL||rB11!=NULL||rB12!=NULL||rB13!=NULL||rB14!=NULL||rB15!=NULL||rB16!=NULL) {
        Set();
        rc = modbus_read_registers(ctx, 258, 1, buffer);
        isError=(rc==-1);
        if (rc == -1)
            fprintf(stderr, "#%d MDS_DIO_16BD::Read() %s\n",address, modbus_strerror(errno));
        else {
            if(rB1!=NULL) *rB1=(buffer[0]>>0)&1;
            if(rB2!=NULL) *rB2=(buffer[0]>>1)&1;
            if(rB3!=NULL) *rB3=(buffer[0]>>2)&1;
            if(rB4!=NULL) *rB4=(buffer[0]>>3)&1;
            if(rB5!=NULL) *rB5=(buffer[0]>>4)&1;
            if(rB6!=NULL) *rB6=(buffer[0]>>5)&1;
            if(rB7!=NULL) *rB7=(buffer[0]>>6)&1;
            if(rB8!=NULL) *rB8=(buffer[0]>>7)&1;
            if(rB9!=NULL) *rB9=(buffer[0]>>8)&1;
            if(rB10!=NULL) *rB10=(buffer[0]>>9)&1;
            if(rB11!=NULL) *rB11=(buffer[0]>>10)&1;
            if(rB12!=NULL) *rB12=(buffer[0]>>11)&1;
            if(rB13!=NULL) *rB13=(buffer[0]>>12)&1;
            if(rB14!=NULL) *rB14=(buffer[0]>>13)&1;
            if(rB15!=NULL) *rB15=(buffer[0]>>14)&1;
            if(rB16!=NULL) *rB16=(buffer[0]>>15)&1;
        }
    }
    if(rC1!=NULL||rC2!=NULL||rC3!=NULL||rC4!=NULL) {
        rc = modbus_read_registers(ctx, 278, 4, buffer);
        isError&=(rc==-1);
        if (rc == -1)
            fprintf(stderr, "#%d MDS_DIO_16BD::Read() %s\n",address, modbus_strerror(errno));
        else {
            if(rC1!=NULL) *rC1=buffer[0];
            if(rC2!=NULL) *rC2=buffer[1];
            if(rC3!=NULL) *rC3=buffer[2];
            if(rC4!=NULL) *rC4=buffer[3];
        }
    }
}

void MDS_DIO_16BD::setRev(int index) {
    if(index<0 || index>15) return;
    rev[index]=!rev[index];
}

Порождающий паттерн будем использовать при создании класса опроса устройств на одной шине RS-485.

Файл ModbusLine.h:

class ModbusLine {
public:
    ModbusLine(modbus_t *ctx, long sleepTime=300);
    ~ModbusLine();
    MDS_DIO_16BD* addMDS_DIO_16BD(int address,
        volatile bool *rB1=NULL,volatile bool *rB2=NULL,volatile bool *rB3=NULL,volatile bool *rB4=NULL,
        volatile bool *rB5=NULL,volatile bool *rB6=NULL,volatile bool *rB7=NULL,volatile bool *rB8=NULL,
        volatile bool *rB9=NULL,volatile bool *rB10=NULL,volatile bool *rB11=NULL,volatile bool *rB12=NULL,
        volatile bool *rB13=NULL,volatile bool *rB14=NULL,volatile bool *rB15=NULL,volatile bool *rB16=NULL,
        volatile bool *wB1=NULL,volatile bool *wB2=NULL,volatile bool *wB3=NULL,volatile bool *wB4=NULL,
        volatile bool *wB5=NULL,volatile bool *wB6=NULL,volatile bool *wB7=NULL,volatile bool *wB8=NULL,
        volatile bool *wB9=NULL,volatile bool *wB10=NULL,volatile bool *wB11=NULL,volatile bool *wB12=NULL,
        volatile bool *wB13=NULL,volatile bool *wB14=NULL,volatile bool *wB15=NULL,volatile bool *wB16=NULL,
        volatile uint16_t *rC1=NULL,volatile uint16_t *rC2=NULL,volatile uint16_t *rC3=NULL,volatile uint16_t *rC4=NULL,
        volatile bool *wReset1=NULL,volatile bool *wReset2=NULL,volatile bool *wReset3=NULL,volatile bool *wReset4=NULL);
protected:
    void Thread();
    modbus_t *ctx;
    long sleepTime;
    std::thread _t;
    bool _stop=false;
    std::vector<ModbusModule*> devices;
};

Файл ModbusLine.cpp:

ModbusLine::ModbusLine(modbus_t *ctx, long sleepTime) : ctx{ctx},sleepTime{sleepTime} {
    _t=std::thread(&ModbusLine::Thread,this);
    printf("ModbusLine create\n");
}
ModbusLine::~ModbusLine() {
    _stop=true;
    _t.join();
    for(auto& dev:devices) delete dev;
    devices.clear();
    modbus_close(ctx);
    modbus_free(ctx);
    printf("ModbusLine delete\n");
}

void ModbusLine::Thread() {
    int isOpen=-1;
    auto olderrno=errno;
    while(!_stop) {
        if (isOpen==-1) {
            cout<<"Try open port..."<<endl;
            if ((isOpen = modbus_connect(ctx)) == -1) {
                if(errno!=olderrno) {
                    fprintf(stderr, "Connection failed: %s\n",modbus_strerror(errno));
                }
            }
            modbus_set_error_recovery(ctx,  (modbus_error_recovery_mode)6);
            modbus_set_response_timeout(ctx, 0,500);
            olderrno=errno;
            nsleep(1000);
            continue;
        }
        bool err=true;
        for(auto& dev:devices) {
            dev->Read();
            if(dev->isError==false) err=false;
            nsleep(10);
            dev->Write();
            if(dev->isError==false) err=false;
            nsleep(10);
        }
        if(err) {
            isOpen=-1;
        }
        nsleep(sleepTime);
    }
}

MDS_DIO_16BD* ModbusLine::addMDS_DIO_16BD(int address,
    volatile bool *rB1,volatile bool *rB2,volatile bool *rB3,volatile bool *rB4,
    volatile bool *rB5,volatile bool *rB6,volatile bool *rB7,volatile bool *rB8,
    volatile bool *rB9,volatile bool *rB10,volatile bool *rB11,volatile bool *rB12,
    volatile bool *rB13,volatile bool *rB14,volatile bool *rB15,volatile bool *rB16,
    volatile bool *wB1,volatile bool *wB2,volatile bool *wB3,volatile bool *wB4,
    volatile bool *wB5,volatile bool *wB6,volatile bool *wB7,volatile bool *wB8,
    volatile bool *wB9,volatile bool *wB10,volatile bool *wB11,volatile bool *wB12,
    volatile bool *wB13,volatile bool *wB14,volatile bool *wB15,volatile bool *wB16,
    volatile uint16_t *rC1,volatile uint16_t *rC2,volatile uint16_t *rC3,volatile uint16_t *rC4,
    volatile bool *wReset1,volatile bool *wReset2,volatile bool *wReset3,volatile bool *wReset4) {
    auto out = new MDS_DIO_16BD(ctx,address,rB1,rB2,rB3,rB4,
        rB5,rB6,rB7,rB8,rB9,rB10,rB11,rB12,rB13,rB14,rB15,rB16,
        wB1,wB2,wB3,wB4,wB5,wB6,wB7,wB8,wB9,wB10,wB11,wB12,wB13,wB14,wB15,wB16,
        rC1,rC2,rC3,rC4,wReset1,wReset2,wReset3,wReset4);
    devices.push_back((ModbusModule*)out);
    return out;
}

Получается, что описание шины RS-485 будет выглядеть следующим образом:

volatile bool b1=false,b2=false,b3=false;
ModbusLine line1(modbus_new_rtu("/dev/ttyS2", 115200, 'N', 8, 1));
line1.addMDS_DIO_16BD(1,&b1,&b2,&b3);

У нас на каждую линию будет создаваться отдельный поток и основное приложение не будет зависеть от опроса устройств. В переменных всегда будут находится актуальные параметры, за это у нас отвечает ключевое слово volatile которое информирует компилятор о том, что значение переменной может меняться извне и компилятор не будет кэшировать эту переменную.

В следующей статье мы посмотрим использование MQTT для обмена между приложениями на ПЛК и мнемосхемой.

Комментарии (34)


  1. Sfinx88
    21.09.2023 11:24
    +1

    Вы и вправду применяете такое в реальных проектах?


    1. Squarc Автор
      21.09.2023 11:24
      +1

      Да,мы используем сейчас подход в разработке проектов на С++


    1. segment
      21.09.2023 11:24

      Ага, там и не такое можно встретить


    1. me21
      21.09.2023 11:24

      Я писал софт для предыдущих версий контроллеров IcpDas. Там DOS-совместимая ОС и разработка велась в Borland C++ 3.1 лохматых годов. В DosBox поверх Windows 10, а код лежал в Git-репозитории.


      1. Squarc Автор
        21.09.2023 11:24

        Да, тоже с таким сталкивались. Было давно. Сейчас работаем только с LP-8x21, MOXA-2112-UC и MOXA 4533-LX


  1. Mitya78
    21.09.2023 11:24

    Если мы говорим о промышленной автоматизации, то понимаем, что программирование будет связано с языками стандарта МЭК 61131-3.

    На основе данного стандарта в промышленные контроллеры устанавливают программное обеспечение от:

    • ISaGRAF, Rockwell Automation (Монреаль, Канада)

    • CoDeSYS, CODESYS GmbH (Кемптен, Германия)

    • ISPSoft, Delta Electronics, Inc (Тайбэй, Тайвань)

    Как-то странно звучит этот отрывок.

    Я, к примеру, в большинстве проектов и CoDeSYS не использую, не говоря про остальные два.


    1. Squarc Автор
      21.09.2023 11:24

      А что вы используете?


      1. Mitya78
        21.09.2023 11:24

        Siemens Simatic.

        Для проектов покрупнее S7-1500, для помельче 1200.

        Программирую в TIA Portal.


        1. Squarc Автор
          21.09.2023 11:24

          И как сейчас обстоят дела с Сименсом? Все по обходным путям?


          1. Mitya78
            21.09.2023 11:24

            Я уже год как не в России.

            Иногда про проекты спрашивают, как понимаю что S7-1200 без проблем, 1500-е посложнее. Но судя по тому что на моей прошлой работе дела не остановились, а они только на S7-1500 работали, то достают.

            Да и тут, так как выходцы из РФ, то спрашивают, бумаги просят предоставить, что никак и никуда.


  1. sav13
    21.09.2023 11:24

    А как вы обеспечиваете выполнение задач, то биш процессов, в реальном времени?


    1. Mitya78
      21.09.2023 11:24

      А что вы подразумеваете под реальным временем?


      1. sav13
        21.09.2023 11:24

        Реальное время, это когда задача выполняется в строго установленное время и не может пропустить какое либо событие.

        В RTOS я как то могу управлять всем этим. А вот в LINUX?

        Приведу пример: Есть у меня счетчик импульсов. Аппаратное прерывание ведь мне не доступно, значит это пользовательский процесс.
        А в это время по UART/RS485 пошла передача. Пропустит он импульсы?

        Да мало ли задач, где требуется жесткое реальное время - управление по прохождению фазой нуля, работа с энкодерами, выдача и расшифровка ШИМ сигнала.


        1. Mitya78
          21.09.2023 11:24

          У меня сейчас на проекте контроллер S7-1516, управляет приточными установками, чиллером, фанкойлами, частотниками по Modbus...

          Среднее время цикла 10 микросекунд.

          Есть и аппаратные прерывания.


          1. sav13
            21.09.2023 11:24

            А причем здесь S7 и LINUX ?


            1. Mitya78
              21.09.2023 11:24

              К тому что и в промконтроллерах никакого реального времени нет.

              Да, я по времени цикла ошибся, 10 миллисекунд.


              1. Siemargl
                21.09.2023 11:24

                может стоит сначала почитать доку по симатику? там ос рв


                1. Mitya78
                  21.09.2023 11:24

                  Тогда я не понимаю что такое система реального времени.

                  Программа в контроллере обрабатывается в цикле, время цикла негарантированно.

                  Есть аппаратные прерывания, есть циклические.


        1. Squarc Автор
          21.09.2023 11:24
          +1

          Для этого в модулях дискретных есть счетчики. ПЛК не используется для такого подсчета импульсов


    1. Squarc Автор
      21.09.2023 11:24

      Реального времени нет... Есть только иллюзия его существования :)


      1. sav13
        21.09.2023 11:24

        Да что вы говорите?

        RTOS, FreRTOS, MBED, QNX и даже LinuxRT вполне обеспечиваю жесткое реальное время


        1. Jury_78
          21.09.2023 11:24

          Думаю, имелось ввиду, что задержка будет всегда. Где то больше, где то меньше.


          1. sav13
            21.09.2023 11:24

            В жестком реальном времени если время выполнения функции превысило установленное, то это считается сбоем системы.

            Запланированное время может быть минуту, а может быть микросекунду. Суть от этого не меняется. Система реального времени должна обеспечить выполнение.

            В микроконтроллерах за временем выполнения следят таймеры с аппаратными прерываниями. Не уложился - все, ошибка всей системы. Потому что в таких системах неопределенность в управлении хуже всего.

            LINUX - система разделения ресурсов и не гарантирует времени выполнения задачи. Кроме того в любой момент времени диспетчер процессов может приостановить выполнения процессу и дать ресурс другому.

            Ну а дальше все зависит от ваших задач. Если для вас не критично время выполнения той или иной задачи или время цикла настолько большое, что диспетчер процессов несколько раз сделает свой цикл, то тогда да.


            1. Jury_78
              21.09.2023 11:24
              -1

              В микроконтроллерах за временем выполнения следят таймеры с аппаратными прерываниями.

              "Реальное время" - это ж всего лишь термин. За которым нет реального времени как такового. Даже аппаратное прерывание это не реальное время. Если для вас не критично выполнение процедуры за 1 сек, то это тоже можно назвать реальное время. Если и 1 нс недостаточно, то и это уже не назовешь реальным временем.


              1. sav13
                21.09.2023 11:24
                +1

                Давайте говорить в рамках отраслевого ГОСТ Р МЭК 61069-4 2017. Там есть однозначное определение жесткого и мягкого реального времени.

                А все эти рассуждения, что есть время, что есть мгновения оставим философам )))


                1. Jury_78
                  21.09.2023 11:24

                  Да ради бога... :) При обсуждении первым делом обычно договариваются от терминах.


                  1. Siemargl
                    21.09.2023 11:24

                    Достаточно иметь образование.

                    https://ru.wikipedia.org/wiki/Система_реального_времени


        1. Squarc Автор
          21.09.2023 11:24

          Если говорить о жестких таймингах, тогда мы упираемся в объем выполняемых операций на такт. Это тоже накладывает немалые ограничения на возможности проекта


          1. sav13
            21.09.2023 11:24
            +2

            Мы упираемся в саму систему. Либо она гарантирует выполнения в наших таймингах, либо нет.

            А реализация Modbus в данном случае это совсем не реальное время, так как данные берутся не из физического канала, а из буфера UART. Или от какого-нибудь другого модуля ядра, но тоже из буфера.


        1. Borjomy
          21.09.2023 11:24

          Основная проблема не уложиться в отведённое время (это, в конце концов, решается увеличением процессорной мощности), а джиттинг.


    1. NutsUnderline
      21.09.2023 11:24

      а оно вообще тут надо, реальное время то? я вот тут вижу бросание пакетами а не каконить ногодрыг, PWM. Для чего нибуть совсем быстро меняющегося это все равно не подойдет


  1. glazko
    21.09.2023 11:24

    А как же быть, когда тебе не хочется зависеть от стороннего программного обеспечения и иметь переносимый код, который с минимальными трудозатратами можно развернуть на множестве устройств, и даже поставить на персональном компьютере?

    Запускайте стартап, например "PLC++", открывайте наработки (особенно интересно посмотреть на вашу реализацию библиотеки Process Object) и уверен к вам присоединятся


    1. Mitya78
      21.09.2023 11:24

      https://www.softwaredefinedautomation.io/

      Я с этими ребятами в прошлом году общался, тогда они были ещё немецкой компанией, насчёт их идеи о софтварном ПЛК.

      Видать, в гору дела пошли.


  1. NutsUnderline
    21.09.2023 11:24

    не хочется зависеть от стороннего программного обеспечения и иметь переносимый код, который с минимальными трудозатратами можно развернуть на множестве устройств

    <картинка с джедаями> разве не для этого были созданы эти самые МЭК 61131-3 :) :) :)