Controlador Digital

Intro

Questão: Como implementar ? Ou como ?

Seja um controlador PI, como o mostrado abaixo:

blocos_PI

Sua equação no plano fica:

Ou de forma mais genérica (e paramétrica):

(eq. (1))

onde: ganho genérico do controlador (provavelmente definido à partir de projeto usando root-locus); pólo do controlador; zero do controlador.

Esta mesma equação pode ser re-escrita usando notação negativa para os expoentes em :

(eq. (2))

A idéia é obter a eq. de diferenças à partir de e a notação anterior é mais prática porque termos como:

.

ou, lembrando das propriedades da Transformada, respresenta uma amostra atrasada (de 1 período de amostragem) do sinal .

Voltando a eq. (2) lembramos ainda que:

Então:

trabalhando esta equação vamos obter:

(eq. (3))

Onde: sinal de controle atual (sinal de atuação); sinal de controle no instante de amostragem anterior; erro atual do sistema; erro no instante de amostragem anterior; ganho genérico do controlador; zero do controlador; pólo do controlador.

Note que a eq. (3) é a equação que deve ser implementada num controlador e deve ser executada de maneira síncrona, isto é, cada vez que se passa o intervalor de tempo correspondendo ao período de amostragem .

Então poderiámos codificar diretamente a eq. (3) ?

u[k] = K * e[k] - K * b * e[k-1] + a * u[k-1]; ? (eq.(4))

Com: float K, u[], e[], a, b, e provavelmente: unsigned int k;.

Até se poderia, mas com um detalhe, a variável k usada como índice para acessar posições nos vetores u[] e e[] , do ponto de vista da eq. (3), varia no intervalo: , ou seja, não existe um limite finito para o tamanho destes vetores. O maior valor atingido pelo vetor vai depender de quanto tempo o algoritmo de controle (a eq. (3)) está rodando.

Na prática, se a eq. (3) for diretamente implementada como a eq. (4), provavelmente vamos incorrer num overflow na memória do micro-controlador (ou tentiva de "inavasão" de áreas de RAM reservadas para outros processos num sistema de tempo-real) usado para implementar este algoritmo de controle. De todas as formas, provavelmente vamos incorrer no disparo do watchdog do microcontrolador (e consequentemente seu re-boot) ou ocorrência de exceção causada por tentativa de invasão de faixas de RAM, depois de certo intervalo de tempo.

Então, cada termo da eq. (4) deverá ser transformado numa simples variável escalar (array ), algo do tipo:

u = K*e - K*b*e1 + a*u1; (eq. (5))

as variáveis teriam de ser float e os valores das variáveis u1 e e1 associados com valores de amostras atrasadas dos sinais de controle e de erro deveriam ser atualizadas de forma adequada dentro do código associado com o algoritmo de controle.

Como o algoritmo de controle deve ser implementado numa task síncrona ou rotina da tratamento de interrupção, resultaria em algo semelhante à:

Note que esta rotina lê informações de um sensor usando um A/D, compara com referência desejada (variável r), calcular o sinal do erro, computa o sinal de controle e lança este sinal para fora usando um D/A.

Note que não é declarada nenhum variável dentro do bloco porque todas são propositalmente globais.

O objetivo da rotina do algoritmo de controle é apenas e exclusivamente executar o algoritmo de controle. Não deve ser usada para mostrar informações num display ou transmitir informações numa porta serial. O programador deve ser dar conta que o tempo de processamento gasto com a execução deste algoritmo deve ser o menor possível.

Se o usuário quiser transmitir ou mostrar dados do processo, ou até mesmo alterar o ganho ou outros parâmetros do controlador ou alterar o próprio valor da referênica (ou set-point), isto deve ser feito fora desta rotina. Motivo pelo qual, as variáveis presentes dentro do algoritmo de controle são globais. Se fosse usado um sistema operacional de tempo real oritentado à objetos, algumas variuáveis poderiam ser declaradas como atributos privados de certos objetos. Mas o caso do código exemplo mostrado aqui, não contempla uma situação como esta. E sim, esta mais direcionada para implementação numa placa micro-controlada tão simples quanto uma Arduino Uno.

No caso de ser usada uma placa como Arduino Uno, a seção de código associada com a função void setup(){ .. } deveria inicializar pinos, porta serial, módulo DAC e configurar adequadamente uma interrupção de software associada com overflow de algum timer da placa micro-controlada (no caso do código mostrado, seria o timer2).

E na seção void loop(){ .. } poderia estar contigo código que mostra num display ou transmite pela porta serial, dados do processo sob controle (conteúdo das variáveis r= referência atual, y= saída atual do processo e u= sinal de controle atual) e ainda deveria haver algum código que permite interação com o usuário para "desligar" o algoritmo de controle, mudar parâmetros do controlador ou mudar refência usada para o processo.

Como o algoritmo de controle sempre estará sendo executado de forma periódica, obedecendo o período de amostragem requerido, o código como um todo passa a impressão para o usuário de que: está lendo dados do processo, processando o algoritmo de controle e processando dados de controle (parâmetros) passados pelo usuário.

Um último detalhe: antes de executar a linha:

é interessante lembrar que qualquer DAC trabalha dentro de uma faixa limitada de valores inteiros. Motivo pelo qual está sendo realizado o casting em relação à variável u. Mas seria bastante aconselhável, antes de simplesmente enviar o sinal de controle u para o DAC, limitar seu valor de acordo com o DAC usado.

No caso do DAC MPC4725, o mesmo é de 12-bits, o que significa que só aceita números unsigned int variando entre: ou na faixa: . Então antes desta linha de código é interessante que seja colocaco um bloco "Saturador" do tipo:

Em resumo estaríamos realizando algo como:

Controlador_digital

Fim.


Fernando Passold, em 30/04/2024