Controle de Servomotores tipo RC

(André Tomaz de Carvalho)

 

          Há diversas formas de gerar trens de pulsos para o controle de servomotores. Os modelos radiocontrolados possuem um circuito dedicado que recebe os comandos de posição através de ondas de rádio e gera os sinais de controle para cada servo.

          Uma alternativa para fazer experiências de controle consiste em gerar o trem de pulsos com um circuito utilizando um CI 555, como mostra a figura abaixo.

 

[photo]

          Esse circuito gera os sinais requeridos para o controle de um servo de RC. A saída é um trem de pulsos positivos com período de 40ms, e os pulsos gerados podem ter duração entre 0,9 ms e 2,1ms, dependendo da posição do potenciômetro de 100k. O período dos pulsos pode ser diminuído reduzindo o valor da resistência de 3Meg. Mais informações estão disponíveis em http://wolfstone.halloweenhost.com/TechBase/svoint_RCServos.html.        Outro modo de gerar os pulsos de controle é utilizar um microcontrolador para gerar  trens de pulsos. Existem inclusive placas comerciais que utilizam essa estratégia, recebendo as posições requeridas para os servos via porta serial ou porta paralela, e gerando os trens de pulsos para um certo número de servos. Para quem quer economizar tempo (e não dinheiro) indo direto para a programação dos servos, essa pode ser uma interessante alternativa.

          No entanto, nesse artigo gostaríamos de destacar uma forma de controle pouco usual, que consiste em gerar os pulsos de controle dos servos diretamente pela porta paralela. Essa alternativa é pouco usual porque requer um truque de temporização do software de controle, como mostraremos a seguir.

 

Geração dos Sinais de Controle diretamente pela Porta Paralela

 

          A porta paralela do PC é um hardware de fácil programação que pode fornecer até 12 saídas digitais em níveis lógicos TTL (8 pelo registrador de dados e mais 4 pelo registrador de controle). Veja para maiores informações:

http://geocities.yahoo.com.br/gedaepage/Doc/porta_paralela.htm  http://geocities.yahoo.com.br/gedaepage/Doc/Linguagens.htm

http://www.portaparalela.hpg.ig.com.br

http://www.rogercom.com/

 

          O hardware da porta paralela tem uma capacidade limitada de fornecer corrente para circuitos externos. Contudo,  como a impedância de entrada do circuito de controle do servo é relativamente alta (da ordem de 1k), os sinais gerados pela porta paralela podem ser conectados diretamente ao servomotor sem danificação do hardware. É no entanto sempre recomendável utilizar um buffer de corrente como o CI 7407 ou até um inversor como o CI 7404 para proteger a porta.

          Ora, temos então um hardware programável capaz de gerar pulsos de 5V de amplitude para o controle dos servos. Que nos falta?

          A dificuldade está na programação dos trens de pulso, pois os tempos envolvidos na geração do PWM, como vimos, são muito pequenos, da ordem de milisegundos.

          Deste ponto em diante, adotaremos a linguagem C++ para falarmos das técnicas de programação. Não obstante, ressaltamos que os conceitos aqui apresentados são válidos para outras linguagens como C, Pascal, Basic, etc. O compilador utilizado foi o Borland C++ Builder 5.

 

          A estrutura essencial de um programa para geração dos pulsos de controle de um servo no pino 2 da porta paralela será a seguinte:

 

Repita:

{

          Envie 1 para o pino 2 da porta paralela;

          Espere o tempo t_on;

          Envie 0 para o pino 2 da porta paralela;

          Espere o tempo t_off;

}

 

          Podemos então resumir nossa tarefa em duas funções distintas: escrever na porta paralela e esperar um certo tempo muito curto. Analisaremos esses problemas a seguir.

 

Programação da Porta Paralela

 

          Escrever um programa de comunicação com a porta paralela é uma tarefa trivial se utilizamos sistemas operacionais como DOS ou Win95/98. Podemos facilmente utilizar as funções inp( ) e outp( ) respectivamente para ler e escrever nos registradores da porta paralela.

          No entanto, sistemas operacionais mais modernos como o Win NT4, Win 2000 e o Win XP, por questões de segurança, não permitem mais que qualquer programa utilize tais recursos. O mesmo programa que rodava em DOS acessando diretamente o hardware da porta paralela, se fosse rodado em Win 2000, apresentaria uma mensagem de erro como a mostrada na figura abaixo.

 

 

 

          Por questão de segurança, nesses sistemas operacionais, as funções de IN / OUT ficam restritas ao sistema operacional e aos drivers registrados. Assim, para acessar a porta paralela, é necessário fazê-lo através de um driver de dispositivo reconhecido como tal pelo Windows.

          Escrever um driver de dispositivo (device driver) não é uma tarefa fácil nem mesmo para programadores muito experientes. Felizmente, há quem tenha resolvido o problema e disponibilizado a soluções para a comunidade eletrônica.

          No nosso caso, utilizamos a DLL “inpout.dll”, disponibilizada em http://www.logix4u.net/inpout32.htm. Essa DLL possui duas funções de acesso à porta paralela, inp32( ) e oup32( ), escritas no modo kernel do Win NT, que funcionam exatamente como as funções inp( ) e outp( ). No site citado há descrição detalhada, código fonte e exemplos de utilização dessas funções. Veja também o programa de exemplo a seguir.

 

Temporização

 

          Em C++ dispúnhamos de uma função de espera,  delay (n); pertencente à biblioteca <dos.h>, que esperava um número inteiro n de milisegundos antes de executar a próxima linha de código. O tempo mínimo de espera dessa função é de 1ms. No Windows, embora haja diferentes alternativas de temporização, esta continua sendo feita nos mesmos moldes, com o mínimo tempo de espera amarrado em 1ms.

          Como faremos então para variar o tempo ton suavemente entre 1 ms e 2 ms a fim de obtermos a variação angular do servo? Mais ainda, como obteríamos pulsos precisamente de 0,9ms ou de 2,1ms para o ajuste calibrado de um determinado servo? Nem a linguagem de programação nem a API do Windows nos dão o recurso de esperar um tempo da ordem de microsegundos. Então resolvemos o problema de uma forma alternativa.

          Suponha que dispomos do processador para realizar exclusivamente as tarefas do nosso programa. Ora, o processador possui uma capacidade de realizar um número muito grande de instruções por segundo, e esse número é calculável. Se soubéssemos exatamente o número de instruções necessário para que o processador demorasse um determinado tempo, digamos, 1 microsegundo, executando-as, poderíamos controlar a temporização do sistema pedindo ao processador que esperasse executando essas instruções.

          A idéia acima foi implementada da seguinte forma:

·       Inicialmente, medimos o tempo em segundos que o computador leva para contar de 0 até um número N1 muito grande dentro de um laço for. Seja t_n1 esse tempo medido em segundos.

·       Se quisermos que o computador espere um determinado tempo t_n2, da ordem de microsegundos, basta agora calcular um número N2 por uma regra de três, de modo que o laço for conte de 0 a N2 em t_n2 segundos.

·       Assim, calculamos os números n_ticks_for_on e n_ticks_for_off  de acordo com os tempos tempo_on e tempo_off desejados para que o servo assuma uma determinada posição angular.

 

 

Controlando vários Servos

          Para controlar mais de um servo, utilizaremos o seguinte artifício. Definimos uma constante T_ON_MAX que seja um limitante superior do da duração dos pulsos de controle. Para cada servo, definiremos as variáveis t_on[i] e calcularemos t_off[i] = T_ON_MAX - t_on[i].

          Finalmente, calcularemos t_off_restante = TEMPO_CICLOk . T_ON_MAX, onde k é o número de servos controlados.

          A estrutura essencial do programa para geração dos pulsos será a seguinte:

 

Repita:

{

          Envie 1 para o pino da porta paralela referente ao servo de índice [0];

          Espere o tempo t_on[0];

          Envie 0 para o pino da porta paralela referente ao servo de índice [0];

          Espere o tempo t_off[0];

 

          Mantendo o sinal do servo anterior em 0, envie 1 para o pino da porta paralela referente ao servo de índice [1];

          Espere o tempo t_on[1];

          Envie 0 para o pino da porta paralela referente ao servo de índice [1];

          Espere o tempo t_off[1];

 

          Mantendo os sinais dos servos anteriores em 0, envie 1 para o pino da porta paralela referente ao servo de índice [2];

          Espere o tempo t_on[2];

          Envie 0 para o pino da porta paralela referente ao servo de índice [2];

          Espere o tempo t_off[2];

 

          Mantendo os sinais dos servos anteriores em 0, envie 1 para o pino da porta paralela referente ao servo de índice [3];

          Espere o tempo t_on[3];

          Envie 0 para o pino da porta paralela referente ao servo de índice [3];

          Espere o tempo t_off[3];

 

          Espere o tempo t_off_restante;

}

 

Note que as formas de onda geradas para o controle dos servos estarão defasadas de T_ON_MAX. Observe ainda que, por esse processo, o número máximo de servos que podem ser controlados é limitado em função de T_ON_MAX e de TEMPO_CICLO.

Por exemplo, caso tenhamos TEMPO_CICLO = 20ms e T_ON_MAX = 2,5ms poderemos controlar até 8 servos, e, nesse caso, teremos t_off_restante = 0. Caso o número de servos seja igual a quatro, teremos t_off_restante = 10ms, e assim por diante.

A figura abaixo ilustra um rascunho do que seriam as formas de onda para o controle de 8 servos. Veja também o exemplo de código para controle de quatro servos a seguir.

 

 

trens de pulsos

 

 

Desempenho Computacional

          “Oras”, alguns leitores devem ter exclamado, “mas você está querendo programar em tempo real no Windows?!”. Sim, eles têm razão. Estamos programando em tempo real, e há sérias limitações quando fazemos isso num sistema operacional inadequado.

          O programa sugerido para controle dos servos apresentou resultados excelentes rodando em DOS. Com efeito, partimos da hipótese de que disporíamos do processador exclusivamente para a nossa aplicação, o que de fato acontece no sistema operacional DOS, pois este executa apenas um programa de cada vez.

          No entanto, o mesmo não ocorre no Windows. O Windows é um sistema multi-tarefa, o que significa que o tempo do processador é compartilhado entre vários processos rodando simultaneamente. O resultado disso é que os tempos obtidos pelo processo sugerido sofrerão ligeiras flutuações, o que equivale a uma certa oscilação de freqüência e um ruído no sinal de posicionamento dos servos. Do ponto de vista mecânico, os servos podem apresentar uma certa trepidação em torno da posição ajustada.

          Infelizmente esse efeito é inevitável, pois o Windows não é um sistema de tempo real. No entanto, ele pode ser bastante minimizado se desativarmos o maior número possível de processos concorrentes com o programa de geração dos pulsos. A solução que estamos apresentando requer uso máximo da CPU, e o computador deve estar totalmente dedicado à execução do programa de controle dos servos.

          Há ainda uma forma de priorizar o processo de geração de pulsos diante de outros processos concorrentes no Windows. Basta abrir o gerenciador de tarefas (ctrl + alt + del), e na aba processos clicar com o botão direito do mouse sobre o nome do programa em execução. Em definir prioridade, escolha a opção de Tempo Real. Não se trata de transformar o Windows de fato em um sistema operacional de tempo real, mas de dar prioridade máxima a uma tarefa na distribuição dos tempos do processador. Isso ajudará a diminuir o ruído, estabilizando a forma de onda gerada.

                  

Conclusão

          Nesse artigo apresentamos como principal resultado algumas técnicas de programação para controle de servomotores diretamente pela porta paralela do PC. Foi mostrada uma forma de acesso ao hardware da porta paralela nos sistemas operacionais Win NT/2000/XP através de um device driver gratuito, e detivemo-nos especialmente nos algoritmos de temporização para geração do PWM.

          Embora o controle dos servos pelo processo aqui descrito apresente no Windows um certo ruído na forma de trepidação mecânica dos motores, consideramos esta técnica como uma ferramenta útil para aqueles que estão começando a utilizar servomotores e ainda não dispõem de um hardware dedicado ao interfaceamento.

 

Dúvidas e Sugestões

          Entre em contato comigo para trocarmos idéias! Meu e-mail é [email protected] .

 

Exemplo: Um Servo Conectado no pino 2 da Porta Paralela

          O código abaixo gera o trem de pulsos para controle de um servo conectado ao pino 2 da porta paralela. O servo inicialmente é posicionado em 90o. Pressionando o caractere ‘a’ aumentamos esse ângulo; pressionando o caractere ‘z’ diminuímos o ângulo; e pressionando o caractere ‘q’ saímos do programa.  A DLL “inpout.dll” deve ser copiada para o diretório do programa para que este funcione corretamente.

Pegue aqui fonte, executável e dll.

//---------------------------------------------------------------------------

 

#include <vcl.h>

#pragma hdrstop

 

#include <windows.h>

#include <time.h>

#include <stdio.h>

#include <conio.h>

#include <iostream.h>

 

 

#define PORT 0x378;           //endereço do registrador de dados da porta paralela

#define TEMPO_CICLO 0.020     //período do trem de pulsos

#define BIGN 1000000000 //número grande para a calibração do laço for

 

 

//---------------------------------------------------------------------------

 

/* prototype (function typedef) for DLL function Inp32: */

typedef short _stdcall (*inpfuncPtr)(short portaddr);

typedef void _stdcall (*oupfuncPtr)(short portaddr, short datum);

 

 

int main(void)

{

 

     HINSTANCE hLib;

     inpfuncPtr inp32;

     oupfuncPtr oup32;

 

 

   ////////// Carrega a DLL de acesso à porta paralela inpout32.dll /////////////

 

     /* Load the library */

     hLib = LoadLibrary("inpout32.dll");

     if (hLib == NULL) {

          printf("LoadLibrary Failed.\n");

          return -1;

     }

 

     /* get the address of the function */

     inp32 = (inpfuncPtr) GetProcAddress(hLib, "Inp32");

     if (inp32 == NULL) {

          printf("GetProcAddress for Inp32 Failed.\n");

          return -1;

     }

     oup32 = (oupfuncPtr) GetProcAddress(hLib, "Out32");

     if (oup32 == NULL) {

          printf("GetProcAddress for Oup32 Failed.\n");

          return -1;

     }

 

 

   ////////// Definição de Variáveis   /////////////

 

   unsigned long n_ticks_for_on;

   unsigned long n_ticks_for_off;

   clock_t start, end;

   double tempo_on;

   double tempo_off;

   double cte_calibr;

 

 

   ////////// Calibracao do laço for   /////////////

 

   clrscr();

   printf("Calibracao do gerador de pulsos...\n");

 

   start = clock();

 

   for(long j=0;j<BIGN;j++);

 

   end = clock();

 

   cte_calibr=((end - start)/CLK_TCK)/BIGN;<

 

 

   ////////// Inicializa com o servo na posição 90 graus  /////////////

 

   tempo_on = 0.0015;

 

   ////////// Gerando Pulsos para 1 servo  /////////////

 

 

   int parada = 0;

 

   while (parada==0)

   {

        if(kbhit())                       //se alguma tecla for apertada

         {

            switch(getch())

            {

                  case 'q':

                  {

                        parada = 1; //satisfaz a condição de parada do laço while

                        break;

                  }

                  case 'a':

                  {

                        tempo_on +=  0.00001;   //soma 10us a tempo_on

                        break;

                  }

                  case 'z':

                  {

                        tempo_on -= 0.00001;     //subtrai 10us a tempo_on

                        break;

                  }

                  default :

                  {

 

                  }

            }

         }

 

 

      clrscr();                           //limpa a tela

       cout << "tempo_on = "<< tempo_on << endl;      //escreve valor de tempo_on

 

        tempo_off = TEMPO_CICLO - tempo_on;           //calcula t_off

      n_ticks_for_on = long(tempo_on/cte_calibr);     //calcula n_ticks_for_on

      n_ticks_for_off = long(tempo_off/cte_calibr); //calcula n_ticks_for_off

 

        oup32(0x378,1);                 //escreve 1 no pino 2 da porta paralela

        for(unsigned long j=0;j<n_ticks_for_on;j++);    //espera t_on

        oup32(0x378,0);                 //escreve 0 no pino 2 da porta paralela

        for(unsigned long j=0;j<n_ticks_for_off;j++);   //espera t_off

 

 

   }

        FreeLibrary(hLib);

        return 0;

}

//---------------------------------------------------------------------------

 

         

Exemplo 2: Controle de 4 Servos

 

          Esse código é muito parecido com o anterior, e controla 4 servos pelo Data Register utilizando a técnica comentada acima.

Pegue aqui fonte, executável e dll.

 

//---------------------------------------------------------------------------

 

#include <vcl.h>

#pragma hdrstop

 

//---------------------------------------------------------------------------

 

#pragma argsused

 

#include <time.h>

#include <stdio.h>

#include <dos.h>

#include <conio.h>

#include <iostream.h>

 

#define TEMPO_CICLO 0.020

#define T_ON_MAX 0.0025

#define BIGN 100000000

#define PORT 0x378

 

/* prototype (function typedef) for DLL function Inp32: */

typedef short _stdcall (*inpfuncPtr)(short portaddr);

typedef void _stdcall (*oupfuncPtr)(short portaddr, short datum);

 

//---------------------------------------------------------------------------

 

 

int main(void)

{

 

     HINSTANCE hLib;

     inpfuncPtr inp32;

     oupfuncPtr oup32;

 

 

   ////////// Carrega a DLL de acesso à porta paralela inpout32.dll /////////////

 

     /* Load the library */

     hLib = LoadLibrary("inpout32.dll");

     if (hLib == NULL) {

          printf("LoadLibrary Failed.\n");

          return -1;

     }

 

     /* get the address of the function */

     inp32 = (inpfuncPtr) GetProcAddress(hLib, "Inp32");

     if (inp32 == NULL) {

          printf("GetProcAddress for Inp32 Failed.\n");

          return -1;

     }

     oup32 = (oupfuncPtr) GetProcAddress(hLib, "Out32");

     if (oup32 == NULL) {

          printf("GetProcAddress for Oup32 Failed.\n");

          return -1;

     }

 

 

   ////////// Definição de Variáveis   /////////////

 

 

      int value[4];       //sinal do data register que controla o servo [i]

      long n_ticks_for_on [4];

      long n_ticks_for_off [4];

      long n_ticks_for_off_restante;

 

      clock_t start, end;

 

      double tempo_on[4];

      double tempo_off[4];

      double tempo_off_restante;

 

      double cte_calibr;

 

 

   ////////// Calibracao do laço for   /////////////

 

   clrscr();

   printf("calibracao do gerador de pulsos...");

   start = clock();

   for(long j=0;j<BIGN;j++);

   end = clock();

   cte_calibr=CLK_TCK*BIGN/(end - start);

 

 

   ////////// Inicializa com os servos na posição 90 graus  /////////////

 

   tempo_on[0]=0.0015;

   tempo_on[1]=0.0015;

   tempo_on[2]=0.0015;

   tempo_on[3]=0.0015;

 

 

   ////////// Gerando Pulsos para 4 servos  /////////////

 

   int parada = 0;

 

 

  while(!parada)

   {

        if(kbhit())

         {

            switch(getch())

            {

                  case 'q':

                  {

                        parada = 1;

                        break;

                  }

                  case 'a':

                  {

                        tempo_on[0] +=  0.00001;

                        break;

                  }

                  case 'z':

                  {

                        tempo_on[0] -= 0.00001;

                        break;

                  }

                  case 'x':

                  {

                        tempo_on[1] +=  0.00001;

                              break;

                  }

                  case 's':

                  {

                        tempo_on[1] -= 0.00001;

                              break;

                  }

                  case 'c':

                  {

                        tempo_on[2] +=  0.00001;

                        break;

                        }

                  case 'd':

                  {

                        tempo_on[2] -= 0.00001;

                        break;

                  }

                  case 'f':

                  {

                        tempo_on[3] +=  0.00001;

                        break;

                  }

                  case 'v':

                        {

                        tempo_on[3] -= 0.00001;

                        break;

                  }

 

            }

         }

 

 

      clrscr();

      cout << "t0 = "<< tempo_on[0]<< endl;

      cout << "t1 = "<< tempo_on[1]<< endl;

      cout << "t2 = "<< tempo_on[2]<< endl;

            cout << "t3 = "<< tempo_on[2]<< endl;

 

         for (int i=0; i<4; i++)

         {

               tempo_off[i] = T_ON_MAX - tempo_on[i];

               n_ticks_for_on[i]=tempo_on[i]*cte_calibr;

               n_ticks_for_off[i]=(tempo_off[i])*cte_calibr;

         }

 

         n_ticks_for_off_restante=(tempo_off_restante)*cte_calibr;

 

 

         value[0]=16;

         value[1]=32;

         value[2]=64;

         value[3]=128;

 

         for(int i=0; i<4; i++)

         {

            oup32(PORT,value[i]);

            for(int j=0;j<n_ticks_for_on[i];j++);

            oup32(PORT,0);

            for(int j=0;j<n_ticks_for_off[i];j++);

         }

         for(int j=0; j< n_ticks_for_off_restante; j++);

 

   }

   FreeLibrary(hLib);

   return 0;

}

 

 

 

Número de Acessos:

Counter
Hosted by www.Geocities.ws

1