[Aporte] Tutorial ARM Cortex-M3 - LPC1768

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

8.jpg


10.jpg


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.
- TF SD / MMC card (SPI) interface.
- Two user button, One Reset button and ISP button , one INT0 button, two user-programmable LED lights.
- Serial ISP download, Standard 20-pin JTAG download simulation debugging interface.
- Can be choose external 5V power supply or USB 5V supply.
- Board size: 95mm * 78mm.
- Expansion all IO.

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.
 

Adjuntos

  • UM10360 - LPC1768.pdf
    4.9 MB · Visitas: 193
Última edición:
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í:

PHP:
//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 :LOL:).

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:

PHP:
#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.
 

Adjuntos

  • LPC1768-Mini-DK2_SCH.pdf
    3.7 MB · Visitas: 132
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.
 
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:

PHP:
…
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:

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

PHP:
…
__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.
 
Última edición:
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

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

PHP:
//------------- 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

PHP:
//--------------------------------------- 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

PHP:
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)

PHP:
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)

PHP:
#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)

PHP:
#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.
 
Última edición:
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)

PHP:
// Defines => ver mensaje anterior

#define TIEMPO_TOGGLE_INICIAL	5

configuracion_PLL.c => no hay modificaciones.

perifericos.c(se agrega a lo anterior)

PHP:
//--------------------------------------- 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)

PHP:
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)

PHP:
#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:

EliminadorRebote7.gif


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)

PHP:
//--------------------------------------- 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)

PHP:
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)

PHP:
#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.
 

Adjuntos

  • Código - Externa - Parte 3.rar
    358.1 KB · Visitas: 91
  • Código - Timer - Parte 2.rar
    350.2 KB · Visitas: 214
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)

PHP:
//--------------------------------------- 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)

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

PHP:
#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

PHP:
#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.
 

Adjuntos

  • Uart - Parte 4.rar
    200.9 KB · Visitas: 76
Última edición:
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)

PHP:
//... 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)

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

PHP:
#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)

PHP:
//... 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

PHP:
#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.
 

Adjuntos

  • RTC - Parte 5.rar
    412.7 KB · Visitas: 69
Última edición:
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)

PHP:
//.... 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)

PHP:
//... 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)

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

PHP:
#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.
 

Adjuntos

  • ADC - Parte 6.rar
    211.2 KB · Visitas: 77
! 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)

PHP:
//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

PHP:
#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.
 

Adjuntos

  • DAC - Parte 7.rar
    212.8 KB · Visitas: 67
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)

PHP:
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)

PHP:
//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

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

PHP:
#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.
 

Adjuntos

  • Generador básico de Señales - DAC.rar
    176.3 KB · Visitas: 59
  • Generador básico de Señales (SOLO SENOIDAL 100kHz) - DAC.rar
    174.7 KB · Visitas: 52
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)

PHP:
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)

PHP:
//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

PHP:
#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).
 

Adjuntos

  • PWM - Parte 8.rar
    214.3 KB · Visitas: 89
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 :rolleyes:.

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 :p.
- 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)

PHP:
//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)

PHP:
//------------------------ 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.

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

PHP:
#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:

cabecerafat16.png


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

cabecerafat16pisadatext.png


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

cabecerafat16pisadatext.png


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

Adjuntos

  • SSP0 - Parte 9.zip
    277.1 KB · Visitas: 65
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.
 
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.

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.

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

xpr_lpc176x_banner.png


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!
 
Última edición:
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 ...
 
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 (n) 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 :unsure:.
 

Adjuntos

  • NGX_PARALLEL_PORT_JTAG.pdf
    44 KB · Visitas: 33
Última edición:
Atrás
Arriba