Comenzando con la Arquitectura ARM

Los desarrollos basados en sistemas Cortex tienen su espacio en Servisystem y aquí podrás compartir toda la información y avances que tengas con esta arquitectura que sorprende a todos.

Moderador: HJ

Re: Comenzando con la Arquitectura ARM

Notapor elgarbe » Dom Abr 14, 2013 9:04 pm

Bueno, finalmente llegó la parte importante, aquí es donde podremos ver en funcionamiento al uC con todos sus periféricos.
Antes que nada comentar que en mi caso solo he probado el Code-Red (como comenta Suky, un IDE Eclipse modificado para funcionar con los micros de NXP, esto es los driver para las LPC_Link, etc.). Sé que hay muchas variantes, como keil, coocox y otros. En mi caso arranco con Code-Red ya que es el sugerido por NXP.
Para la instalación del Red Suit 5 seguiremos el siguiente Link: http://www.code-red-tech.com/RedSuite5/lpcxpresso-5.php En él podemos ver que hay una versión gratuita, que nos permitirá crear firmwares de hasta 128Kb. Luego si queremos crear firmwares más grande tendremos que pagar USD1 por cada Kb pero comprando 256 o 512 Kb.
Bien, una vez bajado e instalado el software tendremos que ver el material de lectura OBLIGATORIO para poder trabajar con la LPCXpresso.
El primer manual a conseguir es el User Manual del uC que posee nuestra placa. Este sería el User Manual del uC LPC1769. Este Manual contiene toda la información necesaria para entender el funcionamiento de cada periférico de nuestro uC. Cada una de las 840 páginas del manual posee información útil, perfectamente explicada. Bajo mi punto de vista micro chip debería leer este manual para copiar como se hace un manual de un micro.
En principio conviene dedicarle una horita a revisar cómo está organizado el manual, la información que contiene, etc. antes de seguir. Tener en cuenta que trabajaremos SIEMPRE y en todo momento con este manual.
Si bien los capítulos 1 a 3 tienen información importante, comenzaremos a trabajar a partir del capítulo 4…
Eso, en la próxima entrega…
A palabras producidas por mentes inoperantes órganos auditivos en posicion de relax

You can be anything you want to be just turn yourself into anything you think that you could ever be - Freddie Mercury
Avatar de Usuario
elgarbe
 
Mensajes: 261
Registrado: Jue Mar 21, 2013 8:27 pm
Ubicación: Villa Ramallo - Buenos Aires - Argentina

Re: Comenzando con la Arquitectura ARM

Notapor elgarbe » Sab May 11, 2013 11:54 pm

Bueno, tratemos de seguir adelante con la parte interesante, las pruebas!!!!
En el post anterior les había comentado que íbamos a arrancar con el Capítulo 4, que habla del Clock y del Power. Pero esos temas son un poquito avanzados por ahora y debido a que las CMSIS ya nos configuran el Clock, lo dejaremos para más adelante.
Lo que haremos como primer paso es el típico Hello World! de los uC, encender y apagar un LED.
Partiremos de la base de que ya tienen instalado y activado el Code Red, por lo que solo debemos dedicarnos a escribir código.
Para comenzar un proyecto desde 0, lo mejor es ir a File->New->Project. En la primer pantalla elegimos c/c++ y dentro de dicha carpeta “LPCXpresso C Project”. Luego nos pide que elijamos un Wizard, elegimos el correspondiente a nuestra placa: “LPC13/17/18” -> “LPC175x-6x” y finalmente “C Project”. Le damos un nombre a nuestro proyecto y elegimos el espacio de trabajo (workspace) por defecto. Luego veremos qué es esto de los workspace. Luego nos pide que elijamos nuestro uC, elegimos “LPC1769” y le damos a siguiente. La última pantalla, es muy importante, en “CMSIS library to link Project to” debemos elegir alguna librería CMSIS, en mi caso elijo la 2p00. Esas librerías tienen estructuras de datos con nombres y miembros iguales a los registros del uC, por lo que facilita enormemente la codificación, también tiene funciones de inicialización del uC, etc. Ya veremos en detalle que es y que trae el CMSIS, por ahora solo saber que necesitamos hacer uso de dichas librerías.
Bien, el wizard finaliza creándonos una estructura de directorios y archivos necesarios para nuestro proyecto. Dentro de la carpeta SRC nos crea el main.c, ahí es donde escribiremos nuestro código.
El main.c recién creado nos quedá así:
Código: Seleccionar todo
Name        : main.c
 Author      :
 Version     :
 Copyright   : Copyright (C)
 Description : main definition
===============================================================================
*/

#ifdef __USE_CMSIS
#include "LPC17xx.h"
#endif

#include <cr_section_macros.h>
#include <NXP/crp.h>

// Variable to store CRP value in. Will be placed automatically
// by the linker when "Enable Code Read Protect" selected.
// See crp.h header for more information
__CRP const unsigned int CRP_WORD = CRP_NO_CRP ;

// TODO: insert other include files here

// TODO: insert other definitions and declarations here

int main(void) {
   
   // TODO: insert code here

   // Enter an infinite loop, just incrementing a counter
   volatile static int i = 0 ;
   while(1) {
      i++ ;
   }
   return 0 ;
}


Bien, por ahora no nos detendremos en demasiados detalles del código creado, simplemente diremos que se incluyen algunos archivos y luego tenemos la función main, con un bucle infinito.
En nuestro primer programa aremos uso del LED que viene soldado al puerto P0.22 de la LPCXpresso.
En el capítulo 9 del user manual tenemos todo lo relacionado con los pines del uC y sus funciones y configuración.
Básicamente tenemos 2 grupos de registros para configurar los pines de cada puerto, el registro PINSEL y el registro PINMODE
Tenemos 10 registros PINSEL, cada uno de 32 bits. Debido a que necesitamos 2 bits por cada pin para su configuración es que cada registro puede configurar 16 pines de un puerto. Estos es, el bit 0 y el bit 1 del registro PINSEL0 configuran el pin 0 del puerto 0. Los valores de configuración son:
00 -> Función primaria del pin, generalmente GPIO
01 -> Primer función alternativa del pin
10 -> Segunda función alternativa del pin
11 -> Tercera función alternativa del pin
Por ejemplo, el pin 22 del puerto 0 tiene como función primaria ser GPIO, como segunda RI1 de la UART1 y como tercera función TD1 del CAN1.
Después de un reset se escriben todos 0 en los pinsel.
El segundo registro de configuración es PINMODE, este registro se usa para configurar el comportamiento de un pin elegido como entrada, tenemos pull_up (00), modo repetición (01), sin pull_up ni pull_down (10) y pull_down (11).
También tenemos un registro PINMODE_OD que nos permite poner una salida en Drenador abierto.
Bien, pasamos ahora al capítulo 9, donde tenemos el uso de un pin configurado como GPIO.
De forma básica tenemos 3 registro para el manejo de las GPIO’s: FIODIR, FIOSET y FIOCLR. Hay dos registros más, pero los veremos más adelante.
FIOxDIR nos permite establecer al pin como entrada (0) o como salida (1). Son registros de 32 bits y tenemos uno por cada puerto. En nuestro caso usaremos el FIO0DIR para configurar el pin 22 como salida.
FIOxSET nos permite poner en 1 los bits que queramos del puerto. Al escribir un 1 en un bit dado, nos enciende dicho bit. Escribir un 0 no tiene efecto alguno.
FIOxCLR nos permite poner en 0 los bits que queramos del puerto. Al escribir un 1 en un bit dado, nos apaga dicho bit. Escribir un 0 no tiene efecto alguno.

Bien con todo esto podemos hacer nuestro primer programa. Lo que haremos es, configurar al pin P0.22 como GPIO, sin PU ni PD, como salida y luego en un bucle infinito encenderemos el pin, haremos un delay, apagaremos el led y repetimos de forma infinita.
Para configurar los pines haremos lo siguiente:
Código: Seleccionar todo
LPC_PINCON->PINSEL1 = (0x0);       //Configuro pines P0.16 a P0.31 como GPIO

LPC_PINCON->PINMODE1 = 0b01010101010101010101010101010101;   //Sin PU ni PD para P0.16 a P0.31

LPC_GPIO0->FIODIR = 0b00000000010000000000000000000000;       //FIODIR=1 -> Output

Si bien no es la forma más elegante de hacer las cosas en C, funciona. Luego veremos la forma “correcta” de hacer esto. En la primer línea pongo todo 0 en el registros PINSEL1, con lo que configuro P0.16 a P0.32 como GPIO. Luego escribo 01 en todos los pines alcanzados por PINMODE1, o sea P0.16 a P0.31. Finalmente configuro como salida el pin 22 del puerto 0 y el resto de los pines como entrada.
Nuestro bucle infinito hará lo siguiente:
Código: Seleccionar todo
   volatile static int i = 0 ;
   volatile static int k = 0 ;
   while(1) {
      for(i=0;i<100;i++){
         for(k=0;k<65535;k++);
      }
      LPC_GPIO0->FIOSET = 0b10000000000000000000000;
      for(i=0;i<100;i++){
         for(k=0;k<65535;k++);
      }
      LPC_GPIO0->FIOCLR = 0b10000000000000000000000;

   }

Como no conocemos ninguna forma de hacer delay, nos inventamos unos con dos lazos for anidados. Luego de cada lazo, encendemos/apagamos el LED. No es el código más bonito, pero es el primero que escribiríamos con nuestros conocimientos previos.
Bien, este es un simple video de lo visto hasta aquí, agrego algo de debug y compilación:



Saludos y hasta la próxima!
Última edición por elgarbe el Dom May 19, 2013 7:00 pm, editado 1 vez en total
A palabras producidas por mentes inoperantes órganos auditivos en posicion de relax

You can be anything you want to be just turn yourself into anything you think that you could ever be - Freddie Mercury
Avatar de Usuario
elgarbe
 
Mensajes: 261
Registrado: Jue Mar 21, 2013 8:27 pm
Ubicación: Villa Ramallo - Buenos Aires - Argentina

Re: Comenzando con la Arquitectura ARM

Notapor elgarbe » Dom May 19, 2013 3:43 pm

Bueno, una vez visto el primer programa y comenzando a familiarizarnos con las estructuras de datos (LPC_GPIO0) y sus miembros (FIOCLR, FIOSET, etc.), las cuales nos permiten hacer uso del I/O veremos algunas cuestiones de programación.
En el ejemplo anterior usamos estas tres líneas para configurar parte del puerto 0:
Código: Seleccionar todo
LPC_PINCON->PINSEL1 = (0x0);       //Configuro pines P0.16 a P0.31 como GPIO
LPC_PINCON->PINMODE1 = 0b01010101010101010101010101010101;   //Sin PU ni PD para P0.16 a P0.31
LPC_GPIO0->FIODIR = 0b00000000010000000000000000000000;       //FIODIR=1 -> Output

Lo primero que cualquiera puede objetar es que con esas instrucciones somos un poco “invasivos” y poco considerados. Esto es porque en vez de configurar lo que a mí me interesa (el Pin22 del puerto 0), estoy configurando la mitad del puerto 0 en un caso y todo el puerto 0 en el otro. Es deseable configurar solamente lo que a mí me interesa y dejar el resto sin tocar.
Para ello debemos hacer uso de una práctica muy poco vista en programación C de uC y es el uso de manipulación de bits.
Hay tres operaciones básicas e importantes para manipular bits, el operando AND, el operando OR y el operando >> ó << (desplazamiento de bits).
Si hago A=01011110, B=10010001 entonces:
C = A AND B -> 00010000
C = A OR B -> 11011111
C = A >> 1 -> 00101111 (se rotan los bits 1 posición a la derecha y se ingresa un 0 por la izquierda)
C = B << 5 -> 00100000
Las preguntas a responder son, como escribir un 0 en una posición determinada sin modificar el resto de los bits y como escribir un 1 en una posición determinada sin modificar el resto de los bits. Otra duda puede ser como escribir combinaciones de 1 y 0 a la vez sin modificar el resto de los bits.
La respuesta está en las operaciones AND y OR.
Escribir un 1 en una posición determinada es lo más simple de resolver. Si tengo el dato a modificar, supongamos 00110101, y el bit que quiero poner en uno, supongamos el 4, podría hacer lo siguiente:
Dato = Dato OR 00001000
De esta forma “enmascaro” el dato original, haciendo un OR con un 1 en la posición en la que quiero me quede ese 1. El resto de los bit’s pasan la máscara sin ser modificados.
Una forma abreviada de escribir lo anterior es:
Dato OR= 00001000 ó lo que es lo mismo Dato |= 00001000
Bien, el problema que presenta esto es que ese 1 en la cuarta posición no sé que representa, y si lo sé, seguramente al mes cuando quiero revisar el código ya me olvide.
Entonces puedo hacer uso de las operaciones rotación.
Defino PIN_LED (1 << 4)
Con esta instrucción estoy definiendo PIN_LED como 00001000 (si fuese 1 byte)
Entonces la instrucción completa sería:
Código: Seleccionar todo
#define PIN_LED   (1 << 22)
LPC_GPIO0->FIODIR |= PIN_LED;
 

Este código es mucho más legible y fácil de mantener, además de no ser invasivo. Con invasivo me refiero a que quizás en una parte anterior del código definí un pin determinado como entrada/salida, por ello no puedo luego escribir todo el registro con datos nuevos. Me tengo que limitar a configurar lo que necesito y solo eso.
Bien, que pasa ahora con escribir 0’s en posiciones determinadas?
Bueno eso es un poquito más complicado, aunque no mucho. La estrategia es hacer un AND, pero no contra la máscara deseada, sino contra el complemento de la máscara deseada.
Por ejemplo, si quiero escribir 2 ceros en los bit’s 4 y 5, la máscara en principio sería 00011000, pero para usar la función AND y poner 0’s uso el complemento -> 11100111. Luego si hago AND de un dato contra esa máscara, los bits 4 y 5 quedarán con 0’s, pero el resto de los bits quedarán con el valor que traían.
Porque usamos una máscara y luego su complemento? Simple, por la longitud del dato. Supongamos que estamos trabajando con un dato de 32 bits. Si quiero poner en 0 los bits 4 y 5 mi máscara sería 11111111111111111111111111100111, una locura! Gracias a la función complemento puedo usar la máscara Complemento(11000) para obtener la máscara anterior. El compilador se encargará de verificar con que registros estamos trabajando y de que longitud debe ser ese complemento. Mejor aún puedo hacer complemento(1 << 4 | 1 << 5).
La operación complemento se simboliza con ~
Entonces juntando todo esto puedo escribir:
Código: Seleccionar todo
LPC_PINCON->PINSEL1   &= (~(3 << 12));
 

Aquí el 3 es en binario 11, lo corro 12 lugares ya que PINSEL1 configura los pines 16 a 32 (2 bits por pin). Luego lo complemento y finalmente hago la función AND (&).
Les dejo como tarea pensar cómo hacer para escribir, por ejemplo, los bits 10 en una posición determinada.
Será hasta la próxima!
A palabras producidas por mentes inoperantes órganos auditivos en posicion de relax

You can be anything you want to be just turn yourself into anything you think that you could ever be - Freddie Mercury
Avatar de Usuario
elgarbe
 
Mensajes: 261
Registrado: Jue Mar 21, 2013 8:27 pm
Ubicación: Villa Ramallo - Buenos Aires - Argentina

Re: Comenzando con la Arquitectura ARM

Notapor elgarbe » Dom May 19, 2013 6:08 pm

Bien, sigamos un poco más con las ideas o conceptos fundamentales utilizados en C para uC.
En el apartado anterior vimos como usar AND, OR, >>, << y ~ para modificar los bits de configuración que queremos y dejar los demás en el estado en que estaban.
Pero hay un concepto más importante, o superior a este y es la idea de “Periféricos Mapeados en Memoria”.
Me toco pasar por una experiencia extraordinaria al pasar de usar CCS al C de los ARM. En CCS existen instrucciones como output_b, input_a, setup_osilator, etc. Esas instrucciones que un principio parece ayudarnos, no hacen otra cosa que aislarnos del verdadero principio de la programación en C de uC. En cambio, cuando terminé de comprender lo que hacían las estructuras de datos (LPC_GPIO) con sus miembros (FIODIR) me entro una sensación de dominio total sobre el software que estaba escribiendo
Como todos saben, cada uC tiene un mapa de memoria y cada periférico del mismo tiene una o varias posiciones en la memoria. Configurar cualquier periférico no es más que escribir ciertos valores en registros específicos de la memoria. Entonces instrucciones de alto nivel como output_b son poco más que indeseables para mí una vez entendido y, sobre todo, asimilado el concepto de periférico mapeado en memoria.
Si les interesa leer más del tema, aquí hay un artículo fantástico:
http://www.downtowndougbrown.com/2010/1 ... ripherals/

Bien, entendiendo un poco dicho concepto veremos como las librerías del CMSIS nos dan estructuras completas de datos para que podamos acceder a cada periférico con los mismos nombres que el User Manual del uC pone a cada bit de cada registros.
Las primeras instrucciones de un programa que usa las CMSIS son:
Código: Seleccionar todo
#ifdef __USE_CMSIS
#include "LPC17xx.h"
#endif

Esto significa que si tenemos declarado el __USE_CMSIS en el compilador (o linkeador, no estoy seguro), entonces debe incluirse el archivo device.h, donde device en nuestro caso es LPC17XX. Cada proveedor de uC provee este archivo para que se pueda utilizar con el resto de las librerías del CMSIS.
Bien, dentro de ese archivo tenemos, entre otras cosas, lo siguiente:
Código: Seleccionar todo
#include "core_cm3.h"         /* Cortex-M3 processor and core peripherals           */
#include "system_LPC17xx.h"   /* System Header                               

Se incluyen los archives del core del uC y del systema. Más adelante veremos esto.
Luego encontramos:
Código: Seleccionar todo
/*------------- General Purpose Input/Output (GPIO) --------------------------*/
typedef struct
{
  union {
    __IO uint32_t FIODIR;
    struct {
      __IO uint16_t FIODIRL;
      __IO uint16_t FIODIRH;
    };
    struct {
      __IO uint8_t  FIODIR0;
      __IO uint8_t  FIODIR1;
      __IO uint8_t  FIODIR2;
      __IO uint8_t  FIODIR3;
    };
  };
  uint32_t RESERVED0[3];
  union {
    __IO uint32_t FIOMASK;
    struct {
      __IO uint16_t FIOMASKL;
      __IO uint16_t FIOMASKH;
    };
    struct {
      __IO uint8_t  FIOMASK0;
      __IO uint8_t  FIOMASK1;
      __IO uint8_t  FIOMASK2;
      __IO uint8_t  FIOMASK3;
    };
  };
  union {
    __IO uint32_t FIOPIN;
    struct {
      __IO uint16_t FIOPINL;
      __IO uint16_t FIOPINH;
    };
    struct {
      __IO uint8_t  FIOPIN0;
      __IO uint8_t  FIOPIN1;
      __IO uint8_t  FIOPIN2;
      __IO uint8_t  FIOPIN3;
    };
  };
  union {
    __IO uint32_t FIOSET;
    struct {
      __IO uint16_t FIOSETL;
      __IO uint16_t FIOSETH;
    };
    struct {
      __IO uint8_t  FIOSET0;
      __IO uint8_t  FIOSET1;
      __IO uint8_t  FIOSET2;
      __IO uint8_t  FIOSET3;
    };
  };
  union {
    __O  uint32_t FIOCLR;
    struct {
      __O  uint16_t FIOCLRL;
      __O  uint16_t FIOCLRH;
    };
    struct {
      __O  uint8_t  FIOCLR0;
      __O  uint8_t  FIOCLR1;
      __O  uint8_t  FIOCLR2;
      __O  uint8_t  FIOCLR3;
    };
  };
} LPC_GPIO_TypeDef;
 

Esta parte del código crea un tipo de dato llamado LPC_GPIO_TypeDef y del tipo estructura. La estructura esta formado por uniones de estructuras y cada miembro de cada estructura es un registro determinado en el User Manual. Por ejemplo, al principio se declara FIODIR (de 32 bits), dentro de ella se declara la parte alta y la parte baja y dentro de ella grupos de 8 bits.
Aquí solo se declara la forma que contendrá la estructura utilizada para hacer IO.
Código: Seleccionar todo
/******************************************************************************/
/*                         Peripheral memory map                              */
/******************************************************************************/
/* Base addresses                                                             */
#define LPC_FLASH_BASE        (0x00000000UL)
#define LPC_RAM_BASE          (0x10000000UL)
#define LPC_GPIO_BASE         (0x2009C000UL)
 

Ahí se define la dirección de memoria "BASE" del GPIO es 0x2009C000 (ver tabla 3 página 12 del manual del LPC17xx).
Luego se define en forma particular cada Puerto (GPI0..4):
Código: Seleccionar todo
/* GPIOs                                                                      */
#define LPC_GPIO0_BASE        (LPC_GPIO_BASE + 0x00000)
#define LPC_GPIO1_BASE        (LPC_GPIO_BASE + 0x00020)
#define LPC_GPIO2_BASE        (LPC_GPIO_BASE + 0x00040)
#define LPC_GPIO3_BASE        (LPC_GPIO_BASE + 0x00060)
#define LPC_GPIO4_BASE        (LPC_GPIO_BASE + 0x00080)


y finalmente lo que realmente nos interesa, la definición de la estructura propiamente dicha para GPIO:
Código: Seleccionar todo
/******************************************************************************/
/*                         Peripheral declaration                             */
/******************************************************************************/
#define LPC_SC                ((LPC_SC_TypeDef        *) LPC_SC_BASE       )
#define LPC_GPIO0             ((LPC_GPIO_TypeDef      *) LPC_GPIO0_BASE    )
#define LPC_GPIO1             ((LPC_GPIO_TypeDef      *) LPC_GPIO1_BASE    )
#define LPC_GPIO2             ((LPC_GPIO_TypeDef      *) LPC_GPIO2_BASE    )
#define LPC_GPIO3             ((LPC_GPIO_TypeDef      *) LPC_GPIO3_BASE    )
#define LPC_GPIO4             ((LPC_GPIO_TypeDef      *) LPC_GPIO4_BASE    )


Se declara la VARIABLE LPC_GPIO0 como un puntero del tipo LPC_GPIO_TypeDef y que apunta a la dirección de memoria LPC_GPIO0_BASE

De esta forma usamos la estructura LPC_GPIO0 como si fuese directamente la memoria y por ende el IO y al escribir en sus miembros estamos escribiendo en la memoria en donde está mapeado el IO.
Desde que entendí esta parte ya no quiero más instrucciones del tipo output_b, ahora simplemente quiero hacer uso de estructuras de datos que me permitan acceder a la memoria directamente y que los nombres de los miembros de las estructuras sean los mismos que los nombres de los registros en los User Manual de cada uC.
Bien, el LPC17xx.h me provee todas las estructuras necesarias para usar todos los periféricos del uC!!!!!! Yo no tengo que escribir nada, simplemente leer el user maual, ver que registros hay que configurar y luego usar la estructura de datos del periférico en cuestión!!!
Ojalá Microchip escribiese esas estructuras de datos para nosotros, la programación en XC8 sería mucho más fácil!
Bueno, será hasta la próxima!!!
A palabras producidas por mentes inoperantes órganos auditivos en posicion de relax

You can be anything you want to be just turn yourself into anything you think that you could ever be - Freddie Mercury
Avatar de Usuario
elgarbe
 
Mensajes: 261
Registrado: Jue Mar 21, 2013 8:27 pm
Ubicación: Villa Ramallo - Buenos Aires - Argentina

Anterior

Volver a ARM

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 1 invitado

cron