# [Aporte] Tutorial ARM Cortex-M3 - LPC1768



## cosmefulanito04 (Abr 17, 2013)

Hace poco compré este kit de desarrollo (LPC1768-Mini-DK2):












*Las características del kit:*



			
				fabricante del kit dijo:
			
		

> - One serial interface, use CP2102 (USB to RS232 interface, support ISP download).
> - RJ45-10/100M Ethernet network interface (Ethernet PHY: LAN8720A).
> - 2.8 inch TFT color LCD interface (SPI interface or 16Bit parallel interface).
> - USB 2.0 interface, USB host and USB Device interface.
> ...



Realmente por las cosas que tiene, más que nada los puertos Usb y Ethernet (que todavía no llegué a verlos), y el costo del mismo (casi $200 argentinos o u$d 32) creo que vale la pena para explorar estos bichos que cada vez son más utilizados por su bajo costo y su alta prestación. 

Yo venía de conocer los ARM7 y el cambio a esta familia realmente ni se nota, en casi todas las cosas es muy similar y solo cambian pequeños detalles haciendolos aún más sencillo a la hora de programar.

*Esquemático:*









El conversor usb a rs232, nos permite trabajar con la uart0 y poder programar el uC directamente desde un puerto usb. Obviamente además de este conector usb, se encuentra el puerto usb propiamente dicho del uC.

Siguiendo con la idea de este tutorial sobre ARM7, tenía pensado crear un mini tutorial más que nada orientado a código y ejemplos sobre los periféricos básicos que se pueden encontrar en estas familias de uC.

En base a eso, tenía planeado dividir el tutorial en varias partes las cuales ya tengo resueltas:

- GPIO.
- PLL y Timers.
- Interrupción externa.
- Uart.
- RTC.
- ADC.
- DAC.
- PWM.
- SPI y SSP (estos 2 últimos aún no pude probarlos con hard).

Nuevamente la idea es plantear un problema sencillo y resolver el código, para lo cual es fundamental tener la hoja de datos del LPC1768 a mano, con lo cual voy a dejarla en este post para que puedan descargarla.

En el próximo mensaje subo el equemático completo.


----------



## cosmefulanito04 (Abr 17, 2013)

Antes que nada, subo el esquemático completo del kit. 

*Las herramientas a utilizar serán:* 

- Entorno de programación Keil4, pueden descargar una versión de evaluación gratuita.
- Flash Magic, lo pueden descargar en forma gratuita.

*Diferencias que vamos a notar entre ARM7 y Cortex:*

Para el que viene de ARM7 (familia LPC21xx), se va encontrar que los registros se acceden de forma distinta, por ej. para acceder al registro PINSEL0 el código será así:


```
//ARM7 - Familia LPC21xx
PINSEL0=0;

//ARM Cortex
LPC_PINCON->PINSEL0=0;
```

Se puede ver que ahora es necesario llamar a una estructura por referencia para poder acceder al registro. La ventaja que le encuentro es la comodidad de tener todos los registros de un periférico encapsulado, por lo que impide que nos confundamos de registros provenientes de otro periférico.

Otro cambio importante son las rutinas de interrupción y como se manejan, pero eso lo vamos a ver más adelante.

*Ejercicio propuesto:*

Haciendo algo similar que en el tutorial de los ARM7, vamos a configurar los puertos para poder encender y apagar los leds que figuran en el esquemático.

Volviendo al tema GPIO, en base al esquemático, la idea es encender el LED1 (P3.25) y apagar el LED2 (P3.26), para luego de un retardo invertir los roles, apagar LED1 y encender LED2. 

Como de momento no sabemos manejar el PLL, los timer ni las interrupciones, el retardo lo realizaremos con 2 for (método cabezón ).

Si bien se trata de trabajar en C, cabe aclarar que la idea es que nosotros seamos capaces de crear nuestras propias funciones en base a los registros y las recomendaciones que nos dá la hoja de datos y no depender de funciones hechas por terceros de la cuales no tenemos idea que cambios realizan durante su ejecución, de esta forma evitamos delegar todo el control del uC.

*Para poder realizar el programa es necesario:*

- Ver el esquemático y analizar como se encienden los leds (si el puerto debe estar en estado bajo o alto).
- Leer la hoja de datos el capítulo 8 (Chapter 8: LPC17xx Pin connect block), página 104.
- Leer la hoja de datos el capítulo 9 (Chapter 9: LPC17xx General Purpose Input/Output (GPIO)), página 120.

*Código:*


```
#include <LPC17xx.H>

#define LED_1 (1<<25)	//Puerto 3.25
#define LED_2 (1<<26)	//Puerto 3.26

int main()
{	
	unsigned int cont,cont2;
	
	LPC_PINCON->PINSEL7=0;			//Declaro al puerto 3 como GPIO -> La función de dichos puertos se manejan a través del PINSEL7 -> Bits 19:18 Puerto 3.25 y Bits 21:20 Puerto 3.26
	LPC_GPIO3->FIODIR|=LED_2|LED_1;	//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	while(1)
	{		
		if(LPC_GPIO3->FIOPIN&LED_1)
			{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
		else
			{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
		
		for(cont=0;cont<1524;cont++)
			for(cont2=0;cont2<65536;cont2++); //Rutina de retardo bastante precaria
	}
}
```

*Registros utilizados:*

PINSEL: sirve para seleccionar la función de un PIN.
FIODIR: sirve para seleccionar si el PIN funciona como entrada o como salida.
FIOSET: sirve para para fijar en "1" la salida del PIN.
FIOCLR: sirve para para fijar en "0" la salida del PIN.
FIOPIN: sirve para leer el estado del PIN, si está en "1" o en "0".

Para el próximo mensaje (les doy una semana para que lean y entiendan este código), voy a explicar como funciona el PLL y los timers, para luego empezar a meternos con las interrupciones. Para los que quieran adelantar, pueden ir al tema del ARM7 que resulta muy parecido en esta familia.


----------



## cosmefulanito04 (Abr 21, 2013)

*Clases de clocks*

En esta familia de uC podemos encontrarnos con varios clocks distintos, los cuales pueden ser:

- Cclk: core clock, el reloj que utilizará el núcleo del uC para ejecutar las distintas instrucciones.
- Pclk: peripheral clock, el reloj que utilizarán los distintos periféricos, ej. uart, timer, SPI, ADC, etc. Dicho reloj puede ser distinto en c/periférico.
- Wd_clk: el reloj que utilizará el watch dog.
- Usb_clk: el reloj que utilizará el puerto USB.

*Osciladores para generar los clocks*

Podemos elegir varias formas de generar nuestros relojes, usando:

- Cristal externo (1MHz hasta 25MHz): dependiendo del cristal nos brindará una buena base de tiempo (relojes más precisos)
- Oscilador interno RC (4MHz): no es una buena base de tiempo y no se recomienda su uso para el Usb_clk.
- Cristal RTC (32,768kHz): cristal de baja frecuencia para una buena base de tiempo usada para el RTC interno del uC. También se lo puede utilizar como generador de clock, con el costo de hacer trabajar al PLL al máximo (mayor consumo e inestabilidad).

*Diagrama en bloques de la generación de los Clocks*



Las etapas anuladas pertenecen al generador de clock del Usb (no es lo que nos interesa en estos momentos).

De izquierda a derecha, siguiendo las flechas, podemos ver las opciones que tenemos a la hora de generar nuestros relojes:

- Flecha roja: que clase de oscilador elegimos (registro CLKSRCSEL).
- Flecha verde: se evita el uso del PLL (registro PLL0CON).
- Flecha azul: se utiliza el PLL (registro PLL0CON).
- Flecha marrón: es el reloj a la salida del PLL (estando activado o no).
- Flecha roja: es el reloj del Cclk que pasará previamente por un divisor de frecuencia "CPU CLOCK DIVIDER" (registro CCLKCFG).
- Flecha verde: serán los distintos Pclk de c/periférico, que previamente pasarán por un divisor de frecuencia propio "PERIPHERAL CLOCK DIVIDER"(registros PCLKSEL0/1).

Por otro lado, el reloj del watch dog podrá depender de:

- Oscilador interno.
- Oscilador por RTC.
- Un reloj externo.

*¿Qué opción vamos a elegir?*

La idea del tutorial es llevar al uC a su máxima frecuencia de core (100MHz) usando un cristal externo de 12MHz (a sabiendas de un mayor consumo), para aprender la configuración más compleja que es usando el PLL, ya que sin el uso del PLL nuestra máxima frecuencia solo dependería del cristal externo que usemos (25MHz máximo).

Por lo tanto siguiendo el diagrama anterior, nuestro camino será:

- Flecha roja => cristal externo de 12MHz
- Flecha azul => usaremos el PLL0
- Flecha marrón => al usar el PLL0 la PLLCLK podrá estar en entre 275MHz y 550MHz (requerimiento propio del PLL0, ya lo vamos a ver).
- Flecha roja => en función de nuestro PLLCLK configuraremos el "CPU CLOCK DIVIDER" para obtener un CCLK de 100MHz.
- Flecha verde => dejaremos configurados a todos los relojes de periféricos en 25MHz usando el "PERIPHERAL CLOCK DIVIDER" en 4 (100MHz/25MHz=4).

*PLL0*

Diagrama en bloques:



Sin entrar en demasiados detalles, los elementos más destacados son:

- Clock de entrada (32kHz hasta 25MHz).
- Divisor de frecuencia de entrada "N".
- Divisor de frecuencia dentro del lazo de realimentación "M".
- Clock de salida que podrá ir de 275MHz hasta 550MHz, también llamada FCCO.

Bits de control:

- PLLC: conexión o no de PLL0.
- PLLE: habilitación del PLL0.
- PLOCK: confirmación de que el PLL0 enganchó en la frecuencia deseada.

Para poder manejar el PLL es necesario configurar correctamente sus registro (cosa que veremos en el código), por lo tanto recomiendo leer la hoja de datos en la página  35. Los registros a utilizar serán:

- PLL0CON
- PLL0CFG
- PLL0STAT
- PLL0FEED

La hoja de datos recomienda usar el PLL a la menor frecuencia posible por 2 motivos:

- Menor consumo.
- Menos errores de enganche => mejor reloj a la salida.

Por lo tanto recomienda que los valores del divisor M y el divisor N sean los más bajos posibles.

*Procedimiento de configuración del PLL0 (usando polling)*

Aclaración: como la configuración solo se dá durante el arranque del uC (es decir una vez), no le veo demasiada utilidad el hecho de utilizar interrupciones para saber si el PLL enganchó o no, entonces por sencillez decidí usar un polling para saber cuando el PLL enganchó.

*1-* Definir la frecuencia de salida del PLL0 en función a la CCLK deseada. Hay que recordar que del PLLCLK (flecha marrón) al CCLK (flecha roja) hay un divisor de frecuencia ("CPU CLOCK DIVIDER"), por lo tanto del diagrama en bloques del oscilador si partimos de *derecha* a izquierda (de la salida al comienzo del bloque), debemos multiplicar la frecuencia:

Definimos CCLK => multiplicamos por el valor entero del divisor "CPU CLOCK DIVIDER" => Obtenemos PLLCLK=FCC0

Si nuestra CCLK=100MHZ el "CPU CLOCK DIVIDER" quedará definido en función del rango aceptable del FCCO (257MHz a 550MHz), mientras menor sea el FCCO, mejor configurado estará el PLL0.

[LATEX]F_{CC0}=C_{CLK}*CPU-CLOCK_{DIVIDER}[/LATEX]

[LATEX]CPU-CLOCK_{DIVIDER}=1 \Rightarrow F_{CC0}=100MHz*1=100MHz[/LATEX] (fuera de rango)
[LATEX]CPU-CLOCK_{DIVIDER}=2 \Rightarrow F_{CC0}=100MHz*2=200MHz[/LATEX] (fuera de rango)
[LATEX]CPU-CLOCK_{DIVIDER}=3 \Rightarrow F_{CC0}=100MHz*3=300MHz[/LATEX] (dentro del rango)

Llegamos a la conclusión que con un "CPU CLOCK DIVIDER" igual 3 se consigue un FCCO dentro del rango del PLL. Por ej. un "CPU CLOCK DIVIDER" igual 4 también sería válido, pero la configuración no sería la óptima (como puse arriba, menor FCCO, mejor).

Resumiendo de "1-", hasta ahora sabemos que:

- CCLK=100MHz.
- "CPU CLOCK DIVIDER" igual 3.
- FCCO=300MHz.
- Frecuencia de entrada del PLL=12MHz (la frecuencia del cristal externo).

*2-* Sabiendo el valor de FCCO y frecuencia de entrada, averiguamos el valor de "M" imponiendo distintos valores de "N" hasta que el valor de "M" sea un entero, según la fórmula que brinda la hoja de datos:

[LATEX]M= \frac{F_{CCO}.N}{2.F_{entrada}}[/LATEX]

[LATEX]N=1 \Rightarrow M= \frac{300MHz.1}{2.12MHz}=12,5[/LATEX] (valor no entero, por lo tanto no es válido)
[LATEX]N=2 \Rightarrow M= \frac{300MHz.2}{2.12MHz}=25[/LATEX] (valor entero!)

En caso de no conseguir un valor de "N" que haga a "M" un valor entero, se deberá replantear la CCLK en función de la frecuencia de cristal que se tenga.

*3-* Configurar el PLL0 según estos pasos:

- Elegir el oscilador => registro CLKSRCSEL (en nuestro caso cristal externo).
- Deshabilitar y desconectar el PLL => registro PLL0CON.
- Realizar Feeding => registro PLL0FEED (procedimiento que indica la hojas de datos => PLL0FEED=0xAA y luego PLL0FEED=0x55).
- Definir los valores de "N-1" y "M-1" => registro PLL0CFG (en nuestro caso 1 y 24 respectivamente).
- Realizar Feeding => registro PLL0FEED.
- Habilitar el el PLL => registro PLL0CON.
- Realizar Feeding => registro PLL0FEED.
- Hacer polling hasta que el PLL enganche => bit PLOCK en el registro PLL0STAT.
- Conectar el PLL => registro PLL0CON.
- Realizar Feeding => registro PLL0FEED.
- Configurar el "CPU CLOCK DIVIDER" => registro CCLKCFG (en nuestro caso 3 => 100MHz).
- Configurar el "PERIPHERAL CLOCK DIVIDER" => registros PCLKSEL0/1 (en nuestro caso 4 => 25MHz).

Eso sería la parte teórica de como se deber configurar el PLL0 para poder obtener una cierta frecuencia de Core. Más adelante subiré una breve explicación de como funcionan los timers para luego ir a un ejercicio y ver como se configura todo desde el código.


----------



## cosmefulanito04 (Abr 22, 2013)

Antes de meternos con los timers, es necesario ver como funcionan las interrupciones y como funciona el control de consumo que tiene el uC.

*Interrupciones*

Para el que viene de ARM7, notará que en la arquitectura Cortex el vector de interrupción ya está definido y simplemente hay que realizar la llamada al handler correspondiente, a diferencia de la familia LPC21xx donde uno tenía que hacer una definición del handler algo similar a C aplicado en PC.

Si nos metemos en el capítulo 6 (pág. 72), podremos ver el vector de interrupción y obtener más información de como funciona en esta arquitectura, por ej. configurar el nivel de prioridad que tendrán las distintas interrupción (0-31, siendo 0 el nivel de mayor prioridad).

Resumiendo, a nosotros lo que nos va interesar es como se crea un handler y como habilitar las interrupciones de c/periférico (algunos poseen habilitación global y propia).

*Habilitación de la interrupción de Timer0:*


```
…
NVIC_EnableIRQ(TIMER0_IRQn); //Función propia de Keil, si bien recomiendo evitar funciones de 3eros, está es corta y funciona bien.
…
```

*Ejemplo de un handler para el Timer0:*


```
void TIMER0_IRQHandler (void)  
 { 
    // nuestra rutina → se aconseja que sea lo más corta posible
     
    //Limpieza del flag de interrupción, a diferencia de otras familias de uC, los ARM requieren una limpieza por soft :(
 }
```

*Consumo*

*El uC tiene varios modos de bajo consumo:*

- Sleep mode: el Cclk se detiene. Requiere de una interrupción o Reset para salir de ese modo.

- Deep Sleep mode: el oscilador principal se detiene al igual que el resto de los clocks que dependen del mismo, la memoria flash entra en stand-by. Solo el RTC y el oscilador interno funcionan. Requiere una interrupción del RTC o del watch-dog (manejado por el oscilador interno) para salir de ese modo.

- Power-down mode: funciona al igual que el “Deep Sleep mode”, pero además apaga la memoria flash y el oscilador interno. Requiere una interrupción del RTC para salir de ese modo.

- Deep Power-down mode: todo el integrado queda apagado salvo el RTC. Requiere una interrupción del RTC para salir de ese modo.

Nosotros en particular vamos a usar el “Sleep mode” cuando entremos en un “loop” de polling, de esta forma evitamos exigir al máximo al uC y reducimos su consumo.

Para poder entrar en este modo es necesario llamar la instrucción assembler WFI (wait for interrupt), para lo cual usamos nuevamente una función de Keil que nos permite llamar a dicha instrucción desde C:


```
…
__wfi();	//Sleep-Mode
…
```

Una vez que llamamos a la función, solo podremos salir de ella cuando se produzca una interrupción, de lo contrario el uC seguirá en “Sleep mode”.

Para más información de como entrar en los otros modos de bajo consumo, ver el capítulo 4 sección 8 página 58.

*Control de consumo en base a los periféricos:*

Por otro lado, el uC nos permite reducir el consumo habilitando o no la alimentación de los distintos periférico, con lo cual si solo vamos a usar el Timer0 y la Uart0, se pueden apagar el resto de los periféricos no utilizados. Para poder realizar esto, utilizaremos el registro PCONP y colocando en “1” los distintos bits habilitaremos los periféricos y colocandolos en “0” los deshabilitaremos (tendremos 32 bits para 32 periféricos distintos).

Por lo tanto, a la hora de utilizar un periférico, lo primero que se debe hacer es habilitar su alimentación, de lo contrario no funcionará.

*Timers*

*Diagrama en bloques de los Timers*



*Modo de uso*

- Base de tiempo (timer convencional que se suele usar): usando como patrón el reloj de periférico previamente configurado, irá contando hasta que haya un "matcheo" establecido (en español sería una igualación) y se produzca una señal de interrupción.

- Captura: mediante una señal conectada a un puerto “capture” se realiza un conteo cuando en la señal hay una presencia de un flanco ascendente/descendente hasta que haya un “matcheo“ establecido y se produzca una señal de interrupción.

La idea es dar una breve explicación del timer como base de tiempo por lo que les recomiendo que luego de esta práctica y usando la hoja de datos, prueben el otro modo, no debería resultarles difícil.

En base al diagrama en bloques, vemos que el timer como base de tiempo cuenta con un pre-escaler de 32 bits (2^32 cuentas), seguido del contador propiamente dicho también de 32 bits (2^32 cuentas), esto significa que una vez que se alcance la cuenta final del pre-escaler recién se producirá una cuenta en el contador y este proceso se repetirá hasta alcanzar el “matcheo” establecido (en los registros match, se puede usar 1 o varios como después voy a mencionar) . Por lo tanto nuestro tiempo quedará definido de la siguiente forma:

[LATEX]Tiempo_{timer}=\frac{\left(Cuenta_{final-preescaler}-Cuenta_{inicial-preescaler}\right).\left(Match_{final-establecido}-Cuenta_{inicial-contador}\right)}{P_{clk}}[/LATEX]

Por lo tanto, simplificando esa cuenta haciendo  Cuenta_inicial_preescaler=0 y Cuenta_inicial_contador=0, el tiempo queda definido como:

[LATEX]Tiempo_{timer}=\frac{Cuenta_{final-preescaler}.Match_{final-establecido}}{P_{clk}}[/LATEX]

Entonces en base a lo visto en el mensaje anterior Pclk=25MHz, si quisieramos configurar al timer para que se produzca una interrupción c/1 mSeg podríamos hacer esto:

[LATEX]
\left\{ \begin{array}{l}
Cuenta_{final-preescaler} = 25000\\Match_{final-establecido} = 1\end{array} \right.
\Rightarrow Tiempo_{timer}=\frac{25000.1}{25MHz} = 1mSeg
[/LATEX]

Si ahora que tenemos definida la base de tiempo de 1 mSeg, si quisieramos configurar al timer para que se produzca una interrupción c/1 Seg podríamos multiplicar x1000 esa base de tiempo usando el contador:

[LATEX]
\left\{ \begin{array}{1}
Cuenta_{final-preescaler} = 25000\\Match_{final-establecido} = 1000\end{array} \right.
\Rightarrow Tiempo_{timer}=\frac{25000.1000}{25MHz} = 1Seg
[/LATEX]

Entonces resulta bastante sencillo de configurar y mediante el uso del pre-escaler se puede fijar las escalas de tiempo:

Cuenta_final_pre-escaler=25 => uS
Cuenta_final_pre-escaler=250=> decenas de uS
Cuenta_final_pre-escaler=2500=> centenas de uS
Cuenta_final_pre-escaler=25000=> mS

Para luego usar el contador como “ajuste fino” del tiempo en la escala deseada. 

Tengan en cuenta que incluso se pueden usar múltiples “matcheos”, es decir supongamos que en 100mSeg deseamos que salte una interrupción a los 10mSeg, 45mSeg y 100mSeg, simplemente usando varios registros de “match” que los timers poseen, podemos fijar esos tiempos y recién resetear la cuenta cuando se alcanza los 100mSeg. 

También permite generar una señal física en los puertos “match” del uC cuando se alcanza un “matcheo”.

Como verán la cantidad de opciones que nos permiten los timers resulta interesante y vasta.

*Registros que usaremos en código:*

- TCR
- TC
- PR
- PC
- MR0/1..etc (puede ser cualquiera)
- MCR

Recomiendo leer la hojas de datos en la página 490.


----------



## cosmefulanito04 (Abr 24, 2013)

En base a los mensajes anteriores, ya tenemos una cierta idea de como funciona el oscilador generador de relojes, el PLL, el control consumo, las rutinas de interrupción y los timers. Ahora vamos a tratar de resolver el primer ejercicio de los leds, pero usando un timer para fijar 10 Seg como base de tiempo.

Para que se entienda mejor el código, voy a separarlo en distintos archivos:
- defines.c
- configuracion_PLL.c
- perifericos.c
- interrupciones.c
- main.c (voy a presentar dos posibles soluciones)
- delay.c (solo utilizado en una solución)

*defines.c*


```
typedef	unsigned char u8;
typedef	unsigned int u16;
typedef	unsigned long int u32;

#define LED_1 (1<<25)	//Puerto 3.25
#define LED_2 (1<<26)	//Puerto 3.26
#define TIEMPO_CAMBIO_DE_ESTADO	10
```

Lo más destacado ahí, es la nueva declaración de los tipos de variables, ahora en vez de tener que usar "unsigned char" a la hora de declarar ese tipo de variables, puedo usar simplemente "u8".

*configuracion_PLL.c*


```
//------------- Configuración PLL --------------//
#define	CLK_RC_INTERNAL		0
#define	CLK_XTAL			1
#define	CLK_XTAL_RTC			2
//------------- Configuración PLL --------------//

#define PLOCK (1<<26)

void configurar_pll(u8 clk_source,u16 valor_m,u8 valor_n,u8 divisor_cclk,u32 divisor_per0,u32 divisor_per1) 
{ 
    LPC_SC->CLKSRCSEL=clk_source;	//Elijo la fuente de CLK para el PLL0 
		
	/* 
    Ejemplo de Configuración del PLL: 
		
		XTAL => PLL => FCCO => DIV1 => CCLK================> Núcleo
								        |=> DIV2 => FPCLK => Periféricos
	
    FCCO = (2 × M × FIN) / N   
		
		M = (FCCO × N) / (2 × FIN)
    
		N = (2 × M × FIN) / FCCO

    M y N se configuran en el registro PLLCFG, bits: 
        .0 - 14 : M (Valores posibles: 6 a 512 -> Para Cristales de alta frecuencia) 
        .23 - 16 : N (Valores posibles: 1 a 32) 

    Se configura DIV1 mediante el registro CCLKCFG: 
        .0 - 255: 1 a 256
		
    Se configura con 2 bits c/periférico el DIV2 mediante los registros PCLKSEL0/1: 
        .00: FCLK/4
        .01: FCLK
        .10: FCLK/2
        .11: FCLK/8	=> excepto CAN => FCLK/6
    
		A tener en cuenta: 
        - Core-Clock máxima según la especificación del modelo 
        - 275MHz < FCCO < 550MHZ 
        - Cada cambio que se realice se deberá completar con el uso del registro PLLFEED según la secuencia que indica la hoja de datos. 
    */ 
           
    /*  
    Configuración del PLL: 

    Se desea Core-Clock=100MHz a partir de un cristal de 12MHz: 
        Se debe cumplir con 275MHz < FCCO < 550MHZ 
				
	FCCO=300MHz => un nº entero de la frecuencia deseada => FCLK=FCCO/3 => CCLKCFG=2 (DIV1)
			
	M = (FCCO × N) / (2 × FIN)  => M=(300MHz*2)/(2*12MHz)=600/12=25 
         
        En el PLLCFG se debe ingresar como M-1=24 y N-1=1
		*/ 
    
    // Se desactiva el PLL     
    LPC_SC->PLL0CON=0; 
    LPC_SC->PLL0FEED=0xAA; 
    LPC_SC->PLL0FEED=0x55; 

    LPC_SC->PLL0CFG=((valor_n-1)<<16)+((valor_m&0x7fff)-1);	//Defino N y M
    LPC_SC->PLL0FEED=0xAA; 
    LPC_SC->PLL0FEED=0x55; 	
   
    // Se habilita el PLL     
    LPC_SC->PLL0CON=0x1; 
    LPC_SC->PLL0FEED=0xAA; 
    LPC_SC->PLL0FEED=0x55;  
   
    // Espera hasta que el PLL enganche a la frecuencia deseada 
    while(!(LPC_SC->PLL0STAT & PLOCK)) ; 
   
    // Se conecta el PLL para generar los clocks
    LPC_SC->PLL0CON=3; 
    LPC_SC->PLL0FEED=0xAA; 
    LPC_SC->PLL0FEED=0x55;  
    
    // Se configura el divisor del Clock - DIV1
    LPC_SC->CCLKCFG=divisor_cclk-1;	// divisor CPU-Clock 
		
    // Se configuran el clock de los periféricos en función del Core-Clock - DIV2
    LPC_SC->PCLKSEL0=divisor_per0;
    LPC_SC->PCLKSEL1=divisor_per1; 
}
```

Vean los pasos de configuración:
1- Deshabilito y desconecto el PLL0 + Feed.
2- Fijo el valor de M y N + Feed.
3- Habilito el PLL0 + Feed.
4- Espero a que enganche el PLL0 a la frecuencia deseada.
5- Se conecta el PLL para generar los clocks.
6- Se fija el valor del divisor del Cclk.
7- Se fija el valor del divisor de c/u de los Pclk.

*perifericos.c*


```
//--------------------------------------- TIMERS -----------------------------------------------------------------//
#define PCTIM0		1

#define MR0I		0
#define MR0R		1
#define MR0S		2

#define COUNTER_EN	0
#define COUNTER_R	1

void configura_timer0(u32 preescaler,u32 matcheo) 
{ 
    LPC_SC->PCONP|=(1<<PCTIM0); //Habilito el timer0 en el control de consumo de los periféricos
    
    /* Los timers tienen 2 contadores, 1 es el prescaler y el otro es el contador en si mismo, ambos tienen registros son de 32 bits 
       por lo tanto por c/u puedo contar 2^32, osea como máximo podria contar hasta 2^64*Base de tiempo ---> muchoooos días :) 
       El timer sería una cosa así:  Port-clk --> Prescaler --> Contador (Nota: Fcristal --> PLL --> Core-clock --> Divisor --> Port-clock) 

       - Prescaler -> 2 registros 
             . TxPR: Es el valor de la cuenta final del prescaler. 
             . TxPC: la cuenta del prescaler, también permite fijar desde donde comienza a contar el prescaler, cuando sea igual a TxPR, desborda y empieza de nuevo, mandandole una cuenta al otro contador 

       - Contador -> 4 registros 
             . TxTCR: Es un registro de control, si vale: 
                -0: el contador no cuenta. 
            -1: el contador cuenta. 
            -2: reseteo el contador. 
          . TxTC: la cuenta del contador, también permite fijar el valor de inicio del contador. 
          . TxMRx: Son varios registros (segun el timer pueden ser 4 o 3), acá se pondrá el valor de la cuenta q se desea alcanzar con el contador, cuando TxTC sea igual se produce un evento. 
            Son varios porq se lo puede configurar para q envie un evento en cuentas distintas, ej: 
            - TxMR0 = 34; Al llegar acá se produce un evento, pero el contador sigue 
            - TxMR1 = 45; Al llegar acá se produce otro evento 
          . TxMCR: Sirve para configurar q acción tomar cuando se produce un evento, es de 32 bits y c/3bits se configura un MRx distinto: 
              Sí se produce un evento en MRx y TxMCR vale: 
            - 001: lanza una interrupcion 
            - 010: se resetea el contador 
            - 100: se para el contador 
            Las 3 acciones se pueden combinar, osea interrumpir y resetear al mismo tiempo. 
    */ 

    //------------------ Configuración del Timer -----------------------------------------------// 
    LPC_TIM0->TCR=0; 										// el contador no cuenta 
    LPC_TIM0->TC=0;  										// el contador comienza de 0 
    LPC_TIM0->PR=preescaler; 						// configuro la cuenta del prescaler tal q le mande una cuenta al contador c/ 1 mSeg 
    LPC_TIM0->PC=0;  										// el prescaler comienza de 0 
    LPC_TIM0->MR0=matcheo; 							// configuro la cuenta del MR0 tal q cuente 1000 mSeg osea 1 seg 
    LPC_TIM0->MCR=(1<<MR0R)|(1<<MR0I);	// configuro q al producirse un evento en MR0, se lance una interrupcion y se resetee el contador 
    //------------------------------------------------------------------------------------------// 
    
    LPC_TIM0->TCR=(1<<COUNTER_EN); 			// el contador empieza a contar     
}  
//--------------------------------------- TIMERS -----------------------------------------------------------------//
```

Vean los pasos de configuración:
1- Habilito la alimentación del Timer0.
2- Paro el contador.
3- Hago 0 la cuenta inicial del contador.
4- Fijo la cuenta final del pre-escaler.
5- Hago 0 la cuenta inicial del pre-escaler.
6- Fijo la cuenta final del contador mediante el Match0.
7- Configuro el evento una vez que se llegue a la cuenta de Match0 (reset e interrupción).
8- Arranco el contador.

*interrupciones.c*


```
void habilitar_interrupciones()
{
	NVIC_EnableIRQ(TIMER0_IRQn);	//Timer 0	
}

//--------- Rutina de la interrupcion timer0 ---------------------// 
void TIMER0_IRQHandler (void)  
 { 
    flag_timer0=1; 
     
    LPC_TIM0->IR=1; // Limpio la interrupción por match0 --> Pag. 493
 } 
//--------- Rutina de la interrupcion timer0 ---------------------//
```

Rutina de interrupción del timer 0, fijense que es muy importante limpiar el flag de su interrupción. Además una función de habilitación de las interrupciones que ahora no tiene mucho sentido, pero que a medida que vayamos agregando periféricos se irán agregando a esa función.

*delay.c (usado solo para la primera variante de main)*


```
void delay_us(u32 tiempo_us)
{
	configura_timer0(25,tiempo_us);	//Pre-escaler 25 => 1uSeg y Xcuentas => XuSeg
	flag_timer0=0;
	
	while(!flag_timer0)
		__wfi();	//Sleep-Mode
	
	LPC_TIM0->TCR=0; // paro el contador	
}

void delay_ms(u32 tiempo_ms)
{
	configura_timer0(25000,tiempo_ms);	//Pre-escaler 250000=> 1mSeg y Xcuentas => XmSeg
	flag_timer0=0;
	
	while(!flag_timer0)
		__wfi();	//Sleep-Mode
	
	LPC_TIM0->TCR=0; // paro el contador	
}

void delay_s(u32 tiempo_s)
{
	u32 cont;
	
	for(cont=0;cont<tiempo_s;cont++)
		delay_ms(1000);
}
```

Estas funciones son válidas solo cuando se usa un Pclk=25MHz. Es interesante ver que cuando se está esperando, se utiliza la función "__wfi" lo que hace que el uC en todo ese tiempo esté en modo sleep.

Por otro lado se puede ver que la cantidad de cuentas para generar 1uS es de solo 25, por lo que se obtendrá un error de cuantización de 1/25=0,04 cuentas. Para mejorar ese inconveniente, se podría aumentar el Pclk a 100MHz, logrando así que se necesiten 100 cuentas para obtener 1uS y bajando el error de cuantización a 1/100=0,01 cuentas.

*main.c (primera variante)*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "delay.c"

int main()
{	
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;								//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;				//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;		//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;		//Pongo en 0 el puerto => led encendido
	
	habilitar_interrupciones();
	
	while(1)
	{						
		if(LPC_GPIO3->FIOPIN&LED_1)
			{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
		else
			{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
		
		delay_s(TIEMPO_CAMBIO_DE_ESTADO);
	}
}
```

Como verán el código es bastante sencillo:
1- Configuro PLL para que use un cristal externo, M=25, N=2, divisor del Cclk=3, divisor de todos los periféricos =4.
2- Inicializo los puertos como GPIO.
3- Habilito interrupciones.
4- Entro en el While principal donde cambiará el estado de los leds, esperará con un delay de 10 seg y repetirá el proceso una y otra vez.

¿Que desventaja tiene este código?

Durante los 10 Seg del delay el uC no puede hacer absolutamente nada, es decir que esa función delay es totamente bloqueante. Para este ejercicio, realmente mucho no importa esto, pero si por ej. tuvieramos que estar pendientes de otro proceso, no podríamos hacer absolutamente nada, recién una vez que pasen los 10 Seg podríamos hacer algo, por lo tanto esta solución es muy precaria.

*main.c (segunda variante)*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"

int main()
{	
	u8 cont=TIEMPO_CAMBIO_DE_ESTADO;
	
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;			//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;		//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;		//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;		//Pongo en 0 el puerto => led encendido
	
	configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	habilitar_interrupciones();
	
	while(1)
	{		
		if(!cont)
		{
			cont=TIEMPO_CAMBIO_DE_ESTADO;
			
			if(LPC_GPIO3->FIOPIN&LED_1)
				{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
			else
				{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
		}
		
		__wfi();	//Sleep-Mode
		
		if(flag_timer0)
		{
			flag_timer0=0;
			cont--;
		}
		
	}
}
```

Pasos similares, pero ahora inicializo el timer0 para que genere una interrupción c/1 Seg. 

Cuando entra al While principal pregunta si la variable "cuenta" es igual a 0, de ser 0 cambia el estado del los leds y vuelve a la variable "cuenta" a 10, de lo contrario sigue. Luego pregunta se se produjo o no una interrupción mediante el "flag_timer0", en caso afirmativo vuelve a 0 el "flag_timer0" y resta a la variable "cuenta", de lo contrario el uC queda en sleep-mode hasta que aparezca una nueva interrupción.

Como verán, en esta variante, el uC esta disponible en la mayoría del tiempo y solo se necesita una interrupción para sacarlo del sleep-mode, esto en el próximo ejercicio resulta en una ventaja.

Para el próximo mensaje vamos a ver la interrupción externa, pero ya pudiendo manejar el PLL, el resto les va a resultar medianamente sencillo.


----------



## cosmefulanito04 (Abr 30, 2013)

Si pudieron entender como configurar el PLL y los timers ya creo que están en condiciones de poder entender por si mismos la parte teórica de las interrupciones externas. Dicha teoría la pueden sacar de la hoja de datos en el "Chapter 3: LPC17xx System control" en la sección 6 (pág. 23).

Lo más destacado es:

- Averiguar como se debe configurar el PIN para que trabaje como EXT"x", registros PINSEL"x".

- Elegir el modo de disparo, registro EXTMODE.

- Elegir la polaridad del disparo, estado alto/flanco ascendente o estado bajo/flanco descendente, registro EXTPOLAR.

A la larga, es simplemente ver la hoja de datos y saber que bits se de c/registro se deben configurar, nada difícil después de lo visto en los anteriores mensajes.

Vayamos directamente al código, *ejercicio propuesto*:

Se desea encender y apagar un led cada 5 segundos fijados con un timer, mediante el pulsador 2 (key2 en el esquemático) por cada vez que sea presionado, agregar 5 segundos. Hacer lo mismo con el pulsador 1 (key1), pero a la inversa, restar 5 segundos.

Los distintos archivos ".C" del anterior mensaje siguen siendo útiles, ahora de esos archivos voy agregar las funciones necesarias teniendo las anteriores que ya subí (obviamente ahora no voy a volver a subirlas).

Para poder solucionar este ejercicio es necesario tener en cuenta el rebote que tendrá el pulsador, para lo cual voy a dar dos posibles soluciones, la sencilla usando un simple delay (ya sabemos las consecuencias que esto puede traer en la optimización del código) y la que utilizará una rutina de anti-rebote más compleja, pero mucho mejor.

*define.C (se agrega a lo anterior)*


```
// Defines => ver mensaje anterior

#define TIEMPO_TOGGLE_INICIAL	5
```

*configuracion_PLL.c * => no hay modificaciones.

*perifericos.c(se agrega a lo anterior)*


```
//--------------------------------------- TIMERS -----------------------------------------------------------------//

//Defines -> ver mensaje anterior

//Timer0 -> ver mensaje anterior

void configura_timer1(u32 preescaler,u32 matcheo) 
{ 
    LPC_SC->PCONP|=(1<<PCTIM1); //Habilito el timer0 en el control de consumo de los periféricos
		//------------------ Configuración del Timer -----------------------------------------------// 
    LPC_TIM1->TCR=0; // el contador no cuenta 
    LPC_TIM1->TC=0;  // el contador comienza de 0 
    LPC_TIM1->PR=preescaler; // configuro la cuenta del prescaler tal q le mande una cuenta al contador c/ 1 mSeg 
    LPC_TIM1->PC=0;  // el prescaler comienza de 0 
    LPC_TIM1->MR0=matcheo; // configuro la cuenta del MR0
    LPC_TIM1->MCR=(1<<MR0R)|(1<<MR0I); // configuro q al producirse un evento en MR1, se lance una interrupcion y se resetee el contador 
    //------------------------------------------------------------------------------------------// 

    //LPC_TIM1->IR=1;	//Habilito la interrupción del timer 1 por match0
		LPC_TIM1->TCR=(1<<COUNTER_EN); // el contador empieza a contar     
}  
//--------------------------------------- TIMERS -----------------------------------------------------------------//

//--------------------------------------- EXTERNAS -----------------------------------------------------------------//
//----------------- Tipo y polaridad del disparo EXT1 ------------------//
#define EXT1_MODO_NIVEL		0
#define EXT1_MODO_FLANCO	(1<<1)
#define EXT1_POL_NEG			0
#define EXT1_POL_POS			(1<<1)
//----------------- Tipo y polaridad del disparo EXT1 ------------------//

//----------------- Configuración del PIN como EXT1 --------------------//
#define PINSEL_EXT1	(1<<22)	//Bit 23:22 PINSEL4 -> Funcionando como /EXT1
#define PIN_EXT1		(1<<11)
//----------------- Configuración del PIN como EXT1 --------------------//

void configurar_externa_1(u32 modo_disparo,u32 polaridad_del_disparo)
{
	LPC_PINCON->PINSEL4&=~((3<<22));
	LPC_PINCON->PINSEL4|=PINSEL_EXT1;
		
	LPC_SC->EXTMODE|=modo_disparo;
	LPC_SC->EXTPOLAR|=polaridad_del_disparo;
}

//----------------- Tipo y polaridad del disparo EXT2 ------------------//
#define EXT2_MODO_NIVEL		0
#define EXT2_MODO_FLANCO	(1<<2)
#define EXT2_POL_NEG			0
#define EXT2_POL_POS			(1<<2)
//----------------- Tipo y polaridad del disparo EXT2 ------------------//

//----------------- Configuración del PIN como EXT2 --------------------//
#define PINSEL_EXT2	(1<<24)	//Bit 25:24 PINSEL4 -> Funcionando como /EXT2
#define PIN_EXT2		(1<<12)
//----------------- Configuración del PIN como EXT2 --------------------//

void configurar_externa_2(u32 modo_disparo,u32 polaridad_del_disparo)
{
	LPC_PINCON->PINSEL4&=~((3<<24));
	LPC_PINCON->PINSEL4|=PINSEL_EXT2;
		
	LPC_SC->EXTMODE|=modo_disparo;
	LPC_SC->EXTPOLAR|=polaridad_del_disparo;	
}
//--------------------------------------- EXTERNAS -----------------------------------------------------------------//
```

Se puede ver las funciones que configurarán las EXT1 y 2, simplemente es ver hoja de datos.

*interrupciones.c (se agrega a lo anterior)*


```
void habilitar_interrupciones() //Función modificada, ahora habilita Timer0/1 y EXT1/2
{
	NVIC_EnableIRQ(TIMER0_IRQn);	//Timer0
	NVIC_EnableIRQ(TIMER1_IRQn);	//Timer1
	NVIC_EnableIRQ(EINT1_IRQn);		//Externa 1
	NVIC_EnableIRQ(EINT2_IRQn);		//Externa 2
}

//Rutina Timer0 => ver mensaje anterior

//--------- Rutina de la interrupcion timer1 ---------------------// 
void TIMER1_IRQHandler (void)  
{ 
	flag_timer1=1; 
	 
	LPC_TIM1->IR=1; // Limpio la interrupción por match0 --> Pag. 493
} 
//--------- Rutina de la interrupcion timer1 ---------------------//

//--------- Rutina de la interrupcion externa 1 ---------------------// 
void EINT1_IRQHandler (void)  
{ 
	flag_ext1=1; 
	 
	LPC_SC->EXTINT|=(1<<1);	// Limpio la interrupción externa 1
} 
//--------- Rutina de la interrupcion externa 1 ---------------------// 

//--------- Rutina de la interrupcion externa 2 ---------------------// 
void EINT2_IRQHandler (void)  
{ 
	flag_ext2=1; 
	 
	LPC_SC->EXTINT|=(1<<2);	// Limpio la interrupción externa 2
} 
//--------- Rutina de la interrupcion externa 2 ---------------------//
```

*main.c (solución usando delays)*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_ext1=0,flag_ext2=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"

int main()
{	
	u16 cont=TIEMPO_TOGGLE_INICIAL;
	u8 tiempo_inicial_variable=1;
	
	configurar_pll();// Cristal de 12MHz => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;															//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;											//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	habilitar_interrupciones();
	
	while(1)
	{		
		if(!cont)
		{
			cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL;
			
			if(LPC_GPIO3->FIOPIN&LED_1)
				{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
			else
				{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
		}
		
		__wfi();	//Sleep-Mode
		
		if(flag_timer0)
		{
			flag_timer0=0;
			cont--;
		}
		
		if(flag_ext1)
		{
			flag_ext1=0;
			
			configura_timer1(25000,200);	//Pre-escaler 250000=> 1mSeg y 200cuentas => 200mSeg
			
			while(!flag_timer1);	//20mSeg para evitar rebote
			flag_timer1=0;
			LPC_TIM1->TCR=0; // paro el contador
			
			if(tiempo_inicial_variable>1)
				tiempo_inicial_variable--;
		}
		
		if(flag_ext2)
		{
			flag_ext2=0;
			
			configura_timer1(25000,200);	//Pre-escaler 250000=> 1mSeg y 200cuentas => 200mSeg
			
			while(!flag_timer1);	//20mSeg para evitar rebote
			flag_timer1=0;
			LPC_TIM1->TCR=0; // paro el contador
			
			tiempo_inicial_variable++;
		}
	}
}
```

Inconvenientes de esta solución:

- Durante 200mSeg tenemos al uC bloqueado en sleep-mode.
- No hay verificación alguna de que el pulsador realmente se presionó y de que no hubo ruido de por medio.

Para plantear la 2da solución hay que entender que un rebote tiene esta forma:






Por lo tanto un método para saber si el pulsador realmente fue presionado y descartar rebotes y ruido es hacer esto:

1- Detectada la interrupción (en este caso por flanco descendente) esperar 5mSeg.
2- Pasados 5mSeg, preguntar si el PIN se encuentra en el estado correcto (en este caso debe estar en estado bajo, por ser flanco descendente), para agregar mayor confiabilidad esperar otros 5mSeg.
3- Pasados otros 5mSeg, nuevamente preguntar el estado del PIN, si es el correcto dar como válido la pulsación, de lo contrario se trata de ruido/rebote.
4- (Opcional) Esperar un cierto tiempo (ej. 300mSeg o 1Seg) y verificar el estado del PIN, si sigue en el estado correcto, se toma como válida otra pulsación (esta condición habilita que el usuario aumente la cuenta manteniendo el botón pulsado).
5- (Opcional) Esperar a que el PIN vuelva al otro estado para repetir la secuencia desde 1, de lo contrario, se repite 4.

Entonces en base a eso, se realizarán las siguientes modificaciones:

*defines.c * => no hay modificaciones.

*configuracion_PLL.c * => no hay modificaciones.

*perifericos.c(se agrega a lo anterior)*


```
//--------------------------------------- TIMERS -----------------------------------------------------------------//
//Defines
//Timer0
//Timer1

void configura_timer2(u32 preescaler,u32 matcheo) 
{ 
    LPC_SC->PCONP|=(1<<PCTIM2); //Habilito el timer0 en el control de consumo de los periféricos
		//------------------ Configuración del Timer -----------------------------------------------// 
    LPC_TIM2->TCR=0; // el contador no cuenta 
    LPC_TIM2->TC=0;  // el contador comienza de 0 
    LPC_TIM2->PR=preescaler; // configuro la cuenta del prescaler tal q le mande una cuenta al contador c/ 1 mSeg 
    LPC_TIM2->PC=0;  // el prescaler comienza de 0 
    LPC_TIM2->MR0=matcheo; // configuro la cuenta del MR0
    LPC_TIM2->MCR=(1<<MR0R)|(1<<MR0I); // configuro q al producirse un evento en MR1, se lance una interrupcion y se resetee el contador 
    //------------------------------------------------------------------------------------------// 
    
    LPC_TIM2->TCR=(1<<COUNTER_EN); // el contador empieza a contar     
}
//--------------------------------------- TIMERS -----------------------------------------------------------------//

//--------------------------------------- EXTERNAS -----------------------------------------------------------------//
//Se mantiene lo anterior

//----------------- Estados Anti-Rebote --------------------------------//
#define ANTI_REB_IDLE								0
#define ANTI_REB_FLANCO_DETECTADO		1
#define ANTI_REB_5MSEG							2
#define ANTI_REB_10MSEG							3
#define ANTI_REB_300MSEG						4
//----------------- Estados Anti-Rebote --------------------------------//

int anti_rebote_ext1(u8* variable_estado)	//Para flanco descendente
{
	switch(*variable_estado)
		{
			case ANTI_REB_IDLE:
				{
					configura_timer1(25000,5);	//Pre-escaler 250000=> 5mSeg y 5cuentas => 5mSeg
					flag_timer1=0;
					*variable_estado=ANTI_REB_5MSEG;					
					break;
				}
			
			case ANTI_REB_5MSEG:
				{
					if(flag_timer1)
					{
						flag_timer1=0;
						if(!(LPC_GPIO2->FIOPIN&PIN_EXT1))	//Verifico el estado Bajo después de 5mSeg
							*variable_estado=ANTI_REB_10MSEG;							
						else
							{
								LPC_TIM1->TCR=0; // Paro el contador
								*variable_estado=ANTI_REB_IDLE;	// Vuelvo al 1er estado 
								LPC_PINCON->PINSEL4|=PINSEL_EXT1;	//Habilito EXT1
								flag_ext1=0;		//	Vuelvo a esperar por otra interrupción
							}	//Rebote detectado											
					}				
					break;
				}
			
			case ANTI_REB_10MSEG:
				{
					if(flag_timer1)
					{
						flag_timer1=0;
						if(!(LPC_GPIO2->FIOPIN&PIN_EXT1))	//Verifico el estado Bajo después de 5mSeg
							{
								configura_timer1(25000,300);				//Pre-escaler 250000=> 300mSeg y 300cuentas => 300 mSeg
								*variable_estado=ANTI_REB_300MSEG;	//Espero 1 seg. para confirmar si el botón sigue presionado						
								return 1;														//Pulsación confirmada
							}
						else
							{
								LPC_TIM1->TCR=0; // Paro el contador
								*variable_estado=ANTI_REB_IDLE;	// Vuelvo al 1er estado 
								LPC_PINCON->PINSEL4|=PINSEL_EXT1;	//Habilito EXT1
								flag_ext1=0;		//	Vuelvo a esperar por otra interrupción
							}	//Rebote detectado											
					}				
					break;
				}
			
			case ANTI_REB_300MSEG:
				{
					if(flag_timer1)
					{
						flag_timer1=0;
						if(!(LPC_GPIO2->FIOPIN&PIN_EXT1))	//Verifico el estado Bajo después de 5mSeg
							return 1;														//Pulsación repetida confirmada por botón apretado							
						else
							{
								LPC_TIM1->TCR=0; // Paro el contador
								*variable_estado=ANTI_REB_IDLE;	// Vuelvo al 1er estado 
								LPC_PINCON->PINSEL4|=PINSEL_EXT1;	//Habilito EXT1
								flag_ext1=0;		//	Vuelvo a esperar por otra interrupción
							}	//Pulsador liberado											
					}				
					break;
				}
		}
	
	return -1;
}

int anti_rebote_ext2(u8* variable_estado)	//Para flanco descendente
{
	switch(*variable_estado)
		{
			case ANTI_REB_IDLE:
				{
					configura_timer2(25000,5);	//Pre-escaler 250000=> 5mSeg y 5cuentas => 5mSeg
					flag_timer2=0;
					*variable_estado=ANTI_REB_5MSEG;					
					break;
				}
			
			case ANTI_REB_5MSEG:
				{
					if(flag_timer2)
					{
						flag_timer2=0;
						if(!(LPC_GPIO2->FIOPIN&PIN_EXT2))	//Verifico el estado Bajo después de 5mSeg
							*variable_estado=ANTI_REB_10MSEG;							
						else
							{
								LPC_TIM2->TCR=0; // Paro el contador
								*variable_estado=ANTI_REB_IDLE;	// Vuelvo al 1er estado 
								LPC_PINCON->PINSEL4|=PINSEL_EXT2;	// Habilito EXT2
								flag_ext2=0;		//	Vuelvo a esperar por otra interrupción
							}	//Rebote detectado											
					}				
					break;
				}
			
			case ANTI_REB_10MSEG:
				{
					if(flag_timer2)
					{
						flag_timer2=0;
						if(!(LPC_GPIO2->FIOPIN&PIN_EXT2))	//Verifico el estado Bajo después de 5mSeg
							{
								configura_timer2(25000,300);				//Pre-escaler 250000=> 300mSeg y 300cuentas => 300 mSeg
								*variable_estado=ANTI_REB_300MSEG;	//Espero 1 seg. para confirmar si el botón sigue presionado						
								return 1;														//Pulsación confirmada
							}
						else
							{
								LPC_TIM2->TCR=0; // Paro el contador
								*variable_estado=ANTI_REB_IDLE;	// Vuelvo al 1er estado 
								LPC_PINCON->PINSEL4|=PINSEL_EXT2;	// Habilito EXT2
								flag_ext2=0;		//	Vuelvo a esperar por otra interrupción
							}	//Rebote detectado											
					}				
					break;
				}
			
			case ANTI_REB_300MSEG:
				{
					if(flag_timer2)
					{
						flag_timer2=0;
						if(!(LPC_GPIO2->FIOPIN&PIN_EXT2))	//Verifico el estado Bajo después de 5mSeg
							return 1;														//Pulsación repetida confirmada por botón apretado							
						else
							{
								LPC_TIM2->TCR=0; 									// Paro el contador
								*variable_estado=ANTI_REB_IDLE;		// Vuelvo al 1er estado 
								LPC_PINCON->PINSEL4|=PINSEL_EXT2;	// Habilito EXT2
								flag_ext2=0;											//	Vuelvo a esperar por otra interrupción
							}	//Pulsador liberado											
					}				
					break;
				}
		}
	
	return -1;
}

//--------------------------------------- EXTERNAS -----------------------------------------------------------------//
```

Estas rutinas irán efectuando el proceso de anti-rebote descrito anteriormente y cuando se confirme una pulsación devolverá 1. Es importante ver tres cosas:

- Necesita usar una variable por referencia (el que no sabe que es esto, le recomiendo leer algo de C y sobre ese tema, pero básicamente sirve para evitar el uso de variables globales).

- No es bloqueante, como ya lo veremos en el main, es una función que devuelve de inmediato el estado en el que está la rutina de anti-rebote. 

- Una vez que el botón deja de ser presionado, se vuelve a configurar el puerto como EXT"x" (en la rutina de interrupción veremos que se deshabilita).

Este tipo de funciones está realizado en base a una máquina de estado bastante simple, ya que se adapta perfecto al procedimiento de anti-rebote.

*interrupciones.c (se agrega y modifica a lo anterior)*


```
void habilitar_interrupciones()
{
	NVIC_EnableIRQ(TIMER0_IRQn);	//Timer0
	NVIC_EnableIRQ(TIMER1_IRQn);	//Timer1
	NVIC_EnableIRQ(TIMER2_IRQn);	//Timer1
	NVIC_EnableIRQ(EINT1_IRQn);		//Externa 1
	NVIC_EnableIRQ(EINT2_IRQn);		//Externa 2
}

//Rutinas de Timer0/1 ya vistas

//--------- Rutina de la interrupcion timer2 ---------------------// 
void TIMER2_IRQHandler (void)  
{ 
	flag_timer2=1; 
	 
	LPC_TIM2->IR=1; // Limpio la interrupción por match0 --> Pag. 493
} 
//--------- Rutina de la interrupcion timer2 ---------------------//

//--------- Rutina de la interrupcion externa 1 ---------------------// 
void EINT1_IRQHandler (void)  
{ 
	flag_ext1=1; 
	
	LPC_PINCON->PINSEL4&=~((3<<22));	//Vuelvo a configurar como GPIO a la externa1 --> para preguntar el estado del PIN
	
	LPC_SC->EXTINT|=(1<<1);	// Limpio la interrupción externa 1
} 
//--------- Rutina de la interrupcion externa 1 ---------------------// 

//--------- Rutina de la interrupcion externa 2 ---------------------// 
void EINT2_IRQHandler (void)  
{ 
	flag_ext2=1; 
	
	LPC_PINCON->PINSEL4&=~((3<<24));	//Vuelvo a configurar como GPIO a la externa2 --> para preguntar el estado del PIN
	
	LPC_SC->EXTINT|=(1<<2);	// Limpio la interrupción externa 2
} 
//--------- Rutina de la interrupcion externa 2 ---------------------//
```

Dentro de lo más destacado, vean que en la rutina EXT1/2 se deshabilita dicho puerto para que trabaje como EXT y que lo haga como GPIO. 

¿Por qué esto? debido a que es necesario saber el estado del PIN durante la rutina de anti-rebote y para eso utilizaremos el registro FIOPIN que pertenece a los GPIO, esto último por ej. lo pueden ver en la linea de la rutina "if(!(LPC_GPIO2->FIOPIN&PIN_EXT1))".

*main.c (solución usando rutina anti-rebote)*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_ext1=0,flag_ext2=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"

int main()
{	
	u16 cont=TIEMPO_TOGGLE_INICIAL;
	u8 tiempo_inicial_variable=1,estado_anti_reb_ext1=ANTI_REB_IDLE,estado_anti_reb_ext2=ANTI_REB_IDLE;
	
	configurar_pll();// Cristal de 12MHz => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;															//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;											//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	habilitar_interrupciones();
	
	while(1)
	{		
		if(!cont)
		{
			cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL;
			
			if(LPC_GPIO3->FIOPIN&LED_1)
				{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
			else
				{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
		}
		
		__wfi();	//Sleep-Mode
		
		if(flag_timer0)
		{
			flag_timer0=0;
			cont--;
		}
		
		if(flag_ext1)
		{
			if(anti_rebote_ext1(&estado_anti_reb_ext1)>0)
			{
				if(tiempo_inicial_variable>1)
					tiempo_inicial_variable--;
			}						
		}
		
		if(flag_ext2)
		{
			if(anti_rebote_ext2(&estado_anti_reb_ext2)>0)
				tiempo_inicial_variable++;					
		}
	}
}
```

Como pueden ver las grandes ventajas de esta solución son:

- No es bloqueante (no tengo que esperar 200mSeg para recuperar el control del uC).
- Posee doble verificación en 5mSeg y 10mSeg.
- Se puede mantener apretado el pulsador y c/300mSeg se tomará como válida una nueva pulsación (el tiempo puede modificarse, en vez de 300mSeg podrían ser 1Seg).

Para el próximo mensaje vamos a ver como funciona la Uart, mientras tanto les subo el código de estas dos soluciones y el código del mensaje anterior.


----------



## cosmefulanito04 (May 6, 2013)

*Diagrama en bloques:*



*Procedimiento de inicialización:*

1- Habilitar alimentación de la Uart”x” (registro PCONP).
2- Configurar el pre-escaler de la Uart”x” (registro PCLKSEL0/1).
3- Configurar el baud rate mediante el “Divisor Latch”  (registros DLL/M). Para modificar el valor del “Divisor Latch” es necesario que el bit “Dlab” del registro “LCR” se encuentre en 1.
4- Luego de configurar el baud rate, dejar en 0 el bit “Dlab”.
5- Habilitar o no el FIFO para utilizar DMA (opcional).
6- Configurar los Pines para que funcionen como Uart”x” (registro PINSEL”x”) y que funcionen como Pull-up (registro PINMODE”x”).
7- Habilitar la interrupción por Rx y Tx (registro IER).
8- Configurar el DMA (opcional).

*Baud Rate:*



Se puede ver que la velocidad depende del Pclock de la Uart”x” y del “Divisor Latch”. 

DivAddVal y MulVal se utilizan para realizar un “ajuste fino” y de esta forma obtener el menor error posible en la velocidad. Por lo tanto, en función de la velocidad que se obtiene sin estos registros, se evalúa si son necesarios o no utilizarlos. En este ejemplo, DivAddVal será igual a 0.

*Ejercicio propuesto:*

En base al ejercicio anterior (usando interrupciones externas), se desea enviar por puerto serie el valor de la cuenta c/1 segundo, mediante el uso de la tecla "a" modificar la cuenta en forma ascendente y mediante la tecla "d" en forma descendente. Se desea que el puerto serie trabaje con 8bits de datos, sin bit paridad y a 9600bps.

Nuevamente usaremos las funciones que ya se desarrollaron anteriormente y se agregarán las necesarias para resolver este ejercicio.

*define.c* => no hay modificaciones.

*configuracion_PLL.c* => no hay modificaciones.

*perifericos.c* (se agrega a lo anterior)


```
//--------------------------------------- TIMERS -----------------------------------------------------------------//
// Todo lo anterior

#define PCTIM3			23

void configura_timer3(u32 preescaler,u32 matcheo) 
{ 
    LPC_SC->PCONP|=(1<<PCTIM3); //Habilito el timer3 en el control de consumo de los periféricos
		//------------------ Configuración del Timer -----------------------------------------------// 
    LPC_TIM3->TCR=0; // el contador no cuenta 
    LPC_TIM3->TC=0;  // el contador comienza de 0 
    LPC_TIM3->PR=preescaler; // configuro la cuenta del prescaler tal q le mande una cuenta al contador c/ 1 mSeg 
    LPC_TIM3->PC=0;  // el prescaler comienza de 0 
    LPC_TIM3->MR0=matcheo; // configuro la cuenta del MR0
    LPC_TIM3->MCR=(1<<MR0R)|(1<<MR0I); // configuro q al producirse un evento en MR1, se lance una interrupcion y se resetee el contador 
    //------------------------------------------------------------------------------------------// 
    
		LPC_TIM3->TCR=(1<<COUNTER_EN); // el contador empieza a contar     
}
//--------------------------------------- TIMERS -----------------------------------------------------------------//

//--------------------------------------- UARTS --------------------------------------------------------------------//
//------------- Configuración Uart0 ---------------//
#define UART_DATA_8_BIT	3
#define UART_DLAB_1			(1<<7)
#define UART_THREI			(1<<1)
#define UART_RXI				(1<<0)
#define UART_RLSI				(1<<2)
//------------- Configuración Uart0 ---------------//

//------------- Baud Rates ------------------------//
#define UART_115200			14
#define UART_57600			27
#define UART_38400			41
#define UART_19200			81
#define UART_9600				163
#define UART_4800				325
#define UART_2400				651
#define UART_1200				1302
//------------- Baud Rates ------------------------//

//----------------- Configuración del PIN como UART0 --------------------//
#define PINSEL_TX_UART0	(1<<4)	//Bit 5:4 PINSEL0 -> Funcionando como /TXD0
#define PINSEL_RX_UART0	(1<<6)	//Bit 7:6 PINSEL0 -> Funcionando como /RXD0
//----------------- Configuración del PIN como UART0 --------------------//

void configurar_uart0(u16 divisor_latch)			//Velocidad (baudios)=PCLK/[16*256*DivisorLatch*(1+DivAddVal/MulVal)]
{  
	LPC_SC->PCONP|=(1<<3); //Habilito la uart0 en el control de consumo de los periféricos
	
	LPC_PINCON->PINSEL0&=~((3<<6)|(3<<4));
	LPC_PINCON->PINSEL0|=PINSEL_RX_UART0|PINSEL_TX_UART0;		//Pines del puerto serie funcionando como tal
	LPC_PINCON->PINMODE0&=~((3<<6)|(3<<4));									//Pines con Pull-up interno
	
	LPC_UART0->LCR=UART_DLAB_1|UART_DATA_8_BIT;		//8bits de datos, 1 bit de stop, sin paridad, DLAB=1. 		
	LPC_UART0->DLL=(divisor_latch&0xff);					//Parte baja
	LPC_UART0->DLM=(divisor_latch>>8)&0xff; 			//Parte alta
	LPC_UART0->LCR=UART_DATA_8_BIT;								//8bits de datos, 1 bit de stop, sin paridad, DLAB=0. 		
	LPC_UART0->IER=UART_THREI|UART_RXI;						//Habilito interrupción por Rx y Tx
}

int recibe_dato_uart0()
{	
	configura_timer3(25000,33);	//Pre-escaler 250000=> 1mSeg y 3cuentas => 33mSeg
	flag_timer3=0;
	
	while((!flag_timer3)&&(!flag_uart0_rx))	//Espera a tener un dato Rx con un time-out de 32mSeg
		__wfi();	//Sleep-Mode
	
	if(flag_uart0_rx)
	{
		flag_uart0_rx=0;
		
		LPC_TIM3->TCR=0; // paro el contador
		flag_timer3=0;
		return 1;
	}
	
	LPC_TIM3->TCR=0; // paro el contador
	flag_timer3=0;
	return -1;
}

int envia_dato_uart0(u8 dato)
{
	configura_timer3(25000,33);	//Pre-escaler 250000=> 1mSeg y 3cuentas => 33mSeg	
	flag_timer3=0;
	
	while((!flag_timer3)&&(!flag_uart0_tx))	//Espera a tener el puerto este libre con un time-out de 33mSeg
		__wfi();	//Sleep-Mode
			
	if(flag_uart0_tx)
	{
		LPC_UART0->THR=dato;
	  flag_uart0_tx=0;
		
		LPC_TIM3->TCR=0; // paro el contador
		flag_timer3=0;
		return 1;
	}				
	
	LPC_TIM3->TCR=0; // paro el contador
	flag_timer3=0;	
	return -1;
}
//--------------------------------------- UARTS --------------------------------------------------------------------//
```

Se agregó:

- Inicialización del Timer 3, usado como time-out para la funciones de envío y recepción. 
- Inicialización de la Uart0.
- Función *bloqueante* envia_dato_uart0 con un time-out de 33mSeg. Espera hasta 33mSeg que el puerto serie esté disponible para enviar datos.
- Función *bloqueante* recibe_dato_uart0() con un time-out de 33mSeg. Espera hasta 33mSeg que el puerto serie reciba un dato (esta última función no será utilizada).

*interrupciones.c* (se agrega a lo anterior)


```
void habilitar_interrupciones()
{
	NVIC_EnableIRQ(TIMER0_IRQn);	//Timer0
	NVIC_EnableIRQ(TIMER1_IRQn);	//Timer1
	NVIC_EnableIRQ(TIMER2_IRQn);	//Timer2
	NVIC_EnableIRQ(TIMER3_IRQn);	//Timer3
	NVIC_EnableIRQ(EINT1_IRQn);		//Externa 1
	NVIC_EnableIRQ(EINT2_IRQn);		//Externa 2
	NVIC_EnableIRQ(UART0_IRQn);		//UART 0
}

//... Rutinas ya vistas

//--------- Rutina de la interrupcion timer3 ---------------------// 
void TIMER3_IRQHandler (void)  
{ 
	flag_timer3=1; 
	 
	LPC_TIM3->IR=1; // Limpio la interrupción por match0 --> Pag. 493
} 
//--------- Rutina de la interrupcion timer3 ---------------------//

//--------- Rutina de la interrupcion Uart 0 ---------------------// 
#define UART_INT_TX			(1<<6)
#define UART_INT_RX			(1<<0)

void UART0_IRQHandler (void)  
{ 
	u8 dummy=LPC_UART0->IIR;						//Limpieza del la interrupción
	
	if((LPC_UART0->LSR)&(UART_INT_RX))	//Pregunto la fuente de interrupción => Rx o Tx
	{
		flag_uart0_rx=1;
		dato_uart0=(u8)(LPC_UART0->RBR);	//Recibo el dato
	}
	else
	{
		flag_uart0_tx=1;
	} 	
} 
//--------- Rutina de la interrupcion Uart 0 ---------------------//
```

Lo más destacado de la rutina de la uart es:

- La necesidad de limpiar el flag de interrupción mediante una lectura falsa al registro IIR.
- Averiguar la fuente de interrupción, si es debido a que el Tx se encuentra vacío o que se recibió un dato. Para lo cual se utiliza el registro de estado LSR.

*funciones_uart.c*


```
#define fin_de_linea '\n'
#define fin_de_linea_para_pc '\n'

void convertir_digitos_u8_serie(u8 digitos[],u8 dato)
{
	u8 aux;
	
	for(aux=2;aux>0;aux--)
		{
		digitos[aux]=(dato%10)+0x30;
		dato=(int)(dato/10);
		}
		
	digitos[0]=dato+0x30;
}

void convertir_digitos_u16_serie(u8 digitos[],u16 dato)
{
	u8 aux;
	
	for(aux=4;aux>0;aux--)
		{
		digitos[aux]=(dato%10)+0x30;
		dato=(int)(dato/10);
		}
		
	digitos[0]=dato+0x30;
}

void enviar_string_uart0(u8 string[])
{
	u8 cont=0;
	
	do{
		envia_dato_uart0(string[cont]);
		cont++;
		
		if(cont==0xff)
			break;			
	}while(string[cont-1]!=fin_de_linea);
}

u8 envia_u8_string_uart0(u8 dato)	//Convierte un byte (0-255) en 3 bytes strings
{
	u8 digitos[3]={0,0,0};
		
	convertir_digitos_u8_serie(digitos,dato);
	
	envia_dato_uart0(digitos[0]);	//Envia el byte convertido en un string de 3 bytes
	envia_dato_uart0(digitos[1]);
	envia_dato_uart0(digitos[2]);
	
	return (digitos[2]+digitos[1]+digitos[0]);
}

u8 envia_u16_string_uart0(u16 dato)	//Convierte un u16 (0-65536) en 5 bytes strings
{
	u8 digitos[5]={0,0,0,0,0};
		
	convertir_digitos_u16_serie(digitos,dato);
	
	envia_dato_uart0(digitos[0]);	//Envia el u16 convertido en un string de 5 bytes
	envia_dato_uart0(digitos[1]);
	envia_dato_uart0(digitos[2]);
	envia_dato_uart0(digitos[3]);
	envia_dato_uart0(digitos[4]);

	return (digitos[4]+digitos[3]+digitos[2]+digitos[1]+digitos[0]);
}
```

Para poder trabajar con facilidad el puerto serie, se propone las siguientes funciones:

- enviar_string_uart0: simplemente manda un string por puerto serie, requiere el fin de linea '\n'.
- envia_u8_string_uart0: convierte un dato unsigned char en un string (3 dígitos máx) y lo envía. 
- envia_u16_string_uart0: convierte un dato unsigned int en un string (5 dígitos máx) y lo envía.

*main.c*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

int main()
{	
	u16 cont=TIEMPO_TOGGLE_INICIAL;
	u8 tiempo_inicial_variable=1,estado_anti_reb_ext1=ANTI_REB_IDLE,estado_anti_reb_ext2=ANTI_REB_IDLE,flag_ascendente=0;
	
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;															//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;											//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	configurar_uart0(UART_9600); //9600bps
	habilitar_interrupciones();
	
	while(1)
	{		
		
		if(!flag_ascendente)
		{
			if(cont==1)
			{
				cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}

		}
		else
		{
			if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL)-1)
			{
				cont=0;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}
		}
		
		__wfi();	//Sleep-Mode
		
		if(flag_timer0)
		{
			flag_timer0=0;
			
			if(!flag_ascendente)
				cont--;
			else
				cont++;
			
			enviar_string_uart0("Contador= \n");
			envia_u16_string_uart0(cont);
			enviar_string_uart0(" \n");
		}
		
		if(flag_ext1)
		{
			if(anti_rebote_ext1(&estado_anti_reb_ext1)>0)
			{
				if(tiempo_inicial_variable>1)
					tiempo_inicial_variable--;
			}						
		}
		
		if(flag_ext2)
		{
			if(anti_rebote_ext2(&estado_anti_reb_ext2)>0)
				tiempo_inicial_variable++;					
		}
		
		if(flag_uart0_rx)
		{
			flag_uart0_rx=0;
			
			if(dato_uart0=='a')
				flag_ascendente=1;	//Cuenta Ascendente
			
			if(dato_uart0=='d')
				flag_ascendente=0;	//Cuenta descendente
		}
	}
}
```

A tener en cuenta:

- No se utilizan las colas FIFO para implementar DMA.
- La función de envío es bloqueante, por lo tanto no es algo del todo optimizado, pero está basado en las funciones tipo linux gcc que utiliza el SO (las cuales también suelen ser bloqueantes).

Les dejo el código y para el próximo mensaje vamos a ver como funciona el RTC.


----------



## cosmefulanito04 (May 12, 2013)

*Diagramas en bloques:*

- Alimentación y oscilador



Requiere un oscilador externo de 32,768 kHz para generar un clock de 1Hz. Mediante el uso de una batería de litio, se evita perder la hora configurada.

- Contadores y alarmas internas



*Procedimiento de inicialización:*

1- Habilitar alimentación del RTC (registro PCONP).
2- Resetear cuenta y deshabilitar conteo (registro CCR).
3- Configurar hora inicial (registros SEC, MIN, HOUR…. etc).
4- Habilitar conteo (registro CCR).
5- Configurar hora de alarma (registros ALSEC, ALMIN,…. etc) [Opcional].
6- Realizar ajuste de calibración (registro CALIBRATION) y habilitar calibración (registro CCR) [Opcional].
7- Habilitar interrupción por conteo (registro CIIR) o por alarma (registro AMR).

*Procedimiento de calibración (Opcional):*

Mediante el pin 1.27 configurado en CLKOUT (registro PINSEL”x”), se mide el desajuste del oscilador del RTC en 1 seg usando un osciloscopio. Se utiliza dicho valor en los bits CALVAL (registro CALIBRATION). 

*Almacenamiento de información (Opcional):*

El RTC nos da la opción de almacenar en 5 registros de 32 bits (GPREG0/1…/4), cualquier información que nos pueda ser de utilidad, de forma tal que si operamos sin fuente de alimentación, dicha información seguirá estando presente gracias a la batería de litio que utiliza el RTC para no perder la hora.

Luego de hacer un brevísimo resumen (se recomienda profundizar el tema con la hoja de datos, Chapter 27: LPC17xx Real-Time Clock (RTC) and backup registers), seguimos con el código.

*Ejercicio parte 1:*

Del ejercicio realizado anteriormente con la UART, se pide generar la base de tiempo de 1 seg mediante el uso del RTC (en ejercicios anteriores usamos el timer0 como base de tiempo).

*defines.c* => no hay modificaciones.

*configuracion_PLL.c* => no hay modificaciones.

*funciones_uart.c* => no hay modificaciones.

*perifericos.c* (se agrega a lo anterior)


```
//... Todo el código visto anteriormente
//--------------------------------------- RTC ----------------------------------------------------------------------//
#define PCRTC						12

#define CLKEN						0
#define CTCRST					1
#define CCALEN					4

#define RTC_SEG					0
#define RTC_MIN					1
#define RTC_HOR					2
#define RTC_DIA_MES			3
#define RTC_DIA_SEMANA	4
#define RTC_DIA_ANIO		5
#define RTC_MES					6
#define RTC_ANIO				7
#define RTC_TAMANIO			8

void configurar_hora_rtc(u16 vector_hora_inicial[])
{
	LPC_SC->PCONP|=(1<<PCRTC); 													//Habilito la RTC en el control de consumo de los periféricos
	LPC_RTC->CCR=(1<<CTCRST);														//Reseteo el RTC y no habilito la cuenta
	
	LPC_RTC->SEC = vector_hora_inicial[RTC_SEG];
  LPC_RTC->MIN = vector_hora_inicial[RTC_MIN];
  LPC_RTC->HOUR = vector_hora_inicial[RTC_HOR];
  LPC_RTC->DOM = vector_hora_inicial[RTC_DIA_MES];
  LPC_RTC->DOW = vector_hora_inicial[RTC_DIA_SEMANA];
  LPC_RTC->DOY = vector_hora_inicial[RTC_DIA_ANIO];
  LPC_RTC->MONTH = vector_hora_inicial[RTC_MES];
  LPC_RTC->YEAR = vector_hora_inicial[RTC_ANIO];
	
	LPC_RTC->CCR=(1<<CCALEN)|(1<<CLKEN);														//Habilito la cuenta sin calibracion
}

void configurar_alarmas_rtc(u16 vector_alarma[])
{
	LPC_RTC->ALSEC=vector_alarma[RTC_SEG];				//Cargo vector alarma
	LPC_RTC->ALMIN=vector_alarma[RTC_MIN];
	LPC_RTC->ALHOUR=vector_alarma[RTC_HOR];
	LPC_RTC->ALDOM=vector_alarma[RTC_DIA_MES];
	LPC_RTC->ALDOW=vector_alarma[RTC_DIA_SEMANA];
	LPC_RTC->ALDOY=vector_alarma[RTC_DIA_ANIO];
	LPC_RTC->ALMON=vector_alarma[RTC_MES];
	LPC_RTC->ALYEAR=vector_alarma[RTC_ANIO];
}

void habilitar_interrupciones_rtc(u8 int_conteo,u8 int_alarma)
{
	LPC_RTC->CIIR=int_conteo;
	LPC_RTC->AMR=int_alarma;	
}

void calibrar_rtc(u16 ajuste_conteo,u8 dir_correccion)
{
	LPC_RTC->CALIBRATION=(1&(dir_correccion<<17))|ajuste_conteo;
	LPC_RTC->CCR&=~(1<<CCALEN);	//Habilito la calibración y sin parar la cuenta
}

void guardar_info_rtc(u32 datos_rtc[])
{
	LPC_RTC->GPREG0=datos_rtc[0];
	LPC_RTC->GPREG1=datos_rtc[1];
	LPC_RTC->GPREG2=datos_rtc[2];
	LPC_RTC->GPREG3=datos_rtc[3];
	LPC_RTC->GPREG4=datos_rtc[4];
}

void leer_info_rtc(u32 datos_rtc[])
{
	datos_rtc[0]=LPC_RTC->GPREG0;
	datos_rtc[1]=LPC_RTC->GPREG1;
	datos_rtc[2]=LPC_RTC->GPREG2;
	datos_rtc[3]=LPC_RTC->GPREG3;
	datos_rtc[4]=LPC_RTC->GPREG4;
}

void leer_hora_rtc(u16 vector_hora[])
{	
	vector_hora[RTC_SEG]=LPC_RTC->SEC;
  vector_hora[RTC_MIN]=LPC_RTC->MIN;
  vector_hora[RTC_HOR]=LPC_RTC->HOUR;
  vector_hora[RTC_DIA_MES]=LPC_RTC->DOM;
  vector_hora[RTC_DIA_SEMANA]=LPC_RTC->DOW;
  vector_hora[RTC_DIA_ANIO]=LPC_RTC->DOY;
  vector_hora[RTC_MES]=LPC_RTC->MONTH;
  vector_hora[RTC_ANIO]=LPC_RTC->YEAR;	
}
//--------------------------------------- RTC ----------------------------------------------------------------------//
```

Se pueden encontrar las siguientes funciones:

- Inicialización y arranque del RTC.
- Configuración de la hora de alarma.
- Habilitación de interrupción, por conteo o por alarma.
- Función de calibración, agregará el offset medido, ya sea positivo o negativo. 
- Almacenamiento y lectura de los registros generales de almacenamiento de información.
- Lectura de la hora actual del RTC.

Para la funciones se podría haber usado alguna estructura en vez de un vector, el inconveniente que le encuentro a eso, es que en un futuro la estructura no permitirá una configuración rápida como un vector con una rutina FOR.

*interrupciones.c* (se agrega y modifica a lo anterior)


```
void habilitar_interrupciones()
{
	NVIC_EnableIRQ(TIMER0_IRQn);	//Timer0
	NVIC_EnableIRQ(TIMER1_IRQn);	//Timer1
	NVIC_EnableIRQ(TIMER2_IRQn);	//Timer2
	NVIC_EnableIRQ(TIMER3_IRQn);	//Timer3
	NVIC_EnableIRQ(EINT1_IRQn);		//Externa 1
	NVIC_EnableIRQ(EINT2_IRQn);		//Externa 2
	NVIC_EnableIRQ(UART0_IRQn);		//UART 0
	NVIC_EnableIRQ(RTC_IRQn);		//RTC
}

//... Todos los handlers vistos anteriormente

//--------- Rutina de la interrupcion RTC ---------------------// 
#define RTCCIF	0
#define RTCALF	1

void RTC_IRQHandler (void)  
{ 
	flag_rtc=1;
	
	LPC_RTC->ILR|=(1<<RTCALF)|(1<<RTCCIF);		//Limpio las interrupciones 	
} 
//--------- Rutina de la interrupcion RTC ---------------------//
```

*main.c*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

int main()
{	
	u16 cont=TIEMPO_TOGGLE_INICIAL;
	u8 tiempo_inicial_variable=1,estado_anti_reb_ext1=ANTI_REB_IDLE,estado_anti_reb_ext2=ANTI_REB_IDLE,flag_ascendente=0;
	
	//-------------- Vectores RTC -----------------//
	u16 vector_hora_inicial_rtc[RTC_TAMANIO]={0,0,0,1,0,1,1,2013};
	//u16 vector_alarma_rtc[RTC_TAMANIO]={0,5,0,1,0,1,1,2013};
	//-------------- Vectores RTC -----------------//
	
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;															//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;											//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	configurar_hora_rtc(vector_hora_inicial_rtc);
	habilitar_interrupciones_rtc((1<<RTC_SEG),0xff);	//Habilito interrupción por c/cuenta de 1 Seg del RCT.
	//configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	configurar_uart0(UART_9600); //9600bps
	habilitar_interrupciones();
	
	while(1)
	{		
		
		if(!flag_ascendente)
		{
			if(cont==1)
			{
				cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}

		}
		else
		{
			if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL)-1)
			{
				cont=0;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}
		}
		
		__wfi();	//Sleep-Mode
		
		//if(flag_timer0)
		if(flag_rtc)	//RTC como base de tiempo => 1 interrupción por c/Seg
		{
			//flag_timer0=0;
			flag_rtc=0;
			if(!flag_ascendente)
				cont--;
			else
				cont++;
			
			enviar_string_uart0("Contador= \n");
			envia_u16_string_uart0(cont);
			enviar_string_uart0(" \n");
		}
		
		if(flag_ext1)
		{
			if(anti_rebote_ext1(&estado_anti_reb_ext1)>0)
			{
				if(tiempo_inicial_variable>1)
					tiempo_inicial_variable--;
			}						
		}
		
		if(flag_ext2)
		{
			if(anti_rebote_ext2(&estado_anti_reb_ext2)>0)
				tiempo_inicial_variable++;					
		}
		
		if(flag_uart0_rx)
		{
			flag_uart0_rx=0;
			
			if(dato_uart0=='a')
				flag_ascendente=1;	//Cuenta Ascendente
			
			if(dato_uart0=='d')
				flag_ascendente=0;	//Cuenta descendente
		}
	}
}
```

El cambio más destacado del anterior ejercicio, es que el timer0 no está habilitado (comentado en el código) y que ahora la base de tiempo de 1 Seg depende exclusivamente del RTC.

*Ejercicio parte 2:*

Del ejercicio realizado anteriormente con la UART, manteniendo al timer0 como base de tiempo de 1 seg, se pide: 

- Configurar una alarma para que se envíe por puerto serie un aviso transcurrido los 5 minutos después del reseteo.
- Si se envía por puerto serie el carácter ‘r’, enviar por puerto serie la cuenta del RTC en el formato “Día del mes/Mes/Año Hora:Min:Seg” c/1seg.
- Si se envía por puerto serie el carácter ‘c’, enviar por puerto serie la cuenta implementada en los ejercicios anteriores para saber en qué momento se dará cambio de estado de los leds.

*defines.c* => no hay modificaciones.

*configuracion_PLL.c* => no hay modificaciones.

*perifericos.c* => no hay modificaciones.

*interrupciones.c* => no hay modificaciones.

*funciones_uart.c* (se agrega y modifica a lo anterior)


```
//... Todas las funciones anteriores
void enviar_hora_rtc_uart0()
{
	u16 vector_lee_hora_rtc[RTC_TAMANIO];
	
	leer_hora_rtc(vector_lee_hora_rtc);
	envia_u16_string_uart0(vector_lee_hora_rtc[RTC_DIA_MES]);
	envia_dato_uart0('/');
	envia_u16_string_uart0(vector_lee_hora_rtc[RTC_MES]);
	envia_dato_uart0('/');
	envia_u16_string_uart0(vector_lee_hora_rtc[RTC_ANIO]);
	envia_dato_uart0(' ');
	envia_u16_string_uart0(vector_lee_hora_rtc[RTC_HOR]);
	envia_dato_uart0(':');
	envia_u16_string_uart0(vector_lee_hora_rtc[RTC_MIN]);
	envia_dato_uart0(':');
	envia_u16_string_uart0(vector_lee_hora_rtc[RTC_SEG]);
	envia_dato_uart0(' ');
}
```

*main.c*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

int main()
{	
	u16 cont=TIEMPO_TOGGLE_INICIAL;
	u8 tiempo_inicial_variable=1,estado_anti_reb_ext1=ANTI_REB_IDLE,estado_anti_reb_ext2=ANTI_REB_IDLE,flag_ascendente=0,flag_mostrar_hora=0;
	
	//-------------- Vectores RTC -----------------//
	u16 vector_hora_inicial_rtc[RTC_TAMANIO]={0,0,0,1,0,1,1,2013};
	u16 vector_alarma_rtc[RTC_TAMANIO]={0,5,0,1,0,1,1,2013};  
	//-------------- Vectores RTC -----------------//
	
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;															//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;											//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	configurar_hora_rtc(vector_hora_inicial_rtc);
	configurar_alarmas_rtc(vector_alarma_rtc);	//Al minuto 5 se espera una alarma.
	habilitar_interrupciones_rtc(0,0);	//Habilito las máscaras interrupción por Alarma => en caso que la cuenta sea igual a la alarma seteada.
	configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	configurar_uart0(UART_9600); //9600bps
	habilitar_interrupciones();
	
	while(1)
	{		
		
		if(!flag_ascendente)
		{
			if(cont==1)
			{
				cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}

		}
		else
		{
			if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL)-1)
			{
				cont=0;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}
		}
		
		__wfi();	//Sleep-Mode
		
		if(flag_timer0)		
		{
			flag_timer0=0;			
			if(!flag_ascendente)
				cont--;
			else
				cont++;
			
			if(!flag_mostrar_hora)
			{
				enviar_string_uart0("Contador= \n");
				envia_u16_string_uart0(cont);
				enviar_string_uart0(" \n");
			}
			else
				enviar_hora_rtc_uart0();
		}
		
		if(flag_ext1)
		{
			if(anti_rebote_ext1(&estado_anti_reb_ext1)>0)
			{
				if(tiempo_inicial_variable>1)
					tiempo_inicial_variable--;
			}						
		}
		
		if(flag_ext2)
		{
			if(anti_rebote_ext2(&estado_anti_reb_ext2)>0)
				tiempo_inicial_variable++;					
		}
		
		if(flag_uart0_rx)
		{
			flag_uart0_rx=0;
			
			if(dato_uart0=='a')
				flag_ascendente=1;	//Cuenta Ascendente
			
			if(dato_uart0=='d')
				flag_ascendente=0;	//Cuenta descendente
			
			if(dato_uart0=='r')
				flag_mostrar_hora=1;	//Muestra el valor del RTC c/1Seg.
			
			if(dato_uart0=='c')
				flag_mostrar_hora=0; 	//Muestra el valor de la cuenta c/1Seg.
		}
		
		if(flag_rtc)
		{
			flag_rtc=0;
			enviar_string_uart0(" ¡¡Alarma del RTC!!! - Pasaron 5 minutos desde el último reseteo. \n");			
		}
	}
}
```

En este código, la base de tiempo de 1Seg sigue siendo el timer0, pero el RTC se lo configura para que a los 5 minutos se envíe un mensaje de alarma.

Acá se puede ver en un "terminal", como se envía el valor que va tomando el RTC c/1Seg, hasta que llega a los 5 minutos:



Si bien la hora del RTC no tiene los dígitos ajustados (todos los valores tienen si o si 5 dígitos), para la prueba nos alcanza. Una forma de mejorar la presentación sería modificando la función "envia_u16_string_uart0" para que envíe una cierta cantidad de dígitos.

Les dejo el código y para el próximo mensaje vamos a ver como funciona el ADC.


----------



## cosmefulanito04 (May 18, 2013)

*Características principales:*

•	12 bit de resolución hasta 200kHz.
•	8 canales disponibles.
•	Tensión de referencia positiva y negativa para mejorar en SPAN.
•	Modo burst, convierte todo el tiempo.
•	Conversión por una transición de entrada o por un Timer-match.

*Procedimiento de inicialización:*

1-	Habilitar alimentación del ADC (registro PCONP).
2-	Configurar el Pre-escaler del ADC (registro PCLKSEL0)
3-	Configurar los pines como ADC (registros PINSEL”x”) y que funcionen como alta impedancia (registros PINMODE”x”)
4-	Configurar Offset (registro ADTRM)[Opcional].
5-	Habilitar interrupción (regitro ADGINTEN).
6-	Configurar el modo DMA[Opcional].

*Los registros a utilizar serán:*

•	ADTRM para fijar el offset.
•	ADINTEN para habilitar la interrupción.
•	ADCR, para configurar el pre-escaler interno, el comienzo de la conversión y el canal.
•	ADGDR, registro donde se almacena la conversión.

Para profundizar más sobre el uso del ADC, dirigirse a la hoja de datos en el “Chapter 29: LPC17xx Analog-to-Digital Converter (ADC)” página 574.

*Ejercicio propuesto:*

En base al ejercicio anterior (en el cual se usó el timer0 como base de tiempo), se pide que al enviar por el puerto serie el caracter 'q', se realice una conversión de ADC c/1Seg por el canal 0 (P0.23) a una frecuencia de clock máxima de 100kHz (para aprovechar los 12 bit de resolución) y se envíe por el puerto serie el resultado de dicha conversión.

*define.c* (se agrega a lo anterior)


```
//.... Lo anterior

#define MOSTRAR_CONTADOR				0
#define MOSTRAR_HORA_RTC				1
#define MOSTRAR_CONVERSION_ADC	      2
```

*configuracion_PLL.c* => no hay modificaciones.

*perifericos.c* (se agrega a lo anterior)


```
//... Todo lo anterior

//--------------------------------------- ADC ----------------------------------------//
#define PCADC				12

#define CLKDIV			8
#define PDN					21
#define ADC_START		24

#define ADCOFFS			4

#define ADGINTEN		8

void iniciar_adc(u8 canal,u8 offset)
{
	LPC_SC->PCONP|=(1<<PCADC); 													//Habilito el ADC en el control de consumo de los periféricos
	
	switch(canal)
	{
		case 0:
			{
				LPC_PINCON->PINSEL1&=~((3<<14));
				LPC_PINCON->PINSEL1|=(1<<14);		//Pin 0.23 funcionando como ADC
				LPC_PINCON->PINMODE1&=~((3<<14));
				LPC_PINCON->PINMODE1|=(2<<14);		//Pin 0.23 funcionando sin pull-up/down				
				break;
			}	
		
		case 1:
			{
				LPC_PINCON->PINSEL1&=~((3<<16));
				LPC_PINCON->PINSEL1|=(1<<16);		//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE1&=~((3<<16));
				LPC_PINCON->PINMODE1|=(2<<16);		//Pin 0.24 funcionando sin pull-up/down				
				break;
			}	
			
		case 2:
			{
				LPC_PINCON->PINSEL1&=~((3<<18));
				LPC_PINCON->PINSEL1|=(1<<18);		//Pin 0.25 funcionando como ADC
				LPC_PINCON->PINMODE1&=~((3<<18));
				LPC_PINCON->PINMODE1|=(2<<18);		//Pin 0.25 funcionando sin pull-up/down				
				break;
			}	
		
		case 3:
			{
				LPC_PINCON->PINSEL1&=~((3<<20));
				LPC_PINCON->PINSEL1|=(1<<20);		//Pin 0.26 funcionando como ADC
				LPC_PINCON->PINMODE1&=~((3<<20));
				LPC_PINCON->PINMODE1|=(2<<20);		//Pin 0.26 funcionando sin pull-up/down								
				break;
			}
			
		case 4:
			{
				LPC_PINCON->PINSEL3|=(3<<28);		//Pin 1.30 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<28));
				LPC_PINCON->PINMODE3|=(2<<28);		//Pin 1.30 funcionando sin pull-up/down												
				break;
			}
		
		case 5:
			{				
				LPC_PINCON->PINSEL3|=0xC0000000;
				LPC_PINCON->PINMODE3|=0x80000000;		//Pin 1.31 funcionando sin pull-up/down																
				break;
			}
		
		case 6:
			{
				LPC_PINCON->PINSEL0&=~((3<<6));
				LPC_PINCON->PINSEL0|=(2<<6);		//Pin 0.3 funcionando como ADC
				LPC_PINCON->PINMODE0&=~((3<<6));
				LPC_PINCON->PINMODE0|=(2<<6);		//Pin 0.3 funcionando sin pull-up/down																
				break;
			}
		
		case 7:
			{
				LPC_PINCON->PINSEL0&=~((3<<4));
				LPC_PINCON->PINSEL0|=(2<<4);		//Pin 0.2 funcionando como ADC
				LPC_PINCON->PINMODE0&=~((3<<4));
				LPC_PINCON->PINMODE0|=(2<<4);		//Pin 0.2 funcionando sin pull-up/down
				break;
			}
		
		default:{return;}
	}
	
	LPC_ADC->ADTRM|=((offset&0x0f)<<ADCOFFS);
}

int convertir_adc(u8 canal,u8 pre_escaler_interno,u16 *valor_adc)
{	
	LPC_ADC->ADINTEN|=(1<<ADGINTEN);	//Habilito la interrupción
	LPC_ADC->ADCR=(1<<ADC_START)|(1<<PDN)|((pre_escaler_interno-1)<<CLKDIV)|(1<<canal);	//Elijo el canal de conversión, divido el clock por 25 (25MHZ a 1MHz<13MHz)
	
	configura_timer3(25000,1);	//Pre-escaler 250000=> 1mSeg y 1cuenta => 1mSeg
	flag_timer3=0;
	
	while((!flag_timer3)&&(!flag_adc))//Espera a tener un dato Rx con un time-out de 32mSeg
		__wfi();	//Sleep-Mode
	
	if(flag_adc)	//Pregunto si realmente termino la conversión en el canal deseado
	{		
		LPC_TIM3->TCR=0; // paro el contador
		flag_timer3=0;
		*valor_adc=(u16)((LPC_ADC->ADGDR>>4)&0xfff);				
		return 1;
	}
	
	LPC_TIM3->TCR=0; // paro el contador
	flag_timer3=0;
	return -1;
}
//--------------------------------------- ADC ----------------------------------------//
```

- Función de inicialización: tiene de argumentos el canal (necesario para configurar el PINSEL"x" y el PINMODE"x") y el valor de offset. No habilita la interrupción (preferí habilitarla cuando se desee realizar la conversión).

- Función de conversión: tiene de argumentos el canal a convertir, el valor del pre-escaler interno y la dirección de la variable tipo "u16" donde se almacenará el resultado. Está función es bloqueante, hasta que termine de realizar la conversión. Antes de comenzar la conversión, se habilita la interrupción, para luego deshabilitarla en su rutina de interrupción.

*interrupciones.c* (se agrega a lo anterior)


```
void habilitar_interrupciones()
{
	NVIC_EnableIRQ(TIMER0_IRQn);	//Timer0
	NVIC_EnableIRQ(TIMER1_IRQn);	//Timer1
	NVIC_EnableIRQ(TIMER2_IRQn);	//Timer2
	NVIC_EnableIRQ(TIMER3_IRQn);	//Timer3
	NVIC_EnableIRQ(EINT1_IRQn);		//Externa 1
	NVIC_EnableIRQ(EINT2_IRQn);		//Externa 2
	NVIC_EnableIRQ(UART0_IRQn);		//UART 0
	NVIC_EnableIRQ(RTC_IRQn);			//RTC
	NVIC_EnableIRQ(ADC_IRQn);			//ADC
}

//... Todo lo anterior

//--------- Rutina de la interrupcion ADC ---------------------// 
#define ADGINTEN		8

void ADC_IRQHandler (void)  
{ 
	u32 dummy=LPC_ADC->ADGDR;					//Lectura falsa para limpiar interrupción
        flag_adc=1;
	LPC_ADC->ADINTEN&=~(1<<ADGINTEN);	//Deshabilito interrupción
} 
//--------- Rutina de la interrupcion ADC ---------------------//
```

Lo más destacado:

- Requiere una lectura falsa del registro ADGDR para limpiar la interrupción.
- Al finalizar la rutina, la interrupción queda deshabilitada.

*main.c*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

int main()
{	
	u16 cont=TIEMPO_TOGGLE_INICIAL,valor_adc;
	u8 tiempo_inicial_variable=1,estado_anti_reb_ext1=ANTI_REB_IDLE,estado_anti_reb_ext2=ANTI_REB_IDLE,flag_ascendente=0,flag_mostrar=MOSTRAR_CONTADOR;
	
	//-------------- Vectores RTC -----------------//
	u16 vector_hora_inicial_rtc[RTC_TAMANIO]={0,0,0,1,0,1,1,2013};
	u16 vector_alarma_rtc[RTC_TAMANIO]={0,5,0,1,0,1,1,2013};  
	//-------------- Vectores RTC -----------------//
	
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;															//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;											//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	configurar_hora_rtc(vector_hora_inicial_rtc);
	configurar_alarmas_rtc(vector_alarma_rtc);	//Al minuto 5 se espera una alarma.
	habilitar_interrupciones_rtc(0,0);	//Habilito las máscaras interrupción por Alarma => en caso que la cuenta sea igual a la alarma seteada.
	configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	configurar_uart0(UART_9600); //9600bps
	iniciar_adc(0,0);	//Inicio el ADC en el canal 0 sin offset
	
	habilitar_interrupciones();
	
	while(1)
	{		
		
		if(!flag_ascendente)
		{
			if(cont==1)
			{
				cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}

		}
		else
		{
			if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL)-1)
			{
				cont=0;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}
		}
		
		__wfi();	//Sleep-Mode
		
		if(flag_timer0)		
		{
			flag_timer0=0;			
			if(!flag_ascendente)
				cont--;
			else
				cont++;
			
			switch(flag_mostrar)
			{
				case MOSTRAR_CONTADOR:
					{
						enviar_string_uart0("Contador= \n");
						envia_u16_string_uart0(cont);
						enviar_string_uart0(" \n");
						break;
					}
				
				case MOSTRAR_HORA_RTC:{enviar_hora_rtc_uart0(); break;}
				
				case MOSTRAR_CONVERSION_ADC:
					{
						if(convertir_adc(0,250,&valor_adc)>0)	//Convierto en el canal 0, con un pre-escaler=250
						{
							enviar_string_uart0(" Conversion= \n");
							envia_u16_string_uart0(valor_adc);
						}
					}
			}				
		}
		
		if(flag_ext1)
		{
			if(anti_rebote_ext1(&estado_anti_reb_ext1)>0)
			{
				if(tiempo_inicial_variable>1)
					tiempo_inicial_variable--;
			}						
		}
		
		if(flag_ext2)
		{
			if(anti_rebote_ext2(&estado_anti_reb_ext2)>0)
				tiempo_inicial_variable++;					
		}
		
		if(flag_uart0_rx)
		{
			flag_uart0_rx=0;
			
			if(dato_uart0=='a')
				flag_ascendente=1;	//Cuenta Ascendente
			
			if(dato_uart0=='d')
				flag_ascendente=0;	//Cuenta descendente
			
			if(dato_uart0=='r')
				flag_mostrar= MOSTRAR_HORA_RTC;				//Muestra el valor del RTC c/1Seg.
			
			if(dato_uart0=='c')
				flag_mostrar=MOSTRAR_CONTADOR; 				//Muestra el valor de la cuenta c/1Seg.
			
			if(dato_uart0=='q')
				flag_mostrar=MOSTRAR_CONVERSION_ADC; 	//Realiza una conversión ADC y muestra su valor c/1Seg.
		}
		
		if(flag_rtc)
		{
			flag_rtc=0;
			enviar_string_uart0(" ¡¡Alarma del RTC!!! - Pasaron 5 minutos desde el último reseteo. \n");			
		}
	}
}
```

- Se configura el clock del ADC a la frecuencia máxima de 100kHz (CCLK=100MHz => PCLK=1/4*CCLK=25MHz => ADC_CLK=1/250*PCLK=100kHz).

- El offset del ADC es 0 y se utiliza el canal 0 (P0.23) para realizar la conversión.

- A diferencia del ejercicio anterior que se usó la variable "flag_mostrar_hora", en este se reemplaza dicha variable por "flag_mostrar" y dependiendo de su valor mostrará el conteo, RTC o la conversión del ADC.

- La alarma de 5 minutos del ejercicio anterior sigue estando habilitada.

Les dejo el código y para el próximo mensaje vamos a ver como funciona el DAC.


----------



## Fogonazo (May 18, 2013)

*! Gracias por el aporte Cosme ¡*


----------



## cosmefulanito04 (May 24, 2013)

Fogonazo dijo:


> *! Gracias por el aporte Cosme ¡*



Lo menos que uno puede hacer después de todo lo que dá este foro.

Sobre el *ADC* me faltó agregar que su frecuencia máxima *no puede superar los 13MHz*, así que ojo.

*DAC*

*Características principales:*

• 10 bit de resolución.
• Basado en red-2R.
• Salida con buffer.
• Velocidad máxima de 1MHz.

*Procedimiento de inicialización:*

1- Su alimentación no requiere ser habilitada, siempre lo esta.
2- Configurar el Pre-escaler del DAC (registro PCLKSEL0)
3- Configurar el pin 0.26 como DAC (registros PINSEL”x”) y que funcione como alta impedancia (registros PINMODE”x”).
4- Configurar el modo DMA[Opcional].

Solo se usará el registro DACR en el cual se especificará el nivel de tensión deseado y la velocidad de conversión (estará relacionada con la corriente máxima que podrá suministrar el buffer de salida), no es necesario utilizar interrupciones.  

Para profundizar más el uso del DAC, leer la hoja de datos en el “Chapter 30: LPC17xx Digital-to-Analog Converter (DAC)” página 582.

*Ejercicio propuesto:*

En base al ejercicio anterior, se pide que además de modificar el estado de los leds, se modifique la tensión sobre el PIN 0.26 de 1V a 2V y viceversa mediante el uso del DAC.

*define.c* => no hay modificaciones.

*configuracion_PLL.c* => no hay modificaciones.

*perifericos.c* (se agrega a lo anterior)


```
//Todo lo anterior

//--------------------------------------- DAC ----------------------------------------//
#define PCLK_DAC	22
#define VALUE_DAC	6
#define BIAS_DAC	16
	#define MAX_UPDATE_BIAS 	0	//700uA corriente máxima con una actualización de 	1	uS
  #define MIN_UPDATE_BIAS 	1	//350uA corriente máxima con una actualización de 2,5	uS

void iniciar_dac()
{
	//Su conexión a fuente siempre está habilitada => no es necesario modificar el registro PCONP
	LPC_PINCON->PINSEL1&=~((3<<20));
	LPC_PINCON->PINSEL1|=(2<<20);		//Pin 0.26 funcionando como DAC
	LPC_PINCON->PINMODE1&=~((3<<20));
	LPC_PINCON->PINMODE1|=(2<<20);		//Pin 0.26 funcionando sin pull-up/down	
	
	LPC_SC->PCLKSEL0&=~((3<<PCLK_DAC));
	LPC_SC->PCLKSEL0|=(3<<PCLK_DAC);		//PCLK_DAC 100MHZ/8=12,5MHz		
}

void asignar_valor_dac(u16 valor_dac,u8 bias)
{
	LPC_DAC->DACR=((bias&0x1)<<BIAS_DAC)|((valor_dac&0x3ff)<<VALUE_DAC);	
}
//--------------------------------------- DAC ----------------------------------------//
```

La función de inicialización es bastante simple, solo configura los pines y reduce al máximo la frecuencia de clock que le llega al DAC. 

*Nota aparte:* el fabricante declara que el *DAC* funciona a una frecuencia máxima de 1MHz, pero no dice nada sobre el máximo clock (o por lo menos yo no encontré nada al respecto), por lo tanto si trabajamos con el CCLK=100MHz, como máximo vamos a poder reducir su frecuencia a PCLK=CCLK/8=12,5MHz, durante las pruebas no tuve ningún inconveniente trabajando con ese clock.

*interrupciones.c* => no hay modificaciones.

*main.c*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

int main()
{	
	u16 cont=TIEMPO_TOGGLE_INICIAL,valor_adc;
	u8 tiempo_inicial_variable=1,estado_anti_reb_ext1=ANTI_REB_IDLE,estado_anti_reb_ext2=ANTI_REB_IDLE,flag_ascendente=0,flag_mostrar=MOSTRAR_CONTADOR;
	
	//-------------- Vectores RTC -----------------//
	u16 vector_hora_inicial_rtc[RTC_TAMANIO]={0,0,0,1,0,1,1,2013};
	u16 vector_alarma_rtc[RTC_TAMANIO]={0,5,0,1,0,1,1,2013};  
	//-------------- Vectores RTC -----------------//
	
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;															//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;											//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	configurar_hora_rtc(vector_hora_inicial_rtc);
	configurar_alarmas_rtc(vector_alarma_rtc);	//Al minuto 5 se espera una alarma.
	habilitar_interrupciones_rtc(0,0);	//Habilito las máscaras interrupción por Alarma => en caso que la cuenta sea igual a la alarma seteada.
	configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	configurar_uart0(UART_9600); //9600bps
	iniciar_adc(0,0);	//Inicio el ADC en el canal 0 sin offset
	iniciar_dac();
	asignar_valor_dac(620,MIN_UPDATE_BIAS);	//Vref=3,3V => Si quiero 2Volts => 2V/3,3V*1024=620,6 cuentas
	
	habilitar_interrupciones();
	
	while(1)
	{		
		
		if(!flag_ascendente)
		{
			if(cont==1)
			{
				cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2; asignar_valor_dac(620,MIN_UPDATE_BIAS); /*Vref=3,3V => Si quiero 2Volts => 2V/3,3V*1024=620,6 cuentas*/}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2; asignar_valor_dac(310,MIN_UPDATE_BIAS); /*Vref=3,3V => Si quiero 1Volt => 1V/3,3V*1024=310,3 cuentas*/}
			}

		}
		else
		{
			if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL)-1)
			{
				cont=0;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}
		}
		
		__wfi();	//Sleep-Mode
		
		if(flag_timer0)		
		{
			flag_timer0=0;			
			if(!flag_ascendente)
				cont--;
			else
				cont++;
			
			switch(flag_mostrar)
			{
				case MOSTRAR_CONTADOR:
					{
						enviar_string_uart0("Contador= \n");
						envia_u16_string_uart0(cont);
						enviar_string_uart0(" \n");
						break;
					}
				
				case MOSTRAR_HORA_RTC:{enviar_hora_rtc_uart0(); break;}
				
				case MOSTRAR_CONVERSION_ADC:
					{
						if(convertir_adc(0,250,&valor_adc)>0)	//Convierto en el canal 0, con un pre-escaler=250
						{
							enviar_string_uart0(" Conversion= \n");
							envia_u16_string_uart0(valor_adc);
						}
					}
			}				
		}
		
		if(flag_ext1)
		{
			if(anti_rebote_ext1(&estado_anti_reb_ext1)>0)
			{
				if(tiempo_inicial_variable>1)
					tiempo_inicial_variable--;
			}						
		}
		
		if(flag_ext2)
		{
			if(anti_rebote_ext2(&estado_anti_reb_ext2)>0)
				tiempo_inicial_variable++;					
		}
		
		if(flag_uart0_rx)
		{
			flag_uart0_rx=0;
			
			if(dato_uart0=='a')
				flag_ascendente=1;	//Cuenta Ascendente
			
			if(dato_uart0=='d')
				flag_ascendente=0;	//Cuenta descendente
			
			if(dato_uart0=='r')
				flag_mostrar= MOSTRAR_HORA_RTC;				//Muestra el valor del RTC c/1Seg.
			
			if(dato_uart0=='c')
				flag_mostrar=MOSTRAR_CONTADOR; 				//Muestra el valor de la cuenta c/1Seg.
			
			if(dato_uart0=='q')
				flag_mostrar=MOSTRAR_CONVERSION_ADC; 	//Realiza una conversión ADC y muestra su valor c/1Seg.
		}
		
		if(flag_rtc)
		{
			flag_rtc=0;
			enviar_string_uart0(" ¡¡Alarma del RTC!!! - Pasaron 5 minutos desde el último reseteo. \n");			
		}
	}
}
```

Lo más destacado:

- Se inicializa el DAC con una salida de 2V.
- A medida que se modifican los leds, sucede lo mismo con la salida del DAC, variando de 1V a 2V y viceversa.
- El resto del programa sigue comportándose igual que en el ejercicio anterior.

Subo el código de este ejercicio y en un rato (cuando el foro lo permita), subo un "bonus track" en el siguiente mensaje sobre un generador de señal basado en el DAC, para que vean sus límites y los problemas cuando el código no está del todo optimizado.


----------



## cosmefulanito04 (May 24, 2013)

*Ejercicio propuesto:*

A partir de un proyecto totalmente nuevo (descartamos de lleno el main anterior, pero seguimos usando las funciones que vinimos desarrollando), se desea crear un generador de señales senoidal, triangular y diente de sierra a partir del uso del DAC, para dichas señales se desea si es posible frecuencias de 100Hz, 1kHz, 10kHz y 100kHz.

Para modificar las frecuencias se utilizarán los pulsadores ya utilizados, tal que con el pulsador 1 (EXT1) disminuya la frecuencia y con el pulsador 2 (EXT2) aumente. En caso de sobrepasar los límites de frecuencia (tanto superior, como inferior), automáticamente cambiar el tipo de señal, (ej si estamos en la señal senoidal 100Hz y presionamos pulsador 1, pase a una diente de sierra de 100kHz).

*Antes de empezar a programar*

De alguna forma necesitamos generar un vector que contenga los distintos valores que tomarán las señales, para lo cual yo utilicé el programa Matlab para crear un vector (también se puede usar el Octave, programa GNU).

Dicho vector fue creado para que almacenara 100 elementos posibles para ser utilizadas en señales de bajas frecuencias. Una vez creado los vectores, los transferí directamente al código como si fueran variables u16 directamente en la memoria RAM como ya verán en el código.

Además puse a prueba la velocidad del DAC para comprobar si realmente trabaja a 1uS, el resultado obtenido es sorprendente, el DAC trabaja incluso por debajo del tiempo que declara el fabricante, consiguiendo una senoidal de hasta 113kHz usando solo 16 puntos, dando una conversión cada 550nS (luego subiré el código de prueba, en el cual se genera dicha senoidal).

Resumiendo la idea:

- Señal de 100Hz => usaré el timer0 configurado en 100uS para muestrear el vector (100 muestras).

- Señal de 1kHz => usaré el timer0 configurado en 10uS para muestrear el vector (100 muestras).

- Señal de 10kHz => usaré directamente el mínimo tiempo que tarda  DAC como base de tiempo para generar la señal (no voy a tener un control de la base de tiempo, por lo tanto la frecuencia resultante dependerá de la cantidad de muestras, en un principio intenté con 100, pero luego usé 50) *[Apunto a estar en el orden de la frecuencia]*.

- Señal de 100kHz => nuevamente usaré directamente el mínimo tiempo que tarda  DAC como base de tiempo para generar la señal (no voy a tener un control de la base de tiempo, solo se utilizarán 10 puntos de muestra) *[Apunto a estar en el orden de la frecuencia]*.

*define.c* (modificaciones realizadas)


```
typedef	unsigned char u8;
typedef	unsigned int u16;
typedef	unsigned long int u32;

#define CIEN_PTS			0
#define CINCUENTA_PTS	1
#define DIEZ_PTS			2

#define SENIAL_SENOIDAL					0
#define SENIAL_TRIANGULAR				1
#define SENIAL_DIENTE_DE_SIERRA	2

#define CIEN_HZ				0
#define MIL_HZ				1
#define DIEZ_MIL_HZ		2
#define CIEN_MIL_HZ		3
```

*configuracion_PLL.c* (se agrega a lo anterior un par de definiciones nuevas)


```
//Todo lo anterior
#define PCLK_TIMER0 	2

#define DIVISOR_PERIFERICOS_1	1
#define DIVISOR_PERIFERICOS_2 2
#define DIVISOR_PERIFERICOS_4	0
#define DIVISOR_PERIFERICOS_6	3		//Solo CAN
#define DIVISOR_PERIFERICOS_8	3
```

Como la idea es tener la mejor base de tiempo, elegí usar el Timer0 a 100MHz y así obtener una resolución de 100 cuentas por c/uS.

*interrupciones.c* => no hay modificaciones (salvo ciertas interrupciones deshabilitadas).

*funciones_de_senial.c*


```
/* 
	Tiempo mínimo de actualización del DAC => 1uS
	
	100Hz 	=> 10k 	pts posibles
	1kHz  	=> 1k 	pts posibles
	10kHz 	=> 100 	pts posibles
	100kHz 	=> 10 	pts posibles
*/

u16 vector_senoidal_100_pts[100]={512,544,577,609,641,672,702,732,761,789,816,841,865,888,909,929,947,963,978,990,1001,1010,1016,1021,1023,1023,1022,1019,1013,1005,996,984,971,955,938,919,899,877,853,828,802,775,747,717,687,656,625,593,561,528,496,463,431,399,368,337,307,277,249,222,196,171,147,125,105,86,69,53,40,28,19,11,5,2,0,1,3,8,14,23,34,46,61,77,95,115,136,159,183,208,235,263,292,322,352,383,415,447,480,512};
u16 vector_triangular_100_pts[100]={0,20,41,61,82,102,123,143,164,184,205,225,246,266,287,307,328,348,369,389,410,430,451,471,492,512,532,553,573,594,614,635,655,676,696,717,737,758,778,799,819,840,860,881,901,922,942,963,983,1004,1023,1004,983,963,942,922,901,881,860,840,819,799,778,758,737,717,696,676,655,635,614,594,573,553,532,512,492,471,451,430,410,389,369,348,328,307,287,266,246,225,205,184,164,143,123,102,82,61,41,20};
u16 vector_diente_sierra_100_pts[100]={0,10,20,31,41,51,61,72,82,92,102,113,123,133,143,153,164,174,184,194,205,215,225,235,246,256,266,276,286,297,307,317,327,338,348,358,368,379,389,399,409,419,430,440,450,460,471,481,491,501,512,522,532,542,552,563,573,583,593,604,614,624,634,644,655,665,675,685,696,706,716,726,737,747,757,767,777,788,798,808,818,829,839,849,859,870,880,890,900,910,921,931,941,951,962,972,982,992,1003,1013};	

void generar_senial(u8 *indice_dato,u8 senial,u8 flag_pts)
{
	switch(senial)
		{
			case SENIAL_SENOIDAL:
			{
				asignar_valor_dac(vector_senoidal_100_pts[*indice_dato],MAX_UPDATE_BIAS);
				break;
			}
			
			case SENIAL_TRIANGULAR:
			{
				asignar_valor_dac(vector_triangular_100_pts[*indice_dato],MAX_UPDATE_BIAS);
				break;
			}
			
			case SENIAL_DIENTE_DE_SIERRA:
			{
				asignar_valor_dac(vector_diente_sierra_100_pts[*indice_dato],MAX_UPDATE_BIAS);
				break;
			}
			
		}
	
	*indice_dato+=1;
		
	if(*indice_dato>99)
		*indice_dato=0;
	
}	

void configurar_timer_por_frecuencia(char frecuencia,u8 *flag_pts)
{
	switch(frecuencia)
		{
			case CIEN_HZ:
			{
				configura_timer0(100,99);	//Pre-escaler 25 => 25MHz/25=1MHz => 100cuentas => 99 uSeg + 1uSeg del DAC
				*flag_pts=CIEN_PTS;
				break;
			}
			
			case MIL_HZ:
			{
				configura_timer0(100,9);	//Pre-escaler 25 => 25MHz/25=1MHz => 10cuentas => 9 uSeg	+ 1uSeg del DAC
				*flag_pts=CIEN_PTS;	
				break;
			}
						
			case DIEZ_MIL_HZ:
			{
				*flag_pts=CINCUENTA_PTS;
				LPC_TIM0->TCR=0; // Paro el Timer0				
				break;
			}
			
			case CIEN_MIL_HZ:
			{
				*flag_pts=DIEZ_PTS;
				LPC_TIM0->TCR=0; // Paro el Timer0
				break;
			}
			
		}
}
```

Se puede ver:

- La creación de 3 vectores de 100 elementos c/u que contendrán las distintas señales.
- La función "generar_senial" solo utilizada en 100 y 1kHz (ya explicaré el porque).
- La función "configurar_timer_por_frecuencia" que se encargará de modificar la base de tiempo según la frecuencia, esta base de tiempo es solo utilizada hasta señales de 1kHz.

*main.c*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_de_senial.c"

int main()
{	
	u8 indice_senial=0,flag_pts=CIEN_PTS;
	int tipo_senial=SENIAL_SENOIDAL,frecuencia=MIL_HZ;
	
	u8 estado_anti_reb_ext1=ANTI_REB_IDLE,estado_anti_reb_ext2=ANTI_REB_IDLE;	
	
	configurar_pll(CLK_XTAL,25,2,3,(DIVISOR_PERIFERICOS_1<<PCLK_TIMER0),0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
		
	configurar_timer_por_frecuencia(frecuencia,&flag_pts);
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	
	iniciar_dac();	
	habilitar_interrupciones();
	
	while(1)
	{						
				
		if(frecuencia<DIEZ_MIL_HZ)
			__wfi();	//Sleep-Mode
		else
		{						
			switch(tipo_senial)
			{
				case SENIAL_SENOIDAL:
				{
					LPC_DAC->DACR=((vector_senoidal_100_pts[indice_senial]&0x3ff)<<VALUE_DAC);	//Máxima velocidad posible sin pasar por funciones!, Bias definido en forma implícita				 
					break;
				}
				
				case SENIAL_TRIANGULAR:
				{
					LPC_DAC->DACR=((vector_triangular_100_pts[indice_senial]&0x3ff)<<VALUE_DAC);	//Máxima velocidad posible sin pasar por funciones!, Bias definido en forma implícita													
					break;
				}
				
				case SENIAL_DIENTE_DE_SIERRA:
				{
					LPC_DAC->DACR=((vector_diente_sierra_100_pts[indice_senial]&0x3ff)<<VALUE_DAC);	//Máxima velocidad posible sin pasar por funciones!, Bias definido en forma implícita													
					break;
				}
			}
						
			if(flag_pts==DIEZ_PTS)
			{
				indice_senial+=10;
				
				if(indice_senial>99)
					indice_senial=0;
			}
			else
			{
				indice_senial+=2;
				
				if(indice_senial>99)
					indice_senial=0;
			}

		}//El DAC funciona a toda velocidad --> no necesito timer
		
		if(flag_timer0)		
		{
			flag_timer0=0;			
			
			generar_senial(&indice_senial,tipo_senial,flag_pts);					
		}
		
		if(flag_ext1)
		{
			if(anti_rebote_ext1(&estado_anti_reb_ext1)>0)
			{
				frecuencia--;
				
				if(frecuencia<0)
				{
					frecuencia=CIEN_MIL_HZ;
					
					tipo_senial--;
					
					if(tipo_senial<0)
						tipo_senial=SENIAL_DIENTE_DE_SIERRA;
				}
				
				configurar_timer_por_frecuencia(frecuencia,&flag_pts);
			}					
		}
		
		if(flag_ext2)
		{
			if(anti_rebote_ext2(&estado_anti_reb_ext2)>0)
			{
				frecuencia++;
				
				if(frecuencia>CIEN_MIL_HZ)
				{
					frecuencia=CIEN_HZ;
					
					tipo_senial++;
					
					if(tipo_senial>SENIAL_DIENTE_DE_SIERRA)
						tipo_senial=SENIAL_SENOIDAL;
										
				}
				
				configurar_timer_por_frecuencia(frecuencia,&flag_pts);
			}
									
		}
				
	}
}
```

Lo más destacado:

- Para las frecuencias altas, traté de no llamar varias funciones y actuar directamente sobre el 
registro. En estás frecuencias el DAC está continuamente convirtiendo.

- Para las frecuencias bajas, usé la función "generar_senial" y durante el tiempo muerto el uC está en modo sleep.

*Resultados obtenidos:*

*Senoidal de 100Hz*



Se puede ver que la forma de la señal obtenida a partir un vector de 100 puntos es casi una senoidal perfecta y su frecuencia ronda los 100Hz.

*Senoidal de 1kHz*



Misma señal obtenida del vector de 100 puntos, su frecuencia ronda 1kHz y su valor eficaz 1,18V.

*FFT de la senoidal de 1kHz*



Si bien la FFT es una aproximación matemática de las componentes de frecuencia que tiene la señal, está bueno para darnos una idea de que tan distorsionada está la señal. Se puede ver que la armónica fundamental tiene un peso de 1,18Vef.

[LATEX]THD_{v}=\frac{\sqrt{V_{RMS-total}^2-V_{RMS-1}^2}}{V_{RMS-1}}[/LATEX]

[LATEX]THD_{v}=\frac{\sqrt{1,18^2-1,18^2}}{1,18}=0[/LATEX]

Es evidente que la distorsión no puede ser 0, ya que el osciloscopio obtiene el espectro a partir de los datos recolectados en el tiempo para luego realizar el FFT y en el medio realiza trunca valores, pero al menos nos da una idea de que no es alta.

*Senoidal de 17,7kHz*



Al intentar obtener 100 puntos a partir de la conversión constante del DAC para generar una senoidal de 10kHz, como mucho logré conseguir una frecuencia de 9kHz y sabiendo que el DAC en realidad es más veloz que 1uS, no queda dudas de que la mayor traba es el código y por lo tanto se debería optimizar, metiéndose un poco más con assembler.

No obstante, decidí bajar la cantidad de puntos y utilizar 50, consiguiendo una senoidal que ronda los 17kHz.

*Senoidal de 86,2kHz*



Al igual que con los 10kHz, la frecuencia máxima obtenida en inferior a la deseada a pesar de solo utilizar 10 puntos. Respecto a la distorsión se ve claramente que con solo 10 puntos la señal presenta armónicos no deseados de mayor frecuencia. Su valor eficaz es de 1,18V.

En forma complementaria, se podría llegar a mejorar la forma de la señal, usando un filtro pasa-bajos analógico con una frecuencia de corte un poco por arriba de la fundamental (en este caso 86kHz), de esta forma se elimina gran parte de las armónicas espurias que se ven en la señal actual.

*FFT de la senoidal de 86,2kHz*



Se puede ver que la armónica fundamental tiene un peso de 1,16Vef. Luego aparecen armónicas a frecuencias muy superiores de la fundamental (1 década por arriba), por lo tanto un filtro mejoraría notablemente la distorsión.

[LATEX]THD_{v}=\frac{\sqrt{1,18^2-1,16^2}}{1,16}=0,186[/LATEX]

Acá ya distorsión es apreciable tanto en el dominio temporal como en el frecuencial, dando un THD del 18,6%.

*Triangular de 100Hz*



Se puede ver que la forma de la señal obtenida a partir un vector de 100 puntos es casi ideal y su frecuencia ronda los 100Hz.

*FFT de la triangular de 100Hz*



Se pueden ver las componentes en frecuencia usando 100 puntos.

*Triangular de 1kHz*



Misma señal obtenida del vector de 100 puntos y su frecuencia ronda 1kHz. 

*Triangular de 15,5kHz*



Mismo inconveniente que se presentó con la señal senoidal, pero ahora incluso peor, en esta señal la frecuencia obtenida es inferior. Se usó 50 puntos.

*Triangular de 78,1kHz*



Mismo inconveniente. Se usó 10 puntos.

*FFT de la triangular de 78,1kHz*



Se pueden ver como aparecen componentes en frecuencia que antes usando 100 puntos no aparecían.

*Diente de sierra de 100Hz*



Se puede ver que la forma de la señal obtenida a partir un vector de 100 puntos es casi ideal y su frecuencia ronda los 100Hz.

*FFT de la diente de sierra de 100Hz*



Se pueden ver las componentes en frecuencia usando 100 puntos.

*Diente de sierra de 1kHz*



Misma señal obtenida del vector de 100 puntos y su frecuencia ronda 1kHz.

*Diente de sierra de 15,6kHz*



Mismo inconveniente que se presento con la señal senoidal, pero ahora incluso peor, en esta señal la frecuencia obtenida es inferior. Se usó 50 puntos.

*Diente de sierra de 79,3kHz*



Mismo inconveniente. Se usó 10 puntos.

*FFT de la diente de sierra de 79,3Hz*



Se puede ver el espectro de la señal generada con solo 10 puntos.

Para el próximo mensaje vamos a ver PWM.


----------



## cosmefulanito04 (Jun 1, 2013)

*Diagrama en bloques:*



*Características principales:*

• Siete registros de matcheo que permiten controlar 6 canales por flanco simple o 3 canales por flanco doble. Estos registros además permiten:
-	Funcionamiento continuo con la opción de generar una interrupción por matcheo.
 -	Parar el timer interno del PWM con la opción de generar una interrupción por matcheo.
 -	Resetear el timer interno del PWM con la opción de generar una interrupción por matcheo.​• Soporte para salidas de PWM con flanco simple o dobles.
• Fijar el periodo del PWM en función del número de cuentas.
• Total control sobre la polaridad del flanco.
• Cambio del duty sincronizado para evitar un pulso erróneo.
• Puede usarse como timer convencional con la salida PWM deshabilitada.
• Contador y Pre-escaler de 32 bits.
• Dos canales de captura de 32 bits para señales de entrada, las cuales en forma opcional pueden generar una interrupción.

*Procedimiento de inicialización:*

1-	Habilitar alimentación del PWM”x” (registro PCONP).
2-	Configurar el Pre-escaler del PWM”x” (registro PCLKSEL0)
3-	Configurar los pines de salida como PWM (registros PINSEL”x”) y su modo de funcionamiento (registros PINMODE”x”).
4-	Configurar cuenta final y duty.
5-	Configurar las interrupciones de matcheo/captura [Opcional].

*Los registros a utilizar para el modo flanco simple serán:*
-	PR que fijará el valor del pre-escaler interno.
-	MR”x” registro de matcheo del duty que variará según el canal de PWM elegido.
-	MR0 fijará la cantidad de cuentas (Duty=100% => cuenta= MR0). A tener en cuenta a la hora de fijar el periodo del PWM.
-	CTCR define el modo de la cuenta, por cuenta interna o por captura.
-	TCR habilita la cuenta y el modo PWM.
-	 PCR selecciona si el canal funciona con flanco simple o doble.

La frecuencia quedará definida como:

[LATEX]f_{PWM}=\frac{Pclk_{pwm}}{Pre escaler_{interno}.Cuenta final}[/LATEX]

*Ejemplos de tipos de salidas (disparo simple o doble):*





Se puede ver que el canal 2 y el 4 funcionan con flancos dobles, en cambio el canal 5 con flanco simple y la cuenta que definirá el periodo es de 100 cuentas. 

Para profundizar más el uso del PWM, leer la hoja de datos en el “Chapter 24: LPC17xx Pulse Width Modulator (PWM)” página 509.

*Ejercicio propuesto:*

Se desea generar una señal PWM de 50kHz en el canal PWM6 (puerto 1.26), la cual pueda variar su duty de 5 – 100% con un paso de 5%, mediante el uso de los pulsadores 1 (EXT1, disminuir duty) y 2 (EXT2, aumentar duty).

*define.c * (se modifica a lo anterior)


```
typedef	unsigned char u8;
typedef	unsigned int u16;
typedef	unsigned long int u32;

#define LED_1 	(1<<25)	//Puerto 3.25
#define LED_2 	(1<<26)	//Puerto 3.26

#define TIEMPO_TOGGLE_SEG			1

#define DUTY_INICIAL					5
```

*configuracion_PLL.c* => no hay modificaciones.

*perifericos.c* (se agrega a lo anterior)


```
//Todo lo anterior

//--------------------------------------- PWM ----------------------------------------//
#define PCPWM1				6

#define PCLK_DAC			22

#define PWM1					4
#define PWM2					8
#define PWM3					10
#define PWM4					14
#define PWM5					16
#define PWM6					20

#define PWM_COUNT_E		0
#define PWM_COUNT_R		1
#define PWM_ENABLE		3

int iniciar_pwm_flanco_simple(u8 canal,u8 duty,u32 preescaler)
{
	LPC_SC->PCONP|=(1<<PCPWM1); 													//Habilito el PWM1 en el control de consumo de los periféricos
	LPC_PWM1->PR=preescaler;	//25MHz/(100*Preescaler)=250kHz/Preescaler  
	
	switch(canal)
	{
		case 1:
			{
				LPC_PINCON->PINSEL3&=~((3<<PWM1));
				LPC_PINCON->PINSEL3|=(2<<PWM1);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM1));	//Pull-up				
				
				LPC_PWM1->MR1=duty;
				break;
			}	
			
		case 2:
			{
				LPC_PINCON->PINSEL3&=~((3<<PWM2));
				LPC_PINCON->PINSEL3|=(2<<PWM2);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM2));	//Pull-up
				
				LPC_PWM1->MR2=duty;
				break;
			}	
		
		case 3:
			{
				LPC_PINCON->PINSEL3&=~((3<<PWM3));
				LPC_PINCON->PINSEL3|=(2<<PWM3);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM3));	//Pull-up
				
				LPC_PWM1->MR3=duty;
				break;
			}
			
		case 4:
			{
				LPC_PINCON->PINSEL3&=~((3<<PWM4));
				LPC_PINCON->PINSEL3|=(2<<PWM4);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM4));	//Pull-up
				
				LPC_PWM1->MR4=duty;
				break;
			}
		
		case 5:
			{				
				LPC_PINCON->PINSEL3&=~((3<<PWM5));
				LPC_PINCON->PINSEL3|=(2<<PWM5);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM5));	//Pull-up
				
				LPC_PWM1->MR5=duty;
				break;
			}
		
		case 6:
			{
				LPC_PINCON->PINSEL3&=~((3<<PWM6));
				LPC_PINCON->PINSEL3|=(2<<PWM6);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM6));	//Pull-up
				
				LPC_PWM1->MR6=duty;
				break;
			}
		
		default:{return -1;}
	}
	LPC_PWM1->MR0=100;	// Máximo valor del duty -> 100%
	LPC_PWM1->LER=(1<<canal)|(1<<0);
	
	LPC_PWM1->MCR|=(1<<1);	//Reseteo el contador cuando llega MR0
	
	LPC_PWM1->CTCR=0;	//Funciona por matcheo , no por captura.
	LPC_PWM1->TCR=(1<<PWM_ENABLE)|(1<<PWM_COUNT_E);	//Habilito la cuenta y el modo PWM
	
	LPC_PWM1->PCR&=~(1<<canal);	//Deshabilito el flanco doble en el canal seleccionado
	LPC_PWM1->PCR|=(1<<(8+canal));	//Habilito salida PWM por flanco simple
	return 1;
}

int iniciar_pwm_flanco_doble(u8 canal,u8 duty1,u8 duty2)
{
	LPC_SC->PCONP|=(1<<PCPWM1); 													//Habilito el PWM1 en el control de consumo de los periféricos
	
	switch(canal)
	{
		case 2:
			{
				LPC_PINCON->PINSEL3&=~((3<<PWM2));
				LPC_PINCON->PINSEL3|=(2<<PWM2);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM2));	//Pull-up
				
				LPC_PWM1->MR1=duty1;
				LPC_PWM1->MR2=duty2;
				LPC_PWM1->LER=(1<<2)|(1<<1);
				break;
			}	
		
		case 3:
			{
				LPC_PINCON->PINSEL3&=~((3<<PWM3));
				LPC_PINCON->PINSEL3|=(2<<PWM3);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM3));	//Pull-up
				
				LPC_PWM1->MR2=duty1;
				LPC_PWM1->MR3=duty2;
				LPC_PWM1->LER=(1<<3)|(1<<2);
				break;
			}
			
		case 4:
			{
				LPC_PINCON->PINSEL3&=~((3<<PWM4));
				LPC_PINCON->PINSEL3|=(2<<PWM4);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM4));	//Pull-up
				
				LPC_PWM1->MR3=duty1;
				LPC_PWM1->MR4=duty2;
				
				LPC_PWM1->LER=(1<<4)|(1<<3);
				break;
			}
		
		case 5:
			{				
				LPC_PINCON->PINSEL3&=~((3<<PWM5));
				LPC_PINCON->PINSEL3|=(2<<PWM5);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM5));	//Pull-up
				
				LPC_PWM1->MR4=duty1;
				LPC_PWM1->MR5=duty2;
				
				LPC_PWM1->LER=(1<<5)|(1<<4);
				break;
			}
		
		case 6:
			{
				LPC_PINCON->PINSEL3&=~((3<<PWM6));
				LPC_PINCON->PINSEL3|=(2<<PWM6);			//Pin 0.24 funcionando como ADC
				LPC_PINCON->PINMODE3&=~((3<<PWM6));	//Pull-up
				
				LPC_PWM1->MR5=duty1;
				LPC_PWM1->MR6=duty2;
				LPC_PWM1->LER=(1<<6)|(1<<5);
				break;
			}
		
		default:{return -1;}
	}
	LPC_PWM1->MR0=100;	// Máximo valor del duty -> 100%
	LPC_PWM1->LER|=(1<<0);
	LPC_PWM1->MCR|=(1<<1);	//Reseteo el contador cuando llega MR0
	
	LPC_PWM1->CTCR=0;	//Funciona por matcheo , no por captura.
		
	LPC_PWM1->TCR=(1<<PWM_ENABLE)|(1<<PWM_COUNT_E);	//Habilito la cuenta y el modo PWM
	
	LPC_PWM1->PCR|=(1<<(8+canal))|(1<<canal);	//Habilito salida PWM por flanco doble
	return 1;
}

void cambiar_duty_pwm(u8 registro_matcheo,u8 duty)
{
	switch(registro_matcheo)
	{
		case 0:{LPC_PWM1->MR0=duty; break;}		
		case 1:{LPC_PWM1->MR1=duty; break;}
		case 2:{LPC_PWM1->MR2=duty; break;}
		case 3:{LPC_PWM1->MR3=duty; break;}
		case 4:{LPC_PWM1->MR4=duty; break;}		
		case 5:{LPC_PWM1->MR5=duty; break;}
		case 6:{LPC_PWM1->MR6=duty; break;}
		default:{return;}
	}
	
	LPC_PWM1->LER=(1<<registro_matcheo);
}

int parar_pwm(u8 canal)
{
	if(canal>6)
		return -1;
	else
	{
		if(!canal)
			return -1;
		
		LPC_PWM1->PCR&=~(1<<(8+canal));	//Deshabilito salida PWM		
	}
	
	return 1;
}
//--------------------------------------- PWM ----------------------------------------//
```

Contiene las siguientes funciones:

- iniciar_pwm_flanco_simple: tiene de argumentos el canal a utilizar, el duty inicial y el pre-escaler interno del PWM. Cuenta final será 100.

- iniciar_pwm_flanco_doble(u8 canal,u8 duty1,u8 duty2):  tiene de argumentos el canal a utilizar, el duty inicial y final. Se podría agregar el pre-escaler. Cuenta final será 100.

- cambiar_duty_pwm: tiene de argumentos el nuevo valor del duty y el registro de Match a modificar (para ser usado en flanco simple o doble).

- parar_pwm: tiene de argumento el canal a parar.

No no hay necesidad de usar interrupciones para generar PWM, si puede ser útil para disparar algún evento, en todo caso se maneja de la misma forma que los timers.

*interrupciones.c* => no hay modificaciones, solo habilito los periféricos que usaré.

*main.c*


```
#include <LPC17xx.H>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"

int main()
{	
	u8 cont=TIEMPO_TOGGLE_SEG,estado_anti_reb_ext1=ANTI_REB_IDLE,estado_anti_reb_ext2=ANTI_REB_IDLE;
	
	u8 duty_variable=1;
		
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;															//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;											//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	
	iniciar_pwm_flanco_simple(6,DUTY_INICIAL,5);	//P1.26 como canal 1 de PWM	--> arranco en 10%, Preescaler=5 => 25MHZ/(100cuentas*5cuentas_prescaler)=50kHz
	
	habilitar_interrupciones();
	
	while(1)
	{		
		
		if(!cont)
		{
			cambiar_duty_pwm(6,duty_variable*DUTY_INICIAL);
			cont=TIEMPO_TOGGLE_SEG;
			
			if(LPC_GPIO3->FIOPIN&LED_1)
				{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
			else
				{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
		}
		
		
		__wfi();	//Sleep-Mode
		
		if(flag_timer0)		
		{
			flag_timer0=0;			
			cont--;			
		}
		
		if(flag_ext1)
		{
			if(anti_rebote_ext1(&estado_anti_reb_ext1)>0)
			{								
				if(duty_variable>1)
					duty_variable--;
			}						
		}
		
		if(flag_ext2)
		{
			if(anti_rebote_ext2(&estado_anti_reb_ext2)>0)
			{
				if(duty_variable<20)
					duty_variable++;
			}
		}
		
	}
}
```

Lo más destacado:

- Se usá el canal PWM 6 (puerto 1.26) con un duty inicial del 5%.

- Se configura el pre-escaler interno del PWM en 5 => Cclk=100MHz => Pclk-pwm=100MHz/4=25MHz (debido al divisor entre Cclk y Pclk) => F-PWM=25MHz/[5*100]=50kHz.

Les dejo el código y para el próximo mensaje vamos a ver el último tema que es el uso de SPI (ya probado en hard).


----------



## cosmefulanito04 (Jun 8, 2013)

*Aclaración:*

Debido a que el kit de desarrollo tiene los puertos SSP0 conectados a la memoria SD (o MMC), explicaré este bus. La diferencia con el SPI, es que este bus permite distintas configuraciones de diversos fabricantes como ya veremos, entre ellas el SPI, otra gran diferencia es que su interrupción si o si está pensada para trabajar en DMA, por lo tanto en el ejemplo que subiré lamentablemente tendremos que trabajar con un polling .

*Características principales:*

- Compatibilidad con mútiples buses (SPI, 4-Wire, TI SSI, National Semiconductor Microwire).
- Comunicación serie sincrónica.
- Trabajar como maestro o esclavo.
- Frames de datos de 4 a 16 bits.
- Transferencia mediante DMA.

*Procedimiento de inicialización:*

1- Habilitar alimentación del SSP”x” (registro PCONP).
2- Configurar el Pre-escaler del SSP”x” (registro PCLKSEL0/1)
3- Configurar los pines de salida como SSP”x” (registros PINSEL”x”) y su modo de funcionamiento (registros PINMODE”x”).
4- Configurar el tipo de bus y su modo de trabajo (registros CR0/1).
5- Configurar su Pre-escaler interno (registro CPSR).
6- Configurar las interrupciones [Opcional - solo útil en modo DMA].
7- Configurar DMA [Opcional].

*Registros a utilizar:*

- CR0/1 configurarán el modo de funcionamiento del bus.
- SR registro de estado, utilizado para realizar polling.
- DR se escribirán/leerán los datos.

*Velocidad del clock:*

[LATEX]f_{SSP-clk}=\frac{f_{pclk}}{Preescaler-interno}[/LATEX]

No voy a entrar en detalles de como funciona una comunicación SPI, por lo tanto les recomiendo que repasen el tema antes de meterse con el código. Para más información de como funciona el SSP, dirigirse a la página 412 de la hoja de datos.

*Ejercicio propuesto:*

A partir del ejercicio que utilizaba el DAC, se desea implementar la lectura y escritura de una memoria SD mediante el uso de un bus SPI. Al enviar el caracter:

- 'l': se leerán los primeros 512 bytes de la memoria.
- 'e': se escribirá el string "Escribe", llenando el resto de la memoria con 0's hasta llegar a los 512 bytes.
- 'v': se escribirá el string "Prueba", llenando el resto de la memoria con 0's hasta llegar a los 512 bytes.

*Antes de empezar:*

- Utilizaremos una librería obtenida de AVR-Freaks para realizar una lectura/escritura tipo RAW (a secas, sin formato FAT, ni nada) que funciona muy bien. Lamentablemente no recuerdo el autor del código .
- No implementaremos ningún formato de archivos.
- Necesitaremos usar las funciones malloc/calloc y free para poder usar buffers de datos extensos, por ese motivo es *muy importante avisarle a Keil que vamos a necesitar que nos asigne memoria RAM (heap)*, eso lo hacemos en el archivo de Start-up que nos brinda keil.

*define.c* (queda igual que en el ejercicio del DAC)

*configuracion_PLL.c* => no hay modificaciones.

*perifericos.c* (se agrega a lo anterior)


```
//Todo lo anterior

//--------------------------------------- SSP0 ----------------------------------------//
#define PCSSP0						21
#define PCSSP1						10

#define SSP_MODO_MAESTRO	0
#define SSP_MODO_ESCLAVO	1

#define SSP0_SCK0					8	//P1.20
#define SSP0_SSEL0				10		//P1.21
#define SSP0_MISO0				14	//P1.23
#define SSP0_MOSI0				16		//P1.24

#define SSP0_GPIO_SSEL0		21

#define DSS_4BIT					3
#define DSS_5BIT					4
#define DSS_6BIT					5
#define DSS_7BIT					6
#define DSS_8BIT					7
#define FRF_SPI						0
#define FRF_TI						(1<<4)
#define FRF_MICROWIRE			(2<<4)
#define	SSP0_CPOL0					0
#define	SSP0_CPOL1					(1<<6)
#define	SSP0_CPHA0					0
#define	SSP0_CPHA1					(1<<7)

#define LBM_OFF						0
#define LBM_ON						1
#define SSE_OFF						0
#define SSE_ON						(1<<1)
#define MS_MASTER					0
#define MS_SLAVE					(1<<2)
#define SLAVE_OUT_DISABLE	(1<<3)

#define	RTIM							(1<<1)
#define RXIM							(1<<2)

#define RNE								(1<<2)
#define RFF								(1<<3)

void iniciar_ssp0(u8 tipo_transferencia,u8 modo,u8 contador_ssp,u8 cpha,u8 cpol)	//8bit de datos
{
	LPC_SC->PCONP|=(1<<PCSSP0); 											//Habilito el SSP0 en el control de consumo de los periféricos
	
	LPC_SSP0->CR1=SSE_OFF;														//SSP deshabilitado
	
	LPC_PINCON->PINSEL3&=~((3<<SSP0_SCK0));						
	LPC_PINCON->PINSEL3|=(3<<SSP0_SCK0);							//Pin 1.20 funcionando como SCK0 del SSP0
	LPC_PINCON->PINMODE3&=~((3<<SSP0_SCK0));					//Pull-up
	
	LPC_PINCON->PINSEL3&=~((3<<SSP0_SSEL0));					//OJO!! usado en modo GPIO
	LPC_PINCON->PINMODE3&=~((3<<SSP0_SSEL0));					//Pull-up
	LPC_GPIO1->FIODIR|=(1<<SSP0_GPIO_SSEL0);					//Como salida
  LPC_GPIO1->FIOSET|=(1<<SSP0_GPIO_SSEL0);					//Estado Idle => "1"
	
	LPC_PINCON->PINSEL3&=~((3<<SSP0_MISO0));					
	LPC_PINCON->PINSEL3|=(3<<SSP0_MISO0);							//Pin 1.23 funcionando como MISO0 del SSP0
	LPC_PINCON->PINMODE3&=~((3<<SSP0_MISO0));					//Pull-up
	
	LPC_PINCON->PINSEL3&=~((3<<SSP0_MOSI0));					
	LPC_PINCON->PINSEL3|=(3<<SSP0_MOSI0);							//Pin 1.24 funcionando como MOSI0 del SSP0
	LPC_PINCON->PINMODE3&=~((3<<SSP0_MOSI0));					//Pull-up
	
	LPC_SSP0->CR0=cpol|cpha|tipo_transferencia|DSS_8BIT;//8 Bit de datos, modo de transferencia a elección
	
	if(modo==SSP_MODO_MAESTRO)
	{
		LPC_SSP0->CR1=MS_MASTER|SSE_ON|LBM_OFF;					//Sin loop back, SSP habilitado, modo maestro
		LPC_SSP0->CPSR=contador_ssp;										//Fijo el contador del clock => 100MHz/4=25MHz => 25MHz/contador=Velocidad SSP
	}//Modo Maestro
	else
	{
		LPC_SSP0->CR1=MS_SLAVE|SSE_ON|LBM_OFF;					//Sin loop back, SSP habilitado, modo maestro
	}//Modo Esclavo
	
	
}

int spiByteOut(u8 dato)
{	
	LPC_SSP0->DR=dato;
	configura_timer3(25000,33);	//Pre-escaler 250000=> 1mSeg y 3cuentas => 33mSeg	
	flag_timer3=0;
	
	while(!((LPC_SSP0->SR&0x1C)&(RFF|RNE))&&!flag_timer3); //Espera a tener un dato Rx con un time-out de 32mSeg
	
	LPC_TIM3->TCR=0;	//Paro el contador
	
	if(!flag_timer3)
		return LPC_SSP0->DR;	
	else
		return -1;
}


//--------------------------------------- SSP0 ----------------------------------------//
```

Lo más destacable:

- Función de inicio que permite elegir el tipo de bus según el fabricante, modo maestro/esclavo, pre-escaler interno, CPHA y CPOL. Es interesante ver que el puerto de selección, lo uso como GPIO, esto es importante y debo manipularlo c/vez que se realiza una transferencia de datos, yo para simplificar, dejo habilitado este puerto durante toooodo el tiempo, *no es lo ideal*.

- Función de envío/recepción (full-duplex ), usará un polling con un time-out. Nota: si se usara el modo DMA, además de poder usar las interrupciones, podríamos usar un time-out que ya tiene incorporado este bus.

*interrupciones.c* => no hay modificaciones, solo habilito los periféricos que usaré.

*memoria_SD.c* (librería obtenida en AVR-Freaks)


```
//------------------------ Data Token -------------------//
#define SD_DAT_TOKEN_READ				0xFE
#define SD_DAT_TOKEN_WRITE			0xFC
#define SD_DAT_TOKEN_STOP				0xFD
//------------------------ Data Token -------------------//

#define MMC_GO_IDLE_STATE						0 
#define MMC_SEND_OP_COND						1 
#define MMC_SEND_CSD								9 
#define MMC_SEND_CID								10 
#define MMC_SEND_STATUS							13 
#define MMC_SET_BLOCKLEN						16 
#define MMC_READ_SINGLE_BLOCK				17 
#define MMC_WRITE_BLOCK							24 
#define MMC_PROGRAM_CSD							27 
#define MMC_SET_WRITE_PROT					28 
#define MMC_CLR_WRITE_PROT					29 
#define MMC_SEND_WRITE_PROT     		30 
#define MMC_TAG_SECTOR_START				32 
#define MMC_TAG_SECTOR_END					33 
#define MMC_UNTAG_SECTOR						34 
#define MMC_TAG_ERASE_GROUP_START   35 
#define MMC_TAG_ERARE_GROUP_END     36 
#define MMC_UNTAG_ERASE_GROUP				37 
#define MMC_ERASE										38 
#define MMC_CRC_ON_OFF							59 

/*############################################## 
#                  mmcCommand                  # 
# Send a 48 bit long command to the mmc. First # 
# parameter (byte) is the  command, the second # 
# is the 32 bit long parameter.                # 
##############################################*/ 
u8 mmcCommand(u8 cmd,u32 arg)
{ 
   spiByteOut(0xFF);                               // Send a leading 0xFF 
   spiByteOut(cmd | 0x40);                         // Send the 6 bit command plus start & transmittion flag 
   spiByteOut(arg>>24);                            // Send the last byte of the parameter 
   spiByteOut(arg>>16);                            // Send the 3rd byte of the parameter 
   spiByteOut(arg>>8);                             // Send the 2nd byte of the parameter 
   spiByteOut(arg);                                // Send the lowest byte of the parameter 
   spiByteOut(0x95);                               // Send the 7 bit CRC and end bit 
   spiByteOut(0xFF); 
   return spiByteOut(0xFF); 
} 

/*############################################## 
#                    initMMC                   # 
# Initialises the SPI & MMC. You should always # 
# call this function before attempting to call # 
# any other mmc function.                      # 
##############################################*/ 
u8 initMMC (void) 
{ 
   u8 i,result;
	
   iniciar_ssp0(FRF_SPI,SSP_MODO_MAESTRO,254,SSP0_CPHA0,SSP0_CPOL0); //8bit de datos, SPP clock=25MHz/254=98,4kHz ==> El preescaler solo es numero PAR!
   LPC_GPIO1->FIOSET|=(1<<SSP0_GPIO_SSEL0);					//Estado Idle => "1"
	
    
   for (i = 0; i < 10; i++) {						// Wait for 80 SPI clockcycles 
       spiByteOut(0xFF);							// for the SPI to initialise 
   } 
   
	 LPC_GPIO1->FIOCLR|=(1<<SSP0_GPIO_SSEL0);					//Estado Idle => "0"
	 
   result = mmcCommand(MMC_GO_IDLE_STATE, 0);	// make mmc go to SPI mode 
   if (result != 1) {								// if the command returns an error value... 
       return result;								// stop inialising and return the error value 
   } 
   while (mmcCommand(MMC_SEND_OP_COND, 0) != 0);	// Wait until the mmc is ready to move on. 
   return 0;										// return that all is well 
} 

/*############################################## 
#                  mmcReadBlock                # 
# Reads a 512 byte block from the mmc. The max # 
# capacity of the mmc is 32MB. If you need any # 
# more,  just change  the 'unsigned  int' into # 
# 'unsigned long' and you're ready to go.      # 
# The first parameter is the block number, the # 
# second is a pointer to a 512 byte array.     # 
##############################################*/ 

u8 mmcReadBlock (u16 adress, u8 *databuffer) { 
                                       // Send the read command, and the start adress as param 
   u8 result = mmcCommand(MMC_READ_SINGLE_BLOCK, (unsigned long)(adress<<9)); 
   u16 i;
	
	 if (result != 0) {                              // If the command returns an error value... 
       return result;                              // stop reading and return the error 
   }                                               // If all is well though... 
   
   while (spiByteOut(0xFF) != (u8)0xFE);         // Wait for the start transmittion flag 
      
   for (i = 0; i != 512; i++) {                    // And loop 512 times to recieve all the data. 
      *databuffer = spiByteOut(0xFF);             // Set an element from the array to the recieved byte 
      databuffer++;                               // And increase the pointer 
   } 
   spiByteOut(0xFF);                               // Recieve and ignore the checksum bytes. 
   spiByteOut(0xFF); 
   return 0;                                       // Return all is well. 
} 

/*############################################## 
#                 mmcWriteBlock                # 
# Writes a 512 byte block to the mmc. The rest # 
# is the same as mmcReadBlock.                 # 
##############################################*/ 

u8 mmcWriteBlock (u16 adress, u8 *databuffer) { 
                                       //Send the write command, and the start adress as param 
   u8 result = mmcCommand(MMC_WRITE_BLOCK, (u32)(adress<<9)); 
	 u16 i; 
	 
	 if (result != 0) {                              // If the command returns an error code... 
       return result;                              // return it 
   }                                               // If everythings OK 
   spiByteOut(0xFF);                               // Send a dummy checksum 
   spiByteOut(0xFF); 
   spiByteOut(0xFE);                               // Send the start transmittion flag 
    
   for (i = 0; i < 512; i++) {                     // Send all 512 bytes. 
       spiByteOut(databuffer[i]);                  // Send the byte 
   } 
   spiByteOut(0xFF);                               // Recieve dummy checksum 
   spiByteOut(0xFF); 
   result = spiByteOut(0xFF) & 0x1F;               // Read the data response token 
   if (result != 0x05) {                           // If something bad happened... 
       return result;                              // Return the error 
   }                                               // If all is well... 
   while (!spiByteOut(0xFF));                      // Wait until the mmc isn't busy anymore 
   return 0;                                       // And return succes! 
}
```

Lo único que me encargo yo, es de inicializar el bus SSP0 a una baja velocidad durante la inicialización de la memoria (100kHz). Luego de la inicialización, se podría llevar la velocidad hasta 25MHz.


```
iniciar_ssp0(FRF_SPI,SSP_MODO_MAESTRO,254,SSP0_CPHA0,SSP0_CPOL0); //8bit de datos, SPP clock=25MHz/254=98,4kHz ==> El preescaler solo es numero PAR!
LPC_GPIO1->FIOSET|=(1<<SSP0_GPIO_SSEL0);					//Estado Idle => "1"
```
 
*main.c*


```
#include <LPC17xx.H>
#include <stdlib.h>

#include "defines.c"

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_ext1=0,flag_ext2=0,flag_uart0_rx=0,flag_uart0_tx=1,dato_uart0,flag_rtc=0,flag_adc=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"
#include "funciones_uart.c"
#include "memoria_SD.c"

int main()
{	
	u16 cont=TIEMPO_TOGGLE_INICIAL,valor_adc;
	u8 tiempo_inicial_variable=1,estado_anti_reb_ext1=ANTI_REB_IDLE,estado_anti_reb_ext2=ANTI_REB_IDLE,flag_ascendente=0,flag_mostrar=MOSTRAR_CONTADOR;
	
	u8 *buffer_datos;
	u16 indice_buffer;
	
	//-------------- Vectores RTC -----------------//
	u16 vector_hora_inicial_rtc[RTC_TAMANIO]={0,0,0,1,0,1,1,2013};
	u16 vector_alarma_rtc[RTC_TAMANIO]={0,5,0,1,0,1,1,2013};  
	//-------------- Vectores RTC -----------------//
	
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;															//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;											//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;	//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;	//Pongo en 0 el puerto => led encendido
	
	configurar_hora_rtc(vector_hora_inicial_rtc);
	configurar_alarmas_rtc(vector_alarma_rtc);	//Al minuto 5 se espera una alarma.
	habilitar_interrupciones_rtc(0,0);	//Habilito las máscaras interrupción por Alarma => en caso que la cuenta sea igual a la alarma seteada.
	configura_timer0(25000,1000);	//Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg
	
	configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);	//Externa 1 configurada para que detecte flancos descendentes
	configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);  //Externa 2 configurada para que detecte flancos descendentes
	configurar_uart0(UART_9600); //9600bps
	
	iniciar_adc(0,0);	//Inicio el ADC en el canal 0 sin offset
	iniciar_dac();
	asignar_valor_dac(620,MIN_UPDATE_BIAS);	//Vref=3,3V => Si quiero 2Volts => 2V/3,3V*1024=620,6 cuentas
	
	habilitar_interrupciones();
	
	while(1)
	{		
		
		if(!flag_ascendente)
		{
			if(cont==1)
			{
				cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2; asignar_valor_dac(620,MIN_UPDATE_BIAS); /*Vref=3,3V => Si quiero 2Volts => 2V/3,3V*1024=620,6 cuentas*/}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2; asignar_valor_dac(310,MIN_UPDATE_BIAS); /*Vref=3,3V => Si quiero 1Volt => 1V/3,3V*1024=310,3 cuentas*/}
			}

		}
		else
		{
			if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL)-1)
			{
				cont=0;
				
				if(LPC_GPIO3->FIOPIN&LED_1)
					{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
				else
					{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
			}
		}
		
		__wfi();	//Sleep-Mode
		
		if(flag_timer0)		
		{
			flag_timer0=0;			
			if(!flag_ascendente)
				cont--;
			else
				cont++;
			
			switch(flag_mostrar)
			{
				case MOSTRAR_CONTADOR:
					{
						enviar_string_uart0((u8 *)("Contador= \r\n"));
						envia_u16_string_uart0(cont);
						enviar_string_uart0((u8 *)("\r\n"));
						break;
					}
				
				case MOSTRAR_HORA_RTC:{enviar_hora_rtc_uart0(); break;}
				
				case MOSTRAR_CONVERSION_ADC:
					{
						if(convertir_adc(0,250,&valor_adc)>0)	//Convierto en el canal 0, con un pre-escaler=250
						{
							enviar_string_uart0((u8 *)(" Conversion= \r\n"));
							envia_u16_string_uart0(valor_adc);
						}
					}
			}				
		}
		
		if(flag_ext1)
		{
			if(anti_rebote_ext1(&estado_anti_reb_ext1)>0)
			{
				if(tiempo_inicial_variable>1)
					tiempo_inicial_variable--;
			}						
		}
		
		if(flag_ext2)
		{
			if(anti_rebote_ext2(&estado_anti_reb_ext2)>0)
				tiempo_inicial_variable++;					
		}
		
		if(flag_uart0_rx)
		{
			flag_uart0_rx=0;
			
			if(dato_uart0=='a')
				flag_ascendente=1;	//Cuenta Ascendente
			
			if(dato_uart0=='d')
				flag_ascendente=0;	//Cuenta descendente
			
			if(dato_uart0=='r')
				flag_mostrar= MOSTRAR_HORA_RTC;				//Muestra el valor del RTC c/1Seg.
			
			if(dato_uart0=='c')
				flag_mostrar=MOSTRAR_CONTADOR; 				//Muestra el valor de la cuenta c/1Seg.
			
			if(dato_uart0=='q')
				flag_mostrar=MOSTRAR_CONVERSION_ADC; 	//Realiza una conversión ADC y muestra su valor c/1Seg.
			
			if(dato_uart0=='l')
				{
					buffer_datos=calloc(512,sizeof(u8));	//512 bytes

					if(!buffer_datos)
						{enviar_string_uart0((u8 *)("Memoria RAM insuficiente.\r\n"));}
					else
					{
						if(initMMC()!=0)
							{enviar_string_uart0((u8 *)("Error de inicio Memoria.\r\n"));}
						else
						{
							if(mmcReadBlock (0,buffer_datos)==0)
							{
								for(indice_buffer=0;indice_buffer<511;indice_buffer++)
									{
										envia_dato_uart0(*(buffer_datos+indice_buffer));
									}
							}
							else
								{enviar_string_uart0((u8 *)("Error de lectura de Memoria.\r\n"));}
						}
						
					}
					
					free(buffer_datos);
				}
			
			if(dato_uart0=='e')
				{
					buffer_datos=calloc(512,sizeof(u8));	//512 bytes

					if(!buffer_datos)
						{enviar_string_uart0((u8 *)("Memoria RAM insuficiente.\r\n"));}
					else
					{
						*buffer_datos='E';
						*(buffer_datos+1)='s';
						*(buffer_datos+2)='c';
						*(buffer_datos+3)='r';
						*(buffer_datos+4)='i';
						*(buffer_datos+5)='b';
						*(buffer_datos+6)='e';
						*(buffer_datos+7)='\r';
						*(buffer_datos+8)='\n';
						
						if(initMMC()!=0)
							{enviar_string_uart0((u8 *)("Error de inicio Memoria.\r\n"));}
						else
						{
							if(mmcWriteBlock (0,buffer_datos)!=0)
								{enviar_string_uart0((u8 *)("Error de escritura de Memoria.\r\n"));}
						}
						
					}
			
					free(buffer_datos);
				}
				
			if(dato_uart0=='v')
				{
					buffer_datos=calloc(512,sizeof(u8));	//512 bytes

					if(!buffer_datos)
						{enviar_string_uart0((u8 *)("Memoria RAM insuficiente.\r\n"));}
					else
					{
						*buffer_datos='P';
						*(buffer_datos+1)='r';
						*(buffer_datos+2)='u';
						*(buffer_datos+3)='e';
						*(buffer_datos+4)='b';
						*(buffer_datos+5)='a';	
						*(buffer_datos+6)='\r';						
						*(buffer_datos+7)='\n';
						
						if(initMMC()!=0)
							{enviar_string_uart0((u8 *)("Error de inicio Memoria.\r\n"));}
						else
							if(mmcWriteBlock (0,buffer_datos)!=0)
								{enviar_string_uart0((u8 *)("Error de escritura de Memoria.\r\n"));}
					}
					
					free(buffer_datos);
				}
		}
		
		if(flag_rtc)
		{
			flag_rtc=0;
			enviar_string_uart0((u8 *)(" Alarma del RTC!!! - Pasaron 5 minutos desde el ultimo reseteo. \r\n"));			
		}
	}
}
```

Lo más destacado:

- El uso de un buffer de 512 bytes (direcciones 0 a 511), obtenido mediante el uso de la función calloc y liberado con free, para lo cual requiere el uso de las librería "stdlib.h".

- La inicialización de la memoria c/vez que realizaré un cambio. Si no realizaba esta inicialización todo el tiempo, aparecían errores, es posible que el problema esté en el bit SSEL que lo dejo habilitado toooodo el tiempo.

*Fotos del terminal con los datos obtenidos de la memoria:*

- Antes de escribir algo, se puede ver la cabecera tipo FAT16:






- Luego de escribir el string "Escribe" seguido de los 0's, la lectura arrojo:






"Contador=" es del string que mando durante la cuenta realizada en ejercicios anteriores.

- Luego de escribir el string "Prueba" seguido de los 0's, la lectura arrojo:






"Contador=" es del string que mando durante la cuenta realizada en ejercicios anteriores.

Con este mensaje se completan todos los temas que mencione, obviamente todavía falta lo más jugoso que es USB y Ethernet, pero de momento no tuve tiempo de verlos. Cuando aprenda a usarlos, continuaré con el tutorial.


----------



## cosmefulanito04 (Ago 14, 2013)

alexv8 dijo:
			
		

> QUE LABURO HERMANO!!
> 
> muchísimas gracias por el aporte, muy bueno!



Gracias.

Aprovecho para dejar un link del tutorial de FreeRTOS para esta familia de uC.

https://www.forosdeelectronica.com/f24/tutorial-freertos-103057/


----------



## Hellmut1956 (Ago 14, 2013)

Impresionante tu tutorial, en especial porque se dedica a cosas que usualmente dejo desatendidas. No es crítica, sino pregunta. ARM exige de todos aquellos que toman una licencia para el desarollo de una componente del tipo ARM Cortex Mx, que estas empresas deben obligatoriamente poner a dispocición del usuario una API para cada función periferica de su componente, donde la APi es comun para todas las componentes de todos los que licencian de ARM un Cortex Mx. Esto tiene la gran ventaja para el que usa un ARM Cortex Mx, que si programa usa estos API definidos por ARM, el esfuerzo para portar un programa de un controlador a otro controlador, sea del mismo proveedor, en nuestro caso los ARM Cortex M de NXP, o sea de otro proveedor, siempre que tenga las periferias que el programa requiere, será sencillamente portable.

Claro, el camino que tu escoges, por un lado muestra que vienes de los ARM no de los tipo Cortex Mx, pero por otro lado permite aprender en detalle como funcionan y se usan ciertas periferias.

Creo que sería util el incluir la referencia a este API en tu tutorial y de aprender a usarlo. Aprendiendo desde el principio a usar esta API educa a escribir programas que serán facilmente portables.

Repito, no es crítica y mas que se entiende basando en tus experiencias previas con los ARM, es solo una sugerencia. Yo estoy aprendiendo para portar un programa para el control de motor de paso de un ARM Cortex M0 de Samsung al LPC1769.

También quiero indicar que NXP, por ejemplo ofrece los LPCXpresso que cuestan algo menos que el kit que presentan e incluyen una interfaz JTAG, adicional a la interfaz USB, lo que es de gran utilidad para observar un programa como es ejecutado en el sistema destino. Permite prácticamente todas las funciones de Debug que en otras arquitecturas requieren usar un simulador.

Estas placas LPCXpresso existen para todas las familias de Cortex M de NXP y siempre usan el mas potente y grande de cada familia. Esas placas son tan baratas y desconectando la interfaz JTAG tan pequeñas, que para mi no es posible ni justificable hacer placas propias para estos controladores, sino solo tarjetas madre a las que monto la placa LPCXpresso en un zócalo, como lo haría con una componente en empaque DIP! No trabajo para NXP, ni tengo algún beneficio de fomentar estas placas, fuera de compartir mis experiencias con otros interesados.


----------



## cosmefulanito04 (Ago 14, 2013)

Hellmut1956 dijo:


> ARM exige de todos aquellos que toman una licencia para el desarollo de una componente del tipo ARM Cortex Mx, que estas empresas deben obligatoriamente poner a dispocición del usuario una API para cada función periferica de su componente, donde la APi es comun para todas las componentes de todos los que licencian de ARM un Cortex Mx. Esto tiene la gran ventaja para el que usa un ARM Cortex Mx, que si programa usa estos API definidos por ARM, el esfuerzo para portar un programa de un controlador a otro controlador, sea del mismo proveedor, en nuestro caso los ARM Cortex M de NXP, o sea de otro proveedor, siempre que tenga las periferias que el programa requiere, será sencillamente portable.



Mirá yo en ese sentido tengo una forma de pensar bastante crítica hacia el uso de librerías de terceros si puedo evitarlo.

Si el periférico a configurar es relativamente sencillo como un Timer, una Uart o un SPI, ¿por qué entregar el control a una librería que no sabes lo que hace?, ya de por si con C estoy entregando bastante control, ¿para que seguir haciendolo?. Otra desventaja que le veo a ese tipo de librerías, es que están pensadas para funcionar en forma muy genérica, más codigo y posiblemente más procesamiento.  

Está bien lo que decís y es un punto a favor eso de estandarizar las librerías y que otros puedan entender tu código con mayor facilidad. Incluso en periféricos más complejos como el Usb o implementar un TCP Stack para el Ethernet no lo veo mal eso, lo mismo podríamos decir del uso de FreeRTOS, creo resultaría una pérdida de tiempo tratar de implementar mi propio kernel (cosa bastaaaante compleja) teniendo kernels respaldados por la comunidad.

Resumiendo, si lo puedo hacerlo yo y no resulta difícil (simplemente leer hoja de datos y entender el funcionamiento de los registros de dicho periférico), prefiero implementar mí código, se que si a futuro se presenta algún comportamiento errático, no voy a tener que tratar de entender una librería hecha por otro. 



Hellmut1956 dijo:


> Creo que sería util el incluir la referencia a este API en tu tutorial y de aprender a usarlo. Aprendiendo desde el principio a usar esta API educa a escribir programas que serán facilmente portables.



No me parece una mala idea, si vos podés agregar al tutorial el uso de esas librerías, será bienvenido, incluso a mí me pueden llegar a ser útiles en un futuro.



Hellmut1956 dijo:


> También quiero indicar que NXP, por ejemplo ofrece los LPCXpresso que cuestan algo menos que el kit que presentan e incluyen una interfaz JTAG, adicional a la interfaz USB, lo que es de gran utilidad para observar un programa como es ejecutado en el sistema destino. Permite prácticamente todas las funciones de Debug que en otras arquitecturas requieren usar un simulador.



Eso es algo que me está haciendo falta ahora en mi kit, poder hacer el Debug sin usar un puerto LPT y la verdad que en este tipo de uC debuggear es de vital importancia.


----------



## Hellmut1956 (Ago 16, 2013)

cosme..: Soy perezoso, por eso me permito acortar tu nombre. Pero vayamos a tu respuestas. En general comparto tu opinión de escribir mir propias librerías, además que ese ejercicio es muy util para aprender en detalle el funcionamiento de periferias.

ARM, me refiero a la empresa que desarrolla los diversos tipos de controladores ARM, pero que no hace componentes físicas ella misma, sino que licencia estos controladores a productores de componentes, llamadas compañías de "semiconductores". De estas existen aquellas con su propia fábrica para producir sus componentes y las tales "fabless semiconductor" compañías, que no tienen fábricas propias, sino que dejan producir estas componentes. ARM no es lo uno ni lo otro, ellos solo por decirlo así definen los productos y venden la licencia para usar esas definiciones. Bueno, aquí entra el papel y la importancia de estos API. Los que adquieren licencias tienen como parte de ese contrato de licencia con ARM, cuando se trata de controladores ARM Cortex M0, M0+, M3, M4 y otros, tienen la obligación a implementar para todas las componentes físicas que realicen, implementar APIs para todas las funciones periféricas que implementen alrededor del "puro controlador ARM Cortex Mx" y estas APIs deben ser idénticas a la especificación para estos APIs por ARM.

La situación competitiva en este mercado de controladores ARM Cortex Mx a razón de lo escrito es muy diferente de la situación competitiva en otros mercados de controladores para sistemas embebidos. Todo proveedor de un controlador ARM Cortex Mx está ofreciendo un producto idéntico al de todos sus competidores, la única diferencia son las funciones periféricas y la combinación de estas funciones periféricas en un producto específico. Cada proveedor de controladores ARM Cortex Mx busca diferenciarse de sus competidores por implementar este con periferias específicas para su objetivo en cierto mercado de productos que usarán estos controladores. De allí resulta que todos los proveedores buscan facilitar al máximo el uso de sus implementaciones de controladores ARM Cortex Mx. Un resultado es que el mercado ofrece placas muy, pero muy económicas resultando en que nosotros podemos tener acceso a placas muy económicas. pero volvamos a los API.

Siendo tan parecidos todos los controladores ARM Cortex Mx de todos los proveedores, una implementación inefectiva de las librerías que implementan en "software" el API definido por ARM para todas las periferias, resultaría en que este proveedor "vendería" su controlador por debajo de precio! Quién usaría para su software y sus productos los productos de un proveedor, si a razón de una mala implementación de las librerías que implementan el API el producto final no es competitivo. Siendo ARM el "nuevo Intel", significa, siendo ARM la arquitectura de controladores, tanto embebidos como para otros mercados, que está por dominar, todo proveedor, hasta AMD lo ha licenciado, tiene que competir ofreciendo mayor eficiencia para realizar productos de sus clientes.

Siendo la situación así, creo que es correcto asumir:

1. Las librerías que componen la API definida por ARM son la implementación mas efectiva posible, pues cada proveedor esta obligado ofrecerla para sus productos.

2. El papel de esta API en el mercado de los ARM Cortex Mx es permitir a los clientes de aquellos que licenciaron de ARM y así diseñan, construyen y venden sus implementaciones, poder cambiar de proveedor o usar varios proveedores sin temer tener grandes problemas cambiando de proveedor! La software va a funcionar, siempre y cuando el producto de otro proveedor tenga las periferias que la software del cliente requiera!

3. Por lo dicho en los puntos "1" y "2" es de primordial importancia aprender y usar estos APIs cuando se usan controladores del tipo ARM Cortex Mx.

Repito cosme..., escribo esto, no por criticarte! Como tu escribes, vienes de usar controladores ARM 7 por ejemplo, significa no del tipo ARM Cortex Mx, y es factible que esto te sea desconocido. En los controladores ARM de los tipos que no son ARM Cortex Mx, lo que escribo no vale, solo en los tipos ARM Cortex Mx!

Esto tiene sus ramificación hasta a los IDE! NXP acabó por comprar "Code Red", proveedor de los IDE para las placas LPCXpresso! La razón es que si la IDE no ofrece el uso óptimo para los productos ARM Cortex Mx de NXP, las inversiones de NXP en lo físico de sus productos ARM Cortex Mx y en la calidad de sus librerías que implementan la API definida por ARM, entonces los programas que resultan como resultado de la compilación y la configuración sería menos que óptima y así pondría en peligro la competitividad de NXP con sus productos ARM Cortex Mx. La IDE es crítica y estratégica para NXP y comprando Code Red NXP puede garantizar que este elemento estratégico siga conforme a sus necesidades. Otra explicación porque el uso de las API en controladores del tipo ARM Cortex Mx tiene otra justificación diferente de lo que rige en general y donde estoy completamente de acuerdo con cosme...!

Como novato, no por el tiempo que conozco C, sino por la cantidad de experiencia que tengo usando C para escribir programas, mi decisión de ir por productos de la empresa NXP resulta de beneficios adicionales que ofrece la combinación de la IDE de "Code Red" y de las placas LPCXpresso de NXP! La IDE de Code Red "conoce" todas las placas LPCXpresso para las diversas familias de controladores ARM Cortex Mx de NXP. Así la IDE automáticamente se configura para generar código para estas placas. eso es lo que odiaba de las "tool chaines" de otros proveedores. veo que ahora otros proveedores están realizando esta ventaja e implementando las IDEs de acuerdo a esto.

La segunda razón de mi decisión para los productos ARM Cortex Mx de NXP, y no de TI, el competidor más próximo en mi selección del proveedor de controladores, son las dimensiones físicas de las placas LPCXpresso!

Aquí NXP coopera con "Embedded Artists", que diseña y produce las placas LPCXpresso. Vayan al sitio de este distribuidor en Europa, el más económico sumando costos de flete y del producto, allí compré mi LPCXpresso 1769 por una suma total de 37,80 Euros. Para lugares como latinoamérica tendrán que investigar y encontrar un distribuidor adecuado. Aquí el enlace al LPCXpresso 1769 que compré y que cuesta 23,80 Euros!






La placa LPCXpresso consiste de 2 partes que se pueden separar físicamente. A la izquierda, ven que la parte tiene un conector para la interfaz USB, a la derecha la placa con el controlador LPC1769. ven que donde las 2 partes se encuentran la placa tiene un puente que conecta la una a la otra. Separando la placa allí y construyéndose un cable para permitir la comunicación entre ambas partes de la placa LPCXpresso1769, es posible usar la placa como una componente con 2 filas de pines, vemos este como fila de "huecos" a lo largo de esta parte y a ambos lados de esta. me atrevo a decir. es imposible para un usuario particular o profesional hacer esta placa uno mismo, comprando las componentes requeridas y armandola y ponerla en función por solo 23,80 Euros! Ni hablar de la calidad de la placa y ni hablar de implementarla de forma tan compacta! este tipo de placa LPCXpresso existe para todas las familias de controladores ARM Cortex Mx y viene siempre usando la variante mas grande y potente de cada familia! por estas razones y por las periferias con las que viene voy a usar un buen número de estas placas en mi modelo de un velero. es excesivo, pero dado el bajo precio de estas placas y dado las funcionalidades ofrecidas y dado el carácter gratuito de la IDE hasta un código de 128 kbytes y las módicas sumas para ampliar el límite del tamaño del código a usar, no veo alternativa mas eficiente.

Pero es tan importante para escribir programas en sistemas embebidos y normalmente el impacto económico hace su uso prohibitivo, es que aquella parte con la interfaz USB, también implementa la interfaz JTAG, que se usa para el encontrar errores en los programas, y esto ejecutando el programa a analizar no en una simulación del controlador, sino físicamente el el controlador LPC1769 en mi caso. Puedo hacer que el programe pare a nivel de las instrucciones del assembler, el nivel mas próximo a la hardware, o al nivel del código "C". Al para el programa la interfaz JTAG me permite ver el valor de variables, de registros en el controlador, en las diversas memorias del controlador, Flash SRAM, etcétera y de cambiar los valores y después por ejemplo seguir con la ejecución del programa paso a paso de instrucción a instrucción. pero también en caso de eventos, como Interrupt o eventos en uno de los pines del controlador. Esta función se llama "debug". esto viene gratuito con cada placa LPCXpresso y forma parte de la funcionalidad de la IDE! Recuerden, la IDE conoce todas las placas LPCXpresso y por lo tanto el "debugger" conoce la placa LPCXpresso y sabe encontrar todos los registros pues se autoconfigura! Como NXP compro Code Red el futuro será brillante y excitante!


----------



## marcos0007 (Ago 31, 2013)

Cosmefulanito,

Excelente tu post, te felicito. Te comento que compre la misma placa con un TFT2,8, del mismo fabricante . Estoy haciendo un proyecto para la universidad y estoy teniendo problemas para conectarme con la placa.
Instale los drivers del CP210x para usb to uart, el windows me lo detecta como com 2 , perfectamente.
El tema es como comunicarme con la placa, por lo que estuve leyendo la mayoria utiliza JTAG, con la tarjeta ULink2 para cortex M3.

Por otra parte, estuve leyendo que se puede programar y comunicarme por el USB-com y atacarlo con el FlashMagic. Es esto posible

En tu experiencia cual es la forma mas sencilla y segura de trabajar con esta placa 

Muchas gracias por todo
Saludos ...


----------



## cosmefulanito04 (Sep 1, 2013)

Con el driver ya instalado, solo te queda usar el flash magic para poder programarlo, los pasos son:

- Elegís el uC -> 1768.
- El puerto COM "virtual" que usa el adaptador USB.
- La velocidad, probá con 9600.
- Interfaz -> None (ISP)
- Oscilador (MHz) -> 12

Por el lado del uC, antes tenés que entrar en modo de programación:

1- Pulsas el botón ISP (y lo mantenés).
2- Pulsas el botón Reset.

Listo con eso ya estás en condiciones de leer o programar el uC.

La desventaja de esta placa es que si querés usar el Jtag, tenés que usar el puerto paralelo  o conseguir cable usb de keil que sale como u$d 20.

Te dejo el esquemático para armar el Jtag usando el puerto paralelo, es bastante simple, el problema es el puerto paralelo .


----------



## marcos0007 (Sep 1, 2013)

Gracias, Cosmefulanito.

Hay forma de leer el programa demo que me vino cargado en la placa, es muy buen demo y quisiera usar algunas sentencias. El tema es que solo tengo el FlashMagic y no vi la opción de leer uC.

laplaca también me vino con el accesport 1.34 pero no me ayudó en nada. El keil uvision 4 no me deja tarabajar con la plca sin el ULINK2.

Voy a probar este tip que me diste.

gracias


----------



## cosmefulanito04 (Sep 1, 2013)

marcos0007 dijo:


> Gracias, Cosmefulanito.
> 
> Hay forma de leer el programa demo que me vino cargado en la placa, es muy buen demo y quisiera usar algunas sentencias. El tema es que solo tengo el FlashMagic y no vi la opción de leer uC.



Lo único que podés hacer es leer el ejecutable ".hex".

Para leer el programa necesitas los archivos fuentes, fijate que en el cd trae bastantes ejemplos, es cuestión de verlos.


----------



## Ardogan (Sep 1, 2013)

Yendo al tema de las librerías y periféricos en ARM, creo que hay que mencionar que existe el CMSIS que es un estandar abierto para lo que es núcleo del procesador.
Realmente no tengo experiencia con ARM (todavía ), pero estuve recientemente en una charla donde tocaban ese tema. 

No incluye los periféricos que son particulares de cada fabricante, pero sí las funciones del núcleo (reloj, inicialización del núcleo, definición de interrupciones, manejo de memoria).

Ventaja?: portabilidad, por lo menos para los aspectos relacionados con el núcleo. No debería haber diferencia al pasar de un micro ARM cortex Mx de NXP a uno de Toshiba, ST, etc.

http://www.keil.com/support/man/docs/gsac/gsac_cmsisoverview.htm


----------



## cosmefulanito04 (Sep 2, 2013)

Ardogan dijo:


> Yendo al tema de las librerías y periféricos en ARM, creo que hay que mencionar que existe el CMSIS que es un estandar abierto para lo que es núcleo del procesador.
> Realmente no tengo experiencia con ARM (todavía ), pero estuve recientemente en una charla donde tocaban ese tema.
> 
> No incluye los periféricos que son particulares de cada fabricante, pero sí las funciones del núcleo (reloj, inicialización del núcleo, definición de interrupciones, manejo de memoria).
> ...



Algo estuve viendo de lo que mencionó *Hellmut1956* y hasta ahora llegué a tu misma conclusión acerca del CMSIS, solo tienen los registros y un archivo de startup en común con todos los fabricantes.

Las funciones para manejar los periféricos las tenés que hacer vos o a lo sumo usar alguna librería que pueda traer el entorno de desarrollo, por ej. Coocox. Después también hay algo llamado *mbed* que son librerías.

Por otro lado, si vemos el videotutorial de Cortex M0 usando STM32fiscovery que subió *Pablet*, el manejo de los registros es muy similar, a pesar de hablar de otro fabricante y Cortex M0 (no M3).


----------



## marcos0007 (Sep 2, 2013)

Muchachos 

me recomendaron esta interface para comunicarme con la placa, leer y hacer debugging

USB-MiniJTAG-EX J-Link JTAG/SWD Debugger/Emula​tor ( la empresa es haoyu electronics)

que opinan?

Saludos


----------



## marcos0007 (Sep 5, 2013)

Me compre una LPC1768-MINI-DK2 estoy usando el keil y el flash magic. El tema es que arme un proyecto para probar y cuando lo compilo tengo problemas. me tira el siguiente error:
ej1.axf: Error: L6218E: Undefined symbol SystemInit (referred from startup_lpc17xx.o).
inclui el startup del lpc1768 (*.s) y el header file LPC17xx (*.h)

Tenes una idea de lo que puede ser?
Gracias
SAludos


----------



## cosmefulanito04 (Sep 6, 2013)

marcos0007 dijo:


> Me compre una LPC1768-MINI-DK2 estoy usando el keil y el flash magic. El tema es que arme un proyecto para probar y cuando lo compilo tengo problemas. me tira el siguiente error:
> ej1.axf: Error: L6218E: Undefined symbol SystemInit (referred from startup_lpc17xx.o).
> inclui el startup del lpc1768 (*.s) y el header file LPC17xx (*.h)
> 
> ...



Fijate en el tutorial que armé que subí varios ejercicios, entre ellos el proyecto en keil de c/u, miralos y tomalos como base de partida.

Lo que te está faltando son justamente esos 2 archivos (o uno de los dos) que por un lado definen el área de memoria y un par de cosas más y por el lado del ".h" los nombres de los registros. El startup lo encontrás en el directorio del keil.


----------



## marcos0007 (Sep 12, 2013)

Cosmefulanito,

Gracias por la ayuda con el Keil. Estuve probando alguno de tus proyectos y me funcionaron. 
Te quería preguntar si tenes algún ejemplo de como usar el ADC de la placa.
Gracias


----------



## cosmefulanito04 (Sep 12, 2013)

Tenés un tutorial gigante dividido en los distintos periféricos, leelo. Además dale bola a la hoja de datos que ahí tenés la información justa.

Sexta parte - ADC

Funciones destacadas que se explican en el tutorial:


```
void iniciar_adc(u8 canal,u8 offset)
```


```
int convertir_adc(u8 canal,u8 pre_escaler_interno,u16 *valor_adc)
```


```
void ADC_IRQHandler (void)
```


----------



## marcos0007 (Sep 12, 2013)

Cosmefulanito, sos un groso!!!
Muchas gracias por el aporte, excelente post.
Ojala todos pudiesemos compartir nuestra experiencia en este foro.
Me baje el proyecto, lo pruebo y comento 
Gracias!!!!


----------



## marcos0007 (Oct 16, 2013)

Estuve luburando con el ADC, que pusiste de ejemplo. Tengo un problema y quería ver si se puede solucionar  como pensé. El tema es que la variable del valor convertido (valor_adc) es de tipo u16. Yo queiro mostrar este valor en la pantalla LCD. el problema es que la función GUI_text del lcd me pido este tipo de parametros.

void GUI_Text(uint16_t Xpos, uint16_t Ypos, uint8_t *str,uint16_t Color, uint16_t bkColor)

Si yo pongo esto 	GUI_Text(60,144,"Peso",White,Blue); me muestra perfectamente en pantalla la palabra peso. Pero no puedo hacer que me muestre el valor de la conversión

Como me pide un string estaba pensando en hace un sprintf de valor_adc y así pasarle ese valor directamente a la función del GUI_TEXT, para que me lo muestre en pantalla. EL tema es que el sprintf no le gusta muestra cualquier fruta. Vos para mandar por la UART convertis el dato adc a palabra. Se podría usar la misma función o conoces otra forma más eficiente


----------



## cosmefulanito04 (Oct 16, 2013)

marcos0007 dijo:


> ...Como me pide un string estaba pensando en hace un sprintf de valor_adc y así pasarle ese valor directamente a la función del GUI_TEXT, para que me lo muestre en pantalla. EL tema es que el sprintf no le gusta muestra cualquier fruta.



Eso debería funcionar, ¿cómo lo estás usando? (utilizá la herramienta "php" del foro para poner código)



marcos0007 dijo:


> ...Vos para mandar por la UART convertis el dato adc a palabra. Se podría usar la misma función o conoces otra forma más eficiente



También te sirve, de hecho lo pensé para evitar el sprintf, o sea, en un ARM mucho sentido no tiene porque tenés Flash para rato, pero en uC más chicos se complica su uso y como ya tenía el código implementado en AVR, lo volví a usar en ARM.

Lo único que tenés que hacer es pasarle un vector u8 con 5 elementos, seguido del u16 que necesitás convertir, al finalizar en el vector tenés el u16 convertido en string.

*La función:*


```
void convertir_digitos_u16_serie(u8 digitos[],u16 dato)
{
	u8 aux;
	
	for(aux=4;aux>0;aux--)
		{
		digitos[aux]=(dato%10)+0x30;
		dato=(int)(dato/10);
		}
		
	digitos[0]=dato+0x30;
}
```

*La usas así:*


```
....
u8 string_u16[5];
u16 dato=5600;

convertir_digitos_u16_serie(string_u16,dato);

GUI_Text(60,144,string_u16,White,Blue);
```

De última cambiale el nombre a la función, sacale el "serie".


----------



## marcos0007 (Oct 16, 2013)

Gracias capo, funcionó perfecto, yo lo había hecho antes eso, pero se ve que tenía un quilombo de librerías porque hay cosas que no me las toma bien. Lo que pasa es que importé las librerías del CMSIS para que funcione el GLCD y el touchscreen. Algo tengo mal.

Si podes ver en al adjunto que mando, es lo que sale por la UART. El mismo programa me funcionaba perfecto antes, es el ejemplo que compartiste en el foro. Pero el tema que ahora me salen símbolos, puede ser que tenga que ver con el LPC1768.h? o el lpc1768_libcfg?

Desde ya muchas gracias por la ayuda con lo del ADC
Saludos


----------



## cosmefulanito04 (Oct 17, 2013)

marcos0007 dijo:


> Gracias capo, funcionó perfecto, yo lo había hecho antes eso, pero se ve que tenía un quilombo de librerías porque hay cosas que no me las toma bien. Lo que pasa es que importé las librerías del CMSIS para que funcione el GLCD y el touchscreen. Algo tengo mal.
> 
> Si podes ver en al adjunto que mando, es lo que sale por la UART. El mismo programa me funcionaba perfecto antes, es el ejemplo que compartiste en el foro. Pero el tema que ahora me salen símbolos, puede ser que tenga que ver con el LPC1768.h? o el lpc1768_libcfg?
> 
> ...



Puede ser que no esté bien configurada la velocidad.

La librería "lpc1768_libcfg" ¿qué hace?, tal vez te cambia el PLL.


----------



## marcos0007 (Oct 17, 2013)

No creo que sea un problema de velocidad, porque cuando cambio la opción, el valor que muestra por la UART cambia. O sea los simbolos cambian pero no son letras sino simbolos. 
NO se pensé que quizás tenga un header file diferente. Voy a seguir investigando.

Che por casualidad estuviste viendo como colocar un boton en la pantalla LCD, ayer comencé a investigar y trabaja mucho con matrices. Mi idea es colocar un pulsador tactil y cuando toco el pulsador que me ejecute una tarea. Estuve revisando la función de calibración del LCD, que plantea algo parecido a lo que quiero. VOy a seguir investigando eso también. Si tengo novedades las comparto

Gracias


----------



## cosmefulanito04 (Oct 17, 2013)

marcos0007 dijo:


> No creo que sea un problema de velocidad, porque cuando cambio la opción, el valor que muestra por la UART cambia. O sea los simbolos cambian pero no son letras sino simbolos.
> NO se pensé que quizás tenga un header file diferente. Voy a seguir investigando.



No entendí bien esto, ¿vos decís que los símbolos cambian según el caracter que mandás?

Si así, eso es normal, tener la velocidad mal configuradad implica recibir cualquier cosa debido a que el receptor está tomando mal los bits que recibe, pero esa "cualquier cosa" que recibís es repetible, es decir que si mando 'b' y recibo '@', eso se repetirá siempre.

Hacé una prueba fácil, envía un caracter y fijate si del otro lado lo recibís bien, en ese caso no es un problema de velocidad.



marcos0007 dijo:


> Che por casualidad estuviste viendo como colocar un boton en la pantalla LCD, ayer comencé a investigar y trabaja mucho con matrices. Mi idea es colocar un pulsador tactil y cuando toco el pulsador que me ejecute una tarea. Estuve revisando la función de calibración del LCD, que plantea algo parecido a lo que quiero. VOy a seguir investigando eso también. Si tengo novedades las comparto



No compré la pantalla, preferí ver bien el tema del USB y el ethernet, pero de momento no lo toqué .


----------



## inesviana493 (Oct 27, 2013)

Hola!

Estoy aprendiendo a usar este dispositivo, de momento no he hecho gran cosa, en clase nos han recomendado hacer este ejercicio para ir practicando, como todavía no tengo la placa en casa y el simulador no se usarlo demasiado bien, me gustaría que me dijeran si voy por buen camino, el enunciado es el siguiente:

El programa a desarrollar en C deberá ser capaz de leer el pulsador SW2 dispuesto en la
tarjeta en el puerto 2 (pin 2.12), y en función de su estado encender parpadeando uno
u otro LED’s de los dos que también tiene la tarjeta en el puerto 1 (pines 1.18 y 1.29).
El modo de funcionamiento será el siguiente: si el pulsador se encuentra pulsado se
deberá parpadear uno de los LED (cualquiera de los dos) con intervalos de medio
segundo encendido medio segundo apagado. Si el pulsador no está pulsado deberá
parpadear el otro LED con intervalos de 1 segundo encendido 1 segundo apagado.

Esto es lo que he programado yo, he intentado aclarar con comentarios cada paso:

```
#include <LPC17xx.H>

#define CONST_500ms_100MHz 10000000  //Retardo de 500ms   

#define SW2 (1<<12)			 //Puerto 2.12  pinsel 4
#define LED_1 (1<<18)    //Puerto 1.18  pinsel 3
#define LED_2 (1<<19)    //Puerto 1.19  pinsel 3


void retardo(int32_t tiempo)
{
   int32_t i;
	
   //El bucle for tarda 25 ms a 100MHz
   for(i=0;i<tiempo;i++);
}

int main()
{    

	LPC_PINCON->PINSEL4=0;
	LPC_PINCON->PINSEL3=0;
	
	LPC_GPIO2->FIODIR=0x00000000;	//Defino al puerto 2 como entrada SW2
	
	LPC_GPIO1->FIODIR|=LED_1;		//Defino al puerto 1.18 como salida
	LPC_GPIO1->FIODIR|=LED_2;	 //Defino al puerto 1.19 como salida
    
    while(1)
    {        
        if(LPC_GPIO2->FIOPIN&=SW2)	//Si el pulsador SW2 es pulsado
        {
					
		LPC_GPIO1->FIOSET=LED_1;  //Apago el LED_1  (se enciende a nivel bajo)
		LPC_GPIO1->FIOSET=LED_2;	//Enciendo el LED_2  (se enciende a nivel alto)					
		retardo (CONST_500ms_100MHz); //retardo de 0.5 segundos
				
	}
        else
        {
					
		LPC_GPIO1->FIOCLR=LED_1;  //Enciendo el LED_1  (se enciende a nivel bajo)
		LPC_GPIO1->FIOCLR=LED_2;	//Enciendo el LED_2  (se enciende a nivel alto)
					
		retardo (CONST_500ms_100MHz*2); //retardo de 1 segundo
					
	}
        
    }
}
```
el programa compila, pero hay cosas que no estoy muy segura de si están bien, como por ejemplo el SW2, no se si al pulsarlo da como resultado nivel alto o bajo, porque al ejecutar paso a paso el programa siempre se mete en la condición if.

Muchas gracias!!


----------



## cosmefulanito04 (Oct 27, 2013)

No me convence el operador "&=" en el if, ya que esa es una operación para asignar el valor del and binario entre dos variables, ejemplo:


```
variable_1&=varible_2;
```

Lo que vos querés hacer es un simple and:


```
if(LPC_GPIO2->FIOPIN&SW2)
...
```

El resto parece estar bien, habría que ver si esa rutina de espera te sirve o no, pero eso lo ves en la práctica.


----------



## marcos0007 (Oct 29, 2013)

Cosmefulanito, como estas, te quería preguntar si a vos te paso que el valor de conversión de adc oscile. Es decir, yo tengo mi programa cuando le pongo una tensión fija (conectandolo a los 3,3v de la placa minidk2) el valor adc es constante y esta quietoo. Lo mismo si le inyecto gnd.
Pero cuando lo conecto a mi amplificador, el cual amplifica una señal de una celda de carga, la entrada al adc esta fija porque la estoy midiendo con un tester. Pero el valor de conversión de adc oscila cíclicamente entre ciertos valores cada 1seg.

Puede ser algo de la velocidad de trabajo? 
gracias


----------



## cosmefulanito04 (Oct 29, 2013)

Es normal eso y depende mucho de lo que estas midiendo.

Varias formas de mejorar esa oscilación:


 Reducir la velocidad de conversión es una buena idea, introducís menos ruido en la conversión.
 Colocar un filtro pasa bajos R-C a la entrada para reducir el ancho de banda, menos ancho de banda, menos ruido.
 Realizar un promedio móvil de las conversiones que vas haciendo, esto funciona como un filtro pasa bajos hecho por soft.


----------



## marcos0007 (Oct 29, 2013)

Estuve variando la velocidad de conversión, pero no me dio buen resultado, ya que lo que logré es que el valor siga oscilando pero que dicho valor cambie de una forma mas lenta.
Pero eso es un proble ya que no responde rapido a los cambios de peso. Estuve viendo que otra solución es mandar todas las entradas de ADC a tierra. Pero no se si puede afectar mucho en la medición. 
Lo que no me cierra es porque no oscila en los extremos? o sea cuando le inyecto 3.3v o 0v

Gracias


----------



## cosmefulanito04 (Oct 29, 2013)

marcos0007 dijo:


> Estuve variando la velocidad de conversión, pero no me dio buen resultado, ya que lo que logré es que el valor siga oscilando pero que dicho valor cambie de una forma mas lenta.



Es una medida mas que deberías tomar (importante), no la única.



marcos0007 dijo:


> Lo que no me cierra es porque no oscila en los extremos? o sea cuando le inyecto 3.3v o 0v.



Es bastante obvio, pensá que tanto los 3,3v como el GND forman parte de la referencia, es imposible obtener una medición distinta a esos niveles.

Volviendo un poco sobre tu problema, si la oscilación es de uno pocos niveles, por ej. 10, estamos hablando de variaciones de 8mV (muy cerca del piso de ruido que medís con un OCR), en cambio si ya la diferencia de nivel es de 100 unidades, ya hablamos de un posible ripple, ¿mediste la tensión con un OCR?.


----------



## marcos0007 (Oct 29, 2013)

Lo que me hace es lo siguiente: Si yo inyecto al adc 3.3v, el valor de adc es 4095 ok?. Bueno lo que yo hago es multiplicar el valor_adc por una constante 0.805669 que me da como resultado 3300, en lugar de 4095.

Cuando yo utilizo la celda de carga tengo variaciones de alrededor de 100mv en el dato que obtengo del adc, es muchísimo por ahora no probé con un filtro pasa bajo ni tampoco con un promediado del valor. Por eso te preguntaba si te había pasado algo similar. YO mido la entrada del la placa con un multímetro y no noto variaciones, se que no es lo mejor para medir pero en este momento no cuento con otro instrumental

Gracias!!


----------



## cosmefulanito04 (Oct 29, 2013)

marcos0007 dijo:


> Cuando yo utilizo la celda de carga tengo variaciones de alrededor de 100mv en el dato que obtengo del adc, es muchísimo por ahora no probé con un filtro pasa bajo ni tampoco con un promediado del valor. Por eso te preguntaba si te había pasado algo similar. YO mido la entrada del la placa con un multímetro y no noto variaciones, se que no es lo mejor para medir pero en este momento no cuento con otro instrumental.



Para mí es lo que estás midiendo lo que te da esos 100mV de oscilación, descartando problemas de ruidos importantes en el PCB/circuitos de prueba.

Probá con lo siguiente, de una tensión bien regulada, conectala al operacional y fijate que medís, tratá de que la alimentación del operacional sea buena y no tengo un ripple excesivo (si podés regular su alimentación, mejor).

De todas formas lo ideal es saber que estas midiendo en la celda y eso lo haces con un OCR, el tester con el filtro pasa bajos te mata la alterna.


----------



## electroandres (Oct 29, 2013)

Se libero el LPCXPRESSO para C++. La nueva generacion de programadores.
Estuve trabajando todo el año con el LPC1769 y es una joya. El IDE anda de maravillas. Pude lograr una interfaz grafica sobre pantalla tactil, lectura de pendrive y reproduccion de musica sin ningun tipo de problema. 
Solo queria dar un consejo: Si se quieren comprar un LPC, comprense el 1769 ya que el 68 tiene un problema al levantar la frecuencia de pll, sacando eso son iguales.


----------



## cosmefulanito04 (Oct 30, 2013)

Desde mi humilde experiencia, ningún problema con el 1768 y el uso del PLL, enganchó perfecto en la frecuencia que quise, incluso a la máxima de 100MHz, no probé "overclockearlo", pero seguro que un par de MHz más se le puede sacar, por lo menos en los ARM7 5 MHz más se le podía sacar.


----------



## marcos0007 (Oct 31, 2013)

Cosmefulanito, ya solucioné el tema de la variación. Tenía mucha oscilación en el ampli diferencial y eso lo veia el ADC. Lo filtré con un RC, además hice un promediado de los valores con eso también mejoró.

Te hago una pregunta, en los archivos de ejemplo que vienen con el kit esta un easyweb, pero para que funcione con LAN 8720 hay que modificar dos archivos el EMAC.C Y EMAC.H. Estuve tratando de identificar que es lo que debo cambiar o que debo redefinir pero estoy perdido. Vos laburaste con esta configuración.

Muchas gracias
Slds


----------



## cosmefulanito04 (Oct 31, 2013)

Nop, todavía no probé ni el usb ni el, ethernet 

Si hacés algo, no dudes en subirlo.


----------



## electroandres (Oct 31, 2013)

El 1769 llega hasta 120Mhz. En teoria el 1768 tambien pero tiene un bug que no se lo permite.
Para empezar a usar el USB recomiendo la utilización de librerias que vienen con la placa de desarrollo de Embedded Artist. Si desean Hostear un pendrive por ejemplo, les recomiendo las librerias de FAT16 del chinito CHAN:http://elm-chan.org/fsw/ff/00index_e.html (las cuales por suerte se encuentran para nuestro queridisimo LPC17xx) Si tengo tiempo, en unos dias subo unos programas para poder levantar un pendrive y poder leer y escribir archivos.
Para todo aquel que tiene experiencia con las funciones FILE tanto de linux como de windows les va a ser muy similar trabajar con esta libreria.


----------



## cosmefulanito04 (Nov 1, 2013)

electroandres dijo:


> En teoria el 1768 tambien pero tiene un bug que no se lo permite.



Bue, pero de ahí a que falle el PLL a la fmax que recomienda la hoja es otra cosa .



electroandres dijo:


> Para empezar a usar el USB recomiendo la utilización de librerias que vienen con la placa de desarrollo de Embedded Artist. Si desean Hostear un pendrive por ejemplo, les recomiendo las librerias de FAT16 del chinito CHAN:http://elm-chan.org/fsw/ff/00index_e.html (las cuales por suerte se encuentran para nuestro queridisimo LPC17xx) Si tengo tiempo, en unos dias subo unos programas para poder levantar un pendrive y poder leer y escribir archivos.
> Para todo aquel que tiene experiencia con las funciones FILE tanto de linux como de windows les va a ser muy similar trabajar con esta libreria.



Dibuje maestro, si podés subilo.


----------



## marcos0007 (Nov 2, 2013)

Cosmefulanito, hay una cosa que no entiendo cuando planteas el programa con interrupciones. Yo reviso el main y veo que todas las condiciones están dentro de un while.
O sea entiendo que esta todo el tiempo encuestando, pej, si el pulsador fue presionado, si el ADC terminó la conversión etc.
Esto es así o hay algo que no estoy viendo. 
Ahora estoy renegando para tratar de que las letras que salen por el LCD sean más grandes porque las que trajo la librería vienen de 8*16 jajaja no se ve nada

Gracias


----------



## cosmefulanito04 (Nov 2, 2013)

marcos0007 dijo:


> Cosmefulanito, hay una cosa que no entiendo cuando planteas el programa con interrupciones. Yo reviso el main y veo que todas las condiciones están dentro de un while.
> O sea entiendo que esta todo el tiempo encuestando, pej, si el pulsador fue presionado, si el ADC terminó la conversión etc.



Si y no.

Tenés dos formas de resolver una rutina de interrupción:


Usando un flag (variable global) solamente para indicar que hubo una interrupción y luego resolver la acción fuera de la rutina de interrupción.
Resolver la acción, *si es corta*, dentro de la rutina de interrupción.

En los ejemplos que subí, usé la primera forma, pero en combinación con la función " __wfi()" para no estar haciendo polling todo el tiempo, solo lo hago cuando se produjo una interrupción, el resto del tiempo el uC está durmiendo.


----------



## markisrodrigo (Nov 11, 2013)

Muy buenas! Soy nuevo en el foro y la verdad es que te leí hace tiempo cosmefulanito y guarde la pagina en favoritos para futuras ocasiones y ahora me vendría de perlas un cable con la LPC1768... No he encontrado mucho sobre el tema por internet (la verdad es que si, pero no legible desde mis cocimientos jajaja) acerca de la interrupción SysTick, como configurar su prioridad, el retardo, etc... Adjunto un pdf con el trabajo con el que estoy teniendo problemas, concretamente con la actividad 6.2 ¿Podrías subir algún tipo de tutorial acerca de esta interrupción? Gracias!!


----------



## cosmefulanito04 (Nov 11, 2013)

Después veo si subo el ejercicio "Segunda Parte - PLL y timers (código)" reemplazando el timer por el SystemTick.


----------



## markisrodrigo (Nov 11, 2013)

cosmefulanito04 dijo:


> Después veo si subo el ejercicio "Segunda Parte - PLL y timers (código)" reemplazando el timer por el SystemTick.



Muchas gracias!


----------



## cosmefulanito04 (Nov 11, 2013)

Por lo que estuve viendo, es bastante sencillo de manejar.

Depende de los siguientes registros:

 *STCTRL:* en este registro se habilita la cuenta regresiva, la interrupción (el System Tick no está dentro del vector de interrupción NVIC, por lo menos yo no lo encontré), el clock fuente (si es mediante el CCLK o un CLK externo mediante el pin P3.26).
 *STRELOAD:* en este registro se fija la cuenta, dicha cuenta podrá ser de 24bits máximo. 

La cuenta se calcula de la siguiente forma:

[LATEX]Cuenta=\frac{T_{mSeg}.Clock}{1000}-1[/LATEX]

Ejemplo de un Tick de 1mSeg usando los 100MHz del CCLK:

[LATEX]Cuenta=\frac{1.100000000}{1000}-1=99999[/LATEX]

Entonces, del ejercicio "Segunda Parte - PLL y timers (código)" para trabajar con el System Tick la modificaciones que haría son las siguientes:

*perifericos.c*


```
//Todo lo anterior

//--------------------------------------- SysTick -------------------------------------------------------------//
#define SYSTICK_CLK					100000000

#define SYSTICK_DISABLE				0
#define SYSTICK_ENABLE				(1<<0)
#define SYSTICK_INT						(1<<1)
#define SYSTICK_CPU_CLK				0
#define SYSTICK_EXTERNAL_CLK	(1<<2)

void configura_systick(u8 configuracion,u32 recarga_ms)
{
	configuracion&=(SYSTICK_EXTERNAL_CLK|SYSTICK_INT|SYSTICK_ENABLE);	//Me aseguro que solo esté seteado el bit 2/1/0	
	
	SysTick->CTRL=configuracion;	
	
	SysTick->LOAD=((u32)((recarga_ms*SYSTICK_CLK)/1000)-1)&0xffffff;	//Me aseguro que sea de 24bits		
}
//--------------------------------------- SysTick -------------------------------------------------------------//
```

Donde se ve que:


 Mediante la variable "configuracion" se habilita la cuenta, la interrupción y la fuente de clock, asegurandome que solo se modifiquen los 3 primeros bits.
 Mediante la variable "recarga_ms" se realiza la cuenta que puse arriba y me aseguro que esa recarga sea de 24 bits.

*interrupciones.c*


```
//--------- Rutina de la interrupcion SysTick ---------------------// 
void SysTick_Handler(void)
{
	u32 dummy=SysTick->CTRL;	//Lectura falsa para limpiar el flag "COUNTFLAG".
	flag_systick=1;
}	
//--------- Rutina de la interrupcion SysTick ---------------------//
```

Donde se ve que:


 Limpieza del flag "COUNTFLAG" mediante una lectura falsa.
 Mediante la variable global "flag_systick", dejo indicado que hubo una interrupción.

*main.c*


```
#include <LPC17xx.H>

#include "defines.c"

#define LED_1 (1<<25)	//Puerto 3.25
#define LED_2 (1<<26)	//Puerto 3.26
#define TIEMPO_CAMBIO_DE_ESTADO	10000

//------- Variables globales para las interrupciones -------------//
u8 flag_timer0=0,flag_systick=0;
//------- Variables globales para las interrupciones -------------//

#include "interrupciones.c"
#include "configuracion_PLL.c"
#include "perifericos.c"

int main()
{	
	u16 cont=TIEMPO_CAMBIO_DE_ESTADO;
	
	configurar_pll(CLK_XTAL,25,2,3,0,0);	// Cristal de 12MHz => M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz 
	
	LPC_PINCON->PINSEL7=0;			//Declaro al puerto 3 como GPIO
	LPC_GPIO3->FIODIR|=LED_2|LED_1;		//Defino al puerto como salida 
	
	LPC_GPIO3->FIOSET=LED_1;		//Pongo en 1 el puerto => led apagado
	LPC_GPIO3->FIOCLR=LED_2;		//Pongo en 0 el puerto => led encendido
	
	configura_systick(SYSTICK_CPU_CLK|SYSTICK_INT|SYSTICK_ENABLE,1);	//Clock interno, interrupción habilitada c/1mSeg, SysTick habilitado
	
        //No necesito modificar el vector NVIC!

	while(1)
	{		
		if(!cont)
		{
			cont=TIEMPO_CAMBIO_DE_ESTADO;
			
			if(LPC_GPIO3->FIOPIN&LED_1)
				{LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|=LED_2;}
			else
				{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR|=LED_2;}
		}
		
		__wfi();	//Sleep-Mode				
		
		if(flag_systick)
		{
			flag_systick=0;
			cont--;
		}
		
	}
}
```

Solo llegué a probarlo con el debugger de keil y funcionaba bien, será cuestión de probarlo con el hard.

Otra alternativa que encontré que es más genérica, que es lo que propone *Hellmut1956*, donde se usa una función SysTick_Config (algo similar a la que hice). Por ejemplo en el FreeRtos creo que usan esa función para fijar el Tick.

Te subo el proyecto con el código que puse arriba.


----------



## markisrodrigo (Nov 13, 2013)

Muchas gracias cosmefulanito! Me ha sido de gran utilidad tu aporte


----------



## marcos0007 (Nov 13, 2013)

Cosmefulanito, sigo con el problema del ADC, estuve problando muchas cosas pero el problema sigue. Tengo gran variación en el dato que tengo en el registro del ADC. Al prinicipio pensé que era ruido de línea acoplado a la señal, pero probé con un divisor de tensión tomando los 3.3v de la placa del micro y referenciandolo a masa y obtuve el mismo resultado. 
Estuve haciendo debugging con el Keil y veo que en cada conversión, el valor del registro del ADC es distinto, eso es lo que veo como variación. Estuve tratando de promediarlo haciendo un if tomando 20 muestras, pero se me hizo muy lento y me mostraba cualquier valor.

La verdad solo me queda probar por DMA, pero no debería ser así. Se te ocurre alguna otra cosa. Gracias
SAludos
!!


----------



## cosmefulanito04 (Nov 13, 2013)

marcos0007 dijo:


> ...Al prinicipio pensé que era ruido de línea acoplado a la señal, pero probé con un divisor de tensión tomando los 3.3v de la placa del micro y referenciandolo a masa y obtuve el mismo resultado.



Podría ser un problema de alimentación, que se te meta mucho ruido por ahí, tratá de evitar el usb de la PC y usá una fuente (si es que no hiciste antes eso).



marcos0007 dijo:


> ...Estuve haciendo debugging con el Keil y veo que en cada conversión, el valor del registro del ADC es distinto, eso es lo que veo como variación. Estuve tratando de promediarlo haciendo un if tomando 20 muestras, pero se me hizo muy lento y me mostraba cualquier valor.



Subí el código que tenés así lo veo, hacelo usando las etiquetas "
	
	



```

```
" o "
	
	



```

```
" del foro.



marcos0007 dijo:


> ...La verdad solo me queda probar por DMA, pero no debería ser así. Se te ocurre alguna otra cosa.



Si ya venís mal con la conversión normal, usando DMA dudo que mejores en algo.

¿Probaste con otro canal?¿bajaste la velocidad de conversión? ¿cómo es el circuito que usas antes del ADC?


----------



## marcos0007 (Nov 14, 2013)

Probé cambair la velocidad de conversión, lo único que hace es que varía menos, pero también la respuesta ante los cambios es más lenta, o sea que nome sirve.

Vos me preguntas por el circuito anterior, pero va más alla de eso. La última prueba que hice es tomar los 3.3v de la placa y dividirlo por dos con resistencias, de modo de obtener 1.65v (fijo, libre de todo tipo de ruido). Así y todo cuando miro el registro del ADC el valor varía demasiado.

A vos tu código te funciona perfectamente? probé con el p0.23 y el p0.25. Los dos hacen lo mismo.

Gracias


----------



## ilcapo (Nov 14, 2013)

muy buen tutorial cosme ! algun dia voy a continuar el de ARM7 lo abande un poco por falta de tiempo!


----------



## cosmefulanito04 (Nov 14, 2013)

ilcapo dijo:


> muy buen tutorial cosme ! algun dia voy a continuar el de ARM7 lo abande un poco por falta de tiempo!



Gracias.

Este tutorial te puede ser muy útil para ARM7, solo cambian pequeñas cosas como el nombre de los registros, pero básicamente es muy similar.



marcos0007 dijo:


> A vos tu código te funciona perfectamente?



Si, por ej. colocando una batería entre masa y ADC0 (P0.23), usando el código "Sexta Parte - ADC", obtengo una conversión entre 1971 y 1977, con varias muestras (1,587v a 1,592v, menos de 4mV!).

¿Configuraste bien el PLL? ¿configuraste bien el pre-escaler del ADC?

En el ejemplo que subí, el PLL está configurado para trabjar con un CCLK=100MHz, PCLK=25MHz, pre-escaler del ADC 250 dando como resultado una frecuencia de muestreo de 100kHz para tener 12 bits de resolución.


----------



## marcos0007 (Nov 14, 2013)

Cosmefulanito, yo probé el código tal cual lo bajé de tu ejemplo. Las cosas que fui probando fue


```
configura_timer0(25000,1000);    //Pre-escaler 250000 => 25MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg 

    iniciar_adc(0,0);    //Inicio el ADC en el canal 0 sin offset
```

cambiarle el 1000 por 500 para que haga la conversión más rápido, otra cosa que cabié fué probar iniciar el canal 2 en lugar del 0. Nada más

En realidad no necesito 12 bit de resolución con 8 alcanzaría. pero varía demasiado
GRacias


----------



## cosmefulanito04 (Nov 14, 2013)

marcos0007 dijo:


> Cosmefulanito, yo probé el código tal cual lo bajé de tu ejemplo. Las cosas que fui probando fue
> 
> 
> ```
> ...



Cambiar el timer0 no hace ni más rápida ni más lenta la conversión del ADC, sino que generás interrupciones de timer en menor tiempo, es decir c/500mS.

La velocidad de conversión se fija cuando realizas una usando la función "convertir_adc", en mi ejemplo usé:


```
if(convertir_adc(0,250,&valor_adc)>0)    //Convierto en el canal 0, con un pre-escaler=250 
{ 
  ....
}
```

En esa condición el pre-escaler está en 250 => PCLK=25MHz => Fsampling=100kHz.

¿Estás usando bien la variable que pasas por referencia? (en este caso "valor_adc")



marcos0007 dijo:


> En realidad no necesito 12 bit de resolución con 8 alcanzaría. pero varía demasiado



Insisto, no es solo un tema de tener mayor o menor resolución, a mayor velocidad de conversión, mayor será el ruido que te entre en la lectura, por eso como primera medida te recomendé bajar la velocidad.


----------



## markisrodrigo (Nov 14, 2013)

Aprovecho que veo que estáis con DAC para una duda sobre un trabajo que tenía (adjunto el pdf), y es en la actividad 7.2, que he de utilizar la función SYSTICK para que cada 1ms me saque las 64 muestras, y bueno, en general no se por donde empezar... ¿Alguna idea de como realizar el código?


----------



## cosmefulanito04 (Nov 14, 2013)

Se me ocurre que podrías usar un timer c/15us para que vaya muestreando el vector y lo saques por el DAC hasta llegar al índice 65, luego c/1mS sincronizas de nuevo y reinicias el índice.

Para eso, yo configuraría el PCLK del timer en 100MHz, de esa forma aumentas la cuenta en el timer y tenés menor error de cuantización, tal como expliqué en el ejemplo "Segunda Parte - PLL y timers (código)":



			
				cosmefulanito04 dijo:
			
		

> Por otro lado se puede ver que la cantidad de cuentas para generar 1uS es de solo 25, por lo que se obtendrá un error de cuantización de 1/25=0,04 cuentas. Para mejorar ese inconveniente, se podría aumentar el Pclk a 100MHz, logrando así que se necesiten 100 cuentas para obtener 1uS y bajando el error de cuantización a 1/100=0,01 cuentas.


----------



## markisrodrigo (Nov 14, 2013)

Me exigen emplear únicamente el SYSTICK (lo siento estoy un poco verde en estos temas, ¿pero entiendo que PCLK es un periférico, no?) y no se que valor he de dar al registro TENMS de SysTick para conseguir 1ms en lugar de los 10ms que están predeterminados, le pregunté al profesor y me respondió escuetamente que debía emplear una frecuencia de 64KHz pero no entendí bien como emplearla ni como declararla en la función.


----------



## cosmefulanito04 (Nov 14, 2013)

Mucho sentido no le veo usar el systemtick a frecuencia tan alta... pero si te piden eso, hace eso 

La cosa es así, vos tenés 64 muestras que debes muestrear a 1mSeg (1kHz), por lo tanto el tiempo de c/muestra deberá ser de 1mSeg/64, eso te dá casi 15uSeg (los que mencioné arriba, que son esos 64kHz que menciona tu pofesor).

Resumiendo, configurá el System Tick para que lance una interrupción c/15uSeg y en la rutina de interrupción y envia por Dac la muestra (y no voy a subirte el código porque es tu tarea).

Si lo querés hacer perfecto, también deberías tener en cuenta el tiempo de conversión del Dac.


----------



## markisrodrigo (Nov 15, 2013)

¡Vale, genial! Ya veo lo que quería decirme el profesor con esa frecuencia jaja, una última cosilla, ¿Cómo calculo el valor del registro de SYSTICK que realiza la cuenta atrás (TENMS) para que en lugar de los 10ms sea de 15us? ¿He de tantearlo o hay alguna forma de calcularlo? Gracias! 

Entiendo que es lo que me piden y me gusta resolverlo yo, no trato de que me resuelvas el ejercicio aunque entiendo que pudiese parecerlo, pero no nos explican casi nada en clase y luego de leerme el manual de LPC17xx sigo con bastantes dudas... Si te sirve como prueba subo el ejercicio por el que te pregunte el otro día y como lo resolví yo


```
/*----------------------------------------------------------------------------
 * Name:    SYSTICK_interrupt.c
 *----------------------------------------------------------------------------*/

#include "LPC17xx.H"
uint32_t pulsador;  
uint32_t SystemFrequency=100000000;

volatile uint32_t msTicks=1;  // Contadores de Ticks de x ms
uint32_t LED1_18=0;
uint32_t LED1_29=0;
uint32_t contador=0;
uint32_t k=1;


void SysTick_Handler (void)			// Rutina (ISR) de atención...
                                //... a la excepción 15 -del SYSTICK-
{
	if (pulsador==0){		// pulsador pulsado
		if(msTicks==1){
			LED1_29=1;		// enciendo LED P1.29
			LED1_18=0;		// apago LED P1.18
		}
		if(msTicks>=100 && msTicks<200){
			LED1_29=0;					// apago LED P1.29
		  	LED1_18=1;					// enciendo LED P1.18
		  	}
		if(msTicks>=200){
		  	LED1_18=0;					// apago LED P1.18
			msTicks=0;
			}
		}
    else{					// pulsador sin pulsar
		if(msTicks==1){
			LED1_29=1;		// enciendo LED P1.29
			LED1_18=0;		// apago LED P1.18
		}
       	if(msTicks>=200 && msTicks<400){
        	LED1_29=0;					// apago LED P1.29
		  	LED1_18=1;					// enciendo LED P1.18
          	}	
		if(msTicks>=400){
	  		LED1_18=0;					// apago el LED P1.18
			msTicks=0;
			}
    	}
	if(LED1_18==1){
		LPC_GPIO1->FIOPIN &= ~(1<<18);		// enciendo LED P1.18
		LPC_GPIO1->FIOPIN &= ~(1<<29);		// apago LED P1.29
		}
	if(LED1_18==0){
		LPC_GPIO1->FIOPIN |= (1<<18);	// apago LED P1.18
		LPC_GPIO1->FIOPIN |= (1<<29);	// enciendo LED P1.29
		}
	if(msTicks==k*100){
		contador++;
		k++;						// Indicador de que ha llegado a 1 segundo
	}
	msTicks++;	                   		// Incrementar contadores cada 10ms  y cada 1s
}

static __INLINE uint32_t SysTick_Configuracion(uint32_t prio_SysTick)
{
	SysTick->CTRL |= (1<<2);						//Selecciono la fuente de reloj interna
	SysTick->CTRL |= (1<<0);						//Habilito la cuenta de SysTick
	SysTick->CTRL |= (1<<1);						//Habilito su posibilidad de generar interrupción
	SysTick->LOAD = SysTick->CALIB;	//Genera una interrupción de 10ms, los cuales los coge del registro de Calibración
	SysTick->VAL = 0;
	NVIC_SetPriority(SysTick_IRQn, prio_SysTick);	//Establece la prioridad que le llega como parametro

 return(0);
}


int main (void)
{
  LPC_GPIO1->FIODIR |= (1<<18);	  // P1.18 definido como salida
  LPC_GPIO1->FIODIR |= (1<<29);	  // P1.29 definido como salida  
  LPC_GPIO2->FIODIR &= ~(1<<12);  // P2.12 definido como entrada
  SysTick_Configuracion((1<<__NVIC_PRIO_BITS) - 1); 
  
	
  // SystemInit ();                // Inicializar relojes " clocks"
  //SysTick_Config (SystemFrequency/100);    // Configurar  SYSTICK

  while (1) pulsador=((LPC_GPIO2->FIOPIN & (1<<12))>>12);
}
```


----------



## cosmefulanito04 (Nov 15, 2013)

markisrodrigo dijo:
			
		

> ¡Vale, genial! Ya veo lo que quería decirme el profesor con esa frecuencia jaja, una última cosilla, ¿Cómo calculo el valor del registro de SYSTICK que realiza la cuenta atrás (TENMS) para que en lugar de los 10ms sea de 15us? ¿He de tantearlo o hay alguna forma de calcularlo? Gracias!



Fijate que en el mensaje _#56_ que te deje el método para calcular la cuenta y que registro tenés que modificar, más detalles fijate en la hoja de datos.

Como evidentemente tu tiempo no será en mSeg, sino que en uSeg, en vez de 1000, deberías usar 1000000 en la división:

[LATEX]Cuenta=\frac{TuSeg}{1000000}.Clock-1[/LATEX]

Ejemplo, 100uS con clock de 100MHz:

[LATEX]Cuenta=\frac{100}{1000000}.100000000-1=9999[/LATEX]



			
				markisrodrigo dijo:
			
		

> ...pero no nos explican casi nada en clase y luego de leerme el manual de LPC17xx sigo con bastantes dudas...



Entiendo, te dan mucha teoría y poca práctica.


----------



## markisrodrigo (Nov 15, 2013)

¡No me acordé de ese mensaje! Muchísimas gracias, con todo esto ya tengo claro como continuar. Te estoy realmente agradecido cosme.


----------



## marcos0007 (Nov 19, 2013)

Cosmefulanito, 
te hago una consulta tengo que convertir un uint32_t en uint8_t, como primer medida tomé tu función obviamente no me funcionó


```
void convertir_digitos_u32_serie(uint8_t digitos[],uint32_t value)
{	  
	  uint8_t aux;
    
    for(aux=4;aux>0;aux--)
        {
        digitos[aux]=(value%10)+0x30;
        value=(int)(value/10);
        }
        
    digitos[0]=value+0x30;

}
```

Después probe directamente así: 

```
UINT32 value;
UINT8 result[4];

result[0] = (value & 0x000000ff);
result[1] = (value & 0x0000ff00) >> 8;
result[2] = (value & 0x00ff0000) >> 16;
result[3] = (value & 0xff000000) >> 24;
```

tampoco funcionó, es para presentar el dato en pantalla anteriormente ya había tenido que pasar de uint16 a uint8, pero eso funcionó correctamente.

Gracias como siempre


----------



## cosmefulanito04 (Nov 20, 2013)

La función no pasa de u32 a u8  .

La función pasa de u32 a String, es decir un vector u8 (o char según corresponda). Ahora, no recuerdo pasar u32, si u16, por lo tanto de esa función hay que corregir la cantidad de dígitos:


```
void convertir_digitos_u32_serie(uint8_t digitos[],uint32_t value)
{	  
	  uint8_t aux;
    
    for(aux=9;aux>0;aux--)
        {
        digitos[aux]=(value%10)+0x30;
        value=(int)(value/10);
        }
        
    digitos[0]=value+0x30;

}
```

Entonces ahora el vector "digitos" deberá ser de 10 elementos.

Ejemplo:


```
u8 vector_digitos[10];

convertir_digitos_u32_serie(vector_digitos,32000);

for(cont=10;cont>0;cont--)
  envia_dato_uart0(vector_digitos[cont-1]);
```

Luego de convertir el u32 a string, por el puerto serie saldrán los siguientes caracteres:



			
				Salida uart dijo:
			
		

> '0'-'0'-'0'-'0'-'0'-'3'-'2'-'0'-'0'-'0'



Alternativa Ansi-C => sprintf.


----------



## marcos0007 (Nov 28, 2013)

Cosmefulanito, consulta, vos pudiste probar multiples canales de ADC en simultáneo?
Estoy armando un código que trabaje en modo DMA y otro en modo Burst, para ver si puedo eliminar la variación que tengo en la medida. pero cuando compilo acusa este error
ERROR: L6218E: UNDEFINED SYMBOL. Antes ya me pasó y era un tema de librerías pero ahora no lo puedo solucionar.

Gracias


----------



## cosmefulanito04 (Nov 28, 2013)

No probé, solo usé un canal en las pruebas.

Tené en cuenta que al trabajar con un multiplexo analógico, solo podes convertir un canal a la vez e incluso en muchos uC c/vez que cambias de canal, necesitas hacer una conversión de descarte.


----------



## marcos0007 (Nov 29, 2013)

Estuve leyendo que en el modo Burst, convierte todos los canales a la vez y así podes obtener las mediciones de las entradas a medir, sin tener que multiplexar. Lo estoy probando, ni bien salga, comento.


----------



## marcos0007 (Nov 30, 2013)

Cosmefulanito una consultoa, etuve volviendo a tu ejemplo ADC-Parte 6, modifiqué la parte de conversión porque quería mostrar un promedio del dato para que varíe menos. Esto me quedó


```
case MOSTRAR_CONVERSION_ADC:
					{
						if(convertir_adc(0,125,&valor_adc)>0)	//Convierto en el canal 0, con un pre-escaler=125
						{	valor_adc=valor_adc*0.805860; 
							contador=contador + 1;
						    valoradc=(valor_adc + valoradc)/contador;
							
							if (contador==8)
								{ enviar_string_uart0(" Conversion= \n");
								 envia_u16_string_uart0(valoradc);
								 contador=0;
								}
							valor_adc=0;
						}
					}
```

El valo 0.805860 es para que cuando le conecte 3300mv me muestre 3300 en lugar de 4095. el tema está en que ahora que le hice el promediado, el valor queda casi constante con una pila de 1.5v. Pero el valor que me muestra es 235. No entiendo porque jaja. No se si se está perdiendo la cuenta o si estoy calculando mientras convierte. Está bien lo que estoy planteando?

gracias


----------



## cosmefulanito04 (Nov 30, 2013)

Necesitas usar un vector de por ej. 16 elementos y hacer una cola circular para un promedio.

Después te subo algo que te puede ser útil.


----------



## marcos0007 (Nov 30, 2013)

gracias capo!!

Estoy "debbugueando" para ver porque me muestre ese valor tan errado.


----------



## cosmefulanito04 (Dic 1, 2013)

marcos0007 dijo:


> gracias capo!!
> 
> Estoy "debbugueando" para ver porque me muestre ese valor tan errado.



Si no me equivoco le erras en dos cosas:


 La variable contador no la limitas, esto implicará desborde en algún punto.
 El promedio siempre lo haces con dos valores, el actual y la sumatoria ya promediada con los valores viejos, por lo tanto en vez de dividir por la variable contador, deberías dividir por 2.

Lo que deberías hacer es un promedio móvil, ejemplo:


```
#define MUESTRAS_MAX 16

int main()
{
  ...
  //--------- Vectores de muestreo --------------//
  u16 muestra_adc[MUESTRAS_MAX];
  u8 indice_muestra_adc=0;
  
  u32 promedio_movil=0;
  //--------- Vectores de muestreo --------------//
  ....
  iniciar_adc(0,0);    //Inicio el ADC en el canal 0 sin offset
  // Inicializo timers, puerto serie, etc
  ....
 
  while(1)
    {
      
      if(flag_timer0) //Configuro el timer0 (o cualquier timer disponible), para que c/cierto tiempo tome una muestra del ADC.
        {
           if(convertir_adc(0,250,&valor_adc)>0)    //Convierto en el canal 0, con un pre-escaler=250 
            { 
               muestra_adc[indice_muestra_adc]=valor_adc;
               indice_muestra_adc++;
               if(indice_muestra_adc==MUESTRAS_MAX)
                 indice_muestra_adc=0;
            } 
         }
        
         if(flag_timer1) //Configuro el timer1 (o cualquier timer disponible), para que c/cierto tiempo envíe el promedio móvil por uart (ej. c/1 seg).
           {
              promediar_datos(muestra_adc,&promedio_movil); //Funcion que promedia las muestras
              enviar_string_uart0(" Conversion promediada= \n"); 
              envia_u16_string_uart0((u16)(promedio_movil));
           }
        
    }
}
```

La función promedio móvil es simplemento un for:


```
void promediar_datos(u16 muestras[],unsigned long int *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);
}
```


----------



## marcos0007 (Dic 1, 2013)

la variable contador, la estoy limitando a 8, osea pregunto si contador es igual a 8 y si es igual, muestro el dato y después pongo el contador en 0.

Voy a probar la función que me pasaste vos. Che te hago una pregunta, viste que te pregunte si habías probado varios canales de ADC en simultáneo? Yo estuve probando pero me pasa algo raro, se me copia el valor del primer canal. Debería parar y arrancar el canal de ADC una vez que termine de convertir?

Gracias


----------



## cosmefulanito04 (Dic 1, 2013)

marcos0007 dijo:


> la variable contador, la estoy limitando a 8, osea pregunto si contador es igual a 8 y si es igual, muestro el dato y después pongo el contador en 0.



En el fragmento de código que subiste no se vió, pero seguís teniendo problemas con la división.



marcos0007 dijo:


> Che te hago una pregunta, viste que te pregunte si habías probado varios canales de ADC en simultáneo? Yo estuve probando pero me pasa algo raro, se me copia el valor del primer canal. Debería parar y arrancar el canal de ADC una vez que termine de convertir?
> 
> Gracias



Como te dije ante (prestá más atención a los mensajes!), en la mayoría de los uC, hay que hacer una conversión de descarte c/vez que cambias de canal, ya que de lo contrario tomás el valor del canal anterior.


----------



## marcos0007 (Dic 1, 2013)

Leí el mensaje, diculpa que hago preguntas obvias, pero soy nuevo en esto. Agradezco mucho tu ayuda.



Estuve probando el código, pero me da error en la función promediar_datos


```
if(flag_timer1) //Configuro el timer1 (o cualquier timer disponible), para que c/cierto tiempo envíe el promedio móvil por uart (ej. c/1 seg). 
           { 
              promediar_datos(muestra_adc,&promedio_movil); //Funcion que promedia las muestras 
              convertir_digitos_u16_serie(peso,promedio_movil);
			  GUI_Text(90,144,"PESO",White,Red);
			  GUI_Text(90,160,peso,White,Red);
			 // enviar_string_uart0(" Conversion promediada= \n");  
             // envia_u16_string_uart0((u16)(promedio_movil)); 
           }
```

..\USER\main.c(369): error:  #159: declaration is incompatible with previous "promediar_datos" (declared at line 182)


```
void promediar_datos(u16 muestras[],unsigned long int *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); 
}
```


----------



## cosmefulanito04 (Dic 1, 2013)

Seguro que el error está en la declaración de la variable de tipo "unsigned long int" por u32, usá un typedef o directamente reemplazá u32.

Fijate que también tengas las definiciones de u8 y u16.


----------



## segismund (Dic 9, 2013)

se les ocurre alguna forma de usar el LPC1768 para medir la frecuencia de una señal analógica?

Pense en usar el ADC y contar las veces que pasa el offset que hay que ponerle a la señal (para que no tenga valores negativos) en un tiempo, pero no se si sera muy correcto...


----------



## aah (Dic 27, 2013)

hola
podrian ayudarme a conseguir un pitido intermitente a una frecuencia de 900Hz. primero tiene que ser fijo y luego pasar a ser intermitente, se que tengo que utilizar un DAC pero no se como.
muchas gracias


----------



## cosmefulanito04 (Dic 27, 2013)

No necesitas usar un DAC para generar un pitido, usá simplemente una señal digital.

Ahora, no esperes que resolvamos tu problema sin siquiera subir una línea de código.


----------



## aah (Ene 2, 2014)

tiene que dar una salida analogica


```
#include "LPC17xx.H"
#define DAC_BIAS 0x00010000
uint32_t f=900; //900Hz
 void DACInit(void);
int main(void)
{
	
	/*inicializar  DAC*/
	DACInit();
		while(1){
			
		LPC_DAC->DACR=(f<<6) | DAC_BIAS;
		
	}
}

	 void DACInit(void)
{
	
	LPC_PINCON->PINSEL1=0x00200000; // p0.26 como DAC salida
	return;
}
```

este es el codigo que he utilizado para el pitido fijo pero no funciona, soy nueva en esto y no tengo ni idea de como hacerlo


----------



## cosmefulanito04 (Ene 2, 2014)

A ver, insisto que no es necesario usar el DAC para generar un pitido, alcanza usando una señal digital en forma alternada a la frecuencia que buscas para generar el sonido buscado.

Dicho esto, volviendo al tema del DAC, un DAC es un periférico que solo se encarga de tirar a la salida el nivel de tensión que vos le indicaste y su rango de funcionamiento irá de 0 a 3,3v. 

El problema conceptual que tenés, es que vos nunca actualizas el valor del DAC, para generar una señal por ej. senoidal vas a tener que ir tirando los niveles de tensión que forman una senoidal en un determinado tiempo.

Por ej. para que entiendas mejor, suponé una senoidal de 1kHz que solo la vas a muestrear en 10 pts, eso implica que necesitas actualizar el valor del DAC cada 1mS/10=100uS, por lo tanto además de usar un DAC vas a necesitas un timer.

Te recomiendo que leas la Séptima Parte - DAC del tutorial.


----------



## aah (Ene 3, 2014)

muchas gracias. sigo queriendo utilizar el DAC asi que seguire probando


----------



## cosmefulanito04 (Ene 3, 2014)

aah dijo:


> muchas gracias. sigo queriendo utilizar el DAC asi que seguire probando



Te recomiendo que leas el "Bonus track" del tutorial, te va dar una idea de como usar el DAC para generar una señal.


----------



## segismund (Ene 7, 2014)

¿cómo de preciso son las interrupciones externas en cuanto a la subida/bajada de flanco? Quiero decir... saltarían con una señal analógica senoidal de 0.2v de amplitud y 50hz de frecuencia por ejemplo?


----------



## cosmefulanito04 (Ene 7, 2014)

Te tiro una ayuda.

Entonces leyendo esa hoja, ¿se puede hacer lo que decís?


----------



## segismund (Ene 10, 2014)

cosmefulanito04 dijo:


> Te tiro una ayuda.
> 
> Entonces leyendo esa hoja, ¿se puede hacer lo que decís?


No vi nada en esa hoja. Lo lleve a la practica y no me funcionó.

Mirando el ADC, hay una cosa que no entiendo.
¿para qué sirve configurar el clock?

Entiendo que sirve para que el ADC esté convirtiendo continuamente con una frecuencia debida al clock, pero en las simulaciones no parece que sea así, sino que necesita de un Timer para que cada X segundos convierta (puesto que se ha activado de nuevo con LPC_ADC->ADCR=(1<<ADC_START); en el Timer)

si no es así, cómo es?

Mi propósito es muestrear una señal a 100kHz conectada al ADC, por tanto, el ADC debe estar continuamente convirtiendo a 100kHz y guardando los valores.

Gracias


----------



## cosmefulanito04 (Ene 11, 2014)

segismund dijo:


> No vi nada en esa hoja. Lo lleve a la practica y no me funcionó.



Si te metés en la sección "Static Characteristics", vas a encontrar un cuadro acerca de las características eléctricas de los pines en general donde menciona:

Vih-min=0,7.Vdd
Vil-max=0,3.Vdd
Vhys=0,4.Vdd (está mal en la hoja)

Por lo tanto si Vdd=3,3v:

Vih-min=2,31v
Vil-max=0,99v
Vhys=1,32v

Esto quiere decir que para detectar un nivel como alto, este deberá ser mayor a 2,31v, para detectar un nivel bajo la tensión deberá ser menor a 0,99v. 

En el medio hay una banda de tensiones que están fuera de esos límites y como los puerto no tienen un schmitt interno, en esos valores la entrada puede valer cualquier cosa.

En tu caso, como la señal es de 0,2v => no superás esa banda de incertidumbre y por lo tanto es imposible que funcione, incluso tampoco superás la tensión máxima de 0,99v de un nivel bajo. Por otro lado tenés el problema de que al ser una senoidal, tu paso por la zona "prohibida" es lento y puede provocar falsos disparos.

¿Qué haría yo?

1) Amplifico esa señal a niveles más comodos.
2) Armo un schmitt trigger con operacionales o utilizo integrados ya armados para esta función que sean compatibles con los nuevos niveles de tensión que vas a tener.

Tené en cuenta que tal vez necesites agregar un offset (una continua) a esa senoidal.



segismund dijo:


> Mirando el ADC, hay una cosa que no entiendo.
> ¿para qué sirve configurar el clock?
> 
> Entiendo que sirve para que el ADC esté convirtiendo continuamente con una frecuencia debida al clock, pero en las simulaciones no parece que sea así, sino que necesita de un Timer para que cada X segundos convierta (puesto que se ha activado de nuevo con LPC_ADC->ADCR=(1<<ADC_START); en el Timer)
> ...



El clock interno del ADC sirve para fijar la velocidad máxima con la que trabajará el ADC. Por ej. si configurás al ADC en 100kHz, sabés que una conversión te demorará 1/100kHz=10uS, de ese tiempo no podés escapar, siempre que conviertás, como mínimo tardás 10uS entre conversión y conversión.

Entonces tenés dos formas de usarlo:

- Disparo continuo.
- Disparo único.

Disparo continuo, el ADC está a full convirtiendo sin que nadie le diga nada y c/vez que termina con una interrupción deberías leer el dato de conversión. La frecuencia de conversión estará dada por el clock interno del ADC.

En cambio el disparo único sirve para hacer conversiones en forma esporádica, no necesariamente en forma periódica. Pero si se desea hacer en forma periódica se lo puede utilizar en conjunto con un timer, de esta forma (según yo lo veo) vas a tener un mayor control sobre los tiempo.

Si en tu aplicación tenés que muestrear en forma periódica a 100kHz, configuralo en forma continua a esa frecuencia y en la rutina de interrupción almacená el nuevo valor de conversión.


----------



## elgarbe (Mar 14, 2014)

Cosme, excelente tutorial!!!!! 
Has probado algo con DMA?
Estoy con una aplicacion (ver topic 42254.0 del foro todopic ya que no puedo postear enlaces) en la que necesito sacar datos de forma serie muy rápido. Para ello eso el SPP0 como SPI y uso solo el MOSI. Eso ya lo tengo funcionando, pero quiero optimizar el código usando DMA.
Yo configuro como disparador del DMA un Match register de un timer. Luego configuro como source un array de char, como dest el registro DR del SSP0, la cantidad de bytes, etc. etc. Pongo el analizador logico en MOSI y veo que los datos salen perfecto. Pero salen en el primer Match del Timer con el MR0... la duda es, que debo hacer luego de que salen por primera vez los datos para que vuelvan a salir en el próximo Match?

Saludos!


----------



## cosmefulanito04 (Mar 16, 2014)

La verdad que no hice nada con DMA 

Estaba viendo algo de ethernet con lwip.


----------



## kakitron (Mar 28, 2014)

segismund dijo:


> se les ocurre alguna forma de usar el LPC1768 para medir la frecuencia de una señal analógica?
> 
> Pense en usar el ADC y contar las veces que pasa el offset que hay que ponerle a la señal (para que no tenga valores negativos) en un tiempo, pero no se si sera muy correcto...



Puedes hacerlo utilizando los timers en modo captura, por ejemplo capturando dos flancos de subida consecutivos en una señal cuadrada.



Me encanta este tutorial Cosme, me es muy útil. Por casualidad, has trabajado con la pantalla táctil que se puede incorporar a la tarjeta? Estuve cacharreando con ella y intentando mostrar datos capturados del ADC, no encuentro una función o una manera de conseguir visualizar en ella las adquisiciones :/
Me mire el manual de usuario y las diferentes librerías pero nada 
Alguno sabéis como podría hacerlo?


----------



## cosmefulanito04 (Mar 28, 2014)

kakitron dijo:


> Me encanta este tutorial Cosme, me es muy útil. Por casualidad, has trabajado con la pantalla táctil que se puede incorporar a la tarjeta? Estuve cacharreando con ella y intentando mostrar datos capturados del ADC, no encuentro una función o una manera de conseguir visualizar en ella las adquisiciones :/
> Me mire el manual de usuario y las diferentes librerías pero nada
> Alguno sabéis como podría hacerlo?



Gracias.

Lamentablemente cuando compré el kit, no compré la pantalla, ahora me arrepiento un poco de eso, pero bue... .


----------



## marcos0007 (Abr 24, 2014)

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.
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í?

Luego configuras el adc:

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

Aca los estarías configurando a 100KHz?

Gracias por las aclaraciones
Saludos


----------



## cosmefulanito04 (Abr 24, 2014)

marcos0007 dijo:


> 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.



marcos0007 dijo:


> 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



marcos0007 dijo:


> 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.


----------



## marcos0007 (Abr 24, 2014)

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


----------



## cosmefulanito04 (Abr 24, 2014)

marcos0007 dijo:


> 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.



marcos0007 dijo:


> 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.



marcos0007 dijo:


> 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.


----------



## marcos0007 (May 4, 2014)

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. 
	
	



```
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


----------



## cosmefulanito04 (May 4, 2014)

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:*


```
#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:*


```
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.


----------



## marcos0007 (May 5, 2014)

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


----------



## cosmefulanito04 (May 5, 2014)

Es verdad, subí esta misma rutina en el mensaje #80 (me había olvidado ).

¿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:


```
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:


```
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.


----------



## marcos0007 (May 6, 2014)

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


----------



## cosmefulanito04 (May 6, 2014)

marcos0007 dijo:


> 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.



marcos0007 dijo:


> 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 .  

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



marcos0007 dijo:


> 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.



marcos0007 dijo:


> 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.


----------



## marcos0007 (Jun 5, 2014)

Cosmefulanito, te hago una pregunta, quise implementar el siguiente código y solo me toma el valor del canal 1


```
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


----------



## cosmefulanito04 (Jun 6, 2014)

No tengo idea de donde salieron esas funciones.

Sobre usar el ADC configurando los pines como salida...  , hay que estudiar un poco más el tema.


----------



## marcos0007 (Jun 7, 2014)

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


----------



## cosmefulanito04 (Jun 8, 2014)

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.


----------



## marcos0007 (Jun 8, 2014)

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


----------



## cosmefulanito04 (Jun 8, 2014)

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.


----------



## sergio1994 (Nov 2, 2014)

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)


----------



## cosmefulanito04 (Nov 2, 2014)

Te falto agregar la variable al principio, pero sería una cosa así:



sergio1994 dijo:


> *variable*|= 1<< (10*2)




```
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)*



sergio1994 dijo:


> *variable*&=~(3<<20)




```
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


----------



## LudCa (Dic 1, 2014)

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?


----------



## Ardogan (Dic 2, 2014)

LudCa dijo:


> Hola cosmefulanito04!!
> ...
> uint64_t DATO;
> uint8_t dato_RH;
> ...



¿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);


----------



## LudCa (Dic 2, 2014)

Ardogan dijo:


> ¿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.


----------



## Ardogan (Dic 2, 2014)

Bien!!!, entonces no era lo que yo decía sino lo que indicaste, el manejo del bit de máscara.


----------



## cosmefulanito04 (Dic 2, 2014)

Tarde, pero bue, más vale tarde que nunca. 

No sé bien como recibís esos datos, tal vez conviene tirarlo directamente a un vector ni bien te llegan, pero bueno ese no era el tema, otra opción al desplazamiento es usar "uniones" que son variables especiales a medida del usuario con el fin de compartir espacio de memoria con distintos tipos de variables estandar, ejemplo para tu caso:



```
#include <stdio.h>
#include <stdlib.h>

union CadenaBits
{
    unsigned long long dato; //Variable de 64 bits
    unsigned char rec_datos[8]; //Vector de 8 bytes
};

int main()
{
    union CadenaBits campo;
    unsigned int cont;
    campo.dato=0x1122334455667788; //8 bytes: 0x11 - 0x22 - 0x33 - 0x44 - 0x55 - 0x66 - 0x77 - 0x88

    for(cont=0;cont<8;cont++)
    {
        printf("Byte %d= 0x%x\n",cont+1,campo.rec_datos[cont]);
    }
    return 0;
}
```

Como se vé en el código, creo una unión, donde el peso en memoria será de 64bits (la variable más grande que tiene la unión) y donde el vector de 8 bytes compartirá ese mismo espacio de memoria.

El resultado en consola de ese código es el siguiente:



			
				resultado dijo:
			
		

> Byte 1= 0x88
> Byte 2= 0x77
> Byte 3= 0x66
> Byte 4= 0x55
> ...



El ordenamiento que se obtiene es el de un little endian (lo ejecuté en PC), pero es lo mismo que obtuviste vos con ese desplazamiento.

Una tercera opción (y creo que la más fácil) es usar punteros:


```
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int cont;
    unsigned long long int dato=0x1122334455667788;
    void *ptr=&dato; //El puntero tomará la dirección en memoria de la variable de 64 bits
    for(cont=0;cont<8;cont++)
    {
        printf("Byte %d= 0x%x\n",cont+1,*(unsigned char*)(ptr+cont)); //Simplemente uso esa dirección de memoria como si fuera un vector
    }

    return 0;
}
```

El resultado es el mismo.


----------



## Ardogan (Dic 2, 2014)

Ya que estamos te pregunto cosme. La solución que proponés ¿funciona independientemente de que el {procesador + compilador} guarde los datos como little endian o big endian?.


----------



## cosmefulanito04 (Dic 2, 2014)

Ardogan dijo:


> Ya que estamos te pregunto cosme. La solución que proponés ¿funciona independientemente de que el {procesador + compilador} guarde los datos como little endian o big endian?.



Es igual, salvo que los bytes te vienen dados vuelta, es decir:



			
				resultado dijo:
			
		

> Byte 1= 0x11
> Byte 2= 0x22
> Byte 3= 0x33
> Byte 4= 0x44
> ...



Esto mismo ocurre con cualquiera de los 3 métodos que uses.


----------



## sergio1994 (Dic 14, 2014)

Cuando sale un 1 logico por un pin, salen 3,3 voltios o 5 voltios por el pin¿?


----------



## cosmefulanito04 (Dic 17, 2014)

sergio1994 dijo:


> Cuando sale un 1 logico por un pin, salen 3,3 voltios o 5 voltios por el pin¿?



¿Leíste la hoja de datos? Te recomiendo que empieces por ahí.


----------



## javileoni (Dic 18, 2014)

Buen post sobre esta placa. Tengo un pequeño problema con del ADC. En la práctica que tengo que realizar, el sistema dispone de un detector de vehículos prioritarios VP (ambulancia, policía y bomberos) que detecta las señales sonoras de estos y envía un pulso de 20 ms al sistema de control del semáforo, para que detenga la secuencia anterior y ponga ambos semáforos con luz roja durante 30 segundos. Durante estos 30 s se debe emitr una señal sonora de 1 kHz.
La señal sonora la hago con el DAC y hasta ahí bien, pero no se como utilizar el ADC para que me detecte los vehiculos prioritarios.
Gracias por vuestro tiempo!


----------



## Fogonazo (Dic 18, 2014)

Compara el valor leído por el ADC con un valor establecido.
Si es *>* es un vehículo prioritario.
Si es *<=* es un vehículo común


Agregar una "Ventana" de tiempo de activación sería bueno


----------



## javileoni (Dic 18, 2014)

Gracias por la respuesta. Tengo una señal cuadrada de 1KHz guardada en un array, pero lo que no llego a entender es correctamente el funcionamiento del ADC. Me he leído el manual de la LPC1768 mil veces y sigo sin pillarlo... 
¿Podrías ponerme un ejemplo de como sería el código para que cuando el ADC sea igual a lo guardado en el array, me salte la interrupción del ADC?


----------



## cosmefulanito04 (Dic 21, 2014)

La idea sería tomar muchas muestras para luego comparar las señales. 

Los pasos que yo haría son:

1- En papel (o cualquier herramienta de análisis), averiguo las componentes en frecuencia que tiene la señal patrón a comparar. En base a este análisis, sabés cual debería ser la frecuencia de muestro del ADC. 

2- Fijás la frecuencia de muestreo en el ADC. Por ej. en una señal cuadrada, tenés infinitos armónicos, hasta 5 armónicos, más o menos obtenés la forma de la señal, pero no así sus flancos, es cuestión de ver exáctamente lo que buscás de esa señal, entonces al menos el ADC para tener la forma de la señal debería muestrear a 10kHz, como el ADC del ARM te permite muestrear hasta 200kHz en 12bits (y si no me equivoco hasta 1MHz en 8 bits llega, es cuestión de ver la hoja de datos), por lo tanto incluso lo podés configurar en 200kHz y estarías tomando hasta 100 armónicos (prácticamente no perdés información).

3- Tratá de usar algún trigger que fije un nivel para que la señal se muestree en una ventana parecida a tu vector patrón.

4- Convertí.

5- Compará.

Ahora volviendo un poco a la tierra, si la señal que intentás medir es una cuadrada, tal vez usando un puerto digital y comparando los tiempos te alcance (de esta forma te evitás el uso del ADC), pero es cuestión de ver que parte de la señal buscás.


----------



## ciernes (Feb 2, 2015)

Fantástico tutorial. Todo muy bien explicado tan solo me a quedado una duda cuando se defines los pines con PINSEL entiendo esto:

```
#define PINSEL_TX_UART1	(1<<4)	//Bit 5:4 PINSEL0 -> Funcionando como /TXD0
#define PINSEL_RX_UART1	(1<<6)	//Bit 7:6 PINSEL0 -> Funcionando como /RXD0
```

son los bits 5:4 y 7:6 pero lo siguiente no lo entiendo:


```
LPC_PINCON->PINSEL1&=~((3<<6)|(3<<4));
```
 

y esto:


```
LPC_PINCON->PINMODE1&=~((3<<6)|(3<<4));
```



3<<6 y 3<<4  ese 3 de que es? no seria 1<<6 y 1<<4?

te agradecería que me aclarases esa duda muchas gracias


----------



## cosmefulanito04 (Feb 3, 2015)

Pensá que c/PIN puede tener hasta 4 modos, por eso los PINSEL los tenés que tomar de a 2 bits, es decir 2 bits definen como se comportará el PIN (o puerto).

Por ej:


```
#define PINSEL_TX_UART1	(1<<4)
```

Ese (1<<4) significá 1 desplazado 4 veces a la izquierda, por lo tanto de esto en binario:

(0b000001<<4) = 0b010000

Esos dos bits altos "01" son los que definen el funcionamiento del puerto como una Uart Tx.


```
LPC_PINCON->PINSEL1&=~((3<<6)|(3<<4));
```

(3<<4)= (0b000011<<4)= 0b00110000
(3<<6)= (0b000000011<<6)= 0b11000000

Hacer la OR (|) dá como resultado:

(3<<6)|(3<<4)= (0b11000000)|(0b0000110000 )= 0b11110000

Hacer la negación (~) dá como resultado:

~((3<<6)|(3<<4))= ~(0b000....11110000)= 0b11.......00001111 (número de 32 bits)

Hacer la AND (&) dá como resultado poner en 0 los bits del registro "PINSEL1" que fijamos previamente.

Es una cuestión de usar máscaras.


----------



## djmoncada (Jun 1, 2015)

Buenas noches, recien he comprado la tarjeta, en super chino, pero tiene cargado un sistema operativo llamado micrium, como hago para quitarle este sistema y poder ejecutar algun programa?, cuando trato de correr el load del keil me arroja el mensaje de * JLink Info: Core is locked-up!


----------



## Karimifni (Dic 4, 2015)

Buenos días, tengo una duda sobre interrupciones, necesito configurar dos pines como interrupciones a parte de los cuatro interrupciones que tenemos Eint (0,1,2,3) , se que cualquier pin en los puertos 0 y 2 se puede configurar como interrupción pero va a estar relacionada con Eint 3, y no sé cómo hacerlos porque en mi práctica necesito 6 interrupciones, 4 los tengo pero necesito configurar 2 más y no sé cómo hacerlo, me puede explicar cómo se hace esta configuración o un ejemplo?
Muchas gracias


----------



## cosmefulanito04 (Dic 5, 2015)

Lo que vas a tener que hacer, es en la rutina de interrupción preguntar por el puerto que cambio de estado y en base a eso, usar un flag o una variable de estado, ejemplo así en el aire:

En la rutina de interrupción:


```
void EINT1_IRQHandler (void)   
{  
    if(puerto...tal 1) //O usando un switch!
    {
        estado_interrupcion_1=1;  
    }
    else if(puerto...tal 2)
    {
        estado_interrupcion_1=2;
    }
      
    LPC_SC->EXTINT|=(1<<1);    // Limpio la interrupción externa 1 
}
```

En el main:


```
int main()  
{  
    ....//inicialización 

    while(1) //Bucle principal
    {
         switch(estado_interrupcion_1)
         {
              case 0:{break;} //Sin interrupcion
              case 1:
              {
                 //Acción al detectar puerto 1
                 break;
              } //Se detectó interrupción del puerto 1
              case 2:
              {
                 //Acción al detectar puerto 2
                 break;
              } //Se detectó interrupción del puerto 2
              ....//etc
         }

         __wfi();    //Sleep-Mode
    }
}
```

Incluso para mejorar la detección, convendría usar máscaras para detectar varias interrupciones al mismo tiempo.


----------



## jcgr24 (May 27, 2016)

Hla a todos!
un pregunta, es posible hacer una señal senoidal con 4 valores que incluyan negativos, entre 511 0 y -512?

Mucha Gracias

Juan C.


----------



## samkp (May 27, 2016)

jcgr24 dijo:


> Hla a todos!
> un pregunta, es posible hacer una señal senoidal con 4 valores que incluyan negativos, entre 511 0 y -512?
> 
> Mucha Gracias
> ...


Directo desde la lpc, no. La placa se alimenta con 3.3v, por lo que generar un voltaje negativo es imposible, desde la placa.
Podes usar un amp operacional configurado como sumador, le pones -1.65v para bajar la tensión, y la salida de la lpc haces que el cerro este en 511 (1.65v).
Claro, para todo esto también vas a necesitar una fuente de tensión negativa.


----------



## cosmefulanito04 (May 28, 2016)

jcgr24 dijo:


> Hla a todos!
> un pregunta, es posible hacer una señal senoidal con 4 valores que incluyan negativos, entre 511 0 y -512?
> 
> Mucha Gracias
> ...



El DAC es de 10 bits, por lo tanto tenés 1024 niveles. Si realizas una senoidal, simplemente vas a tener que colocar un capacitor en serie para eliminar el nivel de continua y ya.


----------



## eduy2k (May 29, 2016)

Hola cosmefulanito04 y todos del foro mi nombre es Eduardo y disculpen si saco un poco de tema el hilo, estoy leyendo este tutorial  ya que anteriormente incursionaba en Pics con lenguaje C y el compilador de CCS y quiero introducirme al mundo de los ARM por ello me compre una placa que traje de china la LANDTIGER que viene con un LPC1768 que es muy similar a la de las fotos con mas accesorios y por eso me pareció perfecto comenzar con este tutorial.
Por otro lado comento que estoy trabajando con el Keil 5 y al colocar el segundo ejemplo de tu tutorial para programar los pll me tira errores por todos lados, en el configuración_pll , periféricos , interrupciones y en delay , no se si sera un problema de configuración del keil o que otra cosa sera, así que si tienen una idea como solucionarlo para seguir adelante estaré muy agradecido.
Por otro lado agradecería que me sugieran algo para leer al respecto ya que estoy medio complicado con este tema,  veo hay muchas cosas dando vuelta y no se por donde empezar, vi un poco de todo como uo/cos-iii, el mbed y el cmsis que creo que este ultimo es el mas viable así que agradecería cualquier ayuda.
Gracias.

Eduardo


----------



## samkp (May 29, 2016)

cosmefulanito04 dijo:


> El DAC es de 10 bits, por lo tanto tenés 1024 niveles. Si realizas una senoidal, simplemente vas a tener que colocar un capacitor en serie para eliminar el nivel de continua y ya.


Si, toda la razón. Le di muchas vueltas al problema... [emoji17]


----------



## cosmefulanito04 (Jun 1, 2016)

eduy2k dijo:


> Hola cosmefulanito04 y todos del foro mi nombre es Eduardo y disculpen si saco un poco de tema el hilo, estoy leyendo este tutorial  ya que anteriormente incursionaba en Pics con lenguaje C y el compilador de CCS y quiero introducirme al mundo de los ARM por ello me compre una placa que traje de china la LANDTIGER que viene con un LPC1768 que es muy similar a la de las fotos con mas accesorios y por eso me pareció perfecto comenzar con este tutorial.
> Por otro lado comento que estoy trabajando con el Keil 5 y al colocar el segundo ejemplo de tu tutorial para programar los pll me tira errores por todos lados, en el configuración_pll , periféricos , interrupciones y en delay , no se si sera un problema de configuración del keil o que otra cosa sera, así que si tienen una idea como solucionarlo para seguir adelante estaré muy agradecido.
> Por otro lado agradecería que me sugieran algo para leer al respecto ya que estoy medio complicado con este tema,  veo hay muchas cosas dando vuelta y no se por donde empezar, vi un poco de todo como uo/cos-iii, el mbed y el cmsis que creo que este ultimo es el mas viable así que agradecería cualquier ayuda.
> Gracias.
> ...



Es probable que te esté faltando las definiciones de los registros de la familia LPC, ya que usas Keil 5, fijate si del proyecto que subí, el archivo que hace referencia a la familia lo podés encontrar en esa versión del Keil.

Por cierto, si podés, subí el informe de error que te tira el compilador.


----------



## Adidi (Jul 2, 2017)

Genial aporte muchas gracias. Respecto a las imágenes que subes con los esquemáticos y demás no me permite verlas, puede ser porque el post esté obsoleto? Hay algún en ese caso algún post más nuevo sobre el mismo tema?

Un saludo


----------



## cosmefulanito04 (Jul 2, 2017)

Lamentablemente cuando creé el thread, no tuve mejor idea que subir las fotos a un servidor externo que cambió sus políticas de uso y esas imágenes se perdieron.


----------



## Miembro eliminado 356005 (Jul 3, 2017)

Este tipo de cosas deberían ir en la parte del Wiki, ¿no? No es lo mismo un hilo de discusión que un curso. Y además, estaría garantizada la supervivencia de las fotos y archivos, supongo.

El enlace al Wiki está en la parte superior derecha, en todas las páginas, en el enlace Witronica.
Bienvenido al wiki de Foros de Electrónica


----------



## Adidi (Jul 11, 2017)

Ok no pasa nada! En serio está genial este post. Adquirí la minidk2 para un proyecto de la universidad y me gustaría profundizar más en esto de la programación de microcontroladores, sobre todo de la minidk2 con el keil que es lo que yo tengo. Sabéis de más post o lugares donde pueda ver proyectos o cualquier tipo de información relacionada con la minidk2 y como programarla etc? Es que a parte de este hilo no he encontrado nada de eso en concreto, también es que soy un poco nuevo en esto de los micros y puede que no esté sabiendo buscar adecuadamente la información 

Muchas gracias de antemano y un saludo


----------



## cosmefulanito04 (Sep 5, 2019)

Hellmut1956 dijo:


> Impresionante tu tutorial, en especial porque se dedica a cosas que usualmente dejo desatendidas. No es crítica, sino pregunta. ARM exige de todos aquellos que toman una licencia para el desarollo de una componente del tipo ARM Cortex Mx, que estas empresas deben obligatoriamente poner a dispocición del usuario una API para cada función periferica de su componente, donde la APi es comun para todas las componentes de todos los que licencian de ARM un Cortex Mx. Esto tiene la gran ventaja para el que usa un ARM Cortex Mx, que si programa usa estos API definidos por ARM, el esfuerzo para portar un programa de un controlador a otro controlador, sea del mismo proveedor, en nuestro caso los ARM Cortex M de NXP, o sea de otro proveedor, siempre que tenga las periferias que el programa requiere, será sencillamente portable.
> 
> Claro, el camino que tu escoges, por un lado muestra que vienes de los ARM no de los tipo Cortex Mx, pero por otro lado permite aprender en detalle como funcionan y se usan ciertas periferias.
> 
> ...



Hace dos años vengo usando las librerías LPCOpen que recomienda NXP y la verdad.... están buenas, te resuelven un montón de cosas, por ej. tener el lwip preparado y funcional en un proyecto ejemplo, el FreeRtos en conjunto con el lwip, muchos periféricos y entre ellos el USB. Realmente, reduce notablemente los tiempos de desarrollo.

Cuando tenga tiempo, subo algo sobre el LPCOpen.


----------

