terça-feira, 21 de fevereiro de 2012

Criando um sensor de nível para Caixa D'água (Parte 2)

Na primeira parte do artigo sobre o medidor de nível para Caixa D'água me preocupei mais em fazer o mínimo para ver o código funcionado, agora vamos a alguns aspectos práticos acerca do funcionamento do disco encoder e da resolução de medição, a final, o que deseja-se saber é o volume de água variável.

A cisterna possui as dimensões de 3,00x4,00x2,00 (Largura x Profundida x Altura), um total de 24.000 Litros e envia água a uma caixa d'água de 1.000 Litros no topo da casa. Naturalmente um sistema automático realiza o bombeamento quando a caixa d'água fica vazia. Meu objetivo com isso é determinar o menor deslocamento linear que é feito quando a bomba envia água para a caixa d'água e com isso encontrar uma resolução boa para efetuar a leitura do volume.

Desprezando um pouco os volumes fixos dos dois tipos de reservatórios para os cálculos tem-se uma variação de 12,5cm cada vez que a bomba carrega a caixa d'água superior, como existem volumes fixos em ambos reservatórios e nunca agitem o limite bem como não se esvaziam por completo pode-se considerar a matade do valor como efetivo e mais ainda, como não se tem controle sob a entrada de água da rua é razoável uma resolução na altura de 0,5cm o que fornece uma precisão de 40 Litros o que é muito bom, no meu caso é claro.

O encoder que construí possui 6 furos simétricos o que dá uma resolução de 60º, para um deslocamento linear de 0,5cm a polia onde será fixado o nível terá, aproximadamente, 0,48 cm de raio, muito pequeno sendo necessária um conjunto de redução. Utilizando um conjunto de redução 1:10 que dá uma polia de 4,7cm de raio (diâmetro de 10,00cm).  O problema da redução é encontrar um jogo desse tamanho, aqui em casa pronto por isso, vou partir para soluções alternativas... Contar com os dois encoder dá uma resolução de 30º que aumenta o diâmetro para 1,9cm (já está quase bom porque tenho uma polia ótima de náilon)

Adaptações aparte alcancei um formato mecânico bom com uma resolução de 15º utilizando os dois encoder's e 12 furos ao invés de 6 e com uma polia de náilon de 4,5cm de diâmetro fornece uma precisão linear de 0,59 cm quase a meta de 0,5. Em fim ficou que nem ta na foto.




O próximo passo é realizar o teste de resolução, para contatar se as contas estão corretas e distância percorrida é válida. Fiz isso da maneira mais simples que encontrei, coloquei uma marca no disco e girei uma volta completa que dá 14,72 cm como resultado.


Com o processo de medição pronto o próximo é estabelecer um modo de apresentar os dados. Apresentar através de um display fica complicado por dois motivos: Em primeiro ligar não tenho um display aqui disponível e em segundo lugar desejo colocar as informações de medição numa planilha para acompanhar graficamente a evolução ao longo do ano.

Por utilizar o Netduino normal (não o plus) só tenho a conectividade USB direta com o PC e já vi que para utiliza-la vai dar um certo trabalho então vou voltar para boa e velha serial. Isso me traz três vantagens claras na hora de implementar:

  1. Como a distância entre o computador e a placa é grande (acima de 10 metros) a serial permite que eu faça a instalação com um cabo trançado;
  2. Já tenho um conversor montado TTL-RS232 que fará o interfaceamento entre a UART do Netduino e o Computador;
  3. O código é bem simples e os exemplos estão disponíveis no blog do Jeroen Swart
Sendo assim o melhor é usar mesmo a serial, em outro post vou me ater ao código da comunicação, mas por agora vou apenas apresenta-lo como uma parte integrante do meu projeto assim  como o hadware de interface que literalmente é uma caixa preta. Meu amigo Gean colocou a placa numa caixa preta com conectores o que facilita muito.


A última parte é a instalação do aparato na cisterna, isso vou deixar para o próximo post.

segunda-feira, 20 de fevereiro de 2012

Criando um sensor de nível para Caixa D'água (Parte 1)

Um dos problemas sérios que eu enfrento em casa todo ano é a falta d'água. Aqui no Rio de Janeiro o péssimo serviço da companhia de águas, a CEDAE, nos obriga a usar uma cisterna de 25000 mil litros, uma verdadeira piscina subterrânea  capaz de guardar água para 2 meses.

Infelizmente isso não foi suficiente esse ano é estou sem água. Naturalmente isso é uma grande oportunidade para testar o Netduino e fazer um medidor de nível para a cisterna assim poderei acompanhar diariamente o nível de água dentro dela, uma coisa importante até mesmo para saber se tudo está em ordem, e quem sabe até reduzir o consumo.

Andei lendo sobre os medidores de nível para ter uma ideia do estado-da-arte dos medidores de nível que estão ai no mercado e posso dizer que uma solução razoável seria usar um  disco perfurado como ENCODER, assim como os velhos mouses de bolinha da década de 90 (com certeza você lembra disso).

Nesse caso necessitasse de um eixo fixo no topo da cisterna onde ficará preso o disco a um rolo onde passará um barbante esticado em que em uma pota terá um contrapeso para manter-lo esticado e na outra uma boiá que acompanhará o nível da cisterna.

Trata-se de um projeto logo por isso farei antes um teste de código. Fiz um teste do software do Netduino e quando este estiver pronto passarei a fase de  construção da peça em si. Para iniciar consegui dois photointerrupter, consegui no fero velho retirando de um micro ondas. O modelo é o GP1S563, trata-se de um conjunto montado de fototransistor com fotodiodo o DataSheet está disponível aqui. Seria interessante comandar o fotodiodo com um pino I/O para minimizar o consumo já que o tempo de integração é elevado, mas vou deixar para uma segunda fase.

O circuito que utilizei para testar foi muito simples. O fotodiodo foi conectado a alimentação de 9V através de um resistor de 82Ohms (Outros valores entre 220Ohms e 60Ohms são aceitáveis), no lado do fototransistor utilizei um método português, coloquei 8 resistores de 1KOhm em série para no emissor para funcionar no corte-saturação e obter um sinal de 3,3V lógico mesmo com a alimentação de 9V sob o fototransistor. A alimentação utilizei através de Vin do Netduino (quando utilizava uma fonte externa de 9V)

Teste do Encoder

O código utilizado no testes é apresentado a seguir:

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using System.IO.Ports;
using System.Text;

namespace SerialTestCode_v01
{
    public class Program
    {
        public static void Main()
        {

           OutputPort _netduinoLed = new OutputPort(Pins.ONBOARD_LED, false);
           InputPort _encoder1 = new InputPort(Pins.GPIO_PIN_D0, 
                                 false, Port.ResistorMode.Disabled );


           while ( true)
           {
               if (_encoder1.Read() == true)
               {
                   _netduinoLed.Write(false);
               }
               else {

                   _netduinoLed.Write(true);
               }



            }

          
        }        
    }
}
O código é bem simples mesmo, com lógica invertida o led psica ao mudar o estado do encoder.

Acredito que a parte mais difícil foi fazer o encoder com dois sensores, a foto abaixo mostra a construção do encoder. Desculpe a falta de detalhes mas a ideia foi fazer um encoder que pudesse fixar uma roldana a ele e prender o sistema móvel com o volume flutuante.

Note que utilizei dois encoder's. Essa prática é para associar a direção do movimento, um único encoder permite somente que faça-se a associação do percurso feito pelo disco, ou seja, quando realiza uma contagem passando através de um furo o próximo furo, obedecendo o sentido de rotação, seria o furo seguinte segundo o sentido, porém, a inversão do sentido de rotação também pode causar que o primeiro furo passe uma segunda vez pelo encoder sendo contado como um descolamento.

No caso da medição do nível da caixa d'água o encoder pode rodar no sentido horário ou anti-horário dependendo do ciclo de carga e descarga desse reservatório, assim, a solução encontrada foi utilizar o método dos dois encoder's. Esse método resolvi utiliza-lo porque a lógica é mais simples e porque eu possuo dois photoswitch's iguais disponíveis para isso, nesse tipo de sistema os encoder ficam posicionados de tal forma que quando um furo corte um encoder o outro permaneça inativo, isso fica mais claro com uma figura, esse desocameneto entre eles provoca, durante a inversão do sentido uma dupla passagem por um encoder e nenhuma passagem pelo seguinte.

Não utilizei a resolução máxima centrando o segundo encoder na posição igual a metade do percurso entre duas marcações do disco assim, a resolução é a mesma de 1/4 de volta. O código inicial está descrito abaixo:


using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;
using System.IO.Ports;
using System.Text;

namespace SerialTestCode_v01
{
    public class Program
    {
        public enum  Orientation 
        {
            CLOCKWISE = 1,
            COUNTERCLOCKWISE
        };

        public enum LastEncoder
        {
            ENCODER1 = 1 ,
            ENCODER2
        };

        static Int64 _counter = 0;
        static Orientation _orientation = Orientation.CLOCKWISE;
        static LastEncoder _lastEncoder = LastEncoder.ENCODER1;
        
        static InterruptPort _encoder1 = new InterruptPort(Pins.GPIO_PIN_D4, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLevelHigh);
        static InterruptPort _encoder2 = new InterruptPort(Pins.GPIO_PIN_D5, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLevelHigh);


        static void Encoder2_OnInterrupt(uint port, uint state, DateTime time)
        {
           Debug.Print("Encoder 2");

           if (_lastEncoder == LastEncoder.ENCODER2)
           {
               if (_orientation == Orientation.CLOCKWISE)
               {
                   _orientation = Orientation.COUNTERCLOCKWISE;
               }
               else
               {
                   _orientation = Orientation.CLOCKWISE;
               }
           }
          


            _lastEncoder = LastEncoder.ENCODER2;
            _encoder2.ClearInterrupt();
        }

        static void Encoder1_OnInterrupt(uint port, uint state, DateTime time)
        {
            Debug.Print("Encoder 1");


            if (_lastEncoder == LastEncoder.ENCODER1)
            {
                if (_orientation == Orientation.CLOCKWISE)
                {
                    _orientation = Orientation.COUNTERCLOCKWISE;
                }
                else
                {
                    _orientation = Orientation.CLOCKWISE ;
                }
           
            }
           


            if (_orientation == Orientation.CLOCKWISE)
            {
                _counter+=1;
            }
            else
            {
                _counter-=1;
            }

            Debug.Print("Acumulado: " + _counter.ToString());
            _lastEncoder = LastEncoder.ENCODER1;
            _encoder1.ClearInterrupt();
        }


        public static void Main()
        {


            //Espera o primeiro pulso do encoder 1 (calibração)
            while (_encoder1.Read() == true) { };
            while (_encoder1.Read() == false) { };
            while (_encoder1.Read() == true) { };

            BlinkerLed(5, new OutputPort(Pins.ONBOARD_LED, false));
                        
            _encoder1.OnInterrupt += new NativeEventHandler(Encoder1_OnInterrupt);
            _encoder1.ClearInterrupt();
            
            _encoder2.OnInterrupt += new NativeEventHandler(Encoder2_OnInterrupt);
            _encoder2.ClearInterrupt();


         Thread.Sleep(Timeout.Infinite);
         
        }


       // Função para piscar o LED
        static void BlinkerLed(UInt32 _times, OutputPort _ledport)
        {

            for (UInt32 i = 0; i <= _times; i++)
            {
                _ledport.Write(!_ledport.Read());
                Thread.Sleep(125);
            }

        }


      
    }
}
Para utiliza-lo basta compreender a utilização de interrupções do .NET Microframework. Utilizei a interrupção de subida dos pinos IO 4 e 5, aos quais atribui os nomes de "_encoder1" e "_encoder2", a ordem  não importa. A partida do código é feita passando o disco pelo _encoder1 completamente, quando isso ocorre o Led da placa do Netduino pisca 5 Vezes (Função BlinkLed) depois a interrupções são habilitadas. Coloquei isso para fixar o sentido de rotação e ele não errar mais, as primeiras tentativas o sentido ficava esporádico, mas ainda existe como melhorar isso.

Habilitas as interrupções segue a parte de contagem que é feita apenas na interrupção do _encoder1. Para entender o código é importante inicia com a prerrogativa de sentido horário e que o próximo encoder a ser ativado é o _encoder2, o que é notável uma vez que o código só parte quando passa no sentido horário pelo _encoder1. Assim para rodar no sentido horário sempre parsará pela sequência: _encoder2 e depois _encoder1 e sucessivamente, qualquer inversão na sequência é tratada como uma mudança no sentido de rotação  está e tratada na condição IF presente me cada interrupção.

Se o ultimo encoder ativo é o mesmo que o atual existe uma inversão na orientação da rotação do disco, assim mudar a variável orientação para decrementar o incrementar o valor conforme o estado atual.