Acesso indexado no Step7

Eu costumo comentar com as pessoas que o divisor de águas entre quem pode afirmar que conhece, e bem o PLC da Siemens e quem sabe o usar o PLC, é exatamente o conhecimento e o uso do tipo ANY. 
Genericamente falando o ANY é um dado muito pretensioso, pois ele pode ser qualquer coisa e apontar para qualquer coisa dentro do PLC. Tradução literal, ANY pode ser qualquer coisa. E ele é. Veja as figuras abaixo. 

Formatação do ANY para variáveis
Formatação do ANY para variáveis na memória.

Formatação do ANY para parâmetros
Formatação do ANY para blocos de dados (parâmetros).

Essas figuras documentam a formatação do dado ANY, que possui em sua estrutura 80 bits. Com essa formatação ele pode simplesmente apontar para qualquer área no PLC. Isso é mais do que ser uma forma de endereçamento variável.
Geralmente, associamos o termo endereçamento variável ou endereçamento indireto ao uso de dados que ora apontam para uma região de memória e ora apontam para outra região. Não é esse o caso, pois o ANY pode inclusive apontar para funções, atuando quase que como um ponteiro para função do C/C++.
Se você precisa somente de um ponteiro que aponte para endereços de memória, você poderia utilizar o tipo POINTER do PLC.  
Veja a figura abaixo. 
Formatação do POINTER
Formatação do Pointer
 

O uso do POINTER e ANY de forma a apontarem para endereços de memória são muito semelhantes, sendo que o POINTER possui em sua estrutura 48 bits, mas por hora me atentarei ao uso do ANY, que é muito mais completo e muito mais mutante. 
Vamos analisar a estrutura completa do ANY.
Primeiramente vamos analisar a estrutura apontando para endereços de variáveis na memória do PLC. 

  1. Byte 0:
    Indica o header do ANY, sendo que no S7 é obrigatório que esse byte possua o valor 10h (B#16#10).
  2. Byte 1:
    Indica o tipo de dados representado pela ANY, podendo ser:

    Código Hexadecimal

    Tipo do Dado Descrição
    B#16#00 NIL Ponteiro Nulo
    B#16#01 BOOL Bits
    B#16#02 BYTE Byte (8 bits)
    B#16#03 CHAR Caracteres (8 bits)
    B#16#04 WORD Palavra (16 bits)
    B#16#05 INT Inteiro (16 bits)
    B#16#06 DWORD Dupla Palavra (32 bits)
    B#16#07 DINT Duplo Inteiro (32 bits)
    B#16#08 REAL Número de Ponto Flutuante (32 bits)
    B#16#09 DATE Data
    B#16#0A TIME_OF_DAY (TOD) Hora do Dia
    B#16#0B TIME Tempo
    B#16#0C S5TIME Tempo tipo S5TIME
    B#16#0E DATE_AND_TIME (DT) Data e Hora (64 bits)
    B#16#13 STRING Texto

  3. Byte 2 e Byte 3 (Word 2):
    Indica o fator de repetição, ou seja, quantos dados esse ANY representa.
  4. Byte 4 e Byte 5 (Word 4):
    Indica o número do DB, caso o ANY esteja representando dados de um DB em específico, ou o valor zero caso não aponte para nenhum DB.
  5. Byte 6:
    Indica o tipo de memória representada pelo ANY, podendo ser:

    Código Hexadecimal

    Área Descrição
    B#16#81 I Área de Entrada
    B#16#82 Q Área de Saída
    B#16#83 M Área de Bit Memory (Flags ou Markers)
    B#16#84 DB Bloco de Dados
    B#16#85 DI Bloco de Dados Instanciados
    B#16#86 L Área Local (L stack)
    B#16#87 V Área de Local Anterior

  6. Byte 7, 8 e 9:
    Nesses bytes está representado o endereço (no padrão Byte.Bit,ou 00000bbbbbbbbbbbbbbbb.xxx, onde b=número do byte e x número do bit). 

Agora vamos analisar a estrutura apontando para parâmetros de blocos na memória do PLC

  1. Byte 0:
    Indica o header do ANY, sendo que no S7 é obrigatório que esse byte possua o valor 10h (B#16#10).
  2. Byte 1:
    Indica o tipo de dados representado pela ANY em questão, podendo ser:

    Código Hexadecimal

    Tipo do Dado Descrição
    B#16#17 BLOCK_FB Número do FB
    B#16#18 BLOCK_FC Número do FC
    B#16#19 BLOCK_DB Número do DB
    B#16#1A BLOCK_SDB Número do SDB
    B#16#1C COUNTER Número do Contador
    B#16#1D TIMER Número do Temporizador

  3. Byte 2 e Byte 3 (Word 2):
    Não relevantes, sendo que o valor dessa Word será sempre 1.        
  4. Byte 4 e Byte 5 (Word 4):
    Não relevante, sendo que o valor dessa Word será sempre 0.
  5. Byte 6:
    Não relevante, sendo que o valor dessa Word será sempre 0.
  6. Byte 8 e 9 (Word 8):
    Indica o número do FB, FC, DB, SDB, Contador ou Temporizador representado pelo ANY. 

Bom, até aqui eu somente fiz uma tradução simples do help do S7. O que por um lado é bom, e por outro é ruim.
O bom é que por mais complexo que pareça, o TIPO ANY é simples de entender, o ruim é que o help básico do S7 não contempla bons exemplos com o ANY. O que pode ser facilmente corrigido, fazendo uma procura no www4.ad.siemens.de. 

Mas façamos o seguinte, outro dia o Williams me perguntou como fazer um FC para reescrever os valores das entradas.
De bate pronto eu comentei com ele que é plenamente possível fazer isso com:
A        I0.0
NOT
=       I0.0
Ou seja, se o código acima (em STL) estiver rodando na primeira linha do OB1, a entrada I0.0 será invertida a cada começo de ciclo de CPU (observe que estou desconsiderando a possibilidade de utilizar-se o particionamento de imagem de periferia do S7) e terá o valor invertido durante este ciclo em qualquer parte do programa.

Para tentar ser mais claro, poderia fazer o seguinte:
A        M4.3
=       I0.0
Se esse código estiver rodando na primeira linha do OB1, a entrada (ou mais correto dizer, o endereço) I0.0 será cópia do valor de M4.3.
Se você não sabia desse fato, fique sabendo agora, e saiba também que todo PLC a principio segue esse comportamento.
Devido a esse comportamento, que eu vivo criando ferramentas de simulação de I/O para tudo quanto é tipo de PLC, assim como queria fazer o Williams. 
Legal, mas aonde entra o ANY nisso tudo? Esse é o ponto meu camarada! O Williams que não é bobo, queria fazer uma função parametrizada que fizesse isso por ele. Logo ele iria entrar com o endereço inicial da entrada que ele iria simular e os valores que deveriam ser forçados. Nada mais prático!
Bom, eu mandei uma sugestão para ele, sendo que poderia criar o seguinte FC:
FUNCTION FC 3 : VOID
TITLE =
FAMILY : Sim
NAME : SimIO
VERSION : 1.0

VAR_INPUT
IoIn : ANY ; //Pointer entrada digital para ser forçada
IN00 : BOOL ; //Valor da entrada 0
IN01 : BOOL ; //Valor da entrada 1
IN02 : BOOL ; //Valor da entrada 2
IN03 : BOOL ; //Valor da entrada 3
IN04 : BOOL ; //Valor da entrada 4
IN05 : BOOL ; //Valor da entrada 5
IN06 : BOOL ; //Valor da entrada 6
IN07 : BOOL ; //Valor da entrada7
END_VAR

VAR_TEMP
DB_NR : WORD ;
END_VAR
BEGIN NETWORK
TITLE =Inicializa apontador
//Ver que esta sendo usado um parametros do tipo ANY
//Deverá ser chamado este bloco passando o primeiro bit da área a ser
//sobreescrita.
//////////////////////////////////////////////////////////////////////////////////////////////
L P##IoIn;
LAR1 ;
//Abre o DB, se estiver sendo utilizado
L W [AR1,P#4.0];
T #DB_NR;
OPN DB [#DB_NR];
//carrega pointeiro de area cruzada. Ver ponteiro any
L D [AR1,P#6.0];
LAR1 ;
//////////////////////////////////////////////////////////////////////////////////////////////
NETWORK
TITLE =Sobreescreve os bits apontados pelo parametro IoIn
//////////////////////////////////////////////////////////////////////////////////////////////
//Entrada 00
A #IN00;
= [AR1,P#0.0];
//Entrada 01
A #IN01;
= [AR1,P#0.1];
//Entrada 02
A #IN02;
= [AR1,P#0.2];
//Entrada 03
A #IN03;
= [AR1,P#0.3];
//Entrada 04
A #IN04;
= [AR1,P#0.4];
//Entrada 05
A #IN05;
= [AR1,P#0.5];
//Entrada 06
A #IN06;
= [AR1,P#0.6];
//Entrada 07
A #IN07;
= [AR1,P#0.7];
//////////////////////////////////////////////////////////////////////////////////////////////
END_FUNCTION

Bom, pra ser sincero, o segredo desse FC está em como o parâmetro ANY é passado para o FC, pois se vocês perceberem, o IoIn é um parâmetro de entrada. Mas se ele é um parâmetro de entrada, como pode estar sendo sobrescrito? No mínimo deveria ser um parâmetro do tipo Input/Output.
Não vamos nos desviar do nosso foco, o ANY. A grande sacada é perceber que o ANY nos informa o tipo e o endereço do dado que está sendo passado, e com isso poderiamos avaliar todas as informações possíveis sobre este dado.
No FC acima, foi executada a instrução
L P#IoIn
LAR1
Mas que raio isso significa?
A instrução L P#IoIn está carregando o ponteiro de 32 bits no acumulador 1 da CPU (ou o ACCU1), ela retorna algum valor do tipo DW#16#8700XXYY, onde:
B#16#87: Corresponde a memória local anterior. E o que significa isso? Resumidamente, significa que o ANY em questão está armazenado na área local (L stack) do bloco que o chamou. Aqui é o segredo da coisa. Pois, com a próxima instrução, LAR1, o conteúdo do ACCU1 será copiado para o registrador de endereço 1 (ou o AR1).
Como o AR1 está apontando exatamente para o ANY que foi passado, basta copiarmos a parte do ponteiro de 32 Bits de área cruzada desse ANY para o AR1, e logo o AR1 irá apontar exatamente para a variável apontanda inicialmente pelo IoIn. Dessa forma:
L D[AR1,P#6.0]
LAR1

Uma vez que você tem o ponteiro de onde sua informação está armazenada, é só acessá-la. Como? Veja isso:
A #IN01
= [AR1,P#0.0]
O que significa esse raio pior ainda?
Calma, vamos analisar.
A instrução A #IN01 não deveria ser problema para quem está querendo aprender o ANY. No caso essa instrução está iniciando uma operação lógica com o bit IN01 (parâmetro de entrada do tipo BOOL, ver declaração do FC).
Ok, mas o que significa =[AR1,P#0.0]? Significa que o resultado da operação lógica (RLO para os íntimos) será atribuída ao endereço apontado por AR1 (que guardou o endereço do parâmetro IoIn, conforme código acima) deslocada de zero bit, conforme o OFFSET P#0.0. Lindo, não?
Se você quisesse escrever no endereço apontado por AR1, porém deslocado de 2 bits você poderia fazer o seguinte:
Opção A:
= [AR1, P#0.2]
Dessa forma você está mudando o deslocamento através do OFFSET da instrução de escrita.
Opção B:
L P#0.2
+AR1
= [AR1,P#0.0]
Sim, amigo. Existe notação de ponteiros no PLC da Siemens. No caso as instruções L P#0.2 +AR1, somou o deslocamento de 2 bits ao endereço apontado por AR1. Evidentemente nesse caso, perdeu-se o endereço apontado por IoIn.

Conclusão:

Percebe-se por este pequeno post, que o uso do ANY é muito mais complexo do que a sua documentação sugere. Até porque fizemos um uso até que simples para ele.
Poderiamos aumentar as possibilidade utilizando dados complexos (maiores que 32 bits) como , array, structs, UDT, date and time e outros, porém tudo parte do principio que o ANY pode apontar para esses dados através de seus 80 bits.
Evidentemente e forçadamente eu tentei evitar o seu uso em lógicas simples, como para intertravamentos ou o uso do ANY onde outros meios fariam a mesma coisa e com uma simplicidade de entendimento e desenvolvimento muito melhor. Logo NÃO SE USA O ANY AONDE NÃO É PRECISO, porém o seu uso pode facilitar e muito certos problemas.
Exemplos aonde o ANY pode ser utilizado:
Leituras e Escritas de records extensos;
Criação de funções com buffers e vetores;
Funções que precisam avaliar o tipo de dado que está sendo passado, e que o mesmo pode mudar ao longo da execução do programa.
Blocos de Comunicação (quase todos os que eu conheço usam o ANY como parâmetro).
Criação de funções que demandem alto processamento de copy, move, set e reset.
Funções que demandem acessos indexados.

No caso de funções que demandem somente acessos indexados simples, eu sugiro antes de usar o ANY o estudo do uso do seguintes acessos indexados:
Ponteiro de 16 Bits com uso de memória (para DBs, temporizadores e contadores). Ex.:
OPN DB[ MW100]
SE[MW100]
CD[MW100];
Ponteiro de 32 Bits com uso de memória (com área interna). Ex.:
L P#0.0
T MD100

L IW[MD100]
Ponteiro de 32 Bits com uso de registrador indireto (com área interna). Ex.:
L P#0.0
LAR1
L IW[AR1,P#0.0]
Ponteiro de 32 Bits com uso de registrador indireto (com área cruzada). Ex.:
L P#I0.0
LAR1
L W[AR1,P#0.0]
Ponteiro de 48 Bits com uso de registrador indireto (com área cruzada).
Este permite o acesso ao número do DB utilizado.
Este ponteiro funciona como o ponteiro de 32 Bits com uso de registrador indireto e com área cruzada. A principal diferença é que ele pode ser declarado como parâmetro em FB e FC. Sua estrutura segue o padrão da figura abaixo.
Formatação do POINTER
Formatação do Pointer

O uso de parâmetros complexos, como o Ponteiro de 48 Bits e o ANY (80 Bits) tem restrições quando utilizados em FB e FC, porém não vou entrar em detalhes, pois o próprio Step7 não deixará você utilizá-los de forma errada, porém faço grandes advertências em relação ao uso dos registradores de endereço, AR1 e AR2.

Cuidados com o uso do AR1 e do AR2:
Cuidado com o uso do AR1 em FCs que chamam blocos que pedem dados complexos (string, array,structure e UDT) como parâmetro. O mesmo vale para FBs que passam esses parâmetros como IN_OUT.
Cuidado com o uso do AR2 dentro de FBs, pois ele é utilizado para acessar os dados do DB Instance, logo dependendo da forma como você alterá-lo, pode impedir o acesso aos dados do DB instance desse FB.

Uma luz no fim do túnel:
Pra quem está dizendo:
Não entendi nada!
Que porcaria esse ANY e esses acessos indexados!
Tô perdido!

Eu dou um conselho. Não entre em pânico!
Felizmente você pode passar a vida toda sem se preocupar em ter que aprender o ANY e seus derivados, pois o seu processo pode ser extremamente simples e não demandar algo do tipo. Ou então você pode precisar de algo do tipo e acabar de vez com os seus problemas programando o PLC com a linguagem SCL (o texto estruturado da IEC61131-3). Veja que facilidade em fazer um acesso indexado sobre um DB indexado sendo que o componente interno desse DB também será acessado de forma indexada:
word_to_db(número_DB).DW[número_componente].
Nada mais simples!
Evidentemente o que está rolando por trás é muito acesso indexado com o ANY e seus derivados. Mas você nem precisaria saber isso!

Publicado em CLP. 9 Comentários »