Postagem em destaque

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte I

Este post é o primeiro de uma série de seis que escrevi tratando de controle de potência e PID (controle proporcional, integral e derivativo...

domingo, 26 de maio de 2013

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte VI

Nesse último artigo da nossa série sobre PID, vamos estudar um caso prático de uso dessa técnica em um sistema de controle. Os outros posts podem ser lidos em:

Controle de Pot. em CA - PID, Arduino e TRIAC - Parte I
Controle de Pot. em CA - PID, Arduino e TRIAC - Parte II
Controle de Pot. em CA - PID, Arduino e TRIAC - Parte III
Controle de Pot. em CA - PID, Arduino e TRIAC - Parte IV
Controle de Pot. em CA - PID, Arduino e TRIAC - Parte V

Como é comum nessa plataforma tão versátil que é o Arduino, existe uma biblioteca pronta para quem quer implementar controles baseados em PID. Essa biblioteca é a PID Library. Ela nos livra do trabalho de calcular a função PID, todo o cálculo já está implementado "dentro" do objeto PID definido. O cálculo não é complicado, quem tiver lido e entendido esses posts que fiz e quiser dar uma espiadinha no arquivo PID_v1.cpp vai entender perfeitamente a implementação. O jeito de usar é simples, tb. De posse dos valores de KpKi e Kd, vc tem que começar um fonte do Arduino assim:

#include "PID_v1.h"
 
// Carga (pino onde será aplicado o output)
#define loadR 3    
 
// Constantes do PID
#define kp 50
#define ki 10
#define kd  0
 
// Variáveis do PID: a nomenclatura é a mesma descrita no post III
double SetPoint, PresentValue, ManipulatedValue;
 
// O último parâmetro indica se o controle é inversa ou 
// diretamente proporcional, ou seja, se a uma alteração do PV
// deve corresponder uma alteração de mesmo sinal (DIRECT) ou 
// de sinal trocado (REVERSE).
PID pid(&PresentValue, &ManipulatedValue, &SetPoint, kp, ki, kd, DIRECT);
 
volatile int power = 100;  
 
long t;
 
void setup()
{
  Serial.begin(9600);
  pinMode(loadR, OUTPUT);
  attachInterrupt(0, zero_crosss_int, RISING); 
// Os valores 10 e 240 foram definidos empiricamente usando-se
// o osciloscópio para ver onde o controle da onda é estável.
  pid.SetOutputLimits(10, 240);
  pid.SetMode(AUTOMATIC); 
  SetPoint=60;
  t=millis();
}
 
void zero_crosss_int()  
{
// Cálculo do ângulo de disparo: 60Hz-> 8.33ms (1/2 ciclo)
// (8333us - 8.33us) / 256 = 32 (aprox).
// Ou seja, cada 1 unidade em power corresponde a 32 microssegundos
// na onda de meio ciclo.
  int powertime = (32*(256-power));      
// Mantém o circuito desligado por powertime microssegundos 
  delayMicroseconds(powertime);   
// Envia sinal ao TRIAC para que ele passe a conduzir 
  digitalWrite(loadR, HIGH);  
// Espera alguns microssegundos para que o TRIAC perceba o pulso
  delayMicroseconds(8.33);      
// Desliga o pulso
  digitalWrite(loadR, LOW);   
}
 
 
// Função que lê o PV. É uma função que lê a saída  
// de um LM35 cinco vezes e acumula, depois tirando a média das
// 5 leituras e calculando a temperatura em °C.
int GetTemp(int sensor)
{
  float temp = 0;
  for(int i=0; i< 5;i++)
  {
    temp += analogRead(sensor);
    delay(20);
  }
  temp = (temp * 0.48875855)/5;
  return (int)temp;
}
 
void loop()
{
  float temp = GetTemp(A0);
  PresentValue=temp;// Calcula o valor manipulado   
  pid.Compute();
// Valor de power, que vai ser usado em
// zero_crosss_int() para atuar no processo.
  power = ManipulatedValue;
// Exibe os valores usados de 1 em 1s.
  if ((millis()-t) > 1000)
  {
    Serial.print(millis()/1000);
    Serial.print(",");
    Serial.print(PresentValue);
    Serial.print(",");
    Serial.println(ManipulatedValue);
    t=millis();
  }
  delay(300);
}
Esse exemplo é bem simples, e mostra como, usando-se a biblioteca pid_v1.h, fica fácil a gente implementar esse tipo de controle nos nossos projetos, ou seja, o pulo do gato está em entender o que se quer do PID e escolher as constantes certas para obter o resultado desejado.

Um outro exemplo, combinando o controle por TRIAC e o PID, segue abaixo. Esse circuito "junta tudo": combina os primeiros posts de controle de potência com os post de PID, num circuito sofisticado e prático para controle em geral, indo de aplicações de controle de processo propriamente dito até robótica, por exemplo, um simples robozinho segue-faixa pode ser muito beneficiado pelo uso desse tipo de algoritmo.

Para terminar, resolvi escrever sobre esse assunto porque não achei na net bons textos práticos em português sobre ele. Aliás, nem em inglês eu vi algo que fosse simples mas ao mesmo tempo descrevesse com rigor os conceitos envolvidos, por isso resolvi dedicar um tempo a isso.

Espero que essa série posts, que tomou mais de um mês para ser feita, seja útil a vcs. Por favor, escrevam sobre o que acharam, para que eu tenha o feedback de como me saí nessa empreitada. Abracadabraço!

#include "PID_v1.h"

// Carga (pino onde será aplicado o output). Esse pino deve ser
// ligado ao MOC. No primeiro circuito do post III dessa série
// o MOC está ligado ao pino 4.
#define loadR 4    

// Constantes do PID
#define kp 50
#define ki 10
#define kd  0

// Variáveis do PID: a nomenclatura é a mesma descrita no post III
double SetPoint, PresentValue, ManipulatedValue;

// O último parâmetro indica se o controle é inversa ou 
// diretamente proporcional, ou seja, se a uma alteração do PV
// deve corresponder uma alteração de mesmo sinal (DIRECT) ou 
// de sinal trocado (REVERSE).
PID pid(&PresentValue, &ManipulatedValue, &SetPoint, kp, ki, kd, DIRECT);

volatile int power = 100;  

long t;

void setup()
{
  Serial.begin(9600);
  pinMode(loadR, OUTPUT);
attachInterrupt(0, zero_crosss_int, RISING); // Os valores 10 e 240 foram definidos empiricamente usando-se // o osciloscópio para ver onde o controle da onda é estável. pid.SetOutputLimits(10, 240); pid.SetMode(AUTOMATIC); SetPoint=60; t=millis(); } void zero_crosss_int() { // Cálculo do ângulo de disparo: 60Hz-> 8.33ms (1/2 ciclo) // (8333us - 8.33us) / 256 = 32 (aprox). // Ou seja, cada 1 unidade em power corresponde a 32 microssegundos // na onda de meio ciclo. int powertime = (32*(256-power)); // Mantém o circuito desligado por powertime microssegundos delayMicroseconds(powertime); // Envia sinal ao TRIAC para que ele passe a conduzir digitalWrite(loadR, HIGH); // Espera alguns microssegundos para que o TRIAC perceba o pulso delayMicroseconds(8.33); // Desliga o pulso digitalWrite(loadR, LOW); } // Função que lê o PV. É uma função que lê a saída // de um LM35 cinco vezes e acumula, depois tirando a média das // 5 leituras e calculando a temperatura em °C. int GetTemp(int sensor) { float temp = 0; for(int i=0; i< 5;i++) { temp += analogRead(sensor); delay(20); } temp = (temp * 0.48875855)/5; return (int)temp; } void loop() { float temp = GetTemp(A0); PresentValue=temp;// Calcula o valor manipulado pid.Compute(); // Valor de power, que vai ser usado em // zero_crosss_int() para atuar no processo. power = ManipulatedValue; // Exibe os valores usados de 1 em 1s. if ((millis()-t) > 1000) { Serial.print(millis()/1000); Serial.print(","); Serial.print(PresentValue); Serial.print(","); Serial.println(ManipulatedValue); t=millis(); } delay(300); }