Este ejemplito no es realmente un nuevo ejemplito, es mas bien un gazpacho de dos o tres de los anteriores. Me explico: estaba esta mañana pensando qué hacer y se me ocurrió buscar alguna aplicación de algo ya funcionando, entonces pensé en hacer algo con la temperatura y algun accionador en función de ella, algo así como un termostato. Pero tenía que tener niveles, no solo el ON/OFF típico de los termostatos, y entonces se encendió la lucecita y se me ocurrió posicionar un Servo en función de la lectura de temperatura.
Y este es el resultado. Una mezcla entre los Ejemplitos anteriores:
Recibiendo del RS232 sobre un Buffer y procesandolo posteriormente, Temperatura con un LM35a y el Controlando un SERVO con el PIC desde nuestro PC.
He intentado explicar exhaustivamente el codigo para indicar exactamente lo que hago en cada línea de forma que tenga poco que explicaros aquí, sin embargo deseo haceros una descripción general de su funcionamiento, y hacer hincapié en algunas de las funciones o trucos implementados.
He partido originalmente del código de <a href="
http://miarroba.com/foros/ver.php?foroid=46840&temaid=4742913" target="_blank">Controlando un SERVO con el PIC desde nuestro PC.[/url]
Como en este código obtengo una señal cada 20 milisegundos, para generar el periodo del
pulso PWM para el servo, la primera modificación que introducí fue la de contar 50 de estas señales para obtener otra cuya periodicidad fuese de
1 segundo.
Este
segundero era el que iba a utilizar para tomar las
muestras de temperatura, no quería que el servo corriese locamente intentando seguir la temperatura cada pocos milisegundos.
Tras eso introducí la función de toma de temperaturas que me devolvía ésta ya convertida en
grados centígrados, y con ella implementé un pequeño truco, realmente una simple regla de tres, para mover el Servo en función de lo leído pero solo en
7 posiciones prefijadas.
Como el Menú tiene definidas 7 posiciones prefijadas ajusté el máximo de temperatura leída a
35 grados centígrados y esto lo hago
igual a 7, en el otro extremo
5 grados centígrados son
igual a 1. Valores mayores de temperatura mantienen el resultado de 7 y menores a 1 matienen el 1. Y con este valor
simulo que he recibido por la
RS232 los caracteres del "1" al "7" que son los que realmente comandan al Servo.
Y como no iba a estar esperando a que la temperatura de mi habitación cambiase entre estos valores tan extremos habilité por
PIN_B0 una resistencia de
calentamiento pegada al
LM35A, el sensor de temperatura. Con los comandos RS232 "
a" y "
d" encendía o apagaba la resistencia y así podia "
forzar" la temperatura y por ende la respuesta del Servo.
Y por último le coloqué los comandos "
z" y "
p" para activar o desactivar el seguimiento de temperatura del Servo. Y con ello dí por finalizado este ejemplito.
Nota importante: Notad que todas las transmisiones vía RS232 y las lecturas de temperatura y su ajuste a los valores necesarios para calcular la posición del Servo se ejecutan siempre
durante el semiperiodo bajo del pulso PWM, esto lleva a que la duración máxima de ejecución de estas funciones deben ser siempre menores a dicho tiempo. (
Ver la nota correspondiente en el código).
// servo_pwm_232_plus_temperatura
// Ejemplo con un servo HITEC HS-300 CW
// Alimentación y pulsos a 5V
// y un LM35a Sensor de Temperatura
// ajustado para:
// Pulso Minimo al Servo para 5º y
// Pulso Maximo al Servo para 35º
// Cuadro de Tiempos :
// Periodo 20 ms (Frecuencia 50 Hz)
// Ancho Pulso minimo 0.75 ms
// Ancho pulso medio 1.50 ms
// Ancho pulso maximo 2.25 ms
// TMR0 a 1:16 -> 1 RTCC cada 4.096 ms
// -> 1 Tick cada 0.096 / 256 = 0.016 ms
// -> 20 ms = (4 x RTCC completas) + (1 * RTCC - 30 ticks)
#include <16f876a.h>
#fuses XT,NOWDT,NOPROTECT,NOLVP,PUT,BROWNOUT
#use delay(clock=4000000)
#use standard_io(b)
#use rs232(baud=19200, xmit=PIN_C6, rcv=PIN_C7) // Ajusto a 19200 baudios para tardar lo menos posible
// en transmitir por la RS232 lo que sea
// ver Nota en la función funciones_tras_duty_cicle()
#define PIN_SERVO1 PIN_B0
const int AJUSTE_FINO_DE_RTCC = 30;
const int ticks_PULSO_MINIMO = 50;
const int ticks_PULSO_MINIMO2 = 63;
const int ticks_PULSO_MINIMO1 = 78;
const int ticks_PULSO_MEDIO = 95;
const int ticks_PULSO_MAXIMO1 = 108;
const int ticks_PULSO_MAXIMO2 = 125;
const int ticks_PULSO_MAXIMO = 140;
int1 flagRTCC = 0;
int1 flagSEGUNDO = 0;
int contRTCC = 0;
int contSEGUNDO = 0;
int1 flagSERVO1 = 0;
int tSERVO1 = ticks_PULSO_MEDIO;
char Keypress = 0;
int adc_temperatura = 0;
int kx = 0;
int1 flagSEGUIDOR = 0;
int1 flagXMITservo = 0;
int1 flagXMITtemperatura = 0;
int1 flagXMITsegon = 0;
int1 flagXMITsegoff = 0;
int1 flagXMITmenu = 0;
#int_rda
void rda_isr() {
Keypress=0x00; // limpio la variable de recepción
if(kbhit()){ // si hay algo pendiente de recibir
Keypress=getc(); // lo obtengo y lo guardo en la variable de recepción
}
}
#int_RTCC
RTCC_isr(){ // RTCC esta ajustada a saltar cada 4.096 ms
++contRTCC; // cuento RTCCs para conseguir 20 ms (el periodo del PWM)
if(contRTCC==4){ // si es la cuarta RTCC que salta
set_TIMER0(AJUSTE_FINO_DE_RTCC); // pongo el valor del timer para que la quinta sea mas corta
}
if(contRTCC==5){ // Si he llegado a la quinta
flagRTCC=1; // pongo en alto el flag que indica que han pasado 20 ms
contRTCC=0x00; // y vuelvo a comenzar
}
}
void menu(void){
// Presento por la RS232 el menú de opciones
printf("TERMO-SERVO Commander" );
printf("[ m ] Este menú" );
printf("[ s ] Transmite valor Servo" );
printf("[ + ] Incrementa pulso" );
printf("[ - ] Decrementa pulso" );
printf("[ t ] Lee y transmite Temperatura" );
printf("[ a ] Activa calentador" );
printf("[ d ] Desactiva calentador" );
printf("[ z ] Activa seguidor de Temperatura" );
printf("[ p ] Desactiva seguidor de Temperatura" );
printf("[1] D.C. 0.75 ms *Minimo" );
printf("[2] D.C. 1.00 ms" );
printf("[3] D.C. 1.25 ms" );
printf("[4] D.C. 1.50 ms *Medio" );
printf("[5] D.C. 1.75 ms" );
printf("[6] D.C. 2.00 ms" );
printf("[7] D.C. 2.25 ms *Maximo" );
printf("Servo al centro." );
}
int lee_temperatura(void){
int grados_temperatura =0x00;
// Lectura del canal 1 -> AN1 LM35a
set_adc_channel(1); // selecciono el canal a convertir
adc_temperatura=read_adc(); // realizo la conversion
grados_temperatura = (int) ((adc_temperatura * 391) / 1000); // convierto a grados centigrados
return(grados_temperatura);
}
void notifica(void){
// Transmite Menu
if(flagXMITmenu==1){
flagXMITmenu =0;
menu();
}
// Transmite Valor del Servo
if(flagXMITservo ==1){
flagXMITservo = 0;
printf(" S=%u
",tSERVO1);
}
// Transmite Temperarura
if(flagXMITtemperatura ==1){
flagXMITtemperatura = 0;
printf(" T=%uº
",lee_temperatura());
}
// Transmite Seguidor ON
if(flagXMITsegon==1){
flagXMITsegon =0;
printf(" Seguidor de Temperatura ON
" );
}
// Transmite Seguidor OFF
if(flagXMITsegoff==1){
flagXMITsegoff =0;
printf(" Seguidor de Temperatura OFF
" );
}
}
void seguidor(void){
int t, x;
// SEGUIDOR de TEMPERATURA
if(flagSEGUIDOR==1){ // Solo compruebo t si esta habilitado
if(flagSEGUNDO==1){ // Solo compruebo t si ha pasado 1 segundo
flagSEGUNDO=0;
t = lee_temperatura(); // leo la temperatura real
x = (int) (t * 7) / 35; // Ajusto 35º a 7 y demas temperaturas proporcionalmente
if(x>7){ x=7; } // Si me sale mayor que 7 igualo a 7 (7ª posicion del servo)
if(x<1){ x=1; } // Si me sale menor que 1 igualo a 1 (1ª posicion del servo)
if(x!=kx){ // Si ha cambiado la temperatura desde la ultima vez la transmito
printf(" T=%uº %u
",t,x);
kx=x;
Keypress="0"+x; // Simulo que pulso la tecla correspondiente al valor de x
}
}
}
}
void funciones_tras_duty_cicle(void){ // ¡Importante! La duración maxima de esta funcion debe ser menor
int1 flag; // al periodo del pulso PWM menos el maximo Duty Cicle previsto
// o sea menor a 20 ms - 2.5 ms = 17.5 ms
// porque de lo contrario se solaparían el nuevo pulso con
// una nueva ejecucion de funciones_tras_duty_cicle()
// si hay algo que notificar
flag = flagXMITmenu || flagXMITservo || flagXMITtemperatura || flagXMITsegon || flagXMITsegoff;
// lo notifica
if(flag==1){
notifica();
}
// y tras notificar ejecuta el seguidor
seguidor();
}
void main() {
int ValTIMER0;
delay_ms(25); // Espero a que se estabilice antes de comenzar
tSERVO1 = ticks_PULSO_MEDIO; // Inicializo el Servo al centro
setup_adc(ADC_CLOCK_INTERNAL); // Le digo al converdor AD que el clock es el interno
setup_adc_ports(RA0_RA1_ANALOG_RA3_REF); // Que vamos a convertir AN0 y AN1 y que AN3 es el voltaje de referencia
setup_counters(RTCC_INTERNAL,RTCC_DIV_16); // Ajusto los contadores a preescaler 1:16 o 1 RTCC cada 4.096 ms
output_low(PIN_B5); // Me aseguro de que el calentador esta apagado
menu();
enable_interrupts(int_rda); // Habilito la interrupción de rececpción rs232
enable_interrupts(INT_RTCC); // Habilito la interrupción deL TIMER0
set_TIMER0(0); // Inicialico el TIMER0 a 0
enable_interrupts(global); // Habilito todas las interrupciones habilitadas
do {
// DISPARO DEL PULSO PWM
if(flagRTCC==1){ // Si han pasado 20 ms ...
flagRTCC=0; // indico que me he enterado
output_high(PIN_SERVO1); // y pongo en alto el pulso
flagSERVO1=1; // e indico que el pulso está en alto
// Cuento 50 veces el pulso (de 20 ms) para un segundo
++contSEGUNDO; // incremento el contador de veces que ocurren 20 ms
if(contSEGUNDO==50){ // si he contado 50 veces
flagSEGUNDO=1; // indico que ha pasado un segundo completo
contSEGUNDO=0; // y vuelvo a comenzar
}
}
// CONTROL DE ANCHO DEL PULSO PWM
//( y otras funciones si proceden tras final del Duty Cicle )
if(flagSERVO1==1){ // Si el pulso del servo esta en alto
valTIMER0 = get_TIMER0(); // obtengo el valor actual del Timer
if(valTIMER0>tSERVO1){ // y si alcanza el valor predeterminado para cortar el pulso
output_low(PIN_SERVO1); // pues lo corto y me quedo tan tranquilo
flagSERVO1=0; // pero lo indico poniendo a bajo el correspondiente indicador
funciones_tras_duty_cicle(); // y tras cortarlo ejecuto cosas mientras no hay Duty Cicle activo
} // ¡IMPORTANTE! ver nota en la funcion funciones_tras_duty_cicle()
}
// CONTROL DESDE LA RS-232
if(Keypress!=0x00 && flagSERVO1==0){
switch(Keypress){
// Periodos Prefijados
case "1": tSERVO1=ticks_PULSO_MINIMO;
break;
case "2": tSERVO1=ticks_PULSO_MINIMO2;
break;
case "3": tSERVO1=ticks_PULSO_MINIMO1;
break;
case "4": tSERVO1=ticks_PULSO_MEDIO;
break;
case "5": tSERVO1=ticks_PULSO_MAXIMO1;
break;
case "6": tSERVO1=ticks_PULSO_MAXIMO2;
break;
case "7": tSERVO1=ticks_PULSO_MAXIMO;
break;
// Ajuste manual
case "+": if(++tSERVO1>ticks_PULSO_MAXIMO){
tSERVO1=ticks_PULSO_MAXIMO;
}
break;
case "-": if(--tSERVO1<ticks_PULSO_MINIMO){
tSERVO1=ticks_PULSO_MINIMO;
}
break;
// Recupera informacion
case "m": flagXMITmenu = 1;
break;
case "s": flagXMITservo = 1;
break;
case "t": flagXMITtemperatura = 1;
break;
// Actuadores
case "a": output_high(PIN_B5);
break;
case "d": output_low(PIN_B5);
break;
case "z": flagSEGUIDOR = 1;
flagXMITsegon = 1;
break;
case "p": flagSEGUIDOR = 0;
flagXMITsegoff = 1;
break;
}
Keypress=0;
}
} while (TRUE);
}
El resultado de una ejecución podeís verlo en la siguiente imagen, en la que muestro como iniciando de la temperatura ambiente de mi habitación activo primero el calentador y tras algunos minutos lo desactivo. La subida y bajada correspondiente de la temperatura la monitorizo, y como podeis ver, el número que acompaña a la temperatura simula la tecla enviada para posicionar al servo en cada una de las posiciones prefjadas en el menú:
Una aplicación práctica de esto puede ser, por ejemplo, el abrir mas o menos una ventana en función de la temperatura del interior, o aplicar distintas velocidades a un ventilador PWM ....
Esto esto todo por ahora, amigos Picmaníacos.