[Aporte] Tutorial ARM Cortex-M3 - LPC1768

Una duda que me surgió. El ADC tiene una capacidad de conversión de 200KHz, pero me resulta poco creible. Serían 200000 conversiones por segundo, es raro.

El fabricante asegura que el ADC puede muestrear a 200kHz, es decir que c/conversión le tome 5uS, no lo veo imposible. En el DAC por ej. comprobé que la velocidad que alcanzaba era mayor a la que declaraba el fabricante.

Siguiendo tu código vos habilitas un timer0 para que pregunte si hay conversión de ADC
configura_timer0(25000,1000);

Por lo que estuve leyendo este timer interrumpe cada un segundo, es así?

Si, porque el clock del timer será de 25MHz (1/4 del core-clk que está en 100MHz después de configurar el PLL) => el pre-escaler interno del timer lo configuro en en 25000 dando un clk c/1mSeg => luego configuro la cuenta de match en 1000 => 1000*1 mSeg=1 Seg

Luego configuras el adc:

(convertir_adc(0,250,&valor_adc)>0)

Aca los estarías configurando a 100KHz?

Si, el tiempo de conversión del ADC será de 10 uS, es decir una vez que pase el segundo por el timer0, el ADC solo tardará 10 uS en obtener una nueva conversión.

La variable valor_adc es un "unsigned int" (al ser el ADC de 12 bits, me alcanza con este tipo de variables) que lo paso por referencia mediante su dirección, de esta forma me ahorro trabajar con variables globales y obtengo el resultado de la conversión en esa variable. ¿Podría trabajar con una variable de 32 bits?, si y al uC le dá igual, solo que pedís un poco más de RAM.
 
Última edición:
Cosmefulanito, tu timer0 esta seteado de tal forma que interrumpa cada 1 segundo, entonces solamente una vez por segundo el adc le pasa el dato a la variable valor_adc. Así no entendí de tu programa, entonces.... De que me sirve setear el adc en 100khz, si yo solamente capturo el valor una vez por segundo. Perdón por la pregunta pero no me queda claro.
O sea yo interrumpo, una vez que interrumpí, tomo el valor, luego lo imprimo. Eso se hace una vez por segundo
 
Cosmefulanito, tu timer0 esta seteado de tal forma que interrumpa cada 1 segundo, entonces solamente una vez por segundo el adc le pasa el dato a la variable valor_adc.

Exactamente.

Así no entendí de tu programa, entonces.... De que me sirve setear el adc en 100khz, si yo solamente capturo el valor una vez por segundo.

Dependerá de la aplicación, podría tranquilamente configurar al ADC en 50kHz, etc, la idea es que al configurar esa frecuencias sabes el tiempo que te demora el ADC, como a 100kHz el ADC sigue trabajando con 12 bits, por un tema de "practicidad" en el ejemplo lo configuré en esa velocidad.

¿Cómo debería hacerse bien la toma de datos? (a pesar de muestrear una continua)

Haciendo un promedio móvil de N muestras, pero si hacía eso, el código iba a ser más complejo y poco entendible para el tutorial. La idea es enseñarte los pasos básicos (siempre los más difíciles) para que después la parte compleja solo sea a nivel de soft (rutinas que siempre son las mismas) y no de hard.

O sea yo interrumpo, una vez que interrumpí, tomo el valor, luego lo imprimo. Eso se hace una vez por segundo

Es así, si por puerto serie previamente enviaste el caracter 'q'.

Otra forma de hacerlo, es configurando al ADC en disparo continuo.
 
Cosmefulanito,

Subo un proyecto donde se muestra en la pantalla LCD, el valor de tres canales de ADC, vi que alguien estaba pidiendo como hacer eso y bueno después de pelear logré mostrarlo perfectamente en pantalla.

Por otra parte te quería preguntar si me podes dar una mano con mi código ya que planteé un promedio y las cosas mejoraron, pero sigo teniendo variación en el último dígito. Lo que quise hacer y no me está funcionando es lo siguiente:

Si tengo una muestra del adc, la resto con la muestra anterior y si la diferencia no es mayor a 10. le pido que no actualice el valor o sea que siga siendo el mismo.
Código:
for(cont2=1;cont2<MAXP+1;cont2++) 
				        { 	
						
							if(muestra_adc1[cont2]-muestra_adc1[cont2-1]<10)
								{muestra_adc1[cont2]=muestra_adc1[cont2-1];
								 //promedio_movil1+=muestra_adc1[cont2];
								 }
							else
								{muestra_adc1[cont2]=muestra_adc1[cont2];}

Si se te ocurre algo, e lo agradezco.

Para los que quieran usar el proyecto, cuando descomprimen todo el archivo, van a encontrar led.uvproj ese es el proyecto entero

Muchas gracias
 

Adjuntos

  • LPC1768_LCD.rar
    1.8 MB · Visitas: 26
Esa rutina no veo que te sirva para hacer un promedio móvil.

Te subo esta rutina que implementé con un AVR, pero que te va a ser fácil implementarla con el ARM.

main.c:

PHP:
#define MUESTRAS_MAX 16 // promedio 16 muestras
#typedef unsigned char u8;
#typedef unsigned int u16;
#typedef unsigned long int u32;

int main(void)
{
  u16 muestras[MUESTRAS_MAX]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; //Vector donde almacenaré las muestras => inicializo las muestras en 0.
  u8 indice_muestras=0; //Indice que irá cambiando a medida que obtenga las muestras => armo una cola circular con el vector "muestras"
  
  u32 promedio_movil=0; // Variable de 32 bits (uint32_t), en ARM va como piña. Lo uso para que no desborde cuando sume las muestras
  
  //.... inicialización necesaria, entre ellos el ADC

  while(1)
    { 
    //.... Código de la rutina
    
    if(....) //Una cierta condición temporal, ej. c/1mSeg
      {
        muestras[indice_muestras]=funcion_conversion_adc();  //Obtengo la conversión y la guardo en el vector de muestras
        indice_muestras++;

        if(indice_muestras==MUESTRAS_MAX) //Condición para hacer una cola circular, llegado a 16 muestras, piso el dato más viejo y lo descarto
          indice_muestras=0;
      }
    
    if(....) //Una cierta condición temporal, ej. c/1Seg donde enviaré el resultado del muestreo, es decir el promedio móvil
      {
        promediar_datos(muestras,&promedio_movil); //Función definida abajo, acá hará se obtendrá el promedio móvil
        //... Otras cosas, ej. imprimir resultado o enviarlo por RS232, etc        
      }
    }
}

funciones.c:

PHP:
void promediar_datos(u16 muestras[],u32 *promedio_movil)
{
	u8 cont;
	
	*promedio_movil=0;
	for(cont=0;cont<MUESTRAS_MAX;cont++)
		*promedio_movil+=muestras[cont];
									
	*promedio_movil=(u16)(*promedio_movil/MUESTRAS_MAX); //Obligo a que el resultado sea un u16 => tipo de variable de una sola muestra
        // La variable "promedio_movil" guardará el resultado
}

Eso sería todo, incluso resulta muy fácil cambiar la cantidad de muestras que necesitás realizar, simplemente cambiando el define.
 
Gracias por el aporte, esa rutina ya la implementé, pero me daba error en la función porque no le gustaba el puntero *promedio_movil. Me daba error, por eso lo tuve que implementar dentro del if.

Mi otra duda, que es lo que mejoraría el promedio, es poder descartar las muestras que no tienen gran diferencia respecto a la anterior. Ejemplo:

M1= 100
M2= 102
M3= 103
M4= 101
M5= 104
M6= 99
M7= 150
M8= 151
M9=280
M10=282
.
.
La diferencia entre las sucesivas conversiones es relativamente poca entonces yo quisiera que directamente me arme el vector de muestras con los valores significativos quedando:
(100,100,100,100,100,100,150,150,280,280) Se entiende cual es la idea.
Igual voy a probar nuevamente la función que me pasaste, Gracias
 
Es verdad, subí esta misma rutina en el mensaje #80 (me había olvidado :oops:).

¿Te tira error en el puntero?, después lo compilo en ARM y veo que pasa, de momento para solucionarlo podrías usar un return:

PHP:
u16 promediar_datos(u16 muestras[])
{
    u8 cont;
    u32 promedio_movil_temp=0;

    promedio_movil_temp=0;
    for(cont=0;cont<MUESTRAS_MAX;cont++)
        promedio_movil_temp+=muestras[cont];
                                    
    return (u16)(promedio_movil_temp/MUESTRAS_MAX); //Obligo a que el resultado sea un u16 => tipo de variable de una sola muestra
}

Y en el main, la variable promedio_movil en vez de ser de 32 bits, alcanza con que sea de 16:

PHP:
int main(void)
{
 ....
  
  u16 promedio_movil=0; // Variable de 16 bits 
  
  //.... inicialización necesaria, entre ellos el ADC

  while(1)
    { 
    //.... Código de la rutina
    
    ...
    
    if(....) //Una cierta condición temporal, ej. c/1Seg donde enviaré el resultado del muestreo, es decir el promedio móvil
      {
        promedio_movil=promediar_datos(muestras); //Función definida abajo, acá hará se obtendrá el promedio móvil
        //... Otras cosas, ej. imprimir resultado o enviarlo por RS232, etc        
      }
    }
}

La diferencia es que ahora pedís un poco más de RAM.

Sobre modificar las muestras según la cercanía, eso no veo correcto, pierde el sentido de hacer el promedio móvil.
 
Estuve probando las funciones, las pude compilar, igualmente tengo problemas en la visualización del valor ya que tengo variaciones de el último dígito y cada tanto salta a algún valor extraño.

Estimo que ese problema es porque las interrupciones no están sincronizadas, es decir, yo interrumpo cada 1mS para tomar una muestra, pero promedio cada 1S.

Hay muchas muestras que las pierdo y por otra parte cuando llegue la interrupción del promedio, puede que el vector muestras[32] no esté completo y es por eso que puede dar u valor erróneo.

Entiendo lo que vos me decis, que no tiene sentido eliminar muestras que no sean significativas, pero eso puede ser una solución a la variación del valor presentado en pantalla.

Gracias
 
Estuve probando las funciones, las pude compilar, igualmente tengo problemas en la visualización del valor ya que tengo variaciones de el último dígito y cada tanto salta a algún valor extraño.

Agregá más muestras, en vez de 32, hacelo de 64 o 128, tené en cuenta que mientras más muestras, más lenta será la variación, es decir si estás en un nivel de tensión de 1v y de pronto la señal pasa a 4v, vas a tardar un rato en ver esos 4v.

Estimo que ese problema es porque las interrupciones no están sincronizadas, es decir, yo interrumpo cada 1mS para tomar una muestra, pero promedio cada 1S.

Estás muestreando de más, es decir tomás 1000 muestras/s y solo imprimís el promedio de las últimas 16 (n).

Para 16 muestras, deberías muestrear c/62mS aproximadamente (1s/16=62,5mS).

Hay muchas muestras que las pierdo y por otra parte cuando llegue la interrupción del promedio, puede que el vector muestras[32] no esté completo y es por eso que puede dar u valor erróneo.

El vector va a estar vacío la primera vez hasta que se completen las primeras 16 (o 32) muestras, después siempre va a tener datos.

Entiendo lo que vos me decis, que no tiene sentido eliminar muestras que no sean significativas, pero eso puede ser una solución a la variación del valor presentado en pantalla.

Si querés algo más complejo que un simple promedio móvil, ya tenés que buscar por el lado de filtros digitales más complejos, FIR o IIR, los últimos se basan en los filtros analógicos.
 
Cosmefulanito, te hago una pregunta, quise implementar el siguiente código y solo me toma el valor del canal 1

Código:
    if(ADC_ChannelGetStatus(LPC_ADC, ADC_CHANNEL_1, ADC_DATA_DONE))
            {     adc_value1 =  ADC_ChannelGetData(LPC_ADC,ADC_CHANNEL_1);}

        if(ADC_ChannelGetStatus(LPC_ADC, ADC_CHANNEL_2, ADC_DATA_DONE))
               {     adc_value2 =  ADC_ChannelGetData(LPC_ADC,ADC_CHANNEL_2);}
    
        if(ADC_ChannelGetStatus(LPC_ADC, ADC_CHANNEL_3, ADC_DATA_DONE))
            {     adc_value3 =  ADC_ChannelGetData(LPC_ADC,ADC_CHANNEL_3);}
¿Qué está mal?
Gracias.

---------- Actualizado después de 2 horas ----------

Para asegurarme de que los canales del ADC que no estoy usando me metan ruido, los tengo que declarar como salida y conectarlos a masa, eso es lo recomendable no?
La sentencia para declarar un pin como salida cual era?

Gracias
 
Última edición por un moderador:
Las funciones vienen en la librería de CMSIS lpc17xx_adc.c
La primero lo que hace es preguntar si la conversión en el canal se ha terminado y la segunda toma el valor del canal del adc.

POrque quise utilizar tu función "convertir adc" para tres canales y no es posible.

Gracias
 
No usé esas librerías, por lo tanto no puedo ayudarte.

Sobre mi función, no lo probé con varios canales, pero debería funcionar, obviamente habrás hecho una conversión para desechar en c/cambio de canal.
 
Cosmefulanito, como sería eso? Ejemplo lo que hice yo fue:
Iniciar_adc(0,0);
iniciar_adc(1,0);
iniciar_adc(2,0);

Despues dentro del main usar Convertir_adc para cada canal.
Vos decis que deberia desechar una conversión, en que momento?

Gracias por la ayuda
 
Esa es la idea, siempre hacés dos conversiones cuando cambiás el canal, la primera la descartas (mide basura) y la otra la tomás como válida.

Esto sucede con la mayoría de los uC.
 
Hola, a ver si me ayudais con las operaciones de desplazamiento de bits que tengo un poco de lio. Queria saber la diferencia y lo que hacen las siguientes instrucciones:
|= 1<< (10*2)
&=~(3<<20)
 
Te falto agregar la variable al principio, pero sería una cosa así:

variable|= 1<< (10*2)

PHP:
variable=variable|(1<< (10*2));

El símbolo "|" representa la operación de la OR lógica.

Donde "1<< (10*2)" significa desplazar el 1, 20 veces a la izquierda => 1<<20 => 0b0000-0000-0001-0000-0000-0000-0000-0000 = 0x00100000 (ojo, tal vez le erré en el bit, verificalo)

variable&=~(3<<20)

PHP:
variable=variable&~(3<< 20);

El símbolo "&" representa la operación de la AND lógica.

Donde "(3<< 20)" es desplazar el número 3, 20 veces a la izquierda ..... => 0b0000-0000-0011-0000-0000-0000-0000-0000 = 0x00300000

Y el símbolo "~" es un negador, por lo tanto ~(3<< 20)=0b1111-1111-1100-1111-1111-1111-1111-1111 = 0xFFCFFFFF
 
Hola cosmefulanito04!!
Veo que sos macanudo con el uC, te consulto algo..
Yo estoy trabajando con un LPC1769, y quiero guardar una trama de bits que me tira un sensor. Este entrega 40 bits, por lo que creo una variable de 64 bits donde guardarlos:
typedef unsigned long uint64_t;

uint64_t DATO;

Como cada Byte es un dato, lo que hago es extraerlos por separado en una variable de 8 bits:
typedef unsigned char uint8_t;

uint8_t dato_RH;
El problema se presenta que cuando desplazo para sacar el dato del 5to byte de la siguiente forma:

dato_RH=(uint8_t) ((DATO>>32)& 0x00FF);

y me responde con:
warning: right shift count >= width of type [enabled by default]
dato_RH= (uint8_t)( (DATO >> 32) & 0x00FF);
^
Finished building.

Alguna opinión?
 
Hola cosmefulanito04!!
...
uint64_t DATO;
uint8_t dato_RH;
...
dato_RH=(uint8_t) ((DATO>>32)& 0x00FF);
...
warning: right shift count >= width of type [enabled by default]
dato_RH= (uint8_t)( (DATO >> 32) & 0x00FF);

¿Será que está tratando de trabajar con la variable DATO convirtiéndola a 32 bits, luego desplazando >>32 (resultado siempre = 0), luego &0x00FF, y luego convirtiendo a uint8_t?.
Para ver si pasa eso habría que ver el código de ensamblador (assembly code).

Que pasa si hacés:
dato_RH= (uint8_t)( (uint64_t)(DATO >> 32) & 0x00FF);
 
¿Será que está tratando de trabajar con la variable DATO convirtiéndola a 32 bits, luego desplazando >>32 (resultado siempre = 0), luego &0x00FF, y luego convirtiendo a uint8_t?.
Para ver si pasa eso habría que ver el código de ensamblador (assembly code).

Que pasa si hacés:
dato_RH= (uint8_t)( (uint64_t)(DATO >> 32) & 0x00FF);

Gracias por tu respuesta, no es exactamente lo que hice, pero en vez de castear a uint64_t ese desplazamiento, castié el 1 o 0 que estaba desplazando para guardar los bits del sensor.
Así quedo:

//mi variable:
uint64_t DATO;

//guardo los bits:
// el 'i' es el indice del desplazamiento:

DATO &= ( ~ (uint64_t)( 0x01 << i ) );
DATO |=((uint64_t) 0x01 << i );

De esta forma, me guarda los 64 bits.
Luego para leerlos:

dato_hum=(uint8_t) ( (DATO>>32)& 0xFF );
No me tira ningún warning, ni pierdo bits.
Muchas Gracias!
Saludos.
 
Atrás
Arriba