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.

2 comentários:

  1. Controle de cisterna muito mais simples, rápido e barato, faz a automação e nem precisa codificar:

    http://www.scribd.com/doc/79956120/Controle-de-Nivel-Reservatorio-Icos

    ResponderExcluir
    Respostas
    1. A sim, sem dúvida, muito mais simples. A vantagem é que utilizando o Netduino é possível ter os dados de nível da cisterna periodicamente. É que eu ainda não acabei de escrever sobre esse medidor de nível, mas já adiantando, com o netduino plus (que tem conexão de internet) vou enviar os dados para o google docs e verificar graficamente a variação de volume d'água na minha cisterna.

      Excluir