Haz una pregunta
  Foros de Electrónica » Diseño digital » Microcontroladores y sistemas embebidos
Foros Registrarse ¿Olvidaste tu contraseña?

Temas similares

17/04/2013 #1

Avatar de cosmefulanito04

[Aporte] Tutorial ARM Cortex-M3 - LPC1768
Hace poco compré este kit de desarrollo (LPC1768-Mini-DK2):





Las características del kit:

fabricante del kit dijo:
- One serial interface, use CP2102 (USB to RS232 interface, support ISP download).
- RJ45-10/100M Ethernet network interface (Ethernet PHY: LAN8720A).
- 2.8 inch TFT color LCD interface (SPI interface or 16Bit parallel interface).
- USB 2.0 interface, USB host and USB Device interface.
- 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.
17/04/2013 #2

Avatar de cosmefulanito04

Primera parte - GPIO
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í:

Código 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 ).

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:

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_1LPC_GPIO3->FIOSET|=LED_2;}
        else
            {
LPC_GPIO3->FIOSET|=LED_1LPC_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.
21/04/2013 #3

Avatar de cosmefulanito04

Segunda Parte - PLL y timers (parte "teórica" del PLL)
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.



(fuera de rango)
(fuera de rango)
(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:



(valor no entero, por lo tanto no es válido)
(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.
22/04/2013 #4

Avatar de cosmefulanito04

Segunda Parte - PLL y timers (parte "teórica" interrupciones, consumo y Timers)
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:

Código 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:

Código 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:

Código 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:



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



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:



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:



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.
24/04/2013 #5

Avatar de cosmefulanito04

Segunda Parte - PLL y timers (código)
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

Código 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

Código 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

Código 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

Código 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)

Código 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)

Código 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_1LPC_GPIO3->FIOSET|=LED_2;}
        else
            {
LPC_GPIO3->FIOSET|=LED_1LPC_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)

Código 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_1LPC_GPIO3->FIOSET|=LED_2;}
            else
                {
LPC_GPIO3->FIOSET|=LED_1LPC_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.
30/04/2013 #6

Avatar de cosmefulanito04

Tercera Parte - Interrupciones externas
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)

Código PHP:
// Defines => ver mensaje anterior

#define TIEMPO_TOGGLE_INICIAL    5 
configuracion_PLL.c => no hay modificaciones.

perifericos.c(se agrega a lo anterior)

Código 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)

Código 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)

Código 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_1LPC_GPIO3->FIOSET|=LED_2;}
            else
                {
LPC_GPIO3->FIOSET|=LED_1LPC_GPIO3->FIOCLR|=LED_2;}
        }
        
        
__wfi();    //Sleep-Mode
        
        
if(flag_timer0)
        {
            
flag_timer0=0;
            
cont--;
        }
        
        if(
flag_ext1)
        {
            
flag_ext1=0;
            
            
configura_timer1(25000,200);    //Pre-escaler 250000=> 1mSeg y 200cuentas => 200mSeg
            
            
while(!flag_timer1);    //20mSeg para evitar rebote
            
flag_timer1=0;
            
LPC_TIM1->TCR=0// paro el contador
            
            
if(tiempo_inicial_variable>1)
                
tiempo_inicial_variable--;
        }
        
        if(
flag_ext2)
        {
            
flag_ext2=0;
            
            
configura_timer1(25000,200);    //Pre-escaler 250000=> 1mSeg y 200cuentas => 200mSeg
            
            
while(!flag_timer1);    //20mSeg para evitar rebote
            
flag_timer1=0;
            
LPC_TIM1->TCR=0// paro el contador
            
            
tiempo_inicial_variable++;
        }
    }

Inconvenientes de esta solución:

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

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



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

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

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

defines.c => no hay modificaciones.

configuracion_PLL.c => no hay modificaciones.

perifericos.c(se agrega a lo anterior)

Código 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(u8variable_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(u8variable_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)

Código 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)

Código 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_1LPC_GPIO3->FIOSET|=LED_2;}
            else
                {
LPC_GPIO3->FIOSET|=LED_1LPC_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.
Archivos Adjuntos
Tipo de Archivo: rar Código - Externa - Parte 3.rar (358,1 KB (Kilobytes), 79 visitas)
Tipo de Archivo: rar Código - Timer - Parte 2.rar (350,2 KB (Kilobytes), 191 visitas)
06/05/2013 #7

Avatar de cosmefulanito04

Cuarta Parte - Uart
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)

Código 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)

Código 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

Código 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

Código 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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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.
Archivos Adjuntos
Tipo de Archivo: rar Uart - Parte 4.rar (200,9 KB (Kilobytes), 65 visitas)
12/05/2013 #8

Avatar de cosmefulanito04

Quinta Parte - RTC
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)

Código 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)

Código 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

Código 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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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)

Código 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

Código 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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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.
Archivos Adjuntos
Tipo de Archivo: rar RTC - Parte 5.rar (412,7 KB (Kilobytes), 57 visitas)
18/05/2013 #9

Avatar de cosmefulanito04

Sexta Parte - ADC
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)

Código 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)

Código 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)

Código 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

Código 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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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_mostrarMOSTRAR_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.
Archivos Adjuntos
Tipo de Archivo: rar ADC - Parte 6.rar (211,2 KB (Kilobytes), 65 visitas)
18/05/2013 #10
Moderador general

Avatar de Fogonazo

! Gracias por el aporte Cosme ¡
24/05/2013 #11

Avatar de cosmefulanito04

Séptima Parte - DAC
Fogonazo dijo: Ver Mensaje
! 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)

Código 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

Código 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_1LPC_GPIO3->FIOSET|=LED_2asignar_valor_dac(620,MIN_UPDATE_BIAS); /*Vref=3,3V => Si quiero 2Volts => 2V/3,3V*1024=620,6 cuentas*/}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_GPIO3->FIOCLR|=LED_2asignar_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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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_mostrarMOSTRAR_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.
Archivos Adjuntos
Tipo de Archivo: rar DAC - Parte 7.rar (212,8 KB (Kilobytes), 60 visitas)
24/05/2013 #12

Avatar de cosmefulanito04

Séptima Parte - Bonus Track
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)

Código 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)

Código 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

Código 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

Código 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.





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.



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.
Archivos Adjuntos
Tipo de Archivo: rar Generador básico de Señales - DAC.rar (176,3 KB (Kilobytes), 47 visitas)
Tipo de Archivo: rar Generador básico de Señales (SOLO SENOIDAL 100kHz) - DAC.rar (174,7 KB (Kilobytes), 42 visitas)
01/06/2013 #13

Avatar de cosmefulanito04

Octava Parte - PWM
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:



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)

Código 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)

Código 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

Código 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_1LPC_GPIO3->FIOSET|=LED_2;}
            else
                {
LPC_GPIO3->FIOSET|=LED_1LPC_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).
Archivos Adjuntos
Tipo de Archivo: rar PWM - Parte 8.rar (214,3 KB (Kilobytes), 81 visitas)
08/06/2013 #14

Avatar de cosmefulanito04

Novena Parte - SSP (como SPI)
Aclaración:

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

Características principales:

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

Procedimiento de inicialización:

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

Registros a utilizar:

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

Velocidad del clock:



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

Ejercicio propuesto:

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

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

Antes de empezar:

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

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

configuracion_PLL.c => no hay modificaciones.

perifericos.c (se agrega a lo anterior)

Código 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)

Código 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 (010i++) {                        // 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_STATE0);    // 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_COND0) != 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 adressu8 *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 (0!= 512i++) {                    // 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 adressu8 *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 (0512i++) {                     // 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.

Código 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

Código 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_1LPC_GPIO3->FIOSET|=LED_2asignar_valor_dac(620,MIN_UPDATE_BIAS); /*Vref=3,3V => Si quiero 2Volts => 2V/3,3V*1024=620,6 cuentas*/}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_GPIO3->FIOCLR|=LED_2asignar_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_1LPC_GPIO3->FIOSET|=LED_2;}
                else
                    {
LPC_GPIO3->FIOSET|=LED_1LPC_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_mostrarMOSTRAR_HORA_RTC;                //Muestra el valor del RTC c/1Seg.
            
            
if(dato_uart0=='c')
                
flag_mostrar=MOSTRAR_CONTADOR;                 //Muestra el valor de la cuenta c/1Seg.
            
            
if(dato_uart0=='q')
                
flag_mostrar=MOSTRAR_CONVERSION_ADC;     //Realiza una conversión ADC y muestra su valor c/1Seg.
            
            
if(dato_uart0=='l')
                {
                    
buffer_datos=calloc(512,sizeof(u8));    //512 bytes

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

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

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

Lo más destacado:

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

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

Fotos del terminal con los datos obtenidos de la memoria:

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



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



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

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



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

Con este mensaje se completan todos los temas que mencione, obviamente todavía falta lo más jugoso que es USB y Ethernet, pero de momento no tuve tiempo de verlos. Cuando aprenda a usarlos, continuaré con el tutorial.
Archivos Adjuntos
Tipo de Archivo: zip SSP0 - Parte 9.zip (277,1 KB (Kilobytes), 52 visitas)
14/08/2013 #15

Avatar de cosmefulanito04

alexv8 dijo: Ver Mensaje
QUE LABURO HERMANO!!

muchísimas gracias por el aporte, muy bueno!
Gracias.

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

[Tutorial] FreeRTOS
14/08/2013 #16

Avatar de Hellmut1956

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.
14/08/2013 #17

Avatar de cosmefulanito04

Hellmut1956 dijo: Ver Mensaje
ARM exige de todos aquellos que toman una licencia para el desarollo de una componente del tipo ARM Cortex Mx, que estas empresas deben obligatoriamente poner a dispocición del usuario una API para cada función periferica de su componente, donde la APi es comun para todas las componentes de todos los que licencian de ARM un Cortex Mx. Esto tiene la gran ventaja para el que usa un ARM Cortex Mx, que si programa usa estos API definidos por ARM, el esfuerzo para portar un programa de un controlador a otro controlador, sea del mismo proveedor, en nuestro caso los ARM Cortex M de NXP, o sea de otro proveedor, siempre que tenga las periferias que el programa requiere, será sencillamente portable.
Mirá yo en ese sentido tengo una forma de pensar bastante crítica hacia el uso de librerías de terceros si puedo evitarlo.

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

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

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

Hellmut1956 dijo: Ver Mensaje
Creo que sería util el incluir la referencia a este API en tu tutorial y de aprender a usarlo. Aprendiendo desde el principio a usar esta API educa a escribir programas que serán facilmente portables.
No me parece una mala idea, si vos podés agregar al tutorial el uso de esas librerías, será bienvenido, incluso a mí me pueden llegar a ser útiles en un futuro.

Hellmut1956 dijo: Ver Mensaje
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.
16/08/2013 #18

Avatar de Hellmut1956

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

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

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

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

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

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

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

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

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

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

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

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

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



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

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


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 ...
01/09/2013 #20

Avatar de cosmefulanito04

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

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

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

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

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

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

Te dejo el esquemático para armar el Jtag usando el puerto paralelo, es bastante simple, el problema es el puerto paralelo .
¿Tienes una mejor respuesta a este tema? ¿Quieres hacerle una pregunta a nuestra comunidad y sus expertos? Registrate

Foros de Electrónica » Diseño digital » Microcontroladores y sistemas embebidos

Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO ©2011, Crawlability, Inc.