Utorial ARM Cortex microcontrolador

utorial ARM Cortex-M3 - LPC1768 Cancelar suscripción de este Tema Buscar en este Tema 23Me Gusta Página 1 de 6 1 2 3

Views 166 Downloads 3 File size 3MB

Report DMCA / Copyright

DOWNLOAD FILE

Recommend stories

Citation preview

utorial ARM Cortex-M3 - LPC1768 Cancelar suscripción de este Tema

Buscar en este Tema

23Me Gusta Página 1 de 6

1 2 3 4 5 > Última » Herramientas

Calificar Tema

17/04/20 13

cosmef ulanito

#1 [Aporte] Tutorial ARM Cortex-M3 - LPC1768

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

04

Fecha de Ingreso: octubre2009 Ubicació n: Bs. As. Mensaje s: 2.508

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 userprogrammable 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. Archivos Adjuntos UM10360 - LPC1768.pdf (4,94 MB (Megabytes), 55 visitas) Me Gusta

A Basalto, Hellmut1956, george.manson.69 y a otros 5 les gusta esto. Última edición por cosmefulanito04; 17/04/2013 a las 18:01



¿Mensaje inapropiado? 

Citar

17/04/2013

cosmeful anito04

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

Fecha de

- Flash Magic, lo pueden descargar en forma gratuita.

Ingreso: octubre2009

Diferencias que vamos a notar entre ARM7 y Cortex:

Ubicación: Bs. As.

Para el que viene de ARM7 (familia LPC21xx), se va encontrar que los

Mensajes:

registros se acceden de forma distinta, por ej. para acceder al registro

2.508

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 #define LED_1 (1FIODIR|=LED_2|LED_1;

//Defino al puerto com

o salida LPC_GPIO3->FIOSET=LED_1;

//Pongo en 1 el puerto => led

apagado LPC_GPIO3->FIOCLR=LED_2;

//Pongo en 0 el puerto => led

encendido while(1) { if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET| =LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR| =LED_2;} for(cont=0;cont 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. Me Gusta

A Basalto, erios, ciernes y a otros 1 les gusta esto.



¿Mensaje inapropiado? 

22/04/20 13

Citar #4

cosmef ulanito 04

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 Fecha de

de interrupción ya está definido y simplemente hay que realizar la

Ingreso:

llamada al handler correspondiente, a diferencia de la familia LPC21xx

octubre-

donde uno tenía que hacer una definición del handler algo similar a C

2009 Ubicació n: Bs. As. Mensajes : 2.508

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 posib le //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 standby. 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. Me Gusta

A erios le gusta esto. Última edición por cosmefulanito04; 22/04/2013 a las 17:54



¿Mensaje inapropiado? 

24/04/ 2013

Citar #5

cosm

Segunda Parte - PLL y timers (código)

efula

En base a los mensajes anteriores, ya tenemos una cierta idea de como

nito0

funciona el oscilador generador de relojes, el PLL, el control consumo, las

4

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:

Fecha de Ingre so: octub re-

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

2009 Ubica ción:

defines.c

Bs.

Código PHP:

As.

typedef

unsigned char u8;

Mensa

typedef

unsigned int u16;

typedef

unsigned long int u32;

jes: 2.508

#define LED_1 (1 Núc leo |=> DIV2 => FPCLK => Per ifé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 re gistros 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 d e 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)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 #define COUNTER_R

0 1

void configura_timer0(u32 preescaler,u32 matcheo)

{ LPC_SC->PCONP|=(1 Prescaler -->

Contador (Nota: Fcristal --> PLL --> Core-clock --> Divisor --> Port-clock) - Prescaler -> 2 registros . TxPR: Es el valor de la cuenta final del prescale r. . TxPC: la cuenta del prescaler, también permite fi jar desde donde comienza a contar el prescaler, cuando sea igual a TxPR, desborda y empieza de nuevo, mandandole una cuenta al ot ro 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 s er 4 o 3), acá se pondrá el valor de la cuenta q se desea alcanz ar 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, pe ro el contador sigue - TxMR1 = 45; Al llegar acá se produce otro evento . TxMCR: Sirve para configurar q acción tomar cuando s e 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;

// configur

o 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;

// config

uro la cuenta del MR0 tal q cuente 1000 mSeg osea 1 seg LPC_TIM0->MCR=(1TCR=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 M=25 N=2 => FCCO => CPU-CLK=100MHz y P-CLK=25MHz LPC_PINCON->PINSEL7=0;

//Decl

aro al puerto 3 como GPIO LPC_GPIO3->FIODIR|=LED_2|LED_1;

//Defino al p

uerto como salida LPC_GPIO3->FIOSET=LED_1;

//Pongo en 1 el puerto => le

d apagado LPC_GPIO3->FIOCLR=LED_2;

//Pongo en 0 el puerto => le

d encendido habilitar_interrupciones(); while(1) { if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET|

=LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR| =LED_2;} delay_s(TIEMPO_CAMBIO_DE_ESTADO); } } Como verán el código es bastante sencillo: 1- Configuro PLL para que use un cristal externo, M=25, N=2, divisor del Cclk=3, divisor de todos los periféricos =4. 2- Inicializo los puertos como GPIO. 3- Habilito interrupciones. 4- Entro en el While principal donde cambiará el estado de los leds, esperará con un delay de 10 seg y repetirá el proceso una y otra vez. ¿Que desventaja tiene este código? Durante los 10 Seg del delay el uC no puede hacer absolutamente nada, es decir que esa función delay es totamente bloqueante. Para este ejercicio, realmente mucho no importa esto, pero si por ej. tuvieramos que estar pendientes de otro proceso, no podríamos hacer absolutamente nada, recién una vez que pasen los 10 Seg podríamos hacer algo, por lo tanto esta solución es muy precaria. main.c (segunda variante) Código PHP:

#include #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 co

mo salida LPC_GPIO3->FIOSET=LED_1;

//Pongo en 1 el puerto => le

d apagado LPC_GPIO3->FIOCLR=LED_2;

//Pongo en 0 el puerto => le

d encendido configura_timer0(25000,1000);

//Pre-escaler 250000 => 25M

Hz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg habilitar_interrupciones(); while(1) { if(!cont) { cont=TIEMPO_CAMBIO_DE_ESTADO; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET| =LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR| =LED_2;} }

__wfi();

//Sleep-Mode

if(flag_timer0) { flag_timer0=0; cont--; } } } Pasos similares, pero ahora inicializo el timer0 para que genere una interrupción c/1 Seg. Cuando entra al While principal pregunta si la variable "cuenta" es igual a 0, de ser 0 cambia el estado del los leds y vuelve a la variable "cuenta" a 10, de lo contrario sigue. Luego pregunta se se produjo o no una interrupción mediante el "flag_timer0", en caso afirmativo vuelve a 0 el "flag_timer0" y resta a la variable "cuenta", de lo contrario el uC queda en sleep-mode hasta que aparezca una nueva interrupción. Como verán, en esta variante, el uC esta disponible en la mayoría del tiempo y solo se necesita una interrupción para sacarlo del sleep-mode, esto en el próximo ejercicio resulta en una ventaja. Para el próximo mensaje vamos a ver la interrupción externa, pero ya pudiendo manejar el PLL, el resto les va a resultar medianamente sencillo. Me Gusta

Última edición por cosmefulanito04; 24/04/2013 a las 17:57



¿Mensaje inapropiado? 

30/04/2 013

cosme

Citar #6

Tercera Parte - Interrupciones externas

Si pudieron entender como configurar el PLL y los timers ya creo que están

fulanit o04

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:

Fecha de

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

Ingreso :

- Elegir el modo de disparo, registro EXTMODE.

octubre -2009 Ubicaci ón: Bs.

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

As. Mensaj es: 2.508

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|=(1TC=0;

// el contador comienza de 0

LPC_TIM1->PR=preescaler; // configuro la cuenta del prescal er 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=(1FIOSET| =LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR| =LED_2;} } __wfi();

//Sleep-Mode

if(flag_timer0) { flag_timer0=0; cont--; } if(flag_ext1) { flag_ext1=0; configura_timer1(25000,200);

//Pre-escaler 25000

0=> 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 25000

0=> 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|=(1TC=0;

// el contador comienza de 0

LPC_TIM2->PR=preescaler; // configuro la cuenta del prescal er 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=(1PINSEL4| =PINSEL_EXT1;

//Habilito EXT1 flag_ext1=0;

o a esperar por otra interrupción }

//Rebote detectado

} break; } case ANTI_REB_10MSEG: { if(flag_timer1) {

//

Vuelv

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_300MS EG;

//Espero 1 seg. para confirmar si el botón sigue presion

ado return 1; //Pulsación confirmada } else { LPC_TIM1->TCR=0; // Paro el con tador *variable_estado=ANTI_REB_IDLE; // Vuelvo al 1er estado LPC_PINCON->PINSEL4| =PINSEL_EXT1;

//Habilito EXT1 flag_ext1=0;

//

Vuelv

o 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 po

r botón apretado else { LPC_TIM1->TCR=0; // Paro el con tador *variable_estado=ANTI_REB_IDLE; // Vuelvo al 1er estado LPC_PINCON->PINSEL4| =PINSEL_EXT1;

//Habilito EXT1 flag_ext1=0;

//

Vuelv

o a esperar por otra interrupción }

//Pulsador liberado

} break; } } return -1; } int anti_rebote_ext2(u8* variable_estado)

//Para flanco desc

endente { 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 con tador *variable_estado=ANTI_REB_IDLE; // Vuelvo al 1er estado LPC_PINCON->PINSEL4| =PINSEL_EXT2;

// Habilito EXT2 flag_ext2=0;

//

Vuelv

o 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_300MS EG;

//Espero 1 seg. para confirmar si el botón sigue presion

ado return 1;

//Pulsación confirmada } else { LPC_TIM2->TCR=0; // Paro el con tador *variable_estado=ANTI_REB_IDLE; // Vuelvo al 1er estado LPC_PINCON->PINSEL4| =PINSEL_EXT2;

// Habilito EXT2 flag_ext2=0;

//

Vuelv

o 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 po r 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 interru

pció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 antirebote. 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 --> Pa g. 493 } //--------- Rutina de la interrupcion timer2 --------------------// //--------- Rutina de la interrupcion externa 1 --------------------// void EINT1_IRQHandler (void) { flag_ext1=1; LPC_PINCON->PINSEL4&=~((3EXTINT|=(1PINSEL7=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 a

pagado LPC_GPIO3->FIOCLR=LED_2;

//Pongo en 0 el puerto => led e

ncendido configura_timer0(25000,1000);

//Pre-escaler 250000 => 25

MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);

//E

xterna 1 configurada para que detecte flancos descendentes configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);

//Ext

erna 2 configurada para que detecte flancos descendentes habilitar_interrupciones(); while(1) { if(!cont) { cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INICIAL;

if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3->FIOSET| =LED_2;} else

{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3->FIOCLR| =LED_2;} } __wfi();

//Sleep-Mode

if(flag_timer0) { flag_timer0=0; cont--; } if(flag_ext1) { if(anti_rebote_ext1(&estado_anti_reb_ext1)>0) { if(tiempo_inicial_variable>1) tiempo_inicial_variable--; } } if(flag_ext2) { if(anti_rebote_ext2(&estado_anti_reb_ext2)>0) tiempo_inicial_variable++; } } } Como pueden ver las grandes ventajas de esta solución son: - No es bloqueante (no tengo que esperar 200mSeg para recuperar el control del uC). - Posee doble verificación en 5mSeg y 10mSeg. - Se puede mantener apretado el pulsador y c/300mSeg se tomará como válida una nueva pulsación (el tiempo puede modificarse, en vez de 300mSeg podrían ser 1Seg).

Para el próximo mensaje vamos a ver como funciona la Uart, mientras tanto les subo el código de estas dos soluciones y el código del mensaje anterior. Archivos Adjuntos Código - Externa - Parte 3.rar (358,1 KB (Kilobytes), 34 visitas) Código - Timer - Parte 2.rar (350,2 KB (Kilobytes), 38 visitas) Me Gusta



¿Mensaje inapropiado? 

06/05/201 3

cosmefu lanito04

Citar #7

Cuarta Parte - Uart

Diagrama en bloques:

Fecha de Ingreso: octubre2009 Ubicación: Bs. As. Mensajes: 2.508

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|=(1TC=0;

// el contador comienza de 0

LPC_TIM3->PR=preescaler; // configuro la cuenta del presc aler 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 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); xterna 2 configurada para que detecte flancos descendentes

//E

configurar_uart0(UART_9600); //9600bps habilitar_interrupciones(); while(1) { if(!flag_ascendente) { if(cont==1) {

cont=tiempo_inicial_variable*TIEMPO_TOGGLE_IN ICIAL; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3>FIOSET|=LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2;} } } else { if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_I NICIAL)-1) { cont=0; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3>FIOSET|=LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2;} } } __wfi();

//Sleep-Mode

if(flag_timer0) { flag_timer0=0; if(!flag_ascendente) cont--; else cont++;

enviar_string_uart0("Contador= \n"); envia_u16_string_uart0(cont); enviar_string_uart0(" \n"); } if(flag_ext1) { if(anti_rebote_ext1(&estado_anti_reb_ext1)>0) { if(tiempo_inicial_variable>1) tiempo_inicial_variable--; } } if(flag_ext2) { if(anti_rebote_ext2(&estado_anti_reb_ext2)>0) tiempo_inicial_variable++; } if(flag_uart0_rx) { flag_uart0_rx=0; if(dato_uart0=='a') flag_ascendente=1;

//Cuenta Ascendente

if(dato_uart0=='d') flag_ascendente=0; } } }

//Cuenta descendente

Me Gusta

A msolinas le gusta esto. Última edición por cosmefulanito04; 06/05/2013 a las 14:43



¿Mensaje inapropiado? 

12/05/2 013

cosme fulanit

Citar #8

Quinta Parte - RTC

Diagramas en bloques:

o04

- Alimentación y oscilador

Fecha de Ingreso : octubre -2009 Ubicaci ón: Bs. As. Mensaje s: 2.508

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 #define RTC_DIA_ANIO

4 5

#define RTC_MES

6

#define RTC_ANIO

7

#define RTC_TAMANIO

8

void configurar_hora_rtc(u16 vector_hora_inicial[]) { LPC_SC->PCONP|=(1HOUR = 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=(1ALDOM=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_correccionGPREG2=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|=(1PINSEL7=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 a

pagado LPC_GPIO3->FIOCLR=LED_2;

//Pongo en 0 el puerto => led e

ncendido configurar_hora_rtc(vector_hora_inicial_rtc); habilitar_interrupciones_rtc((1 1000cuentas => 1000Hz/1000cuentas=1Hz=1S eg configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);

//E

xterna 1 configurada para que detecte flancos descendentes configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);

//Ext

erna 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_INIC IAL; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3>FIOSET|=LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2;} } } else { if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INI CIAL)-1) { cont=0; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3-

>FIOSET|=LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2;} } } __wfi();

//Sleep-Mode

//if(flag_timer0) if(flag_rtc)

//RTC como base de tiempo => 1 interrup

ció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 #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,201 3}; 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 a

pagado LPC_GPIO3->FIOCLR=LED_2;

//Pongo en 0 el puerto => led e

ncendido 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áscar

as interrupción por Alarma => en caso que la cuenta sea igual a la alarma seteada. configura_timer0(25000,1000);

//Pre-escaler 250000 => 25

MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);

//E

xterna 1 configurada para que detecte flancos descendentes configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);

//Ext

erna 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_INIC IAL; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3>FIOSET|=LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2;} } } else { if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INI CIAL)-1) { cont=0; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3>FIOSET|=LED_2;} else

{LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2;} } } __wfi();

//Sleep-Mode

if(flag_timer0) { flag_timer0=0; if(!flag_ascendente) cont--; else cont++; if(!flag_mostrar_hora) { enviar_string_uart0("Contador= \n"); envia_u16_string_uart0(cont); enviar_string_uart0(" \n"); } else enviar_hora_rtc_uart0(); } if(flag_ext1) { if(anti_rebote_ext1(&estado_anti_reb_ext1)>0) { if(tiempo_inicial_variable>1) tiempo_inicial_variable--; } } if(flag_ext2) { if(anti_rebote_ext2(&estado_anti_reb_ext2)>0) tiempo_inicial_variable++;

} if(flag_uart0_rx) { flag_uart0_rx=0; if(dato_uart0=='a') flag_ascendente=1;

//Cuenta Ascendente

if(dato_uart0=='d') flag_ascendente=0;

//Cuenta descendente

if(dato_uart0=='r') flag_mostrar_hora=1;

//Muestra el valor del

RTC c/1Seg. if(dato_uart0=='c') flag_mostrar_hora=0;

//Muestra el valor de

la cuenta c/1Seg. } if(flag_rtc) { flag_rtc=0; enviar_string_uart0(" ¡¡Alarma del RTC!!! - Pasaron 5 minutos desde el último reseteo. \n"); } } } En este código, la base de tiempo de 1Seg sigue siendo el timer0, pero el RTC se lo configura para que a los 5 minutos se envíe un mensaje de alarma. Acá se puede ver en un "terminal", como se envía el valor que va tomando el RTC c/1Seg, hasta que llega a los 5 minutos:

Si bien la hora del RTC no tiene los dígitos ajustados (todos los valores tienen si o si 5 dígitos), para la prueba nos alcanza. Una forma de mejorar la presentación sería modificando la función "envia_u16_string_uart0" para que envíe una cierta cantidad de dígitos. Les dejo el código y para el próximo mensaje vamos a ver como funciona el ADC. Archivos Adjuntos RTC - Parte 5.rar (412,7 KB (Kilobytes), 27 visitas) Me Gusta

Última edición por cosmefulanito04; 12/05/2013 a las 11:07



¿Mensaje inapropiado? 

18/05/2 013

cosme fulanit

#9 Sexta Parte - ADC

Características principales:

o04

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

Citar

de

Procedimiento de inicialización:

Ingreso : octubre -2009 Ubicació n: Bs. As. Mensaje s: 2.508

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 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 on una actualización de

0 1

uS

#define MIN_UPDATE_BIAS una actualización de 2,5

//700uA corriente máxima c

1

//350uA corriente máxima con

uS

void iniciar_dac() { //Su conexión a fuente siempre está habilitada => no es nec esario modificar el registro PCONP LPC_PINCON->PINSEL1&=~((3 led e

ncendido 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áscar

as interrupción por Alarma => en caso que la cuenta sea igual a la alarma seteada. configura_timer0(25000,1000);

//Pre-escaler 250000 => 25

MHz/250000=1000Hz => 1000cuentas => 1000Hz/1000cuentas=1Hz=1Seg configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);

//E

xterna 1 configurada para que detecte flancos descendentes configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);

//Ext

erna 2 configurada para que detecte flancos descendentes configurar_uart0(UART_9600); //9600bps iniciar_adc(0,0);

//Inicio el ADC en el canal 0 sin offs

et iniciar_dac(); asignar_valor_dac(620,MIN_UPDATE_BIAS);

//Vref=3,3V => S

i 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_INIC IAL; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3>FIOSET|=LED_2; asignar_valor_dac(620,MIN_UPDATE_BIAS); /*Vref= 3,3V => Si quiero 2Volts => 2V/3,3V*1024=620,6 cuentas*/} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2; asignar_valor_dac(310,MIN_UPDATE_BIAS); /*Vref= 3,3V => Si quiero 1Volt => 1V/3,3V*1024=310,3 cuentas*/}

} } else { if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INI CIAL)-1) { cont=0; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3>FIOSET|=LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2;} } } __wfi();

//Sleep-Mode

if(flag_timer0) { flag_timer0=0; if(!flag_ascendente) cont--; else cont++; switch(flag_mostrar) { case MOSTRAR_CONTADOR: { enviar_string_uart0("Contador= \n"); envia_u16_string_uart0(cont); enviar_string_uart0(" \n"); break; }

case MOSTRAR_HORA_RTC:{enviar_hora_rtc_uart0(); break;} case MOSTRAR_CONVERSION_ADC: { if(convertir_adc(0,250,&valor_adc)>0) //Convierto en el canal 0, con un pre-escaler=250 { enviar_string_uart0(" Conversion= \ n"); envia_u16_string_uart0(valor_adc); } } } } if(flag_ext1) { if(anti_rebote_ext1(&estado_anti_reb_ext1)>0) { if(tiempo_inicial_variable>1) tiempo_inicial_variable--; } } if(flag_ext2) { if(anti_rebote_ext2(&estado_anti_reb_ext2)>0) tiempo_inicial_variable++; } if(flag_uart0_rx) { flag_uart0_rx=0; if(dato_uart0=='a') flag_ascendente=1;

//Cuenta Ascendente

if(dato_uart0=='d') flag_ascendente=0;

//Cuenta descendente

if(dato_uart0=='r') flag_mostrar= MOSTRAR_HORA_RTC; //Muestra el valor del RTC c/1Seg. if(dato_uart0=='c') flag_mostrar=MOSTRAR_CONTADOR; //Muestra el valor de la cuenta c/1Seg. if(dato_uart0=='q') flag_mostrar=MOSTRAR_CONVERSION_ADC;

//Real

iza 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 DAC - Parte 7.rar (212,8 KB (Kilobytes), 20 visitas) Me Gusta



¿Mensaje inapropiado? 

Citar

24/0 5/20 13

cos mef

#12 Séptima Parte - Bonus Track

Ejercicio propuesto:

ula nito 04

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.

Fech a de

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

Ingr

(EXT2) aumente. En caso de sobrepasar los límites de frecuencia (tanto

eso:

superior, como inferior), automáticamente cambiar el tipo de señal, (ej si

octu

estamos en la señal senoidal 100Hz y presionamos pulsador 1, pase a una

bre-

diente de sierra de 100kHz).

200 9 Ubic

Antes de empezar a programar

ació n:

De alguna forma necesitamos generar un vector que contenga los distintos

Bs.

valores que tomarán las señales, para lo cual yo utilicé el programa Matlab

As.

para crear un vector (también se puede usar el Octave, programa GNU).

Men saje s: 2.50 8

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 #define CINCUENTA_PTS

0

#define DIEZ_PTS

1 2

#define SENIAL_SENOIDAL

0

#define SENIAL_TRIANGULAR #define SENIAL_DIENTE_DE_SIERRA

1 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

#define DIVISOR_PERIFERICOS_8

3

//Solo CAN

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

10kHz

=> 100

pts posibles

100kHz

=> 10

pts posibles

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,1 021,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,8 40,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,2 25,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,25 6,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,7 57,767,777,788,798,808,818,829,839,849,859,870,880,890,900,910,92 1,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[*indi ce_dato],MAX_UPDATE_BIAS); break; } case SENIAL_DIENTE_DE_SIERRA: { asignar_valor_dac(vector_diente_sierra_100_pts[*i

ndice_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); 5MHz/25=1MHz => 10cuentas => 9 uSeg

//Pre-escaler 25 => 2

+ 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 #include "defines.c" //------- Variables globales para las interrupciones ------------// u8 flag_timer0=0,flag_timer1=0,flag_timer2=0,flag_timer3=0,flag_e xt1=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=AN TI_REB_IDLE; configurar_pll(CLK_XTAL,25,2,3, (DIVISOR_PERIFERICOS_1 FCCO => CPU-CLK=100MHz y P-CLK=25MHz configurar_timer_por_frecuencia(frecuencia,&flag_pts); configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);

//Ext

erna 1 configurada para que detecte flancos descendentes configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG); na 2 configurada para que detecte flancos descendentes iniciar_dac(); habilitar_interrupciones(); while(1) { if(frecuenciaDACR=((vector_senoidal_100_pts[indice_senial]&0x3ff)0) { frecuencia--; if(frecuenciaCIEN_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

Me Gusta

A electroconico le gusta esto.



¿Mensaje inapropiado? 

01/06/201 3

cosmefu lanito04

Citar #13

Octava Parte - PWM

Diagrama en bloques:

Fecha de Ingreso: octubre2009 Ubicación

Me Gusta

: Bs. As. Mensajes: 2.508

A electroconico le gusta esto.



¿Mensaje inapropiado? 

Citar

08/06/2 013

cosme fulanit

#14 Novena Parte - SSP (como SPI)

Aclaración:

o04

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 Fecha de Ingreso :

subiré lamentablemente tendremos que trabajar con un polling

.

Características principales:

octubre -2009

- Compatibilidad con mútiples buses (SPI, 4-Wire, TI SSI, National

Ubicaci

Semiconductor Microwire).

ón: Bs.

- Comunicación serie sincrónica.

As. Mensaj es: 2.508

- 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

#define SSP0_SSEL0

10

#define SSP0_MISO0

14

#define SSP0_MOSI0

16

#define SSP0_GPIO_SSEL0

//P1.20 //P1.21 //P1.23 //P1.24

21

#define DSS_4BIT

3

#define DSS_5BIT

4

#define DSS_6BIT

5

#define DSS_7BIT

6

#define DSS_8BIT

7

#define FRF_SPI #define FRF_TI #define FRF_MICROWIRE

0 (1 1000Hz/1000cuentas=1Hz=1Seg

configurar_externa_1(EXT1_MODO_FLANCO,EXT1_POL_NEG);

//E

xterna 1 configurada para que detecte flancos descendentes configurar_externa_2(EXT2_MODO_FLANCO,EXT2_POL_NEG);

//Ext

erna 2 configurada para que detecte flancos descendentes configurar_uart0(UART_9600); //9600bps iniciar_adc(0,0);

//Inicio el ADC en el canal 0 sin offs

et iniciar_dac(); asignar_valor_dac(620,MIN_UPDATE_BIAS); i quiero 2Volts => 2V/3,3V*1024=620,6 cuentas

//Vref=3,3V => S

habilitar_interrupciones(); while(1) { if(!flag_ascendente) { if(cont==1) { cont=tiempo_inicial_variable*TIEMPO_TOGGLE_INIC IAL; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3>FIOSET|=LED_2; asignar_valor_dac(620,MIN_UPDATE_BIAS); /*Vref= 3,3V => Si quiero 2Volts => 2V/3,3V*1024=620,6 cuentas*/} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2; asignar_valor_dac(310,MIN_UPDATE_BIAS); /*Vref= 3,3V => Si quiero 1Volt => 1V/3,3V*1024=310,3 cuentas*/} } } else { if(cont==(tiempo_inicial_variable*TIEMPO_TOGGLE_INI CIAL)-1) { cont=0; if(LPC_GPIO3->FIOPIN&LED_1) {LPC_GPIO3->FIOCLR|=LED_1; LPC_GPIO3>FIOSET|=LED_2;} else {LPC_GPIO3->FIOSET|=LED_1; LPC_GPIO3>FIOCLR|=LED_2;} } }

__wfi();

//Sleep-Mode

if(flag_timer0) { flag_timer0=0; if(!flag_ascendente) cont--; else cont++; switch(flag_mostrar) { case MOSTRAR_CONTADOR: { enviar_string_uart0((u8 *)("Contador= \ r\n")); envia_u16_string_uart0(cont); enviar_string_uart0((u8 *)("\r\n")); break; } case MOSTRAR_HORA_RTC:{enviar_hora_rtc_uart0(); break;} case MOSTRAR_CONVERSION_ADC: { if(convertir_adc(0,250,&valor_adc)>0) //Convierto en el canal 0, con un pre-escaler=250 { enviar_string_uart0((u8 *)(" Conver sion= \r\n")); envia_u16_string_uart0(valor_adc); } } } }

if(flag_ext1) { if(anti_rebote_ext1(&estado_anti_reb_ext1)>0) { if(tiempo_inicial_variable>1) tiempo_inicial_variable--; } } if(flag_ext2) { if(anti_rebote_ext2(&estado_anti_reb_ext2)>0) tiempo_inicial_variable++; } if(flag_uart0_rx) { flag_uart0_rx=0; if(dato_uart0=='a') flag_ascendente=1;

//Cuenta Ascendente

if(dato_uart0=='d') flag_ascendente=0;

//Cuenta descendente

if(dato_uart0=='r') flag_mostrar= MOSTRAR_HORA_RTC; //Muestra el valor del RTC c/1Seg. if(dato_uart0=='c') flag_mostrar=MOSTRAR_CONTADOR; //Muestra el valor de la cuenta c/1Seg. if(dato_uart0=='q') flag_mostrar=MOSTRAR_CONVERSION_ADC; iza una conversión ADC y muestra su valor c/1Seg. if(dato_uart0=='l')

//Real

{ buffer_datos=calloc(512,sizeof(u8));

//5

12 bytes if(!buffer_datos) {enviar_string_uart0((u8 *)("Memoria RA M 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_buff er 1768. - El puerto COM "virtual" que usa el adaptador USB.

Fecha de Ingreso: octubre-2009 Ubicación: Bs. As.

- La velocidad, probá con 9600. - Interfaz -> None (ISP) - Oscilador (MHz) -> 12

Mensajes: 2.508

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

.

Archivos Adjuntos NGX_PARALLEL_PORT_JTAG.pdf (44,0 KB (Kilobytes), 5 visitas)