Mesmo para as séries S7-300 e S7-400, na Etapa 7, as versões clássicas dos cronômetros oferecidos ao desenvolvedor eram suficientes - esses são os cronômetros IEC padrão, implementados como blocos de funções e os cronômetros S5 (que, a propósito, ainda existem para a série S7- 1500). No entanto, em alguns casos, o desenvolvedor não usou ferramentas padrão e implementou seus próprios temporizadores, na maioria das vezes na forma de funções. Tais funções-temporizadores eram necessárias com uma abordagem de “TI” para programação, na qual operavam não com instâncias separadas dos blocos funcionais de equipamentos tecnológicos, com a ligação correspondente de entradas e saídas, mas com matrizes de estruturas. Por exemplo, uma matriz de uma estrutura de tipo de entrada discreta. Ou uma matriz de uma estrutura agregada. Essa abordagem de programação tem o direito de existir, pois permite salvar seriamente a memória de trabalho da CPU, mas,por outro lado, dificulta a leitura do código do programa. Um programador de terceiros e com uma simples aparência de um programa no LAD não consegue descobrir imediatamente, mas montes de índices, matrizes e funções para processá-los estão fora de questão; aqui, sem documentação para o software (e sem meio litro, é claro), em nenhum lugar.Essas matrizes de estruturas eram normalmente processadas em funções. Em princípio, nada impedia o processamento de blocos funcionais, mas sempre havia uma pergunta importante - como trabalhar com temporizadores nesses casos? Os temporizadores padrão assumem um número (S5) ou uma instância de um bloco de funções (IEC). Isso, lembro a você, trata do processamento de matrizes de estruturas para CLPs Simatic clássicos e de “torcer” os números do temporizador nessas estruturas e, mais ainda, as instâncias são difíceis ou simplesmente impossíveis.Por esse motivo, criamos nossa própria funcionalidade de timer como uma função. Em princípio, para a operação de qualquer timer, você precisa saber apenas algumas coisas - o estado da entrada, a configuração da hora e quanto tempo se passou desde a ativação.Para as séries 300 e 400, havia duas maneiras de determinar esse tempo. O primeiro é examinar o tempo de execução do OB1 principal (existe uma variável correspondente no próprio OB1) ou OBs cíclicos e aumentar o acumulador de tempo interno a cada chamada do temporizador, desde que a "verdade" seja inserida. Não é uma boa opção, pois esse tempo é diferente para OB1 e OBs cíclicos. O segundo método é a função do sistema TIME_TCK, que, a cada chamada, retornava um único valor - o contador interno de milissegundos do processador central.
Assim, para um timer do tipo TON (em atraso), o algoritmo de operação foi o seguinte:- na borda ascendente da solicitação de resposta, redefina a saída e lembre-se do valor atual do temporizador do sistema TIME_TCK
- «» , ( , TIME_TCK 0 (2 ^ 31 — 1), ). , . , «», — «»
- «»,
Com o advento da série "milésima", a situação mudou um pouco. O fato é que a linha S7-1500 herdou o suporte para a chamada do sistema TIME_TCK e os amantes da abordagem "em pé e na rede" (de que outra forma você pode chamar um programa que apenas processa matrizes de estruturas enquanto opera com índices assustadores?) calmamente continuam a usar suas melhores práticas.A série S7-1200 de controladores básicos é baseada em uma arquitetura diferente e possui diversas diferenças em relação ao S7-1500. Incluindo a falta de uma chamada do sistema TIME_TCK. Nas fileiras de desenvolvedores que não têm flexibilidade suficiente de pensamento, a insatisfação desapareceu - é impossível executar cópias / pastas de programas antigos. No entanto, a tarefa de determinar quanto tempo passou desde que a chamada anterior pode ser executada usando a função de tempo de execução.Esta função retorna o tempo decorrido desde a chamada anterior, em segundos, como um número real de precisão dupla LREAL. Os detalhes são descritos na ajuda. Para fins internos, é necessária uma variável MEM adicional (também do tipo LREAL).Darei as fontes da primeira aproximação da função e darei algumas notas.Declaração de função:FUNCTION "PerversionTON" : Void
VERSION : 0.1
VAR_INPUT
IN : Bool;
PT : Real;
END_VAR
VAR_OUTPUT
Q : Bool;
END_VAR
VAR_IN_OUT
INPrv : Bool;
MEM : LReal;
TimeACC : UDInt;
END_VAR
VAR_TEMP
udiCycle : UDInt;
udiPT : UDInt;
END_VAR
Com as entradas / saídas, tudo fica claro: IN, Q e PT. Eu defino a configuração da hora na forma de uma real, são segundos. Só queria (mas em vão, mas mais sobre isso abaixo). Mais sobre as variáveis da área InOut. Como temos uma função, não temos uma área STAT, não há variáveis que retenham seu valor durante a próxima chamada de função e essas variáveis são necessárias:INPrv - para determinar a borda positiva da solicitaçãoMEM - variável auxiliar para a chamada do sistema para o tempo de execução do trabalhoTimeACC - acumulador de tempo , que armazenará o número de microssegundos do atraso em execução no momento.As variáveis TimeACC, udiCycle e udiPT são especificadas no formato UDINT, um número inteiro não assinado, 4 bytes. Apesar de ter especificado o tempo como real e a função de tempo de execução retornar real tanto quanto precisão dupla, prefiro executar operações simples de soma e comparação com operandos inteiros para economizar tempo do processador. O tempo no meu caso é levado em consideração para o microssegundo. O motivo é simples - se você aumentar o tempo para milissegundos, com o OB1 quase vazio (por exemplo, se apenas um temporizador for chamado em todo o programa do controlador e nada mais), serão possíveis "pulos" de ciclos, às vezes o programa é executado por 250 μs. Porém, nesse caso, o valor máximo permitido do acumulador de tempo será de 4.294 segundos, quase 4.295 (2 ^ 32 - 1 = 4.294.967.295). Não há nada a ser feito, tal "otimização" requer sacrifício.Texto da função.#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000);
#udiPT := REAL_TO_UDINT(#PT * 1000000);
IF (#IN AND (NOT #INPrv)) THEN
#TimeACC := 0;
#Q := FALSE;
ELSIF (#IN AND #INPrv) THEN
#TimeACC += #udiCycle;
IF #TimeACC >= #udiPT THEN
#Q := TRUE;
#TimeACC := #udiPT;
ELSE
#Q := FALSE;
END_IF;
ELSE
#Q := FALSE;
#TimeACC := 0;
END_IF;
#INPrv := #IN;
ENO := #Q;
As duas primeiras linhas são o recálculo da configuração do timer do número de segundos especificado no formato REAL para o número de microssegundos. O tempo em microssegundos decorrido da chamada de bloco do programa anterior também é determinado.Além disso, o algoritmo é o seguinte, e eu já o dei:- na borda ascendente da entrada IN, restaure a saída Q e reinicie o acumulador
- se a "verdade" continuar a ser inserida, aumentamos o acumulador de tempo pelo valor já conhecido do udiCycle e o comparamos com a configuração de tempo. Se a configuração do tempo for excedida, o temporizador funcionou, dê a saída "true", caso contrário, dê a saída "false"
- no caso de aplicar entrada falsa à entrada IN, redefina a saída Q e redefinir o acumulador de tempo.
No final da função, para determinar a borda da entrada IN, lembre-se do seu valor anterior. Também forneça à saída ENO (ao usar uma função em linguagens gráficas, como LAD) o valor da saída Q.Asseguramos que a função esteja funcionando, após o que se torna interessante avaliar sua velocidade e, se necessário, melhorar (já parece à primeira vista que vários cálculos vão para ocioso e desperdiçando tempo da CPU em vão). Para avaliar o desempenho, declaro uma matriz de 1000 estruturas de dados de timer.Declaração da estrutura. Seus campos duplicam as variáveis de entrada e saída da função timer.TYPE "typePervTONdata"
VERSION : 0.1
STRUCT
IN : Bool;
PT : Real;
Q : Bool;
INPrv : Bool;
MEM : LReal;
TimeACC : UDInt;
END_STRUCT;
END_TYPE
Uma matriz de estruturas é declarada no bloco de dados globais do TortureTON:TONs : Array[0..999] of "typePervTONdata";
O código a seguir é executado no bloco organizacional OB1:FOR #i := 0 TO 999 DO
"TortureTON".TONs[#i].IN := "startton";
"PerversionTON"(IN := "TortureTON".TONs[#i].IN,
PT := "TortureTON".TONs[#i].PT,
Q := "TortureTON".TONs[#i].Q,
INPrv := "TortureTON".TONs[#i].INPrv,
MEM := "TortureTON".TONs[#i].MEM,
TimeACC := "TortureTON".TONs[#i].TimeACC);
END_FOR;
Anunciados 1000 "instâncias" de temporizadores, cada um com um tempo de 10 segundos. Todos os 1000 temporizadores começam a contar o tempo pelo valor da variável do marcador startton.Inicio as funções de diagnóstico do controlador (S7-1214C DC / DC / DC, versão FW 4.4, versão Step7 - V16) e observo o tempo do ciclo de varredura do controlador. Em "inativo" (quando "falso" chega à entrada dos cronômetros), todo o mil é processado em média por 36 a 42 milissegundos. Durante a contagem regressiva de dez segundos, essa leitura cresce em cerca de 6-8 milissegundos e às vezes rola por 50 ms.Examinamos o que pode ser aprimorado no código de função. Primeiro, as linhas no início do bloco do programa:#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000);
#udiPT := REAL_TO_UDINT(#PT * 1000000);
Eles sempre são chamados, independentemente de o cronômetro contar o tempo, não contar ou já contar. Um grande desperdício de dinheiro é carregar a CPU não muito poderosa da série 1200 com cálculos envolvendo materiais de dupla precisão. É razoável transferir ambas as linhas para a parte do código que processa a contagem regressiva (se a "verdade" continuar aparecendo). Também é necessário duplicar o cálculo do udiCycle em um código que processa uma margem positiva na entrada do timer. Isso deve aliviar a "operação ociosa" do timer quando o valor de entrada for falso. Na prática, os temporizadores nos controladores lógicos programáveis geralmente trabalham "inativos". Por exemplo, o tempo de filtragem do retorno do contato é dezenas de milissegundos. O pulso de controle de uma saída discreta é de algumas centenas de milissegundos, geralmente de 0,5 a 1,0 segundos.O tempo para monitorar a execução do comando da unidade (por exemplo, o tempo em que a válvula se abre completamente) é de dezenas de segundos a vários minutos. O PLC na produção funciona 24 horas por dia e 365 (e às vezes mais!) Dias por ano. Ou seja, na maioria das vezes a entrada do timer é "zero" e o timer não conta nada, ou uma "unidade" chega por um longo tempo e o timer já conta tudo. Para descarregar a CPU do segundo estágio inativo (o temporizador já contou), é necessário verificar no estágio “a entrada continua recebendo a verdade” - se o temporizador já contou o tempo todo e configurou a saída como verdadeira. Nesse caso, nenhum cálculo deve ser realizado.na maioria das vezes, a entrada do timer é "zero" e o timer não conta nada, ou uma "unidade" chega por um longo período de tempo e o timer já conta tudo. Para descarregar a CPU do segundo estágio inativo (o temporizador já contou), é necessário verificar no estágio “a entrada continua recebendo a verdade” - se o temporizador já contou o tempo todo e configurou a saída como verdadeira. Nesse caso, nenhum cálculo deve ser realizado.na maioria das vezes, a entrada do timer é "zero" e o timer não conta nada, ou uma "unidade" chega por um longo período de tempo e o timer já conta tudo. Para descarregar a CPU do segundo estágio inativo (o temporizador já contou), é necessário verificar no estágio “a entrada continua recebendo a verdade” - se o temporizador já contou o tempo todo e configurou a saída como verdadeira. Nesse caso, nenhum cálculo deve ser realizado.Para fazer essas alterações, é necessário transferir a saída do Q timer da área OUTPUT para a área IN_OUT, e o valor da saída será armazenado em variáveis externas (neste exemplo, em uma matriz de estruturas). Após o refinamento, todo o código de função, incluindo a declaração, é o seguinte:FUNCTION "PerversionTON" : Void
VERSION : 0.1
VAR_INPUT
IN : Bool;
PT : Real;
END_VAR
VAR_IN_OUT
Q : Bool;
INPrv : Bool;
MEM : LReal;
TimeACC : UDInt;
END_VAR
VAR_TEMP
udiCycle : UDInt;
udiPT : UDInt;
END_VAR
BEGIN
IF (#IN AND (NOT #INPrv)) THEN
#TimeACC := 0;
#Q := FALSE;
#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000);
ELSIF (#IN AND #INPrv) THEN
IF (NOT #Q) THEN
#udiCycle := LREAL_TO_UDINT(RUNTIME(#MEM) * 1000000);
#udiPT := REAL_TO_UDINT(#PT * 1000000);
#TimeACC += #udiCycle;
IF #TimeACC >= #udiPT THEN
#Q := TRUE;
#TimeACC := #udiPT;
END_IF;
END_IF;
ELSE
#Q := FALSE;
#TimeACC := 0;
END_IF;
#INPrv := #IN;
ENO := #Q;
END_FUNCTION
Depois disso, o tempo de execução melhora: o processamento do tempo ocioso dos temporizadores é de 23 ms, com um tempo de filtragem de trabalho de 37 a 40 ms.Este código de função não verifica se há um valor inválido na configuração do temporizador - um valor negativo (se o material for convertido em um número inteiro não assinado, a configuração será distorcida) ou um valor superior a 4294,9 segundos (a configuração de tempo transbordará e distorcerá). Você deve controlar o valor do valor de PT no código ou confiar a tarefa de verificar o intervalo da configuração de tempo (de 0 a 4294,9 segundos) no sistema do operador de nível superior. A verificação da faixa por meio do programa PLC aumenta o tempo de processamento para aproximadamente 45-46 ms (e, em geral, a maneira mais correta é ajustar o tempo do temporizador não no formato REAL, mas no formato UDINT em milissegundos e sem sentido).O projeto de aplicativo com um cronômetro para o ambiente do TIA Portal Etapa 7 versão 16 está disponível aqui .