Isso acontece com os controladores de programa (PLCs) em um ambiente CODESYS. Todo mundo que lidou com esse sistema sabe que em qualquer projeto existe uma biblioteca Standard.lib, na qual são implementados cronômetros, gatilhos, contadores e várias outras funções e blocos de funções. Muitas dessas unidades são usadas constantemente em programas de CLP. E a própria biblioteca, como as linguagens de programação CODESYS, é uma modalidade da norma IEC 61131-3, ou seja, projetado para ajudar na programação de tarefas clássicas de CLP.
Uma das características dos programas de CLP é que o ciclo principal do programa deve ser executado sem atrasos significativos, não deve ter ciclos internos com um tempo de saída indefinido ou chamadas síncronas de funções "ponderadas", especialmente no que diz respeito às comunicações em canais lentos. As imagens de entrada e saída do processo são atualizadas apenas nos limites do loop principal, e quanto mais "ficarmos" dentro de uma iteração do loop, menos saberemos sobre o estado real do objeto de controle, eventualmente o watchdog do overflow do loop funcionará. Muitos podem me opor, dizendo que os CLPs modernos são ambíguos, há suporte para interrupções de hardware. Concordo, mas falar sobre esses sistemas não está incluído nos meus planos, mas quero falar sobre (quase, pseudo-escolher) um PLC de uma implementação de tarefa única (sem interrupções) baseada na plataforma de microprocessadores Arduino, na qual existe apenas um loop principal. A propósito, não será fora do lugar dizer que o artigo
PLC CONTROLLINO compatível com Arduino, parte 1, sobre a tentativa de implementar o Arduino em hardware, me promoveu a escrever esta nota. PLC
Algumas palavras sobre o Arduino. Do ponto de vista do programador PLC, o Arduino é um controlador típico com um loop muito rápido ou, inversamente, muito lento (). Não há restrições no tempo de execução do ciclo, e ele pode funcionar um e um número infinito de vezes - de acordo com o plano do programador. Quando o programa é simples e reduz a execução de operações seqüenciais, controladores, sem eventos paralelos, basta alternar operações com ciclos de verificação de condição aninhados sem fim e atrasos síncronos do tipo delay (). As etapas seqüenciais de um programa desse tipo serão executadas literalmente linha por linha, de maneira simples e lógica. Porém, assim que surgir a necessidade de programar operações paralelas, é necessário alterar o paradigma do programa.
Em um sistema de tarefa única, o paralelismo visível só pode ser alcançado por uma varredura seqüencial muito rápida de estados paralelos, sem demorar muito tempo em cada chamada de função ou verificação de condição. Não há problemas com entradas e saídas físicas, as funções funcionam com rapidez suficiente, mas o delay () se torna um freio injustificado. E aqui entram os temporizadores sem bloqueio, exatamente os clássicos na programação de CLP. A conclusão é que eles usam um contador de milissegundos e todas as ações estão vinculadas aos valores desse contador global.
E agora vamos lembrar o mesmo Standard.lib do CODESYS. Apenas implementou os temporizadores sem bloqueio da IEC-s. Tomei como base e portou as funções de temporizadores e gatilhos para o código da biblioteca do Arduino (C ++). I.e. tentou aproximar o Arduino do CLP clássico.
Abaixo, darei uma breve descrição dos blocos de funções portadas (FB) do CODESYS e seus análogos na minha biblioteca
plcStandardLib , todos os diagramas de tempo estão corretos para a nova biblioteca do Arduino. Uma descrição mais detalhada dos blocos de origem pode ser encontrada, por exemplo, na ajuda em russo do CODESYS.
TON - bloco de funções "temporizador com atraso ativado"
TON(IN, PT, Q, ET)
Entradas IN e PT dos tipos BOOL e TIME, respectivamente. As saídas Q e ET são semelhantes aos tipos BOOL e TIME. Enquanto IN for FALSE, a saída Q = FALSE, a saída ET = 0. Assim que IN se tornar VERDADEIRO, a contagem regressiva (em milissegundos) no ET de saída começará com um valor igual a PT. Além disso, o contador não aumenta. Q é VERDADEIRO quando IN é VERDADEIRO e ET é PT, caso contrário, FALSO. Então
assim, a saída Q é definida com um atraso de PT a partir da borda da entrada IN.
No IDE do Arduino:
Opções de anúncio:
TON TON1(); TON TON1(unsigned long PT);
Casos de uso:
Q = TON1.Run(boolean IN); // " " TON1.IN = IN; TON1.Run(); Q = TON1.Q;
TON Diagrama de tempo de operação:
TOF - bloco de funções “temporizador com desligamento atrasado”
TOF(IN, PT, Q, ET)
Entradas IN e PT dos tipos BOOL e TIME, respectivamente. As saídas Q e ET são semelhantes aos tipos BOOL e TIME. Se IN for TRUE, a saída será Q = TRUE e a saída será ET = 0. Assim que IN for FALSE, a contagem regressiva (em milissegundos) da saída ET será iniciada. Quando a duração definida é atingida, a contagem regressiva para. A saída Q é FALSE se IN for FALSE e ET for PT, caso contrário, TRUE. Assim, a saída Q é redefinida com um atraso PT do declínio da entrada IN.
No IDE do Arduino:
Muito semelhante ao TON, para abreviar:
TOF TOF1(unsigned long PT);
Diagrama de tempo do TOF:
TP - bloco de função do temporizador de impulso
TP(IN, PT, Q, ET)
Entradas IN e PT dos tipos BOOL e TIME, respectivamente. As saídas Q e ET são semelhantes aos tipos BOOL e TIME. Enquanto IN é FALSE, saída Q = FALSE, saída ET = 0. Quando IN é alternado para TRUE, a saída Q é definida como TRUE e o temporizador começa a contar (em milissegundos) na saída ET até que a duração especificada por PT seja atingida. Além disso, o contador não aumenta. Assim, a saída Q gera um pulso de duração PT ao longo da borda da entrada IN.
No IDE do Arduino:
Muito semelhante ao TON, para abreviar:
TP TP1(unsigned long PT);
Gráfico de tempo de operação do TP:
R_TRIG - bloco de funções "detector frontal"
O bloco de função R_TRIG gera um pulso na borda ascendente do sinal de entrada. A saída Q é FALSE, desde que a entrada CLK seja FALSE. Assim que o CLK obtém TRUE, Q é definido como TRUE. Na próxima vez que o bloco de funções for chamado, a saída será redefinida para FALSE. Assim, o bloco libera um único impulso a cada transição CLK de FALSE para TRUE.
Exemplo de CODEDESYS em ST:
RTRIGInst : R_TRIG ; RTRIGInst(CLK:= VarBOOL1); VarBOOL2 := RTRIGInst.Q;
No IDE do Arduino:
Anúncio:
R_TRIG R_TRIG1;
Casos de uso:
Q = R_TRIG1.Run(boolean CLK);
F_TRIG - unidade de função "detector de declínio"
O bloco de funções F_TRIG gera um pulso na borda posterior do sinal de entrada.
A saída Q é FALSE até a entrada CLK ser TRUE. Assim que o CLK estiver definido como FALSE, Q será definido como TRUE. Na próxima vez que o bloco de funções for chamado, a saída será redefinida para FALSE. Assim, o bloco libera um único impulso a cada transição CLK de TRUE para FALSE.
No IDE do Arduino:
F_TRIG F_TRIG1; Q = F_TRIG1.Run(boolean CLK);
RS_TRIG - bloco de funções gatilho RS / SR_TRIG - bloco de funções gatilho SR
Comute com o interruptor dominante desligado, gatilho RS:
Q1 = RS (SET, RESET1)
Alterne com dominante ativado:
Q1 = SR (SET1, RESET)
As variáveis de entrada SET e RESET1 são iguais à variável de saída Q1 do tipo BOOL.
No IDE do Arduino:
RS_TRIG RS_TRIG1; Q = RS_TRIG1.Run(boolean SET, boolean RESET); // " "
SR_TRIG SR_TRIG1; Q = SR_TRIG1.Run(boolean SET, boolean RESET); // " "
Código Fonte e Exemplo
plcStandardLib_1.h #ifndef PLCSTANDARDLIB_1_H_ #define PLCSTANDARDLIB_1_H_ #if ARDUINO >= 100 #include <Arduino.h> #else #include <WProgram.h> #endif class TON { public: TON(); TON(unsigned long PT); boolean Run(boolean IN); boolean Q; // boolean IN; // unsigned long PT; // unsigned long ET; // - private: boolean _M; // unsigned long _StartTime; }; class TOF { public: TOF(); TOF(unsigned long PT); boolean Run(boolean IN); boolean Q; // boolean IN; // unsigned long PT; // unsigned long ET; // - private: boolean _M; // unsigned long _StartTime; }; class TP { public: TP(); TP(unsigned long PT); boolean Run(boolean IN); boolean Q; // boolean IN; // unsigned long PT; // unsigned long ET; // - private: boolean _M; // unsigned long _StartTime; }; class R_TRIG // { public: R_TRIG(); boolean Run(boolean CLK); boolean CLK; // boolean Q; // private: boolean _M; // }; class F_TRIG // { public: F_TRIG(); boolean Run(boolean CLK); boolean CLK; // boolean Q; // private: boolean _M; // }; class RS_TRIG // { public: RS_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; // boolean RESET; // boolean Q; // //private: }; class SR_TRIG // { public: SR_TRIG(); boolean Run(); boolean Run(boolean SET, boolean RESET); boolean SET; // boolean RESET; // boolean Q; // //private: }; #endif
plcStandardLib_1.cpp #include "plcStandardLib_1.h" TON::TON() { IN = false; PT = 0; _M = false; _StartTime = 0; Q = false; ET = 0; } TON::TON(unsigned long PT) { IN = false; TON::PT = PT; _M = false; _StartTime = 0; Q = false; ET = 0; } boolean TON::Run(boolean IN) { TON::IN = IN; if (!TON::IN) { Q = false; ET = 0; _M = false; } else { if (!_M) { _M = true; // _StartTime = millis(); // ET = 0; // = 0 } else { if (!Q) ET = millis() - _StartTime; // } if (ET >= PT) Q = true; } return Q; } TOF::TOF() { IN = false; PT = 0; _M = false; _StartTime = 0; Q = false; ET = 0; } TOF::TOF(unsigned long PT) { IN = false; TOF::PT = PT; _M = false; _StartTime = 0; Q = false; ET = 0; } boolean TOF::Run(boolean IN) { TOF::IN = IN; if (TOF::IN) { Q = true; ET = 0; _M = true; } else { if (_M) { _M = false; // _StartTime = millis(); // ET = 0; // = 0 } else { if (Q) ET = millis() - _StartTime; // } if (ET >= PT) Q = false; } return Q; } TP::TP() { IN = false; PT = 0; _M = false; _StartTime = 0; Q = false; ET = 0; } TP::TP(unsigned long PT) { IN = false; TP::PT = PT; _M = false; _StartTime = 0; Q = false; ET = 0; } boolean TP::Run(boolean IN) { TP::IN = IN; if (!_M) { if (TP::IN) { _M = true; // _StartTime = millis(); if (ET < PT) Q = true; } } else { if (Q) { ET = millis() - _StartTime; // if (ET >= PT) Q = false; } else { if (!TP::IN) { _M = false; ET = 0; } } } return Q; } R_TRIG::R_TRIG() { CLK = false; _M = false; Q = false; } boolean R_TRIG::Run(boolean CLK) { R_TRIG::CLK = CLK; Q = R_TRIG::CLK && !_M; _M = R_TRIG::CLK; return Q; } F_TRIG::F_TRIG() { CLK = false; _M = true; Q = false; } boolean F_TRIG::Run(boolean CLK) { F_TRIG::CLK = CLK; Q = !F_TRIG::CLK && !_M; _M = !F_TRIG::CLK; return Q; } RS_TRIG::RS_TRIG() { SET = false; RESET = false; Q = false; } boolean RS_TRIG::Run(boolean SET, boolean RESET) { RS_TRIG::SET = SET; RS_TRIG::RESET = RESET; Q = !RESET and (SET or Q); return Q; } boolean RS_TRIG::Run() { Q = !RESET and (SET or Q); return Q; } SR_TRIG::SR_TRIG() { SET = false; RESET = false; Q = false; } boolean SR_TRIG::Run(boolean SET, boolean RESET) { SR_TRIG::SET = SET; SR_TRIG::RESET = RESET; Q = SET or (!RESET and Q); return Q; } boolean SR_TRIG::Run() { Q = SET or (!RESET and Q); return Q; }
plcStandardLib_1_example.ino #include "plcStandardLib_1.h" #define LED 13 #define ButtonIn 7 TON TON1(500);
Por exemplo, para filtrar a conversa dos contatos do botão (ao abrir também!), Este código é suficiente:
FiltredButtonIn = TON1.Run(digitalRead(ButtonIn))
Em conclusão: é assim que o CODESYS se parece com um gerador de pulsos baseado em uma cadeia de temporizadores TON e TP. No início, o TON é coberto por feedback com inversão e, a partir dele, é gerado um único gerador de pulsos, que inicia a operação do gerador de pulsos TP. No meu exemplo do Arduino, um análogo disso é assim:
digitalWrite(LED, TP2.Run(TON2.Run(!TON2.Q)))