Usando el ADC del LPC2129

En esta entrada mostraré la utilización del ADC sincronizada con uno de los timers del LPC2129. Así mismo, también veremos la forma de utilizar las interrupciones del ADC.

Cabe mencionar que los periféricos de los microcontroladores de NXP son básicamente idénticos entre diferentes números de parte, por lo que este tutorial puede servir también a otros miembros de la familia LPC2000, así como de la reciente LPC1000 (Cortex M). Lo que sí va a cambiar es el manejo de las interrupciones, ya que ARM7TDMI utiliza el VIC, mientras que Cortex-M utiliza el NVIC.

El ADC es de 10 bits, pero también puede ser configurado a un número menor de bits de resolución. Entre menos bits más rápida es la conversión. Así mismo, existen dos maneras de realizar las conversiones: modo burst, y modo software controlled. En modo burst se realizan las conversiones lo más rápido posible, porque una vez que una conversión está lista, comienza la siguiente, es decir, una conversión tras otra a máxima velocidad. Y en el modo software controlled se tienen diferentes formas de disparar las conversiones:

  • Bajo control del usuario
  • Bajo control de un pin externo
  • Bajo control de un eje (subida o bajada) de los registros MAT de los timers

La que nos va a interesar es la última, ya que podemos sincronizar las conversiones con una fuente estable de tiempo. Aunque para ello también tendremos que configurar a uno de los timers del micro.

El ADC del LPC2129 tiene 4 canales, y nosotros podemos decidir qué canales utilizar para nuestra aplicación en particular. Tanto en modo burst como en modo software controlled las conversiones se llevan a cabo en scan mode. Esto es, en caso de tener habilitados más de un canal del ADC, las conversiones se realizarán a partir del canal más bajo hasta el canal más alto, y el ciclo se repite indefinidamente. Por ejemplo, supóngase que se tienen habilitados los canales AD1 y AD3. Entonces la primer conversión corresponderá a AD1, la segunda conversión a AD3, la tercer conversión a AD1, y así sucesivamente. Si tuviéramos tres canales habilitados, AD0, AD1 y AD3, la secuencia sería así: AD0->AD1->AD3->AD0->AD1…

Para lidiar con esto, el registro de resultado de la conversión no sólo incluye el valor leído, sino también el canal que generó la interrupción. De esta manera podemos preguntar por el canal que generó la más reciente conversión.

Otra cosa que es importante configurar correctamente es el clock del ADC. Según el datasheet, éste debe correr como máximo a 4.5MHz, independientemente de la velocidad del sistema. El valor se obtiene dividiendo el PCLK entre 4.5MHz. Por ejemplo, normalmente los LPC2129 corren a su máxima velocidad, que es de 58.9824 MHz, así que 58.9824M / 4.5M = 13.01. El valor entero más cercano es 13, pero 58.9824M / 13 = 4.53MHz, con lo cual nos estaríamos pasando del valor límite. Así que escogemos el siguiente entero, 14. Entonces tenemos que 58.9824M / 14 = 4.21, con lo cual ya estamos dentro del rango máximo permitido. Este valor, 14, se escribirá en los bits 8..15 del registro ADCR.

Debo mencionar algo, los canales del timer que se usan para sincronía no son arbitrarios, ya están definidos y no se pueden utilizar otros (ver bits 24..26 de ADCR).

Finalmente, si queremos que las conversiones generen interrupciones, entonces hay que hacérselo saber al módulo ADC en el registro ADINTEN. Lo interesante de esto es que podemos tener unos canales del ADC que generen interrupciones una vez que la conversión esté lista, mientras que otros canales también habilitados no lo hagan.

A continuación se encontrarán snippets para utilizar al ADC sincronizado con el timer. Las especificaciones del ejemplo son las siguientes:

Canales habilitados actualmente: 1 (AD3), pero se pueden agregar fácilmente otros.

Timer utilizado: T1. T1, en conjunto con el registro match MR3, se utiliza para generar al tick del sistema a 1ms. El flanco de subida en MR0 se utilizará como sincronía para el ADC, pero éste no generará ninguna interrupción.

La conversión en AD3 generará una interrupción.

Compilador: GCC

Tarjeta de desarrollo: Olimex-LPC2129

ADC

uint32_t adcVal3;
void adc_ISR(void)
{
 uint32_t r,ch;
 r=ADGDR;
 ch=(r>>24)&0x07;
 adcVal3=(r>>6)&0x3FE;

/*
 * La conversión inicia en el flanco de subida de MAT1.0; sin embargo, si no
 * se hace nada al respecto, dicho pin nunca regresará al estado bajo, por
 * lo cual únicamente se generará una interrupción (la primera).
 *
 * Para hacer que el pin MAT1.0 regrese al estado bajo para que el ciclo
 * inicie la siguiente vez es necesario "resetearlo" por software. Esto se
 * logra poniendo a cero el bit '0' de T1EMR, el cual controla a MAT1.0
 */
 T1EMR&=~BIT(0);

/*
 * Se limpia la interrupción
 */
 VICVectAddr=0;
}
void adc_init(void)
{
 PINSEL1|=(1<<22)|(1<<24)|(1<<26)|(1<<28);

/*
 * B.8..15 = 14 (58M / 14 = 4.1M)
 * B.16 = 0 (Software controlled)
 * B.17..19 = 0 (10 bits, 11 clocks)
 * B.21 = ADC On
 * B.27 = 0 (start conversion on rising edge)
 */
 ADCR=(14<<8)|(0<<16)|(0<<17)|(1<<21)|(0<<27);

 /*
 * AD3 generará un interrupción cada vez que la conversión esté lista
 */
 ADINTEN=BIT(3);

/* conecta el AD3, que es donde está conectado el potenciómetro (para
 * pruebas)
 * 
 */
 ADCR|=BIT(3);

/*
 * Configura las interrupciones
 */
 VICIntSelect&=~BIT(18); /* IRQ */
 VICIntEnable|=BIT(18); /* En */ 
 VICVectCntl1=(18)|BIT(5); /* Prioriridad 1, vectorizada */
 VICVectAddr1=(unsigned long) adc_ISR;

/*
 * Limpia cualquier interrupción pendiente
 */
 VICVectAddr=0;
}

void adc_Start(void)
{
 /*
 * Start conversion when the edge selected by bit 27 occurs on MAT1.0
 */
 ADCR|=(6<<24);
}

Timer

void tick_isr(void)
{
 static int i=0;
 tim0++;
 if(tim_delay>0)  tim_delay--;

 T1IR=(1<<3);
}
/*
 * El tick está implementado sobre T1MR3
 */
void tick_init(void)
{
 /*
 * detiene al timer
 */
 T1TCR=2;

/*
 * El TC se incrementa debido a PCLK
 */
 T1CTCR=0;

/*
 * Interrupción y reset en MAT1.3 (ancho de pulso)
 */
 T1MCR|=(3<<9);

/* 
 * Base de tiempo de 1us=58982400Mhz/1.0Mhz
 */
 T1PR=59;

/*
 * el tick está corriendo a 1ms=1us*1000
 */
// T1MR3=1000;
 T1MR3=982;

/*
 * el ADC se sincroniza con el flanco de subida en MAT1.0 cada 500us
 *
 * Este valor es irrelevante (aunque siempre debe ser menor que T1MR3) ya
 * que las conversiones, a final de cuentas, tendrán el mismo periodo que
 * T1MR3
 */
 T1MR0=500;

/*
 * para pruebas (osciloscopio) se conecta el pin P0.12
 */
// PINSEL0|=(2<<24);

/*
 * External match control 0 = SET cada que T1MR0 == TC, y no se requiere
 * generar ninguna interrupción
 */
 T1EMR|=(2<<4);

/*
 * Configura las interrupciones para el tick del sistema
 */
 VICIntSelect&=~(1<<5); /* IRQ */
 VICIntEnable|=(1<<5); /* En */ 
 VICVectCntl0=(5)|(1<<5);
 VICVectAddr0=(unsigned long) tick_isr;

/*
 * Limpia cualquier interrupción pendiente
 */
 T1IR=(1<<3);

/*
 * El timer arranca
 */
 T1TCR=1;
}

void pause(uint32_t d)
{
 tim_delay=d;
 while(tim_delay>0)
 ;
}

Main

main()
{
...
adc_init();
tick_init();
...
adc_Start();
 for(;;)
 {
   ...
 }
}

Espero que este pequeño tutorial les haya servido. Cualquier comentario, duda o aclaración son bienvenidos.

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s