Tutorial ARM desde 0x00000000

Hola a toda la gente del foro, este tutorial trata del ARM7 TDMI procesador LPC2148 :

porque elegí este? porque no es ni el mas basico ni el mas completo de todos y creo que es el mejor para arrancar ademas despues se puede migrar a cualquier otro ARM sin muchos cambios

Tengo pensado hacer entre 6 y 8 entregas,totalmente en español para que esté al alcance de todos.Por lo tanto voy a pedirles que me ayuden en la traduccion del manual del LPC2148 (Rev. 4 — 23 April 2012). En los archivos adjuntos les dejo la traduccion de los capitulos 6 y 8 que son los que vamos a utilizar para arrancar con todo esto, para la segunda entrega voy a necesitar un valiente traductor para el capitulo 7 (traduccion de cuadros incluido) porque no voy a disponer del tiempo necesario para hacerlo

Cada entrega comienza con una explicacion teorica y despues LOS DESAFIA a realizar algunos ejercicios practicos.Todo lo necesario para hacer las practicas está en los archivos adjuntos, los ejercicios se deben realizar utilizando solo la teoria desarrollada hasta el momento esto es muy importante para que este tutorial sea PASO A PASO,(por ejemplo en esta primera entrega no se explican las Interrupciones por lo tanto no hay que utilizarlas en estos primeros ejercicios)

Lo mas importante de este tutorial es que entre entrega y entrega hay que ir entregando los ejercicios resueltos!! asi los van discutiendo entre todos y van aprendiendo a programar mejor y conociendo mas los pines del LPC2148, esto es fundamental sino el tutorial no tiene sentido, y ademas me da el tiempo para preparar la proxima entrega :). Como ayuda para las practicas el Compilador que vamos a utilizar es el KEIL que tiene un simulador, y el que consiga la placa mucho mejor. El programa Flash Magic será el encargado de pasar el programa a la placa.

Conocimiento previo: un poco de C ( si alguna vez programaste pics en C con eso ya es mas que suficiente)

Se agradece la participacion en esta primera entrega de Cosmefulanito04 y todos los nuevos colaboradores para proximas entregas seran bienvenidos :)

Bueno larguemos ya! les dejo toda la info necesaria en los archivos adjuntos :) ojala se prendan muchos con los ejercicios !!!! :)

saludos!
 

Adjuntos

  • entrega_1.rar
    1.3 MB · Visitas: 202
  • info necesaria.rar
    1.2 MB · Visitas: 169
Muy bueno, la verdad vi que le pusiste todas las pilas (y).

Esta piola que hayas subido el circuito de una placa de desarrollo para que todos se puedan basar en un mismo hard.

Lo que les recomiendo a la gente que no tenga el hard en sus manos, pero quiera aprender sobre estos bichos, bajense el Proteus que permite simular esta familia de uC y en conjunto con el esquemático que subió ilcapo, pueden ir programando y probando como va quedando.

Por último les recomiendo que aprendan a tener siempre cerca las hojas de datos del uC y tal como hizo ilcapo, ver como se comportan los registros de los periféricos que necesitamos utilizar. Si bien uno al principio se asusta con la cantidad de páginas que tienen, tengan en cuenta que esas hojas son como una enciclopedia de como funciona el uC en su totalidad y por lo tanto es muy probable que ustedes en su proyecto solo usen un par de periféricos/registros y el resto les resulte redundante. Además como buena práctica, traten de entender las hojas en inglés, no todos los días van a tener un ilcapo que les traduzca las hojas y si bien algún conocimiento de inglés se necesita, siempre las hojas son iguales, breves y concisas en la información que dan, por lo tanto el inglés necesario es muy básico y técnico (no van a encontrar una poesía en un datasheet... bahh hasta ahora nunca me paso eso :LOL:)
 
Hola como estan? Soy nueva en el foro, hice los ejercicios de la " Entrega I ". La subo asi lo ven, y si tienen distinto me comparten, y terminamos armando un buen tutorial Saludos :)
 

Adjuntos

  • TP1 Dig 3.rar
    5.3 KB · Visitas: 104
Por lo que ví están bien, salvo pequeños detalles o algún consejo:

- En los primeros 3 ejercicios no inicializaste los PINSEL, si bien como explicó ilcapo se inicializan en 0 después del reset, es recomendable igual configurarlos.

- En el 1er ejercicio, cuando haces esto:

PHP:
if(IOPIN1 & (0x01000000)) //Evaluamos el estado actual de P1.24
  {
  IOSET1 = (1<<24); // Si estába apagado, los prendemos
  }
...

Podrías reemplazar 0x01000000 en el if por (1<<24):

PHP:
if(IOPIN1 & (1<<24)) //Evaluamos el estado actual de P1.24
  {
  IOSET1 = (1<<24); // Si estába apagado, los prendemos
  }
...

Así resulta mucho más fácil leer el código. Esto lo hiciste en el resto de los ejercicios.

- En los ejercicios 7 y 8, creo que te equivocaste con el valor inicial de la variable "pos", debería ser 8 o para evitar confusión, nuevamente usar desplazamiento (1<<3).

- En los ejercicios 7 y 8, creo que te falto hacer un AND a la hora de modificar el PINSEL

El resto está perfecto según lo que se pidió e hiciste muy bien en usar máscaras a la hora de modificar los registros como PINSEL o IODIR, ya que lo ideal es no modificar el resto de los puertos que no usas.

Por último, tengan en cuenta que los ejercicios que involucran los pulsadores tienen un comportamiento "ideal" y no real, ya que no se tiene en cuenta los rebotes del pulsador.
 
Con respecto a la configuracion del Pinsel: ( no esta en los primeros ejercicios q envió leluza pero luego esta correcta la configuracion)

/*Configuracion del GPIO P1.24*/
PINSEL2 &= 0xFFFFFFF7; // Pongo el bit3 a 0 entonces me quedan como GPIO los pines 16
// al 25 del puerto 1

Fijense que usó pinsel 2 para configurar el puerto 1!! , un error muy comun hubiese sido colocar pinsel 1 !! , porque para los otros registros que estamos viendo:

IOSET0 : para puerto 0 (cero con cero)
IOSET1: para puerto 1 (uno con uno)
IOCLR0: para puerto 0
IOCLR1: para puerto 1

idem para IODIR,,etc,,,, el unico que no "respeta la logica" es el pinsel
(todo esto esta en las hojas de datos)

*********************************************************************

El error fino que hay en los ejercicios es que el led se enciende por bajo ( esto es asi para que la energia del led la suministremos con una fuente externa y no usemos como fuente al procesador!! :) )

por lo tanto en el programa para encender el led usamos: IOCLR1 = pin_24;
y para apagarlo usamos: IOSET1 = pin_24;

**********************************************************************
No se si lo habia comentado, si no lo repito, en el Keil podemos escribir IOSET1 = IO1SET
ó IOCLR1 = IO1CLR
en otros compiladores solo te deja escribirlo de una ú otra forma,y si no la respetas te tira un error ( de esos que te hacen renegar :) ) un punto mas a favor para el Keil vs Otros

*********************************************************************
Esperamos unos ejercicios mas y largamos con el Practico 2:INTERRUPCIONES EXTERNAS
animense todos que son faciles!

Si alguno tiene problemas con la simulacion de los ejercicios en el KEIL que avise porque es muy facil tambien !! y la simulacion nos va a ser muy util para el practico 2 (y)

mientras tanto..... habrá algun valiente traductor del capitulo 7 :unsure: ?? :D

saludos!
 
En primer lugar gracias por el tutorial. Para no repetir el mismo codigo, se me ocurrio sugerir algunas alternativas muy simples:

Ejercicio 1 usando el operador ? :
PHP:
#include "LPC214x.h"
int main (void){
	int j;
	IO1DIR |= (1<<24);      // P1.24 como salida
	while(1){
		IO1PIN = (IO1PIN & (1<<24)) ? (0<<24) : (1<<24);   // Cambia el estado del LED
		for (j = 0; j < 100000; j++ ); 					// Retraso
	}
}

Ejercicio 2 usando el operador ^ para cambiar el estado de los LEDs:
PHP:
#include "LPC214x.h"
int main (void){
	int j;
	IO0DIR |= (1<<17);	 // P0.17 como salida
	IO1DIR |= (1<<24);	 // P1.24 como salida	
	IO0SET  = (1<<17);     // LED en P0.17 apagado
    IO1CLR = (1<<24); 	 // LED en P1.24 encendido
	while(1){
		IO0PIN ^= (1<<17);				// Cambia el estado del LED
		IO1PIN ^= (1<<24);				// Cambia el estado del LED			
		for (j = 0; j < 100000; j++); 	// Retraso
	}
}

Ejercicio 7 usando Fast GPIO:
PHP:
#include <LPC214x.h>
int main (void){
	int j, n;
	SCS |= 0x1;              // Activa Fast GPIO
	FIO0DIR |= 0xAAA8;	   // P0 pines 3,5,7,9,11,13 y 15 como salidas
    FIO0MASK |= 0x5557;      // Configura registro "mask" para pines usados como salidas
	FIO0SET |= 0xAAA8;       // Inicio con todos los LEDs apagados

	while(1){
		for (n = 3; n < 15; n = n + 2 ){    // Genera secuencia (3->5->7->9->11->13)
			FIO0PIN = 0xFFFF ^ (1 << n);	// Enciende LEDn 
			for (j = 0; j < 1000; j++); 	// Retraso
		}			
		for (n = 15; n > 3; n = n - 2 ){    // Genera secuencia (15->13->11->9->7->5)
			FIO0PIN = 0xFFFF ^ (1 << n);	// Enciende LEDn
			for (j = 0; j < 1000; j++); 	// Retraso
		}
	}
}

Saludos
 
Interesante código para ver otras alternativa. (y)

Sobre el operador "?", te soy sincero, es la 1era vez que lo veo y ni sabía de su existencia en C :oops:.

Sobre el operador "^", para el que no lo conozca es el operador XOR y en este caso es usado como "negador controlado" tal como hizo carferper en su código.

XOR:

tabla_verdad_compuerta_XOR_2_entradas.gif


Entonces si la máscara contiene 1's en ciertos bits, se puede negar dicho bit, en cambio si la máscara contiene 0's en ciertos bits, dichos bits mantendrán su valor original.

Ejemplo con variables de 8bits:

PHP:
var1=0b00101001;
mascara_negadora=0b00001111;

var1=var1^mascara_negadora;  // 0b00101001 XOR 0b00001111 = 0b00100110

Vean que solo quedó negado el nibble inferior en cambio el nibble superior mantuvo su valor, esto se debe a que la máscara tenía 1's en el nible inferior y 0's en el superior. Esto mismo se aplica para variables de 4, 8, 16, 32, N bits.
 
Muy buen aporte el tutorial pero podrian decirme donde se puede adquirir la placa de desarrollo para el ARM que estan tratanndo...bueno seria porder llevar a la practica real el tutorial..
gracias ....
 
Les dejo el código para poder configurar correctamente el PLL de un LPC y luego manejar un timer por interrupciones. El programa simplemente enciende y apaga un LED que se encuentra en el puerto 15 c/1seg. El código se compone de 3 archivos fuentes:

- pllconfig.c:

PHP:
#include <LPC213x.h>

void feed(void)
{
  PLLFEED=0xAA;
  PLLFEED=0x55;
}

#define PLOCK 0x400

void inicia_pll()
{
	/*
	Ejemplo de Configuración del PLL:

	Fcontrol-interna-PLL=2*Fcristal*M*P

	Core-Clock=Fcristal*M

	Port-Clock=Core-Clock/D

	M y P se configuran en el registro PLLCFG, bits:
		.0 - 4 : M (Valores posibles: 1 a 32)
		.5 - 6 : P (Valores posibles: 1,2,4,8)

	D se configura con el registro VPBDIV, bits:
		.0 - 1: D (4,1,2,--) 

	A tener en cuenta:
		- Core-Clock máxima según la especificación del modelo
		- 156MHz < Fcontrol-interna-PLL < 320MHZ
		- Cada cambio que se realice se deberá ser completado con el uso del registro PLLFEED según la secuencia que indica la hoja de datos.
	*/
	  	
	/* 
	Configuración del PLL:

	Se desea Core-Clock=60MHz a partir de un cristal de 12MHz:
		Core-Clock=Fcristal*M => M=Core-Clock/Fcristal=60/12=5
		
		Se debe cumplir con 156MHz < Fcontrol-interna-PLL < 320MHZ

		Fcontrol-interna-PLL=2*Fcristal*M*P=2*12MHz*5*P=120MHz*P => Pmin=156/120=1,3 redondeando para arriba P=2
		Verificamos => Fcontrol-interna-PLL=2*12MHz*5*2=240MHz => Cumple con la condición
		
	*/
	PLLCFG=0x24; // P=2 y M=5
	feed();
  
	// Se activa el PLL	
	PLLCON=0x1;
	feed();
  
	// Espera hasta que el PLL enganche a la frecuencia deseada
	while(!(PLLSTAT & PLOCK)) ;
  
	// Se habilita al PLL como Core-Clock
	PLLCON=0x3;
	feed();
 	
  
	// Se configuran el clock de los periféricos en función del Core-Clock
	VPBDIV=0;	//Divide por 4 => Valor de "D" para configurar el Port-Clock
	
}

- Timer.c

PHP:
//--------- Rutina de la interrupcion timmer---------------------//
__irq void timer0(void)
 {
 	flag_timmer0=1;
	
	T0IR=0x01; // Limpio registro de interrupcion
	VICVectAddr=0xFF;
 }
 //---------------------------------------------------------//

void configura_timmer0()
{
	
	/* 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 -----------------------------------------------//
	T0TCR=0x0; // el contador no cuenta
	T0TC=0x0;  // el contador comienza de 0
	T0PR=15000; // configuro la cuenta del prescaler tal q le mande una cuenta al contador c/ 1 mSeg
	T0PC=0x0;  // el prescaler comienza de 0
	T0MR0=1000; // configuro la cuenta del MR0 tal q cuente 1000 mSeg osea 1 seg
	T0MCR=0x03; // configuro q al producirce un evento en MR0, se lance una interrupcion y se resetee el contador
	//------------------------------------------------------------------------------------------//

	/* Registros de interrupciones:
	   VICIntEnable: habilita las 16 posibles fuentes de interrupciones
	   VICIntSelect: configura a las interrupciones como:
	   		- FIQ: interrupciones rapidas
			- IRQ: interrupciones comun, q a su vez pueden ser:
			 	* vectorizadas 
				* no vectorizadas
	   VICVectCntl_0 al 15: en el se indican los índices de interrupciones habilitadas y sí es vectorizada o no.
	   VICVectAddr_0 al 15: la dirección donde se encuentra la rutina de atención
	*/

	//------------------------ Configuracion de las interrupciones ------------------------------//
	VICIntSelect=0x0; // Lo configuro como IRQ
	VICIntEnable=0x10; // Interrupcion del Timer 0 activado, su indice es 4 al ser el bit 4	(importante para el reg. q viene)
	VICVectCntl0=0x24; // Los 4 1eros bits sirven para indicar el indice, el 6to bit si vale 1 indica q es vectorizado y si vale 0 q no
	VICVectAddr0= (unsigned long) timer0; // Le digo q la direccion de la subrutina timer0 q funciona como interrupción
	//--------------------------------------------------------------------------------------------//
}

- Main.c

PHP:
#include <LPC213x.h>

unsigned char flag_timmer0=0;

#include "pllconfig.c"
#include "timer.c"

#define puerto_15 		(1<<15)

int main()
{
	inicia_pll();
	
	PINSEL0=0;
	PINSEL1=0;	

	configura_timmer0();

	IODIR0=puerto_15;
	IOSET0=puerto_15;
	
	T0TCR=1; // el contador empieza a contar
    
	while(1)
		{
		if(flag_timmer0)
	   		{
			flag_timmer0=0;

			if(IOPIN0&puerto_15)
				IOCLR0=puerto_15; // Apago led
			else
				IOSET0=puerto_15; // Prendo led	
			}

		}
}

Les subo el proyecto en keil y el esquemático en Proteus para que puedan simularlo.
 

Adjuntos

  • PLL y Timer.zip
    109.8 KB · Visitas: 45
Hola amigos, estoy trabajando con el lpc 2148 y estoy teniendo problemas con la interrupción UART1, si la hace pero solo una vez, me gustaría saber si alguien tiene algún código para ver las configuraciones que debe de tener y muchas gracias por su atención.
 
Hola, bueno les comento que apenas estoy leyendo todo lo relativo a procesadores ARM.
Aclaro que aún no me quedan muchas cosas claras (en lenguaje ensamblador) como el modo ARM ,thumb, thumb-2 , privilegiado y no privilegiado etc.
Temas que ahora mismo estoy aprendiendo, en fin la cuestión es que mientras aprendo he decidido empezar a practicar cosas sencillas a la par para ir adquiriendo experiencia y (maña).
Bueno por ahora he tratado de realizar algo tan sencillo como cargar el valor hexadecimal 0x00000020 a la dirección 0x400FE608 de un microcontrolador tiva C cortex m4 f
Bueno el código lo estoy realizando en el compilador IAR para arm versión 7.10.xxx
Todo va bien excepto por la instrucción mov.w R0,#0x400FE608 que me marca un error el cual dice que el operando esta fuera de rango, el cual se supone puede ser de 32 bits.
¿Alguien me podría dar una idea de lo que estoy haciendo mal?
Se los agradecería mucho.
Código:
   NAME    main
        
        PUBLIC  __iar_program_start
        
        SECTION .intvec : CODE (2)
        CODE32
        
__iar_program_start
        B       main

        
        SECTION .text : CODE (2)
        CODE32

main    
        MOV.W   R0,#0x00000020
        MOV.W   R1,#0x400FE000
        STR.W   R0,[r1,#0x608]

        NOP
        B main

        END
---------- Actualizado después de 1 hora ----------

Bueno, les comento que acabo de revisar y el cortex m4 f al parecer no soporta el modo 32 bits en ensamblador :cry:
He ahí el problema, lo que si se puede hacer es separar la palabra en 2 partes de 16 bits o 4 de 8 bits, aunque todavía no me queda claro como acomodar las partes en el registro que sí es de 32 bits, pero lo voy a intentar y ya les comentare el resultado.
Buenas noches a todos.
 
Última edición por un moderador:
buenas tardes compañeros , sigo intentando hacer parpadear un led con el tiva C 123xxx en lenguaje ensamblador, sin mucho exito y como les comente en el mensaje anterior apenas estoy aprendiendo la arquitectura de ARM pero estoy haciendo mi tarea que es estudiarlo; bueno he estado leyendo los manuales de ARM y ti pero me surgen ,muchas dudas. Sé que el cortex M4 ejecuta sus instrucciones en modo thumb, y sé tambien que existe el CMSIS (cortex microcontroller software interface) que por lo que pude entender es una capa de abstarccion (API) para poder acceder a ciertos perifericos y demas prestaciones del micro independientemente del fabricante del micro , ahora mis dudas son;

?el modo thumb lo indica alguna directiva del compilador-preproceasdor o la instruccion misma?

?se pueden usar las instrucciones thumb sin hacer uso de las librerias CMSIS? y de ser posible esto, puedo acceder a registros que no sean de uso general como lo son r0 r1 etc usando su direccion inmediata?

espero me puedan ayudar ya que existe muy poca informacion para ARM en ensamblador (por obvias razones) muchas gracias por su atencion y bonita tarde :D
 
Atrás
Arriba