(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]](image001.gif)
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.
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
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.
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.
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.
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_CICLO – k . 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.

“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.
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.
Entre
em contato comigo para trocarmos idéias! Meu e-mail é [email protected] .
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;
}
//---------------------------------------------------------------------------
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: