fbpx

Expresate

Si además quieres enviarnos un Artículo para el Blog y redes sociales, pulsa el siguiente botón:

Avisos
Vaciar todo

Servos y PWM

40 Respuestas
10 Usuarios
0 Reactions
15.2 K Visitas
ionthas
Respuestas: 153
Topic starter
(@ionthas)
Estimable Member
Registrado: hace 17 años

Buenas a todos.

Últimamente he estado mirándome el tema del PWM estoy intentando controlar un servomotor FUTABA 3003, pero tengo dos problemas.

En primer lugar, la mecánica del servo tiene un ángulo de 360 grados (ya que le quite el tope al trucarlo). Pero controlándolo por PWM el ángulo que puedo recorrer del máximo al mínimo es de menos de 50 grados. Me gustaría saber cómo puedo hacerlo para ampliar ese rango.

En segundo lugar, cuando el servo llega a la posición (media, mínima o máxima), se queda vibrando. ¿Eso es normal? En caso de que no lo sea me gustaría saber cómo puedo evitar que vibre. He comprobado que vibra ya que estoy todo el rato enviándole pulsos. ¿Como lo puedo hacer para que cuando el servo este posicionado donde lo quiero pare de enviar pulsos?

Adjunto el código que utilizo para controlar el servo:


#include <16F876A>
#device adc=8

#FUSES NOWDT //No Watch Dog Timer
#FUSES RC //Resistor/Capacitor Osc with CLKOUT
#FUSES NOPUT //No Power Up Timer
#FUSES NOPROTECT //Code not protected from reading
#FUSES NODEBUG //No Debug mode for ICD
#FUSES BROWNOUT //Reset when brownout detected
#FUSES LVP //Low Voltage Programming on B3(PIC16) or B5(PIC18)
#FUSES NOCPD //No EE protection
#FUSES NOWRT //Program memory not write protected

#use delay(clock=20000000)
#use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)

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

//Registros
#byte PORTB = 0x06
#byte TRISB = 0x86

//LED
#bit LED = PORTB.1

//SERVOS
#bit servo1 = PORTB.0

//Otros
#define ON 1
#define OFF 0

const int AJUSTE_FINO_DE_RTCC = 61;
const int ticks_PULSO_MINIMO = 62;
const int ticks_PULSO_MEDIO = 93;
const int ticks_PULSO_MAXIMO = 125;

int1 flagRTCC = 0;
int contRTCC = 0;
int1 flagSERVO1 = 0;
int tSERVO1 = ticks_PULSO_MEDIO;
int ValTIMER0;

#int_RTCC
void RTCC_isr()
{

++contRTCC;

if(contRTCC == 4)
{
set_TIMER0(AJUSTE_FINO_DE_RTCC);
}
if(contRTCC == 5)
{
flagRTCC = 1;
contRTCC = 0x00;
}

}

void main(){

//Configuracion
//---------------------------------------------------------------------------

setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_spi(SPI_SS_DISABLED);
setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
setup_timer_1(T1_DISABLED);
setup_timer_2(T2_DISABLED,0,1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);

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

setup_counters(RTCC_INTERNAL,RTCC_DIV_16);

// Configura les interrupciones.
enable_interrupts(global);
enable_interrupts(int_RTCC);

// Inicialitza el timer a 0
set_TIMER0(0);

TRISB = 0x00;
PORTB = 0

while(1){

// DISPARO DEL PULSO PWM
if(flagRTCC == 1)
{
flagRTCC = 0;
servo1 = ON;
flagSERVO1 = 1;
}

// CONTROL DE ANCHO DEL PULSO PWM
if(flagSERVO1 == 1)
{
valTIMER0 = get_TIMER0();

if(valTIMER0 > tSERVO1)
{
flagSERVO1 = 0;
servo1 = OFF;
}

}

}

}

Saludos y gracias.

Responder
39 respuestas
dragonet80
Respuestas: 1328
(@dragonet80)
Ardero
Registrado: hace 17 años

Copio aquí una pregunta que me ha enviado Egrit:
He estado leyendome este post tuyo http://foro.webdearde.com/viewtopic.php?f=21&t=2353&hilit=PWM&start=10#p25661 " onclick="window.open(this.href);return false; porque yo también necesito realizar el control de un servomotor mediante el PWM, y no quería subir un tema que tiene bastante tiempo ya abierto.

Te planteo mis dudas : 1º La señal que he de mandarle al servo debe tener como frecuencia los 50Hz o me vale cualquier otra frecuencia? Es que he estado preguntando y hay gente que me ha dicho que da igual el tiempo total de la señal, siempre y cuando el nivel alto de la señal esté comprendido entre los márgenes de trabajo del servo.
2º Cuando intento calcular los parámetros que quiero utilizar para generar esa frecuencia me encuentro con problemas porque con la ayuda del CCS no me aclaro, me surgen dudas y valores que se salen del rango comprendido para el timer escogido.

Si pudieras ayudarme con eso me harías un gran favor, es que quiero hacer un proyecto de un robot porque me hace mucha ilusión pero me he encontrado con estos problemas al programarlo. Te he escrito a ti porque he visto que controlas mucho el tema ese y la explicación me aclaró varias cosas pero no termino de cogerle el truco.
Gracias 😀

Un servo funciona de esta manera:
http://es.wikipedia.org/wiki/Servo " onclick="window.open(this.href);return false;

Por tanto, lo realmente importante es el ancho de pulso. Que le varies la frecuencia, a veces sigue funcionando, a veces produce desviaciones, eso puede depender de la electrónica del servo. Si te ajustas a los 50Hz te curas en salud y te aseguras que todos los servos funcionarán correctamente.

Para generar la frecuencia correcta primero tienes que tener en cuenta que resolución quieres tener y que timer vas a usar. Maneras de hacerlo puede haber muchas. Casi mejor que pongas lo que tú has hecho hasta el momento y así concretamos más.

Responder
egrit
Respuestas: 4
(@egrit)
New Member
Registrado: hace 15 años

Buenas, revivo esto después de mucho tiempo estancado el proyecto.
He vuelto a ponerme con el programa y he logrado hacer girar los servos en ambas direcciones, pero lo que no consigo es detener un servo y seguir moviendo el otro, escribo el código del programa que tengo y me comentais, saludos.
Con el programa lo que consigo es que los dos servos se muevan en la misma dirección.

#include <16F876A.h>
#fuses XT, NOWDT, NOPROTECT, NOLVP
#use delay (clock=4000000)
setup_counters(RTCC_INTERNAL,RTCC_DIV_1);
setup_timer_1(T1_DISABLED);
setup_ccp1(CCP_PWM);
setup_ccp2(CCP_PWM);
setup_timer_2(T2_DIV_BY_16,255,1);
void main()
{
while(true)
{
set_pwm1_duty(0);
set_pwm2_duty(110);
}
}

**EDITO: se me había olvidado decir que los tiempos calculados para esa configuracion de timer son:
0,9ms=110
1,5ms=183
2,1ms=250

Responder
bastian
Respuestas: 384
(@bastian)
Ardero
Registrado: hace 17 años

Hola!

Una pregunta: ¿El cristal que usas es de 4MHz?.

Puedo estar equivocado, pero mis cálculos son estos:

El periodo del PWM es:
PWMT=(PR2+1)*4*Tosc*(Preescaler TMR2)=256*4*(1/4MHz)*16=4.096ms
lo que da una frecuencia de PWMF=1/PWMT=1/4,096ms=244.14Hz

Por lo que yo se, el PWM de control de los servos es de 50Hz, y tu PWM casi multiplica por 5 esa velocidad, con lo que no creo que te funcione.

Para generar una frecuencia de 50Hz con el CCP configurado de esa manera necesitas una Fosc máxima de:
Tosc= PWMT/((PR2+1)*4*(Preescaler TMR2))=20ms/(256*4*16)=1,22us => 819,2Khz

Yo recuerto haberlo hecho conb un resonador de 500KHz, pero prefiero hacerlo con la interrupción del timer. 😉

Salu2!

Salu2!

Responder
egrit
Respuestas: 4
(@egrit)
New Member
Registrado: hace 15 años

Hola, exactamente Bastian, el cristal que uso es de 4MHz.
Con respecto a lo de la frecuencia de la señal, hice caso a estoPor tanto, lo realmente importante es el ancho de pulso. Que le varies la frecuencia, a veces sigue funcionando, a veces produce desviaciones, eso puede depender de la electrónica del servo. Si te ajustas a los 50Hz te curas en salud y te aseguras que todos los servos funcionarán correctamente.
Con esta configuración los servos funcionan pero no logro detenerlos, así que supongo que dragonet lleva razón.
Explicame mejor eso de como lo harias con la interrupción que dices.

Responder
bastian
Respuestas: 384
(@bastian)
Ardero
Registrado: hace 17 años

Hola!

Primero, te pido perdón, por que no me había leído el post completo... , yo no lo he probado con frecuencias tan altas, pero con frecuencias de hasta 60Hz funciona bien, a partir de 60 a mi me dio problemas y lo hago siempre de 50Hz. De todas formas si Dragonet80 dice que funciona... seguro que lo hace. 🙄 🙄 🙄

A otra cosa: Si en lugar de utilizar las unidades de captura para controlar un servo con cada una, utilizas un timer (las unidades de captura también lo utilizan) y su rutina de interrupción asociada puedes controlar con un timer hasta 8 servos (incluso hasta 10 pero yo no lo he probado).

Partimos de la siguiente base:

PWM de frecuencia 50Hz => Tpwm=20ms => con Ton entre 0,3 y 2,3 ms. Como lo realmente importante es controlar el ancho de pulso y este en ningún caso supera los 2,5ms, podemos dividir el periodo de un pwm (20ms) en 8 ciclos(un servo controlado en cada ciclo) de 2,5ms (8*2,5ms=20ms) y en cada uno de esos ciclos controlar con el timer el Ton del PWM de control de cada uno de los 8 servos.

En cada uno de esos ciclos, tendremos dos fases; en una controlaremos el Ton del PWM y en la otra el Toff para que los dos sumen el tiempo del ciclo que hemos decidido. Es decir, al principio de la fase 1 pones en On el PWM del servo 1 y temporizas (configuras el timer ) hasta que tengas que ponerlo en Off, que es donde empieza la fase dos. Ahí vuelves a temporizar hasta llegar al final del primer ciclo (final de la fase 2 y principio de la 3) que pondrás a uno el PWM del siguiente servo... y asi hasta el último.

Vamos a ponerlo con tu caso concreto:
2 servos = 2 ciclos de 10ms.

en el primer ciclo controlamos el primer servo
Fase 1 = Ton servo 1
Fase 2 = Toff servo 1= 10ms - Ton servo 1
En el segundo ciclo controlamos el servo 2
Fase 3 = Ton servo 2
Fase 2 = Toff servo 2= 10ms - Ton servo 2
Si sumas los tiempos de todas las fases siempre te da 20ms.
Bueno creo que quedará mucho más claro con una imagen:
PWM 2servo

Para conseguir eso tienes que configurar el timer y la interrupción asociada: por ejemplo con el timer 1:

#include <16f876.h>
#fuses XT,LVP
#use delay (clock=4000000)

#byte portA=0x05
#byte TRISA=0x85
#byte portB=0x06
#byte TRISB=0x86
#byte portC=0x07
#byte TRISC=0x87

#define PERIODO 65536-10000

#bit servoI=portC.2
#bit servoD=portC.1

//Variables control de servos
int16 Memo1,PWMI=1300; //Ancho de pulso servo1
int16 PWMD=1300; //Ancho de pulso servo2
int8 fase=0; //variable para contar fases del periodo

#int_TIMER1 //interrupcion timer 0
void TIMER1_isr (void)
{
switch (fase)
{
case 0: //Fase1
Memo1=PWMI;
set_timer1(65536-Memo1); //la proxima interrupción sera dentro de PWMI us
servoI=1;
fase++;
break;
case 1: //Fase 2
servoI=0;
set_timer1(PERIODO + Memo1); //la proxima interrupción sera dentro de PERIODO-PWMI us
fase++;
break;
case 2: //Fase 3
Memo1=PWMD;
set_timer1(65536-Memo2); //la proxima interrupción sera dentro de PWMD us
servoD=1;
fase++;
break;
case 3: //Fase3
servoD=0;
set_timer1(PERIODO + Memo2); //la proxima interrupción sera dentro de PERIODO-PWMD us
fase=0;
break;
}
}

void main (void)
{
TRISC=0xf9;
setup_timer_1(T1_INTERNAL|T1_DIV_BY_1); //Configuramos el timer 1
set_timer1(0xffff-100); //Tiempo de espera de 100us
enable_interrupts(INT_TIMER1); //Activamos las interrupciones asociadas al timer 1
enable_interrupts(GLOBAL);
/*
Precision del timer= 1us(1/4 Tclock) x 1 (preescaler)= 1us
Temporizacion máxima T=(1/4 Tclock)x(preescaler)x 65535
T= 1us x 1 x 65535 = 65535 us
Anchura del pulso para:

Servo parado = 1300us => T0=65535 - 1300 = 64235
Ancho real = 1300us

Servo adelante = 2300us => T0=65535 - 2300 = 63235
Ancho real = 2300us

Servo atras = 300us => T0 = 65535 - 300 = 65235
Ancho real = 300us

Periodo del ciclo:
T = 10000us => T0 = 65535 - 10000 = 55535 */
while (1)
{
//tu código aquí
}
}

Con esto, solo tienes que poner valores entre 300 y 2300 en las variables PWMD y PWMI para controlar dos servos (conectados a los pines 1 y 2 del puerto C.

Dos cosas más:

El código es un copia-pega de un programa que hice hace uno o dos años y que he modificado ligeramente para la ocasión, por lo que puede contener fallos, errores, etc... revisalo y adaptalo a tus necesidades.

En la temporización, no se tiene en cuenta el tiempo que tarda en ejecutarse la rutina de interrupción por lo que siempre durará unos cuantos microsegundos más de la cuenta. 😉

Salu2!

Responder
Página 6 / 8
Compartir: