quinta-feira, outubro 08, 2009

Controlando um Servomotor com um PIC

Prosseguindo na minha exploração do "servo de parabólica", apresento aqui o hardware e software de um pequeno teste de controle, usando um processador PIC.

Neste teste utilizo dois botões, um para mover o motor para "esquerda" e outro para "direita" (é claro que estas direções dependem da posição de montagem do motor).

O Hardware

Dada a simplicidade da aplicação, usei um PIC12F629 I/P (o modelo PIC12F675 também pode ser usado sem alterações). Este microcontrolador é encapsulado em um CI DIP de 8 pinos e possui memória de programa para 1K instruções e 64 bytes de Ram. Pode trabalhar com um clock interno de 4MHz e tem funções típicas de um microcontrolador como watchdog e timer.

Tirando os dois pinos de alimentação, os outros seis podem ser usados como entrada e saída digital. No meu projeto, 3 destes pinos são utilizados para programação in-circuit, dois são usados como entradas (para conexão das teclas) e o último é usado como saída para controlar o motor (clique para ampliar):

As teclas são ligadas diretamente entre os pinos de entrada e o terra; o microcontrolador será configurado pelo software para ativar os resistores de pull-up internos.

O Software

Apesar dos recursos limitados, é possível programar o PIC12F629 em C. Um arquivo de include (Servo.h) contém as opções de configuração (fuses) do PIC e as definições de nomes mais "amigáveis" para os pinos de entrada e saída:
#include <12F629.h>#device adc=8
#FUSES NOWDT //No Watch Dog Timer
#FUSES INTRC_IO //Internal RC Osc, no CLKOUT
#FUSES NOCPD //No EE protection
#FUSES NOPROTECT //Code not protected from reading
#FUSES MCLR //Master Clear pin enabled
#FUSES NOPUT //No Power Up Timer
#FUSES BROWNOUT //Reset when brownout detected
#use delay(clock=4000000)
 
#define MOTOR PIN_A2
#define BOTAO_RG PIN_A4
#define BOTAO_LF PIN_A5

O programa principal (Servo.c) começa com as definições de algumas constantes e variáveis:
#include "C:\Pic\Servo\Servo.h"

#define TMP_OVF 64 // tempo uSeg entre interrupções do timer
#define TMP_PERIODO 20000 // periodo do PWM em uSeg
#define TMP_BOTAO 500000l // tempo uSeg entre testes dos botões
#define DUTY_0 400 // tempo para 0 graus
#define DUTY_90 1000 // tempo para 90 graus
#define DUTY_180 1600 // tempo para 180 graus

unsigned int16 cnt_duty, cnt_periodo; // contadores para PWM
int16 cnt_botao; // contador p/ teste do botão
int16 duty; // duty atual

O programa principal consiste na programação do timer e das entradas e saídas. Em seguida o programa fica em um loop infinito, apenas tratando interrupções:
void main()
{
// configura os periféricos
#use fast_io(A)
set_tris_A(0xFB);
port_a_pullups(0x30);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
SET_TIMER0 (192); // conta de 192 a 255
setup_timer_1(T1_DISABLED);
setup_comparator(NC_NC);setup_vref(FALSE);

// inicia as variaveis
cnt_periodo = TMP_PERIODO / TMP_OVF;
cnt_duty = duty = DUTY_90 / TMP_OVF;
cnt_botao = TMP_BOTAO / TMP_PERIODO;

enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);

for (;;)
;
}

Alguns pontos de destaque:
  • O pragma fast_io(A) indica que o programa será responsável pelo controle de direção dos pinos. Sem este pragma o código gerado pelo compilador gastará um byte a mais e conterá instruções para garantir a direção antes de cada acesso.
  • Normalmente o timer conta de 0 a 255, volta a zero e recomeça a contagem. A interrupção de timer ocorre na passagem de 255 a 0. No meu programa estou forçando o timer a contar de 192 a 255, para reduzir o tempo entre as interrupções e obter uma precisão maior.

O processamento em si ocorre na rotina de interrupção, que ocorre a cada 64 microsegundos. A variável cnt_periodo conta as interrupções para determinar o período do PWM (20 milisegundos). Uma segunda variável, cnt_duty conta as interrupções para determinar o tempo que o sinal ficará em nível "1" (é este tempo que determina a posição do motor).

O botões são examinados apenas em intervalos "grandes" (a cada meio segundo). Isto fornece um debounce e deixa mais fácil observar o funcionamento do teste.
//////////////////////////////////////////////
// Interrupcao de tempo real
// Ocorre a cada 64 uSeg
//////////////////////////////////////////////
#int_RTCC
void RTCC_isr()
{
SET_TIMER0 (192); // conta de 192 a 255
cnt_periodo--;
if (cnt_periodo == 0)
{
// fim de um ciclo
cnt_periodo = TMP_PERIODO/TMP_OVF;
cnt_duty = duty;
output_high (MOTOR);

// testa botões
cnt_botao--;
if (cnt_botao == 0)
{
cnt_botao = TMP_BOTAO / TMP_PERIODO;
if (input (BOTAO_LF) == 0)
{
if (duty > ((DUTY_0 + TMP_OVF - 1) / TMP_OVF))
duty--;
}
else if (input (BOTAO_RG) == 0)
{
if (duty < ((DUTY_180 - TMP_OVF + 1) / TMP_OVF))
duty++;
}
}
else if (cnt_duty != 0)
{
cnt_duty--;
if (cnt_duty == 0)
output_low (MOTOR);
}
}
O Teste em Funcionamento

O vídeo abaixo mostra o projeto funcionando:

video

21 comentários:

André Oliveira disse...

Olá Daniel, eu sou o André do http://int2float.blogspot.com, intreressante o seu projeto, bem simples e funcional. Minha pergunta é, você testou vários modelos de servos de parabólica? Todos são iguias no ponto de vista do controle?
Valeu!

Daniel Quadros disse...

André,

Não fiz muitos testes, mas estes servos são vendidos como compatíveis entre si. Tenho dois modelos comigo, um Motorsat e um Gardiner. Minha impressão é que tem uma variação entre os dois em termos de largura x ângulo, mas não sei precisar bem quanto. Vou fazer mais testes e publico o resultado.

[]

Samuel disse...

Olá Daniel, sou iniciante e gostaria de saber qual programa você usou para
Compilar o código estou tentando com o mikro c e não esta dando certo, pode me ajudar?

Samuel disse...
Este comentário foi removido pelo autor.
Daniel Quadros disse...

Samuel,

Eu usei o CSC C. Não conheço o Mikro C, mas provavelmente vai ser preciso dar uma boa mexida no código. Algumas coisas que desconfio serem particulares do CSC C: #fuses, #device, #use, #int e as rotinas que chamo da biblioteca (set_tris, output_high, etc).

Anônimo disse...

Olá, ali no esquemático "PGM"o que é entra nele 3 pinos do pic e sai um fio para o gnd.Obrigado

Daniel Quadros disse...

O conector PGM é usado para a programação "in-circuit" (isto é, sem retirar o PIC do circuito). Eu o programador McFlash da Mosaico que utiliza um conector RJ12. Alguns detalhes adicionais estão nos posts http://dqsoft.blogspot.com/2008/08/microcontroladores-parte-4.html http://dqsoft.blogspot.com/2010/06/placa-prototipo-pic-projeto_23.html e http://dqsoft.blogspot.com/2010/06/placa-prototipo-pic-montagem.html

Anônimo disse...

Ola Daniel, primeiro gostaria de saber o que se refere aquele PGM no circuito eletrico?

Ah outra coisa estou tentando simular este projetinho no proteus porem aparace uma messagen de erro "effect of writing OSCCAL register not modelled".

NOTA: Nao estou utilizando o tal "PGM"...

ME ajuda Please!!!

cl@ disse...

Ola
nao tenho muito conhecimento em programação não sei fazer a programação deste exemplo, talves vc não me enviaria o arquivo .c para mim. obrigado
cladilb@hotmail.com

Junior disse...

Daniel, meu nome é junior, estou pedindo ajuda para controlar 4 micro-servo motor modelo SG90, estou utilizando uma variação do compilador ccs, eu quero saber com voce, se este codigo que voce postou serve para este servo, lembrando que estou utilizando o pic 18f2550 de 28 pinos.

Daniel Quadros disse...

Junior,

Não conheço este servo, mas o princípio do controle deve ser o mesmo: o tempo em que o sinal fica em "1" determina a posição do servo.

Deve dar para aproveitar a estrutura do código, porém no seu caso são quatro motores, portanto você terá quatro pares de variáveis para controle do "duty". Além disso, os valores dos contadores vão depender do clock que você estiver usando; aconselho usar um cristal externo e trabalhar com um clock maior que os 4MHz que eu utilizei.

Junior disse...

Daniel, meu nome é junior, ja testei seu codigo para controlar micro-servo motor(SG90), com o pic18f2550 utilizando a biblioteca SanUSB.h disponibilizada pelo grupo SanUSB e não obtive sucesso, mas seu codigo faz todo sentido no controle de pwm, mas eu não sei o que eu estou errando. Utilizo uma fonte de celular Nokia para alimentar 4 micro-servo motor, e utilizo o pic18f2550 com um cristal de 20MHz, a biblioteca utiliza o include do pic18f4550 mas diz que pode ser utilizado para a familia 18f. se voce puder me dar uma dica, desde já agradeco pela sua atenção.

Daniel Quadros disse...

Junior: em princípio o controle do servo é muito simples, basta gerar os pulsos com a largura correta. Não conheço a biblioteca SanUSB, portanto fica difícil ajudá-lo.

rooonijog disse...

junior
eu sei o seu problema
eh no codigo!
ja aconteceu comigo.
de olhada no cristal tbm
se tive duvida mande o codigo
q eu dou uma olhada
mande para jogad01@hotmail.com
se tiver a simulaçao no troteus pode mandar
fica mais facil de textar
ok

Anônimo disse...

olá
Faço engenharia mecanica e nao tenho muito conhecimento sobre servomotores, mas estou em um projeto que necessito muito de algum sistema que abra uma valvula hidraulica por meio eletronico, no caso o problema de utilizar servomotor é que tem um giro de 180 graus e gostaria de saber se pode me ajudar em como poderia utilizar um servomotor que de 8 voltas completas.
Se possivel me enviar um email ficaria muito grato, felipefadanni@yahoo.com.br

agradeço desde de já
felipe fadanni

Daniel Quadros disse...

Felipe,

Se voê quer um motor que dê voltas completas, o ideal é usar um stepper motor. O controle é bem mais trabalhoso, mas vale a pena. Estou preparando alguns posts a respeito, mas deve demorar um bom tempo até eu concluir e publicar.

deivid disse...

olá, daniel queria uma ajuda sua, queria controlar um servo motor para a direção de um carro de controle remoto, mas nem sei programar nada, queria uma força sua, se tiver enteressado aqui esta meu e-mail deividcosta_@hotmail.com

Anônimo disse...

Toma vergonha na cara e começa a usar software original! Pirataria é crime!

Daniel Quadros disse...

Anônimo: você poderia dizer para quem você está mandando este recado e a qual software você se refere? No meu caso eu usei um compilador C da CSC comprado direitinho.

Anônimo disse...

Olá, desculpa encomodar, mas como voce calculou pra achar esses valores de 0,90 e 180 graus?
#define DUTY_0 400 // tempo para 0 graus
#define DUTY_90 1000 // tempo para 90 graus
#define DUTY_180 1600 // tempo para 180 graus

estou aprendendo agora a programar pwm, e trabalhar com servo motor, e estou tendo um pouco de dificuldade ..

Daniel Quadros disse...

"Anônimo",

Estes valores foram obtidos experimentalmente. Algum tempo depois eu refiz os testes com um osciloscópio e encontrei valores diferentes:

http://dqsoft.blogspot.com.br/2010/03/ci-555-parte-4b-tira-teima-com-o.html