TODOPIC
Microcontroladores PIC => Lenguaje C para microcontroladores PIC => Mensaje iniciado por: kanaryes en 01 de Mayo de 2008, 14:48:37
-
Hola a todos,
quisiera saber como puedo ir activando las distintas entradas analogas(AN0,AN1,AN2....) para que el conversor analogico digital me vaya transformando estas entradas anlogicas una a una, ya que por el momento solo puedo transformar una (AN0).
Con los bits CHS2 ~ CHS0 del registro ADCON0 (000 = AN0 .... 111 = AN7) selecciono al principio del programa con que entrada empiezo la conversion, pero al acabarla, debo volver a configurar el ADCON0 ??.
Dejo esquema del ciruito a implementar.
Gracias y un saludo :)
-
No se en que lenguaje quieres que te ayuden, pero por lo que hablas debe ser ensamblador.
Con el CCS es muy facil cambiar el multiplexor de las entradas analógicas.
En la ayuda está muy claro.
saludos.
-
Hola.
En CCS se debe hacer uso de la funcion set_adc_ports() ... para saber que combinacion de puertos analogicos se puede hacer debes ver en el datasheet. Ya en el bucle de conversion se escoge el canal con set_adc_channel() y se guarda el resultado con el read_adc().
Otra manera es con los registros ADCON0 y ADCON1, pero hay que definirlos con anterioridad con #byte ADCON0=xxx y se carga el valor igualando el registro con el valor correspondiente: ADCON0 = 0x01; por ejemplo. Si quieres modificar un bit del ADCONx se define con #bit ADON=ADCONx.xxx y listo. Las direcciones de los registros estan en el datasheet.
Chao
-
Saludos!
Con los bits CHS2 ~ CHS0 del registro ADCON0 (000 = AN0 .... 111 = AN7) selecciono al principio del programa con que entrada empiezo la conversion, pero al acabarla, debo volver a configurar el ADCON0 ??.
En efecto, con los bits CHS2:CHS0 de ADCON0 selecciones que canal vas a leer... así que si por ejemplo, terminaste de leer AN0 y quieres pasar a AN1, colocas CHS2:CHS0 en 001 (es decir, sólo pones en 1 CHS0) y listo, ya has seleccionado AN1 para la próxima lectura... y así sucesivamente... repito, no tienes que modificar todo el registro ADCON0, sólo esos bits en particular.
Eso sí, recuerda que entre una conversión y otra debes hacer una pequeña espera, de unos 40 o 50us... para que la conversión pueda comenzar de nuevo... para más detalles lee la datasheet... aunque no especificaste el pic que usas, pero creo que ese es un valor típico...
Bueno espero que sirvan los comentarios que te hemos dado, trata de ser más específico y decir qué lenguaje usas y el pic con el que trabajas para poder ayudarte mejor.
Ok nos leemos! :mrgreen:
-
Hola,
Gracias por las respuestas; Como el título del post y el propio foro indican, es lenguaje C y el pic16f877 :mrgreen:
Probare con los bits CH1:CH2 haber que tal.
Gracias y un saludo
-
Hola Kanaryes, No hace falta escribir sobre llos bits CH1:CH2 para cambiar el canal del ADC, puedes hacerlo muy facil con la instrucción set_adc_channel(). CCS recomienda evitar escribir sobre los registros en memoria directamente, puede haber conflicto si la aplicación intenta leerlos o escribirlos por su cuenta, solo hazlo si estas completamente seguro de que el compilador no esta tambíen interviniendo sobre los mismos bytes.
Con respecto al tema del tiempo de multiplexado de la entrada analógica 50 us es un valor que te puede andar en la mayoria de los casos y en otros no, si eres tan quisquilloso como yo y no te gusta desperciar ni un us de tiempo puedes leerte el PIC mid-range reference manual de Microchip, en el apartado del ADC de 10 bits esta como calcular los tiempos del ADC del 16F877.
Un saludo.
-
Saludos!
Pues como dice Gonzalo, no tienes que escribir en CH1:CH2 porque CCS lo hace por tí cuando usas la instrucción set_adc_channel()...
Ok nos leemos! :mrgreen:
-
Hola, respondiendo a tu pregunta mi solución seria:
#include <16F877A.h>
#device adc = 8
#fuses XT,....
#use delay(clock = 4000000)
#byte trisa=0x85
float V1,V2;
void Leer_ADC1(void){
setup_port_a( ALL_ANALOG );
setup_adc( ADC_CLOCK_INTERNAL );
set_adc_channel( 0 );
V1 = read_adc();
delay_ms(20);
setup_adc (adc_off);
}
void Leer_ADC2(void){
setup_port_a( ALL_ANALOG );
setup_adc( ADC_CLOCK_INTERNAL );
set_adc_channel( 1 );
V2 = read_adc();
delay_ms(20);
setup_adc (adc_off);
}
void main(){
set_tris_a(0x0F);
while (TRUE){
Leer_ADC1();
lcd_gotoxy(1,1);
printf(lcd_putc,"Volt= %5.2f",V1);
Leer_ADC2();
lcd_gotoxy(2,1)
printf(lcd_putc,"Volt= %5.2f",V1);
}
}
Puede que exista algun error de mala escritura pero si tengo mas tiempo, prometo enviar el codigo y la simulación.
Espero haya sido de ayuda el comentario....Saludos
-
Kcire, algunas consideraciones acerca de tu código, no necesariamente son errores pues el programa andaria de todas formas, solo voy a indicar algunas cosillas que pueden cambiarse para estilizar y optimizar un poco el código. He salteado los errores de sintaxis pues no vienen al caso:
1) No es necesario escribir en el registro trisa para configurar los puertos del ADC como entradas, salvo que estes utilizando el preprocesador #use fast_io, lo cual no es tu caso. La instrucción set_adc_channel configura el registro tris automáticamente.
2) Seleccionar todos los canales del puerto A como analógicos no es lo mejor. Hay que tratar de elegir la configuración de pines (Las diferentes posibilidades estan contempladas en el archivo de cabecera ".h") que más se adecue a la cantidad de canales que necesitamos, de lo contrario estamos facilitando que ingrese ruido al ADC, aparte del hecho de que desperdiciamos pines de nuestro puerto A.
3) No hay que utilizar el reloj interno del ADC si estamos trabajando con el PIC despierto y a frecuencias de cristal mayores a 1 MHz. Ya lo he explicado en otros mensajes y puede que sea pesado con ese tema :mrgreen:, pero no obtendras lecturas correctas si no cambias la instrucción setup_adc.
4) Estas apagando y configurando el ADC mas veces de lo estrictamente necesario, esto no es critico, pero no aporta a tu codigo mas que perdida de valiosos microsegundos. Recomiendo configurar el ADC solo durante la carga del pic y luego seleccionar el canal del ADC cuando necesitamos leer los mismos. Es más, si se puede seleccionar el canal varios ciclos de reloj antes de la lectura del ADC, nos evitamos tener que esperan con un delay justo antes de convertir la tensión.
5) La función read_adc() retorna el valor convertido en un tipo de datos int (para tu caso ya que #device adc=8) asi que asignarle una variable tipo float (y sobretodo es global, por lo que ocupa memoria en forma permanente) no es lo correcto. Si tienes que mostrarla luego en el printf en formato decimal, utiliza una sola variable float local (asi ocupa memoria tan solo un periodo corto de tiempo) que tome los valores de las variables int leidas del ADC.
6) El delay de 20 ms tiene varios problemas, primero, deberia estar antes de la función read_adc y despues de la instruccion set_adc_channel para poder dar tiempo al capacitor del Sample&Hold poder cargarse adecuadamente. Segundo, si cumple esta función, el retardo es excesivamente largo, con algunas decenas de microsegundos basta.
Kanaryes, esperamos tu respuesta acerca de como te ha quedado el código. Un saludo.
-
Saludos! :-)
Como siempre, excelente observaciones Dr. Gonzalo!
Ahora bien, tengo algunas cosas que quisiera me confirmaras...
1- En cuanto a lo de configurar el trisa, tengo entendido que cuando no se usa fast_io, el compilador escribe los valores en el tris cada vez que hay una instrucción que usa alguno de los pines del porta... lo cual hace que el programa final sea más largo... no es recomendable usar siempre fast_io, definir uno mismo el tris mediante set_tris_x y así el compilador no tiene que hacer la definición del tris repetidas veces?
2- En cuanto a esto:
void Leer_ADC1(void){
setup_port_a( ALL_ANALOG );
setup_adc( ADC_CLOCK_INTERNAL );
set_adc_channel( 0 );
V1 = read_adc();
delay_ms(20);
setup_adc (adc_off);
}
void Leer_ADC2(void){
setup_port_a( ALL_ANALOG );
setup_adc( ADC_CLOCK_INTERNAL );
set_adc_channel( 1 );
V2 = read_adc();
delay_ms(20);
setup_adc (adc_off);
}
No es mejor hacer una sola función para la lectura a/d, pasándole como parámetro el canal que se desea leer?
Por ejemplo, yo lo haría así:
int conversion (int canal)
{
int value;
set_adc_channel(canal);
delay_us(20);
value=read_adc();
return(value);
}
y luego llamar a la función desde el main, por ejemplo:
V1=conversion(0);
V2=conversion(1);
3- Esta parte:
setup_port_a( ALL_ANALOG );
setup_adc( ADC_CLOCK_INTERNAL );
No debería estar dentro del main, antes del while(TRUE)?
Bueno son solo algunas cosas... no sé que tan "detalles" serán... pero pienso que un buen programa debe ser lo más compacto posible... y evitar la repetición de instrucciones mientras no sea necesario... es así Dr. Gonzalo?
Ok espero las respuestas... y gracias kcire, por el aporte... bienvenido al foro! :)
Nos leemos! :mrgreen:
-
Hola Gonzalo, yo soy otro que usa el setup_adc( ADC_CLOCK_INTERNAL ); para frecuencias mayores de 1Mhz.
No te digo que no tengas razón, porque lo habrás comprobado, yo lo hago así porque se lo ví hacer a los de CCS. Este es un ejemplo ... y hay más:
/////////////////////////////////////////////////////////////////////////
//// EX_ADMM10.C ////
//// ////
//// This program displays the min and max of 30 A/D samples over ////
//// the RS-232 interface. The process is repeated forever. ////
//// This version uses a 10 bit ADC. ////
//// ////
//// Configure the CCS prototype card as follows: ////
//// Insert jumper from output of POT to pin A5 ////
//// Use a 10K POT to vary the voltage. ////
//// ////
//// Jumpers: ////
//// PCM,PCH pin C7 to RS232 RX, pin C6 to RS232 TX ////
//// ////
//// This example will work with the PCM and PCH compilers. The ////
//// following conditional compilation lines are used to include a ////
//// valid device for each compiler. Change the device, clock and ////
//// RS232 pins for your hardware if needed. ////
/////////////////////////////////////////////////////////////////////////
//// (C) Copyright 1996,2003 Custom Computer Services ////
//// This source code may only be used by licensed users of the CCS ////
//// C compiler. This source code may only be distributed to other ////
//// licensed users of the CCS C compiler. No other use, ////
//// reproduction or distribution is permitted without written ////
//// permission. Derivative programs created using this software ////
//// in object code form are not restricted in any way. ////
/////////////////////////////////////////////////////////////////////////
#if defined(__PCM__)
#include <16F877.h>
#device ADC=16
#fuses HS,NOWDT,NOPROTECT,NOLVP
#use delay(clock=20000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)
#elif defined(__PCH__)
#include <18F452.h>
#device ADC=16
#fuses HS,NOWDT,NOPROTECT,NOLVP
#use delay(clock=20000000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)
#endif
void main() {
int i;
long value, min, max;
printf("Sampling:");
setup_port_a( ALL_ANALOG );
setup_adc( ADC_CLOCK_INTERNAL );
set_adc_channel( 0 );
do {
min=0xffff;
max=0;
for(i=0; i<=30; ++i) {
delay_ms(100);
value = Read_ADC();
if(value<min)
min=value;
if(value>max)
max=value;
}
printf("\n\rMin: %4LX Max: %4LX\n\r",min,max);
} while (TRUE);
}
Un saludo
-
Hola firepic, gracias por el cumplido :-/ :-/ :-/
Vamos a analizar lo que has comentado en tu ultimo mensaje. Con respecto a lo que has dicho en el punto 1, no puedo estar mas de acuerdo contigo, utilizar #use fast_io ahorra tiempo al procesador ya que las instrucciones y funciones que vienen incluidas con el CCS (conocidas comunmente como built-in functions) no pierden tiempo configurando el registro tris cada vez que son llamadas. La unica desventaja (por asi decirlo, aunque realmente no lo sea) que esto conlleva es que la persona que realiza el programa debe tener pleno control del estado de los puertos durante cada instante del programa, y configurar los mismos mediante set_tris cuando se lo requiera.
El metodo #use fixed_io es otro método tambien muy útil sobretodo si la direcciones de los puertos no cambian mucho durante el transcurso del programa. El problema del fixed_io es que la mayoria de la gente malinterpreta su función y lo asimila mucho a la configuración #use standart_io, cuando en realidad es muy similar, sino identica, a la fast_io cuando en tiempo de ejecución hablamos. La confusión se da, a mi entender, porque en la ayuda de CCS no se explica adecuamente el tema (para variar :?) lo que da lugar a malinterpretaciones del mismo. No se a ustedes que les parece, pero no quiero explayarme mas sobre el fixed_io porque deformaria la temática original del post, asi que sigamos con el ADC :-).
firepic, con respecto a lo que has expuesto en el segundo y tercer punto, tambien estoy complementamente de acuerdo contigo y te aplaudo de pie, pues has explicado en forma breve y concisa como debería realizarse correctamente el programa.
Con respecto a los "detalles" los he subestimado de tal forma para no parecer un maniatico obsesivo por la optimización, limpieza y orden de codigo, que, en realidad lo soy :D :D. El problema de programar en lenguaje C ,y en realidad cualquier lenguaje que no sea ASM, es que es muy fácil colocar cosas de mas y derrochar recursos de nuestro PIC, a veces a tal punto de necesitar uno más potente o con más memoria que el esencialmente requerido.
Pocher, a mi me gusta mucho CCS, creo que dentro de todo es un buen programa, pero he aprendido con las malas que ni la ayuda ni los ejemplos que ofrecen son del todo correctos, uno puede darse cuenta de ello dandose una vueltita por el foro de la pagina del compilador donde los usuarios cuentan realmente la verdad de las cosas (extraña coincidencia, lo de fixed_io es un claro ejemplo de ello). Microchip hace referencia a las limitaciones del oscilador RC interno del ADC en muchas de sus Application Notes y reference manual's. Puedes leer este tema si te interesa, a lo largo de el hablo varias veces sobre la configuración del oscilador del ADC:
Seguimiento a los voltajes de I/O de un trafo (http://www.todopic.com.ar/foros/index.php?topic=21209.0)
Quiero terminar mostrando una taba sacada del Microchip PIC mid-range MCU reference manual, en el apartado del ADC de 10 bits:
(http://s1.subirimagenes.com/otros/previo/thump_463998osciladores-adc.jpg)
Lean lo que dice la nota 4 que se aplica a la fila de la tabla que ocupa el oscilador RC interno, traduzco:
Para frecuencias del dispositivo por encima de 1 MHz, el dispositivo debe estar en modo Sleep durante toda la conversión, o la precisión del ADC puede estar fuera de las especificaciones.
En otros articulos el tema se trata con un poco más de profundidad, sin embargo queria exponer esa simple consideración.
Nos estamos escribiendo.
-
Saludos! :-)
Gracias Dr. Gonzalo por tus explicaciones! :-/ Muy interesante lo del punto 4 que mencionas de la DS. En lo que tengas un chance me gustaría que abrieras un hilo explicándo eso mejor... puedes? :?
Ok nos leemos! :mrgreen:
-
La Nota4 de Microchip es muy directa, entonces en el ejemplo de CCS donde utilizan una fosc=20Mhz, sí ó sí debieran de haber usado un adc_div_32, sin embargo mirando la tabla el TAD está justo en la frontera del TADmín=1,6us ... creo que la única forma de salir de dudas es comprobándolo prácticamente como tú has hecho y en próximos proyectos lo voy a tener en cuenta.
-
Hola Gonzalo_BlackHawk, muchas gracias por el comentario la verdad que si concuerdo con lo que dices pues la verdad yo di el codigo sin considerar esos factores, y a primera mano, bueno tengo dudas sobre los puntos 3 y 4, ya que no me quedaron claros, y la verdad no quisiera quedar con esa ?, si podrias justificar eso que dices seria de mucha ayuda, gracias de antemano.
Adjunto la simulación en Proteus del ADC con 2 entradas espero lo vean todos quienes estan interesados, y espero sus comentarios que la verdad son de mucha ayuda.
Saludos Kcire
-
Hola Kcire, la verdad que muy bueno el circuito, lo unico que veo que no has corregido es que estas apagando y encendiendo y volviendo a configurar el ADC cada vez que tomas una medicion. Salvo que necesites ahorrar energia, coloca las instrucciones setup_adc(adc_clock_div_32) y setup_adc_ports( RA0_RA1_RA3_ANALOG) al principio del main y no apagues nunca el ADC.
Asi ahorraras instantes cada vez que conviertas un valor, y como el proceso es continuo, esos instantes se transforman en muchas mas mediciones para una determinada cantidad de tiempo.
Prometo volver para explicar lo del ADC, estoy medio apurado y no llego a escribir todo, a ver si el fin de semana puedo sentarme :-).
Un saludo.
-
Hola, estoy utilizando el compilador de PICC Lite, tengo 3 potenciómetros conectados a las patillas RA0, RA1 y RA2. En lo que estoy atascado es en saber una vez que dentro de la interrupción del ADC, cuando el flag de ADIF==1, cómo recorro los puertos para leer los valores y cómo saber ese valor convertidoa qué entrada corresponde.
Muchas gracias por las respuestas.
void interrupt convertir (void){
if(ADIF==1)
{
ADIF=0; //Bajo banderita
temp=(ADRESH<<8)+ADRESL; //Justifico a la derecha
templcd=hexadec(temp); //A decimal
iniciar_lcd();
lcd_control(0x80); //Pone el mensaje en la 1ª fila
prueba(); //Envia al LCD
if (templcd<RA1){ //si la T actual es menor que la que introducimos
if (RA1>RA2){ //Si la Tª min rango>Tmax rango=>Error
lcd_control(0x80); //Primera fila del lcd
error(); //Rango mal introducido
RC0=1; //apaga led calor xq hay error en rango
}
else{
lcd_control(0x80); //fila 1 lcd
RC0=0; //apago led calor RA1
prueba3(); //mostramos calor en el lcd
}
}
-
Saludos kanaryes!
Pues no he trabajado con PICC Lite, sino con CCS; pero me imagino que el principio es muy parecido.
Por ahí en uno de mis mensajes te aporté un código, no sé si lo has visto, es este:
int conversion (int canal)
{
int value;
set_adc_channel(canal);
delay_us(20);
value=read_adc();
return(value);
}
y luego llamar a la función desde el main, por ejemplo:
V1=conversion(0);
V2=conversion(1);
De manera que desde el main, dentro del while, llamas a la función para convertir el canal analógico digital, y el canal que se está leyendo está guardado en la variable canal. Si colocas esa variable como global podrías leerla desde la función de la interrupción por ADC y listo, tienes qué pin estás leyendo....
También podrías hacer en el main algo más elegante como:
int pin=0;
// **********
Valor[pin]=conversion(pin);
pin++;
if (pin==3) pin=0;
Así pin tomaría los valores 0,1,2... y leería correspondientemente los canales 0,1 y 2 del pic, guardándolos en Valor[0], Valor[1] y Valor[2], respectivamente.
Bueno es una sugerencia.
No sé si eso es lo que estabas buscando... espero haberte ayudado en algo.
Ok nos leemos! :mrgreen:
donde pin serían los valores