Computing">
Arquitectura de 32bits ARM
Arquitectura de 32bits ARM
Arquitectura de 32bits ARM
Presentaremos una serie de ejemplos aplicados a un procesador en particular, tratando de ejemplificar de forma
clara y concisa los conceptos enunciados.
Historia
El diseño del ARM comenzó en 1983 como un desarrollo de la
empresa Acorn Computers Ltd. Roger Wilson y Steve Furber lideraban
el equipo, cuya meta era, originalmente, el desarrollo de un
procesador avanzado, pero con una arquitectura similar a la del MOS
6502. La razón era que Acorn tenía una larga línea de computadoras
personales basadas en dicho micro, con lo que la idea era desarrollar
uno nuevo con el que los desarrolladores de aplicaciones para dichos
ordenadores se sintieran cómodos.
La arquitectura del ARM2 posee un bus de datos de 32 bits y ofrece un espacio de direcciones de 26 bits, junto con
16 registros de 32 bits. Uno de estos registros se utiliza como contador de programa, aprovechándose sus 4 bits
superiores y los 2 inferiores para contener los flags de estado del procesador.
El ARM2 es probablemente el procesador de 32 bits útil más simple del mundo, ya que posee sólo 30.000
transistores. Su simplicidad se debe a que no está basado en microcódigo (sistema que suele ocupar alrededor de
la cuarta parte de la la pastilla del procesador) y a que, como era común en aquella época, no incluye caché.
Gracias a esto, su consumo de energía es bastante bajo, a la vez que ofrece un mejor rendimiento que un 286. Su
sucesor, el ARM3, incluye una pequeña memoria caché de 4 Kb, lo que mejora los accesos repetitivos a memoria.
A finales de los años 80, Apple Computer comenzó a trabajar con Acorn en nuevas versiones del núcleo ARM.
En Acorn se dieron cuenta de que el hecho de que el fabricante de un procesador fuese también un fabricante de
ordenadores podría alejar a la competencia, por lo que se decidió a crear una nueva compañía llamada Advanced
RISC Machines, que sería la encargada del diseño y gestión de las nuevas generaciones de procesadores ARM.
Ocurría esto en el año 1990.
Este trabajo derivó en el ARM6, presentado en 1991. Apple utilizó el ARM 610 (basado en el ARM6), como
procesador básico para su innovador PDA, el Apple Newton. Por su parte, Acorn lo utilizó en 1994 como
procesador principal en su RiscPC.
El núcleo mantuvo su simplicidad a pesar de los cambios: en efecto, el ARM2 tiene 30.000 transistores, mientras
que el ARM6 sólo cuenta con 35.000. La idea era que el usuario final combinara el núcleo del ARM con un número
Técnicas Digitales II – Arquitectura de 32bits – ARM Autor:Pablo Luis Joaquim 3/44
opcional de periféricos integrados y otros elementos, pudiendo crear un procesador completo a la medida de sus
necesidades.
La mayor utilización de la tecnología ARM se alcanzó con el procesador ARM7TDMI, con millones de unidades en
teléfonos móviles y sistemas de videojuegos portátiles.
DEC licenció el diseño, lo cual generó algo de confusión debido a que ya producía el DEC Alpha, y creó el
StrongARM. Con una velocidad de reloj de 233 Mhz, este procesador consumía solo 1 watt de potencia (este
consumo de energía se ha reducido en versiones más recientes). Esta tecnología pasó posteriormente a manos de
Intel, como fruto de un acuerdo jurídico, que la integró en su línea de procesadores Intel i960 e hizo más árdua la
competencia.
Freescale (una empresa que derivó de Motorola en el año 2004), IBM, Infineon Technologies, Texas Instruments,
Nintendo, Philips, VLSI, Atmel, Sharp, Samsung y STMicroelectronics también licenciaron el diseño básico del
ARM.
El diseño del ARM se ha convertido en uno de los más usados del mundo, desde discos rígidos hasta juguetes.
Hoy en día, cerca del 75% de los procesadores de 32 bits poseen este chip en su núcleo.
Arquitectura ARM7
Introducción
Hablaremos en particular sobre la arquitectura ARM7TDMI por ser la mas utilizada en la gran mayoría de las
aplicaciones industriales de microprocesadores RISC de 32bits. Provee bajo consumo, pequeño tamaño, y alto
rendimiento, tan necesario en aplicaciones portátiles y embebidas.
Existen otras familias ARM7 compatibles como la ARM7TDMI-S, ARM720T, y la ARM7EJ-S, todas ellas con sutiles
diferencias entre sí.
El software desarrollado para ARM7TDMI es totalmente compatible hacia arriba, es decir con familias ARM9,
ARM9E, ARM10, StrongARM y XScale.
Existen sistemas operativos embebidos para estas arquitecturas como Windows CE, Linux, Palm OS y Symbian
OS; sistemas operativos en tiempo real como QNX, Wind River, VxWork y VRTX de Mentor Graphics.
El núcleo ARM7TDMI esta basado en arquitectura Von Neumann, con un bus de 32-bits de datos e instrucciones.
Los datos pueden accederse de a 8, 16, o 32 bits.
Arquitectura VonNeumann
El concepto central en la Arquitectura Von Neumann es que las instrucciones y los datos tenían que almacenarse juntos en un medio
común y uniforme , en vez de separados , como hasta entonces se hacía. De esta forma , no sólo se podían procesar cálculos , sino
que también las instrucciones y los datos podían leerse y escribirse bajo el control del programa. A partir de esta idea básica se sigue
que un elemento en la memoria tiene una calidad ambigua con respecto a su interpretación; esta ambigüedad se resuelve , sólo
temporalmente , cuando se requiere ese elemento y se ejecuta como una instrucción , o se opera como un dato. Un beneficio de esta
ambigüedad es el hecho de que un dato , obtenido como resultado de algunas operaciones en la unidad aritmetico-lógica del
computador , podía colocarse en la memoria como si fuera cualquier otro dato , para entonces usarlo y ejecutarlo como si fuera una
instrucción.
Técnicas Digitales II – Arquitectura de 32bits – ARM Autor:Pablo Luis Joaquim 4/44
El pipeline de instrucciones (3 etapas)
Permite superponer en el tiempo la ejecución de
varias instrucciones a la vez.
No requiere hardware adicional. Solo se necesita
lograr que todas las partes del procesador trabajen a
la vez.
Trabaja con el concepto de una línea de montaje:
o Cada operación se descompone en partes.
o Se ejecutan en un mismo momento diferentes
partes de diferentes operaciones.
o Cada parte se denomina etapa (stage).
El resultado: Una vez entrado en regimen ejecuta a razón de una instrucción por ciclo de clock.
Una instrucción de salto provoca el borrado del pipeline.
Excepciones
Se llaman excepciones a los eventos que se producen durante la ejecución de un programa, por ejemplo, para
atender una interrupción desde un periférico. Antes de tratar de manejar una excepción, el procesador ARM7TDMI
resguarda el estado actual del programa para que luego de darle atención al handler pueda regresar a su estado
original.
Si dos o mas excepciones llegan simultáneamente, se atenderán de acuerdo a un orden prefijado según la
siguiente tabla:
PRIORIDAD EXCEPCIÓN
1 (Máxima Reset
Prioridad)
2 Data Abort
3 FIQ
4 IRQ
5 Prefetch Abort
6 (Mínima Prioridad) Undefined instruction
SWI
El handler de las excepciones se ejecuta siempre en modo ARM. Si una excepción ocurre en modo THUMB, el
procesador pasa automáticamente a modo ARM. El regreso a modo THUMB al finalizar la ejecución del handler
también es automático. Puede ejecutarse en modo THUMB, cambiándolo “manualmente”, pero es necesario
cambiar a modo ARM antes de volver para finalizar correctamente.
Al hablar de excepciones nos referimos tanto a interrupciones de hardware como de software, aunque estas
últimas no puedan considerarse un evento inesperado, se consideran excepciones por el tratamiento que se hace
de ellas.
Lo mismo sucede con el RESET del microprocesador, su tratamiento es similar al de una excepción.
Vector de Excepciones
En la dirección de cada vector se pone una instrucción de salto a la dirección de la rutina de tratamiento de la
excepción, excepto en las interrupciones FIQ, que al ser el último vector, puede empezar el código de forma
inmediata, con el consiguiente ahorro de tiempo.
Formatos de memoria
El ARM7TDMI puede configurarse para trabajar tanto con el formato de almacenamiento de words big-endian
como little-endian. El sistema Big-Endian, adoptado por motorota entre otros, consiste en representar los bytes en
el orden “natural”, a diferencia del sistema little-endian adoptado por Intel, entre otros, en el que los datos se
almacenan al revés.
A estas arquitecturas que manejan ambos formatos a veces se las denomina middle-endian.
ARM de 32bits
THUMB de 16bits
Una instrucción en modo ARM es de 32bits de longitud. El modo THUMB es un modo de trabajo comprimido de
16bits. Se pueden lograr velocidades de acceso mayores a memorias de 16bits y mayor densidad de código
trabajando en modo THUMB, lo que hace a esta arquitectura ideal para aplicación embedded.
El código generado en modo THUMB utiliza mas instrucciones para la misma tarea. Con lo cual el código
en modo ARM es mas eficiente para maximizar el rendimiento en aplicaciones donde el tiempo de
ejecución es crítico.
El modo THUMB no posee ciertas instrucciones necesarias para el manejo de excepciones, con lo cual el
micro conmuta a modo ARM para atender una excepción.
En caso de tener una memoria de código de 16bits, trabajando en modo ARM será necesario hacer dos accesos a
memoria para leer el código de operación, con lo cual en estos casos es recomendable utilizar el modo THUMB.
Sin embargo la RAM, en estos sistemas, suele ser de 32bits de ancho, con lo cual al ejecutar código desde la RAM
en modo ARM suele ser lo más adecuado cuando el tiempo es crítico en la operación.
Ambos modos de trabajo pueden utilizarse alternativamente en el mismo programa, como veremos mas adelante.
Modos de operación
El ARM7TDMI tiene siete modos de trabajo:
Todos los modos a excepción del User Mode son modos privilegiados. Estos se utilizan para atender
interrupciones de hardware, excepciones, interrupciones de software. Cada modo privilegiado tiene asociado un
SPSR (Saved Program Status Register). Este registro se utiliza para almacenar el CPSR (Current Program Status
Register) de la tarea que estaba en curso antes que la excepción ocurra.
En estos modos privilegiados, hay bancos de registros específicos para cada uno. Estos vuelven a su estado
original automáticamente al volver al estado previo de la excepción.
El System Mode no tiene bancos de registros, utiliza los registros de modo usuario.
El System Mode ejecuta las tareas que requieren un tratamiento privilegiado y permite invocar a toda clase de
excepciones.
El conjunto de registros de modo ARM contiene 16 registros, de r0 a r15. Además de un registros CPSR, que
contiene los flags de condición y los del modo de ejecución actual. Los registros r0 a r13 son de propósito general
para almacenar datos o direcciones.
Los registros r14 y r15 tienen funciones especiales: Link Register (aquí se copia el r15 cuando se ejecuta una
instrucción BL (Branco with Link) y Program Counter (PC) respectivamente.
Flags de condición:
o N: Bit de signo, 0-positivo, 1-negativo
o Z: Bit de cero, 0-resultado de la operación no dio 0, 1-resultado de la operación dio 0
o C: Bit de Carry
o V: Overflow
o Q: Desbordamiento/Saturación (solo en versiones E).
Bits de modo:
o M0,M1,M2,M3 y M4 representan el modo de operación del micro
RISC y CISC
CISC:
CISC es un modelo de arquitectura de computadores (del inglés Complex Instruction Set Computer, Repertorio de
instrucciones complejo). Los microprocesadores CISC tienen un conjunto de instrucciones que se caracteriza por
ser muy amplio y permitir operaciones complejas entre operandos situados en la memoria o en los registros
internos, en contraposición a la arquitectura RISC.
Antes de 1980 el principal objetivo en el desarrollo de los repertorios de instrucciones era aumentar su complejidad
de forma de reducir la del compilador.
Simples instrucciones del repertorio se descomponían en complejas secuencias de operación utilizando varios
ciclos de clock.
Los procesadores se vendían por su nivel de complejidad, número de modos de direccionamiento, tipos de datos
que manejaba, etc.
Esto se origino en los años 70 con el desarrollo de minicomputadoras. Estas tenían una memoria principal
relativamente lenta asociada al procesador que estaba constituído por varios circuitos integrados mas simples.
Sin embargo esta memoria ROM ocupaba una desproporcionada proporción del área del chip, dejando poco
espacio para otras funcionalidades que pudieran mejorar el rendimiento del procesador.
Así la industria de los procesadores se volcó a realizar cada vez mas complejos procesadores, con mayor cantidad
de transistores, siguiendo las demandas del mercado de las minicomputadoras, cada vez mas complejas. Esto
dejaba a los chips basados en CISC (Repertorio de instrucciones complejo) de fines de los 70s un repertorio de
instrucciones que comprometía al limitado silicio disponible.
Este tipo de arquitectura dificulta el paralelismo entre instrucciones, por lo que, en la actualidad, la mayoría de los
sistemas CISC de alto rendimiento implementan un sistema que convierte dichas instrucciones complejas en varias
instrucciones simples del tipo RISC, llamadas generalmente microinstrucciones.
Los CISC pertenecen a la primera corriente de construcción de procesadores, antes del desarrollo de los RISC.
Ejemplos de ellos son: Motorola 68000, Zilog Z80 y toda la familia Intel x86 usada en la mayoría de ordenadores
personales del planeta.
Hay que hacer notar, sin embargo que la utilización del término CISC comenzó tras la aparición de los
procesadores RISC como nomenclatura despectiva por parte de los defensores/creadores de éstos últimos.
RISC:
En este escenario de repertorios de instrucciones cada vez mas complejo nacen los procesadores RISC. Este
concepto fue utilizado en el desarrollo de los procesadores ARM, de hecho ARM significa “Advanced RISC
microprocessor”.
En 1980 Patterson y Ditzel publican un paper titulado “The Case for the Reduced Instruction Set Computer” (“El
caso de la computadora de repertorio de instrucciones reducido”) . En este trabajo exponían la visión de que la
arquitectura para un procesador sinlge-chip no tenía que ser la misma que la óptima arquitectura que para un
procesador multi-chip. Su argumento estaba sustentado en los resultados de un proyecto acerca del diseño de un
procesador de un egresado de Berkeley, el cual incorporaba una arquitectura para un repertorio de instrucciones
reducido (RISC).
Este diseño, conocido com Berkeley RISC I, era mucho mas simple que el de los procesadores CISC comerciales
de aquellos tiempos, además de que requería mucho menos esfuerzo en su desarrollo; sin embargo nunca
demostró un rendimiento similar.
El repertorio de instrucciones RISC I difería del de una minicomputadora CISC en varios aspectos:
Un tamaño fijo de palabra de instrucción de 32 bits con algunos formatos; los procesadores CISC
tipicamente tenían una longitud de palabra variable con muchos formatos.
Un arquitectura de load-store(cargar-almacenar) donde las instrucciones que procesan el dato solo operan
con registros y estan separadas de las que se utilizan para acceder a memoria. Los procesadores CISC
tipicamente almacenan valores en memoria que serán utilizados como operandos en instrucciones de
procesamiento de datos.
Un gran banco de registros de 32 bits, todos ellos pueden ser usados con cualquier propósito, permitiendo
a la arquitectura load-store trabajar eficientemente,. Los registros en un procesador CISC se iban haciendo
mas grandes y la mayoría para distintos propósitos (por ejemplo los registros de datos y direcciones en el
Motorola MC68000).
Estas diferencias simplificaban mucho el diseño del procesador y permitían al diseñador implementar una
arquitectura cuyas características aumentaban el rendimiento del dispositivo:
Patterson y Ditzel decían que RISC ofrecía las siguientes ventajas principales:
Pequeño tamaño: Un procesador tan simple requería menos transistores y menos silicio. Por lo cual toda
una CPU cabría en un chip, y dejaría mas espacio de la pastilla para otras funcionalidades, como memoria
cache, funciones para manejo de memoria, hardware para manejo de datos en punto-flotante, y mucho
mas.
Menos tiempo de desarrollo: Un procesador tan simple debía tomar menor tiempo de diseño y entonces
debía tener un menor costo de diseño.
Un mayor rendimiento: Esto era difícil de ver, en un mundo donde los rendimientos mayores se conseguían
aumentando la complejidad del sistema, este se convertía en un punto difícil de asimilar.
El argumento era algo así: las cosas pequeñas tienen frecuencias mas altas (los insectos mueven sus alas
más rápido que los pájaros pequeños, y estos más rápido que los grandes, y así siguiendo), por lo cual un
procesador mas simple debía permitir frecuencias de clock mas altas.
Estos argumentos se respaldaron en resultados experimentales y en procesadores prototipo (el Berkeley RISC II
que surgió luego del RISC I).
Los fabricantes de procesadores que estaban escépticos en un principio, y principalmente los mas nuevos en el
mercado vieron en esta arquitectura una oportunidad de bajar sus costos de desarrollo. Estos diseños RISC
comerciales, de los cuales el ARM fue el primero, mostraron que la idea funcionaba, y desde 1980 todos los
procesadores de propósitos generales han utilizado conceptos de la arquitectura RISC en una mayor o menor
medida.
Coprocesador
Se pueden conectar hasta 16 coprocesadores a un sistema ARM7TDMI.
Debug
El ARM7TDMI incorpora extensiones de hardware
para debug (debug embebido), facilitando el
desarrollo de las aplicaciones.
Se detendrá la ejecución cuando los valores pre-programados coincidan con los valores leídos en la unidad
especificada causando un breakpoint (código), o watchpoint (datos).
Así es posible especificar que deseamos que se detenga la ejecución del programa cuando el PC valga un
determinado valor, generando de esta forma un breakpoint.
La interfaz JTAG circuitalmente es muy sencilla, solo consta de buffers y adaptadores de nivel, por lo cual cualquier
interfaz JTAG debería poder analizar el port JTAG de cualquier ARM7, de la marca o línea que sea. Presentamos
un circuito propuesto. Adicionalmente es necesario tener un software que permita comunicarse con el micro a
través de la interfaz.
ARM7EJ-S
Versión sintetizable, incorpora las bondades del ARM7TDMI.
Soporta ejecución acelerada de Java y operaciones DSP.
Emplea tecnología ARM Jazelle (Máquina JAVA embebida).
ARM720T
Para sistemas que requieren manejo completo de memoria virtual y espacios de ejecución protegidos.
Memoria caché de 8K
MMU: unidad controladora de memoria.
Para aplicaciones de plataforma abierta como Windows CE, Linux, Palm OS y Symbian OS.
Buffer de escritura.
Bus de interface AMBA AHB.
Coprocesador de interface ETM para expansión del sistema y debugueo en tiempo real.
Coprocesador para control interno de la memoria caché y la MMU.
Memoria externa puede soportar procesadores adicionales o canales DMA, con pérdida mínima de
rendimiento.
Familia ARM9
Es una familia constituida por los procesadores ARM920T, ARM922T Y ARM940T.
Construida en base al procesador ARM9TDMI.
Set de instrucciones de 16 Bits.
El procesador es RISC de 32 Bits.
Buffer de escritura de 8 entradas.
Pipeline de 5 estados que alcanza 1.1 MIPS/MHz, expandible a 300 MIPS.
Bus de interface AMBA de 32 Bits.
MMU (Memory Management Unit) que soporta Windows CE, Symbian OS, Linux, Palm OS.
ARM920T y ARM922T
Macroceldas basadas en el ARM9TDMI RISC de 32 Bits convenientes para una gama de aplicaciones
basadas en plataforma OS, ofrecen caches para instrucciones y datos, son idénticos pero se
diferencian en que uno es de 16k y el otro de 8k.
MMU permitiendo soporte para otros sistemas operativos importantes.
ARM940T
Comparten características de los anteriores.
Se caracteriza por ser de 4k para caches de instrucciones y datos.
MPU habilitada para soportar sistemas operativos en tiempo real (RTOS).
Familia ARM11
Arquitectura con un potente repertorio de instrucciones tipo ARMv6
El repertorio de instrucciones Thumb reduce los requerimientos de memoria en un 35%.
Tecnología Jazelle para ejecución eficiente de JAVA embebido.
Extensiones de DPS embebidas.
Extensiones SIMD (Single Instruction Multiple Data) para aplicaciones multimedia, llevando al doble el
rendimiento en el procesamiento de video.
Tecnología ARM TrustZone otorga niveles de seguridad on-chip (ARM1176JZ-S y ARM1176JZF-S cores)
Núcleo Thumb-2, mejora el rendimiento, el uso de energía y la densidad de código (ARM1156T2-S y
ARM1156T2F-S)
Bajo consumo:
o 0.6mW/MHz (0.13µm, 1.2V) incluyendo los controladores de cache
o Modos de bajo consume para ahorro de energía
o Gerenciamiento inteligente de la energía (Intelligent Energy Manager (IEM)) en forma dinámica
(ARM1176JZ-S and ARM1176JZF-S)
Procesadores de alto rendimiento:
o Pipeline de 8 etapas, aumenta la velocidad del clock (9 etapas para el ARM1156T2(F)-S)
o Pipelines separados para las operaciones de carga-almacenamiento y aritméticas
o Predicción de salto (Branch Prediction) y Retorno de pila (Return Stack)
Sistema de memoria de alto rendimiento
o Posee cache de 4-64k
o Se puede manejar una memoria adicional mediante canal DMA para aplicaciones multimedia
o Sistema de memoria de alto rendimiento de 64 bits aumenta la velocidad de acceso a los datos
para procesamiento de aplicaciones multimedia y aplicaciones de redes.
o Sistema de memoria ARMv6 acelera el cambio de contexto del Sistema Operativo
Interfaz de interrupciones vectorizadas y modo de baja latencia, aumentando la velocidad de respuesta y el
rendimiento en tiempo real
Coprocesador para Punto Flotante (ARM1136JF-S, ARM1176JZF-S y ARM1156T2F-S) para aplicaciones
de control de automóviles/industrials y aceleración gráfica 3D
El entorno de trabajo es el uVision3 de la firma KEIL recientemente incorporada al consorcio de empresas ARM.
Led
Este programa permite prender y apagar un led con ayuda de la interfaz JTAG, de lo contrario no podremos ver el
parpadeo que pasa muy rápido.
#include <stdio.h>
#include <ADuC7026.H>
void Configurar_GPIO(void)
{
//Control de cada pin, selecciona la función de cada pin
GP0CON=0x00000000;
GP1CON=0x00000000;
GP2CON=0x00000000;
GP3CON=0x00000000;
GP4CON=0x00000000;
//Setea el contenido del bit de cada puerto, 0 no afecta a la salida, 1 pone un 1 en la salida
GP0SET=0x00000000;
GP1SET=0x00000000;
GP2SET=0x00000000;
GP3SET=0x00000000;
GP4SET=0x00000000;
//Setea el contenido del bit de cada puerto, 0 no afecta a la salida, 1 pone un 0 en la salida
GP0CLR=0x00000000;
GP1CLR=0x00000000;
GP2CLR=0x00000000;
GP3CLR=0x00000000;
GP4CLR=0x00000000;
}
void main(void)
{
Configurar_GPIO();
while(1){
GP4SET=0x00040000;
GP4CLR=0x00040000;
}
}
Como vemos, los registros son de 32 bits, incluso para manejar los puertos de entrada/salida.
Para setear un puerto (poner un 1 en la salida) utilizamos la función GPxSET y para limpiarlo (poner un 0 en la
salida) utilizamos la función GPxCLR.
Para observar los resultados o utilizamos el simulador o utilizamos las herramientas de debug como el JTAG, para
ello debemos configurarlo:
Lo hacemos seteando en Project->Option for Target ‘Target 1’ -> Debug -> Settings->Especificamos la ruta de
acceso a la dll correspondiente al driver provisto con el JTAG, en nuestro caso la dirección de la instalación por
default es: C:\ADuC702x\code\midaslinkrdi_v268g\mIDASLinkRDI.dll.
Presionamos OK y ahora tendremos habilitada la opción de Download directamente desde el menú Flash-
>Download.
Es posible en este estado evaluar los registros internos del procesador utilizando las mismas herramientas de si
estuviéramos en modo Simulación.
Serie_por_Pooling
Este programa nuevamente muestra el uso de registros de 32bits y la configuración de la RAM estándar de un
ARM7. Como vemos, su configuración es muy similar a la de un 16550.
Técnicas Digitales II – Arquitectura de 32bits – ARM Autor:Pablo Luis Joaquim 20/44
#include <stdio.h>
#include <ADuC7026.H>
void Configurar_GPIO(void)
{
//Control de cada pin, selecciona la función de cada pin
GP0CON=0x00000000;//Configuro al P0.7 como SIN
GP1CON=0x00000011;
GP2CON=0x00000000;//Configuro al P2.0 como SOUT
GP3CON=0x00000000;
GP4CON=0x00000000;
//Setea el contenido del bit de cada puerto, 0 no afecta a la salida, 1 pone un 1 en la salida
GP0SET=0x00000000;
GP1SET=0x00000000;
GP2SET=0x00000000;
GP3SET=0x00000000;
GP4SET=0x00000000;
//Setea el contenido del bit de cada puerto, 0 no afecta a la salida, 1 pone un 0 en la salida
GP0CLR=0x00000000;
GP1CLR=0x00000000;
GP2CLR=0x00000000;
GP3CLR=0x00000000;
GP4CLR=0x00000000;
}
void Configurar_Serie(void)
{
COMCON0 |=0x80; //DLAB=1
COMDIV1 = 0x00;
COMDIV0 = 0x84;//0x084
COMIEN0 = 0x00;
void main(void)
{
unsigned int i;
/*--------------------------------------------------------------------------
Generacion del Baud Rate:
Baud_Rate=41.78MHz/(2^CD*16*2*DL)
De esta expresión despejamos DL
Por ejemplo: Baud Rate:9600bps y CD=0 --> DL=0x88
COMDIV0 y COMDIV1 son los divisores, alli cargamos el valor obtenido del CD
COMTX:Registro de Transmisión
COMSTA0: LSR
COMSTA1: MSR
Configurar_Serie();
while(1){
GP4SET=0x00040000;
for(i=0;i<65536;i++);
GP4CLR=0x00040000;
Descargamos el programa con nuestra unidad JTAG, o podemos hacerlo via puerto serie, ya que este
microcontrolador, al igual que muchos ARM7 tienen la ventaja de poseer un ISP, es decir un sistema de
programación In-System a través del port serie.
Multi_Interrupciones
El siguiente programa permite ver el uso y configuración de una IRQ y de una FIQ.
#include <ADuC7026.h>
//-------------------------------------------------------------------
// Prototipos de Funcion
//-------------------------------------------------------------------
void IRQ_Handler(void) __irq; //Prototipo de funcion de la IRQ
void FIQ_Handler(void) __fiq; //Prototipo de funcion de la FIRQ
void delay(int);
void Inicializacion(void);
long ADCconvert(void);
void ADCpoweron(int);
void My_IRQ_Function(void);
//-------------------------------------------------------------------
// Función: void main(void)
//-------------------------------------------------------------------
//GPIOs
GP0CON = 0x00100000; // ADCbusy = 1 (P0.5)
GP4DAT = 0x04000000; // P4.2 -> Salida
GP3DAT = 0xff000000; // P3 -> Salida
// Timer 1
T1LD = 0x20000; // Valor del Contador (16-bit)
T1CON = 0xC4; // Decreciente,Enable,Periodic(lo reinicio a mano),Binary,CoreClk/16
//IRQ
IRQEN = XIRQ0_BIT+GP_TIMER_BIT; // IRQ: XIRQ0,Timer1
FIQEN = ADC_BIT; // FIQ: ADC
while(1)
{
GP3DAT ^= 0x00ff0000; // P3 = ~P3
ADCCON = 0x4E3; // Single Conversion, Single_Ended,
// Start Conversion, Enable ADCbusy, ADC power on,
// ADC aquisition time= 2 clocks, fADC/2 = 1MSPS
delay(0x2000); // Delay para el parpadeo de P3 (luego del arranque del ADC
// hay que esperar 5us para tener una conversión correcta)
}
void Inicializacion(void)
{
// Inicializamos el ADC y el DAC
ADCpoweron(20000); // power on ADC
ADCCP = 0x00; // conversion on ADC0
DAC1CON = 0x13; // AGND-AVDD range
REFCON = 0x01; // Conectamos la referencia interna de 2.5v externamente
return;
}
//-------------------------------------------------------------------
// Función: IRQ
//-------------------------------------------------------------------
void IRQ_Handler() __irq
{
if ((IRQSTA & GP_TIMER_BIT) != 0) // Timer1 IRQ?
{
T1CLRI = 0; // Es un INTA de la irq del timer
GP4DAT ^= 0x00040000; // P4.2=~P4.2
}
if ((IRQSTA & XIRQ0_BIT) != 0) // XIRQ0 IRQ?
{
GP4DAT ^= 0x00040000; // P4.2=~P4_2
while(GP0DAT & 0x00010); // Esperamos hasta que XIRQ=0
}
return ;
}
//-------------------------------------------------------------------
// Función: FIQ
//-------------------------------------------------------------------
void FIQ_Handler() __fiq
{
if ((FIQSTA & ADC_BIT) != 0) // ADC FIQ ?
{
DAC1DAT = ADCDAT; // ADC0 = DAC1
} // Debemos leerlo para limpiar la interrupción del ADC
return ;
}
Como vemos el handler de la interrupción correspondiente al timer1 debe limpiar el bit T1CLRI, de esta manera
indicamos al micro que la interrución ha sido atendida y que ya puede comenzar un nuevo ciclo de conteo, es
similar a la señal INTA del 8259. Este contador no requiere de recarga manual y posee varios modos de trabajo
que se detallan en la hoja de datos del microcontrolador.
Otras interrupciones también requieren de alguna indicación por parte del programa en ejecución de que han sido
atendidas, es muy importante verificar esto en cada nuevo handler que programemos, de lo contrario la
interrupción sucederá sólo una vez y no volverá a repetirse hasta que reiniciemos el sistema.
Compilamos y descargamos el programa con algunos de los métodos mencionados anteriormente y ejecutamos.
Lo que obtenemos es un parpadeo de un LED y la variación de intensidad de otro LED conforme variamos la
posición de un potenciómetro conectado al conversor AD seleccionado.
Utilizando la interfaz JTAG y parando la ejecución en la interrupción del Timer1, vemos como quedan habilitados
los registros que denotan la necesidad de la atención de la interrupción.
Serie_por_Interrupciones
//-------------------------------------------------------------------
// Función: void main(void)
//-------------------------------------------------------------------
//GPIOs
GP0CON = 0x00100000; // ADCbusy = 1 (P0.5)
GP1CON = 0x00000011; // P1.0->SIN, P1.1->SOUT
GP4DAT = 0x04000000; // P4.2 -> Salida
GP3DAT = 0xff000000; // P3 -> Salida
// Timer 1
T1LD = 0x20000; // Valor del Contador (16-bit)
T1CON = 0xC4; // ecreciente,Enable,Periodic(reinicia),Binary,CoreClk/16
//IRQ
IRQEN = XIRQ0_BIT+GP_TIMER_BIT+UART_BIT; // IRQ: XIRQ0,Timer1,UART
FIQEN = ADC_BIT; // FIQ: ADC
while(1)
{
GP3DAT ^= 0x00ff0000; // P3 = ~P3
ADCCON = 0x4E3; // Single Conversion, Single_Ended,
// Start Conversion, Enable ADCbusy, ADC power on,
// ADC aquisition time= 2 clocks, fADC/2 = 1MSPS
}
Técnicas Digitales II – Arquitectura de 32bits – ARM Autor:Pablo Luis Joaquim 25/44
void delay (int length)
{
while (length >=0)
length--;
}
void Inicializacion(void)
{
// Inicializamos el ADC y el DAC
ADCpoweron(20000); // power on ADC
ADCCP = 0x00; // conversion on ADC0
DAC1CON = 0x13; // AGND-AVDD range
REFCON = 0x01; // Conectamos la referencia interna de 2.5v externamente
return;
}
//-------------------------------------------------------------------
// Función: IRQ
//-------------------------------------------------------------------
void IRQ_Handler() __irq
{
if ((IRQSTA & GP_TIMER_BIT) != 0) // Timer1 IRQ?
{
T1CLRI = 0; // Es un INTA de la irq del timer
GP4DAT ^= 0x00040000; // P4.2=~P4.2
}
if ((IRQSTA & XIRQ0_BIT) != 0) // XIRQ0 IRQ?
{
GP4DAT ^= 0x00040000; // P4.2=~P4_2
while(GP0DAT & 0x00010); // Esperamos hasta que XIRQ=0
}
if ((IRQSTA & UART_BIT) != 0)
{
switch(COMIID0)
{
case 0://MODEM STATUS INTERRUPT
//Leer COMSTA1 para limpiar la interrupción
break;
case 1://NO INTERRUPT
break;
case 2://TRANSMIT BUFFER EMPTY INTERRUPT
//Escribir en COMTX o leer COMIID0 para limpiar la interrupción
if (CantidadEnColaTransmision()!=0)
{
COMTX=RetirarDatoColaTransmision();
TxEnCurso = SI;
}
else
{
TxEnCurso = NO;
}
break;
case 4://RECEIVE BUFFER FULL INTERRUPT
//Leer COMRX para limpiar la interrupción
AgregarDatoColaRecepcion(COMRX);
break;
case 6://RECEIVE LINE STATUS INTERRUPT
//Leer COMSTA0 para limpiar la interrupción
break;
}
}
return ;
}
//-------------------------------------------------------------------
// Función: FIQ
//-------------------------------------------------------------------
//-------------------------------------------------------------------
// Función: InicializarSerie
//-------------------------------------------------------------------
void InicializarSerie(void)
{
// --------------------------------------------------------------------------
// Generacion del Baud Rate:
// Baud_Rate=41.78MHz/(2^CD*16*2*DL)
// De esta expresión despejamos DL
// Por ejemplo: Baud Rate:9600bps y CD=0 --> DL=0x88
//
// COMDIV0 y COMDIV1 son los divisores, alli cargamos el valor obtenido del CD
//
// COMTX:Registro de Transmisión
//
// COMRX: Registro de recepción
//
// COMIEN0: Habilita la interrupción serie
// Esta puede ser por: Modem Status - Rx - Tx - Buffer Full
//
// COMIEN1: Habilita el modo de trabajo en red (network)
//
// COMIID0: Identifica la interrupción de la que se trata
//
// COMIID1: Identifica interrupciones del modo network
//
// COMCON0: Registro de control
// el bit 7 es el DLAB, cuando este bit esta en 1 se puede acceder a COMDIV0 y COMDIV1,
// y cuando esta en 0 a los regisrtros COMTX Yy COMRX
//
// COMCON1: Registro de control de las líneas de control de flujo (modem)
//
// COMSTA0: LSR
//
// COMSTA1: MSR
//
// COMSCR: Scratchpad (se utiliza en el modo network)
//
// COMDIV2: Se utiliza para generar Baud Rates fraccionarios
//
// COMADR: Es la dirección para el modo de trabajo en red.
// ----------------------------------------------------------------------------
COMCON0 |=0x80; //DLAB=1
COMDIV1 = 0x00;
COMDIV0 = 0x84; //0x084
Este programa es muy similar al anterior, con la salvedad de que hemos agregado la interrupción de puerto serie,
debemos por lo tanto, no solo identificar que se trata de la interrupción de puerto serie, sino también de que tipo de
interrupción del puerto serie, si es por transmisión, recepción, Error, etc.
Se han hecho uso para este ejemplo de las funciones de manejo de colas de puerto serie del Ing.Cruz.
Se ha agregado a dichas funciones la siguiente, que simplemente se ocupa de manejar la transmisión de cadenas:
//-------------------------------------------------------------------
// Función: txs
//-------------------------------------------------------------------
void txs(char* dato)
{
Lo que se observa luego de la ejecución como captura en el port serie de nuestra PC es:
Tipos_de_Memoria
Main.c
//-------------------------------------------------------------------
// MACROS
//-------------------------------------------------------------------
#include <stdio.h>
#include <ADuC7026.H>
#include "defines.h"
#include "Variables.h"
//-------------------------------------------------------------------
// Prototipos de Funcion
//-------------------------------------------------------------------
void IRQ_Handler(void) __irq; //Prototipo de funcion de la IRQ
void FIQ_Handler(void) __fiq; //Prototipo de funcion de la FIRQ
void delay(int);
void Inicializacion(void);
void InicializarSerie(void);
long ADCconvert(void);
void ADCpoweron(int);
void My_IRQ_Function(void);
void Funcion_en_RAM(void);
//-----------RO-CODE --------------------------------------------
//Es equivalente a CODE, mapeada como Read-Only Code Memory
//GPIOs
GP0CON = 0x00100000; // ADCbusy = 1 (P0.5)
GP1CON = 0x00000011; // P1.0->SIN, P1.1->SOUT
GP4DAT = 0x04000000; // P4.2 -> Salida
GP3DAT = 0xff000000; // P3 -> Salida
// Timer 1
T1LD = 2612; //1mS, Valor del Contador (16-bit)
T1CON = 0xC4; // Decreciente,Enable,Periodic(reinicia),Binary,CoreClk/16
//IRQ
IRQEN = XIRQ0_BIT+GP_TIMER_BIT+UART_BIT; // IRQ: XIRQ0,Timer1,UART
FIQEN = ADC_BIT; // FIQ: ADC
Funcion_en_RAM();
txs(Texto_Inicio);
while(1)
{
GP3DAT ^= 0x00ff0000; // P3 = ~P3
ADCCON = 0x4E3; // Single Conversion, Single_Ended,
// Start Conversion, Enable ADCbusy, ADC power on,
// ADC aquisition time= 2 clocks, fADC/2 = 1MSPS
//-------------------------------------------------------------------
// Función: delay
//-------------------------------------------------------------------
void delay (int length)
{
while (length >=0)
length--;
}
//-------------------------------------------------------------------
// Función: Inicializacion
//-------------------------------------------------------------------
void Inicializacion(void)
{
// Inicializamos el ADC y el DAC
ADCpoweron(20000); // power on ADC
ADCCP = 0x00; // conversion on ADC0
DAC1CON = 0x13; // AGND-AVDD range
REFCON = 0x01; // Conectamos la referencia interna de 2.5v externamente
return;
}
//-------------------------------------------------------------------
// Función: ADCpoweron
//-------------------------------------------------------------------
void ADCpoweron(int time)
{
ADCCON = 0x20; // power-on ADC
while (time >=0) // Luego del arranque del ADC
time--; // hay que esperar 5us para tener una conversión correcta
//-------------------------------------------------------------------
// Función: IRQ_Handler
return ;
}
//-------------------------------------------------------------------
// Función: FIQ_Handler
//-------------------------------------------------------------------
void FIQ_Handler() __fiq
{
if ((FIQSTA & ADC_BIT) != 0) // ADC FIQ ?
{
DAC1DAT = ADCDAT; // ADC0 = DAC1
// Debemos leerlo para limpiar la interrupción del ADC
}
return ;
}
//-------------------------------------------------------------------
// Función: InicializarSerie
//-------------------------------------------------------------------
void InicializarSerie(void)
{
// --------------------------------------------------------------------------
// Generacion del Baud Rate:
// Baud_Rate=41.78MHz/(2^CD*16*2*DL)
// De esta expresión despejamos DL
// Por ejemplo: Baud Rate:9600bps y CD=0 --> DL=0x88
//
// COMDIV0 y COMDIV1 son los divisores, alli cargamos el valor obtenido del CD
//
// COMTX:Registro de Transmisión
//
// COMRX: Registro de recepción
//
// COMIEN0: Habilita la interrupción serie
//-----------ER-SDATA--------------------------------------------
// Execute RAM, son funciones que se ejecutan desde RAM.
//-------------------------------------------------------------------
// Función: Funcion_en_RAM
//-------------------------------------------------------------------
void Funcion_en_RAM(void) __ram
{
delay(10);
}
Variables.h
//-------------------------------------------------------------------
// Variables
//-------------------------------------------------------------------
//-----------RO-DATA --------------------------------------------
// Equivale a CONST, Mapeada como Read-Only Data Memory
const char Texto_Inicio[] = "PRUEBA PARA DIG 2";
//-----------RW-DATA --------------------------------------------
//Mapeada como Read/Write
unsigned int BlockSize = 1024;
//-----------ZI-DATA --------------------------------------------
//Mapeada como zona ZERO-INITIALIZED (inicializada a cero)
struct{
unsigned int dato1[16];
unsigned long dato2[8];
}Tabla_D;
//En sistemas embebidos, donde tenemos memoria no-volatil es necesario que los datos
//retenidos en RAM no se pierdan al encender nuevamente el equipo, por lo cual
//es contraproducente la inicialización automática mencionada anteriormente,
//en estos casos se utiliza la directiva:
#pragma NOINIT
char No_Inicializada[100]; //Variable no inicializada
#pragma INIT
struct Nodo
{
struct Nodo *Sig;
char dato;
};
struct Nodo Lista[100] __at 0x00010000;
Seno_con_Ruido.h
0.07793,0.12456,0.19026,0.26301,0.26347,0.36896,0.45799,0.44297,0.56365,0.54276,0.68335,0.66916,0.68507,0.
80496,0.80138,0.88054,0.85242,0.96089,0.97666,1.01682,1.03827,1.04474,1.04923,1.08232,1.08017,1.08327,1.06
202,1.00774,1.06060,1.04172,1.00753,1.00211,0.92880,0.92529,0.88669,0.88858,0.84685,0.75286,0.74806,0.6605
8,0.64938,0.56268,0.58087,0.50181,0.41634,0.40354,0.28476,0.19582,0.22018,0.13187,0.06155,0.02671,-
0.12408,-0.15729,-0.15218,-0.29881,-0.30852,-0.37888,-0.41002,-0.44994,-0.56923,-0.59119,-0.59422,-
0.72676,-0.69447,-0.72943,-0.81294,-0.85272,-0.85538,-0.86125,-0.85659,-0.92465,-0.93254,-0.98225,-
0.94258,-0.95643,-0.97990,-0.97673,-0.88891,-0.95496,-0.85120,-0.86071,-0.80872,-0.79876,-0.84426,-
0.77647,-0.71610,-0.69832,-0.61728,-0.60285,-0.48973,-0.50043,-0.47980,-0.34450,-0.26984,-0.27783,-
0.21080,-0.11258,-0.08105,…
Dig2.m
% Generamos una senoidal con ruido, luego la pasamos a un archivo para
% utilizarla en nuestro proyecto en Keil
% Funcion para generar una Senal senoidal
% Devuelve el contenido del buffer donde se han adquirido las N muestras
% [A]=Senoidal(Amplitud,Frecuencia, Fase,DC,N,Fs)
figure(1);
subplot(3,1,1);
plot(A);
subplot(3,1,2);
plot(B);
Ejecutamos dicho .m en MATLAB 7.0 y generamos el archivo “Seno_con_Ruido.h”, esta es una herramienta que
va volviendose indispensable a la hora de pensar en trabajar en procesamiento de señales. A la hora de generar
patrones de muestra para probar nuestros algoritmos sería realmente muy complicado hacerlo manualmente.
Se adjuntan al proyecto los .m necesarios para generar también señales Senoidales, Cuadradas y Triangulars.
Para ejecutar los scripts basta con pararse en la línea de comandos del MATLAB y escribir el nombre del scripts,
este se ejecutará, dejándonos lista la señal que incluiremos en nuestro proyecto.
Incluímos el .h generado en nuestro archivo Variables.h, donde hemos generado distintos tipos de variables con
distintos modos de trabajo en memoria.
const: Almacena las variables en memoria de código, en flash, es muy similar a cuando utilizábamos la palabra
code en los programas realizados en 8051.
RW-DATA: Las variables aquí declaradas se almacenarán y trabajarán desde la memoria de datos, pudiendose
leer y escribir.
ZI-DATA: Las variables aquí declaras se inicializan a 0 al arrancar el programa. En sistemas embebidos donde
contamos con una NVRAM, es necesario resguardar el valor que tenían los datos previamente al apagado del
equipo, por lo que para evitar que cada vez que prendamos el equipo estas variables en NVRAM se “borren”
debemos indicarselo al compilador, esto lo hacemos con las sentencias de precompilación: #NOINIT y #INIT.
RO-CODE: Zona de código de solo lectura, es donde almacenamos el firmware del equipo.
__at: En caso de que tengamos dispositivos mapeados como memoria, debemos indicar la posición absoluta en la
que se encuentran para poder direccionarlos a la hora de leer o escribir en ellos, para ello se utiliza la palabra
reservada __at y a continuación la posición de memoria donde el dispositivo se encuentra mapeado. Esto es
similar al _at_ de los programas realizados en 8051.
Tambien es posible fijar la dirección en memoria de una variable haciendo uso de la misma directiva __at.
Es recomendable que en los casos en que queremos utilizar dispositivos mapeados como memoria, en el momento
de la declaración de la variable con la que se accede al dispositivo antepongamos la palabra reservada volatile de
esta forma el compilador no tratará de optimizar los accesos a memoria cuando utilicemos dicha variable y lo hará
solo de la manera en la que nosotros lo indiquemos.
ERAM: RAM ejecutable. Es posible ejecutar funciones desde la SRAM del micro, esto sirve para ejecutar
programas cuyo tiempo de ejecución sea crítico, como la RAM es una memoria mas rápida que la FLASH
podemos ganar tiempo de ejecución, si además conjugamos esto con el modo de trabajo mas conveniente (ARM o
THUMB) de acuerdo a la longitud de palabra de la memoria con la que trabajemos (16 o 32 bits) ganaremos
velocidad en la ejecución de esa rutina.
Debe recordarse que el código generado siempre se encuentra en FLASH, la única diferencia es que al iniciar el
programa el micro copia este código en RAM para su ejecución, con lo que no ahorramos espacio de código al
declarar funciones en RAM, de hecho el compilador reserva ese espacio en RAM también, así que perdemos
espacio de código y de datos. Esto solo se recomienda en los casos en que la velocidad de ejecución sea crítica.
Sin embargo otra aplicación podría ser la de la actualización del firmware, para lo que tendremos una función
corriendo en RAM que puede comunicarse por puerto serie y descargar el nuevo firmware que será copiado en
FLASH, una vez terminado este proceso reiniciamos la ejecución del microcontrolador. Esta operación no sería
posible si la rutina encargada de grabar la flash estuviera también en memoria de código, ya que se pisaría en
medio del proceso y este quedaría inconcluso junto con un sistema sin firmware.
Esto significa que si tratáramos de hacer: unsigned int var1 __at 0x0001; obtendríamos un error de alineación
como el siguiente:”Insufficient aligned absolute ardes”. Para resolverlo basta con colocar esta variable en una
dirección de memoria que corresponda con la alineación del tipo que corresponda.
Estos microcontroladores no poseen un área de memoria reservada para trabajar con bits, con lo que al declarar
una variable de este tipo lo que hacemos es en realidad es trabajar con una variable del tipo que el compilador
crea necesario.
ARM y THUMB
FFT
//-------------------------------------------------------------------
// MACROS
//-------------------------------------------------------------------
#include <stdio.h>
#include <math.h>
#include <ADuC7026.H>
#include "defines.h"
//-------------------------------------------------------------------
// Variables
//-------------------------------------------------------------------
const char Texto_Inicio[] = "PRUEBA PARA DIG 2\n";
//-------------------------------------------------------------------
// Prototipos de Funcion
//-------------------------------------------------------------------
void IRQ_Handler(void) __irq; //Prototipo de funcion de la IRQ
void Inicializacion(void);
void InicializarSerie(void);
void My_IRQ_Function(void);
#pragma ARM
void fft(float* xr,float* xi,float* XR,float* XI);
__inline unsigned int IBR(unsigned int m, unsigned int v);
#pragma THUMB
//GPIOs
GP1CON = 0x00000011; // P1.0->SIN, P1.1->SOUT
//IRQ
IRQEN = UART_BIT;//UART
txs(Texto_Inicio);
fft(xr,xi,XR,XI);
ModuloFase(XR,XI,Aux1,Aux2);
txs("Modulo\n");
for(i=0;i<TAMANO_VECTOR;i++)
{
sprintf(texto,"%1.2f\n",Aux1[i]);//El módulo
txs(texto);
}
txs("Fase\n");
for(i=0;i<TAMANO_VECTOR;i++)
{
sprintf(texto,"%1.2f\n",Aux1[i]);//La Fase
txs(texto);
}
while(1)
{
}
//-------------------------------------------------------------------
// Función: IRQ
//-------------------------------------------------------------------
void IRQ_Handler() __irq
{
return ;
}
//-------------------------------------------------------------------
// Función: InicializarSerie
//-------------------------------------------------------------------
void InicializarSerie(void)
{
// --------------------------------------------------------------------------
// Generacion del Baud Rate:
// Baud_Rate=41.78MHz/(2^CD*16*2*DL)
// De esta expresión despejamos DL
// Por ejemplo: Baud Rate:9600bps y CD=0 --> DL=0x88
//
// COMDIV0 y COMDIV1 son los divisores, alli cargamos el valor obtenido del CD
//
// COMTX:Registro de Transmisión
//
// COMRX: Registro de recepción
//
// COMIEN0: Habilita la interrupción serie
// Esta puede ser por: Modem Status - Rx - Tx - Buffer Full
//
// COMIEN1: Habilita el modo de trabajo en red (network)
//
// COMIID0: Identifica la interrupción de la que se trata
//
// COMIID1: Identifica interrupciones del modo network
//
// COMCON0: Registro de control
// el bit 7 es el DLAB, cuando este bit esta en 1 se puede acceder a COMDIV0 y COMDIV1,
// y cuando esta en 0 a los regisrtros COMTX Yy COMRX
//
// COMCON1: Registro de control de las líneas de control de flujo (modem)
//
// COMSTA0: LSR
//
// COMSTA1: MSR
//
// COMSCR: Scratchpad (se utiliza en el modo network)
//
// COMDIV2: Se utiliza para generar Baud Rates fraccionarios
//
// COMADR: Es la dirección para el modo de trabajo en red.
// ----------------------------------------------------------------------------
COMCON0 |=0x80; //DLAB=1
COMDIV1 = 0x00;
COMDIV0 = 0x84; //0x084
//-------------------------------------------------------------------
// Función: IBR
//-------------------------------------------------------------------
#pragma ARM
__inline unsigned int IBR(unsigned int m, unsigned int v) __ram
{
//Al usar la directiva __inline del CARM, lo que hacemos es eliminar
//el llamado a la función, entonces en cada lugar donde se pretende
//acceder a ella se reemplazará con el código de la rutina.
//De esta manera aumentamos la velocidad del sistema, reduciendo
//el tiempo del llamado a la función.
//Esto es recomendable en funciones donde buscamos la máxima
//velocidad de procesamiento.
unsigned int i,p=0,c;
for(i=0;i<v;i++)
{
c=(m>>i)&1;
p+=c<<(v-i-1);
}
float v;
v=(float)log(N)/((float)log(2));
for(i=0;i<N;i++)
{
XR[i]=xr[i];
XI[i]=xi[i];
}
for(i=0;i<N;i++)
{
Aux1[i]=cos(2*Pi*i/N);
Aux2[i]=sin(2*Pi*i/N);
}
for(L=1;L<=v;L++)
{
for(k=0;k<N-1;k=k+N2)
{
m=k>>((unsigned int)v-L);
p=IBR(m,(unsigned int)v);
for(I=0;I<N2;I++,k++)
{
T1R=Aux1[p]*XR[k+N2]-Aux2[p]*XI[k+N2];
T1I=Aux2[p]*XR[k+N2]+Aux1[p]*XI[k+N2];
XR[k+N2]=XR[k]-T1R;
XI[k+N2]=XI[k]-T1I;
XR[k]=XR[k]+T1R;
XI[k]=XI[k]+T1I;
}
}
N2=N2/2;
}
}
#pragma THUMB
//-------------------------------------------------------------------
// Función: ModuloFase
//-------------------------------------------------------------------
void ModuloFase(float* xr,float* xi,float* Modulo,float* Fase)
{
unsigned int k;
if(xr[k]!=0)
Fase[k]=atan(xi[k]/xr[k]);
else
Fase[k]=0;
}
}
En este programa hemos definido zonas de código que serán compiladas y ejecutadas utilizando el modo ARM y
otras usando el modo THUMB.
La principal ventaja del modo THUMB es que permite ahorrar código, sin embargo a la hora de ejecutar rutinas de
gran complejidad de cálculo o que requieran una alta velocidad se recomienda utilizar el modo ARM y las funciones
ejecutándose desde RAM.
Esto se debe a que las memorias RAM normalmente son de 32bits de largo de palabra, con lo que es posible leer
un código de ejecución en un solo ciclo, mientras que si lo hicieramos con la FLASH en modo ARM, como estas
normalmente son de 16bits tendríamos que hacer dos accesos consecutivos a memoria.
Por otro lado, el modo THUMB, que es de 16bits, se ejecuta mas rápido de FLASH, aunque utiliza mayor cantidad
de instrucciones para la misma tarea, con lo que el modo ARM es lo mas recomendable a la hora de trabajar con
aplicaciones donde el tiempo de ejecución es crítico.
En este ejemplo las rutinas que ejecutan la fft de nuestra señal almacenada en memoria se ejecutan desde RAM.
La función IBR (bit revearsal) es necesaria para decodificar la información entregada por el algoritmo de la fft y
debe hacerse por cada resultado obtenido, por lo que también es crítica, sin embargo como esta función solamente
la utilizamos en este lugar podemos ahorrarnos el tiempo del salto a la subrutina (ahorrándonos el tiempo de
resguardo de registros en la pila), con lo que mantenemos la estructura de función, pero en la compilación esta
pasa a formar parte de las funciones donde la estemos llamando, generando la repetición de código en cada lugar.
Esto es particularmente útil para generar las funciones de atención de interrupción a llamar desde el handler
general de la IRQ. Lo que hacemos es generar nuestros handlers anteponiendo en su declaración la directiva
__inline y estaremos generando ese código dentro de la interrupción sin crear saltos a función, ni perder tiempo de
ejecución.
La función ModuloFase convierte los vectores que contienen la parte imaginaria y real resultantes de la fft y
devuelven el mismo resultado en módulo y fase, esta función ya no es crítica, se hace para adaptar los datos para
que sean comprensibles por el usuario.
Por último sacamos la fft resuelta por puerto serie, abrimos una ventana de hyperterminal y guardamos e resultado
en un archivo, luego lo importamos a Excel y graficamos las muestras recibidas:
80
70
60
50
40
30
20
10
0
Muestras
Fase
90
80
70
60
50
40
30
20
10
0
Muestras
Usuario&Supervisor
Main.c
//-------------------------------------------------------------------
// MACROS
//-------------------------------------------------------------------
#include <stdio.h>
#include <math.h>
#include <ADuC7026.H>
#include "defines.h"
//-------------------------------------------------------------------
// Variables
//-------------------------------------------------------------------
const char Texto_Inicio[] = "PRUEBA PARA DIG 2\n";
void Inicializacion(void);
void InicializarSerie(void);
void My_IRQ_Function(void);
//-------------------------------------------------------------------
// Función: main
//-------------------------------------------------------------------
void main (void)
{
// Timer 1
T1LD = 0x20000; // Valor del Contador (16-bit)
T1CON = 0xC4; // Decreciente,Enable,Periodic(lo reinicio a mano),Binary,CoreClk/16
//GPIOs
GP1CON = 0x00000011; // P1.0->SIN, P1.1->SOUT
GP4DAT = 0x04000000; // P4.2 -> Salida
//IRQ
IRQEN = GP_TIMER_BIT+UART_BIT; // IRQ: Timer1,UART
txs(Texto_Inicio);//Comienza la función
TimerInit();
TimerStart(0,10);
while(1)
{
}
}
//-------------------------------------------------------------------
// Función: IRQ
//-------------------------------------------------------------------
void IRQ_Handler() __irq
{
if ((IRQSTA & GP_TIMER_BIT) != 0) // Timer1 IRQ?
{
T1CLRI = 0; // Es un INTA de la irq del timer
Timer_Tick();
TimerEvent(); //La SWI (Modo supervisor)
}
return ;
}
//-------------------------------------------------------------------
// Función: Tareas
//-------------------------------------------------------------------
//Las tareas no guardan ni recuperan regitros y no vuelven a ninguna parte.
//Se utilizan como pequeños programas tipo "void main(void)"
//Es el SO el que debe encargarse de resguardar los registros que correspondan.
void Tarea0 (void) __task
{
while (1);
}
//-------------------------------------------------------------------
// Función: InicializarSerie
//-------------------------------------------------------------------
void InicializarSerie(void)
{
// --------------------------------------------------------------------------
// Generacion del Baud Rate:
// Baud_Rate=41.78MHz/(2^CD*16*2*DL)
// De esta expresión despejamos DL
// Por ejemplo: Baud Rate:9600bps y CD=0 --> DL=0x88
//
// COMDIV0 y COMDIV1 son los divisores, alli cargamos el valor obtenido del CD
//
// COMTX:Registro de Transmisión
//
// COMRX: Registro de recepción
//
// COMIEN0: Habilita la interrupción serie
// Esta puede ser por: Modem Status - Rx - Tx - Buffer Full
//
// COMIEN1: Habilita el modo de trabajo en red (network)
//
// COMIID0: Identifica la interrupción de la que se trata
//
// COMIID1: Identifica interrupciones del modo network
//
// COMCON0: Registro de control
// el bit 7 es el DLAB, cuando este bit esta en 1 se puede acceder a COMDIV0 y COMDIV1,
// y cuando esta en 0 a los regisrtros COMTX Yy COMRX
//
// COMCON1: Registro de control de las líneas de control de flujo (modem)
//
// COMSTA0: LSR
//
// COMSTA1: MSR
//
// COMSCR: Scratchpad (se utiliza en el modo network)
//
// COMDIV2: Se utiliza para generar Baud Rates fraccionarios
//
// COMADR: Es la dirección para el modo de trabajo en red.
// ----------------------------------------------------------------------------
COMCON0 |=0x80; //DLAB=1
COMDIV1 = 0x00;
COMDIV0 = 0x84; //0x084
//-------------------------------------------------------------------
// Función: TIMER TICK
//-------------------------------------------------------------------
//El código de esta función se copia en el lugar desde donde la
//estamos llamando.
extern __inline void Timer_Tick(void)
{
unsigned char j;
for(j=0;j<8;j++)
{//Tengo 8 timers que puedo usar
if(TmrRun[j])
{//Si hay alguno habilitado
TmrRun[j]--;
if(!TmrRun[j])
Evento=Evento|((unsigned char)0x01<<j);
}
}
}
//-------------------------------------------------------------------
// Función: TimerInit
// Inicializa los Timers
//-------------------------------------------------------------------
void TimerInit(void) __swi (1)
{
char i;
for(i=0;i<8;i++)
TmrRun[i]=0;
}
//-------------------------------------------------------------------
// Función: kTimerStart
// Configura los Timers(la usan las funciones de modo supervisor)
//-------------------------------------------------------------------
void kTimerStart(unsigned char Evento, unsigned int Ticks)
{
unsigned char mascara = 1;
TmrRun[Evento]=Ticks;
return;
}
//-------------------------------------------------------------------
// Función: TimerStart
// Configura los Timers(la usan las funciones de modo usuario)
//-------------------------------------------------------------------
void TimerStart(unsigned char Evento, unsigned int Ticks) __swi (2)
{
unsigned char mascara = 1;
TmrRun[Evento]=Ticks;
return;
}
//-------------------------------------------------------------------
// Función: kTimerStop
// Detiene alguno de los Timers (la usan las funciones de modo supervisor)
//-------------------------------------------------------------------
// Función: TimerStop
// Detiene alguno de los Timers(la usan las funciones de modo usuario)
//-------------------------------------------------------------------
void TimerStop(unsigned char Evento) __swi (3)
{
TmrRun[Evento]=0;
Evento=Evento & (~(((unsigned char)0x01)<<Evento));
}
//-------------------------------------------------------------------
// Función: TimerEvent
// Atiende los eventos programados
//-------------------------------------------------------------------
//*Las Interrupciones de software (swi) aceptan parámetros y devuelven valores.
//*Los parámetros se le pasan por registros, con lo cual el máximo
//número de parámetros que podemos pasar son 8 bytes.
//*Se ejecutan en Modo Supervisor (Están protegidas de otras interrupciones
//del sistema).
//*Es muy similar a una CallGate en un sistema IA-32
void TimerEvent (void) __swi(0)
{
unsigned char j;
for(j=1;j;j<<=1){
switch(Evento&j){
case 0x01://Evento 0
//---------LA FUNCIÓN DEL TIMER-------------
GP4DAT ^= 0x00040000; // P4.2=~P4.2
kTimerStart(0,10);
//------------------------------------------
Evento=Evento&0xfe;
break;
case 0x02://Evento 1
//---------LA FUNCIÓN DEL TIMER-------------
//------------------------------------------
Evento=Evento&0xfd;
break;
case 0x04://Evento 2
//---------LA FUNCIÓN DEL TIMER-------------
//------------------------------------------
Evento=Evento&0xfb;
break;
case 0x08://Evento 3
//---------LA FUNCIÓN DEL TIMER-------------
//------------------------------------------
Evento=Evento&0xf7;
break;
case 0x10://Evento 4
//---------LA FUNCIÓN DEL TIMER-------------
//------------------------------------------
Evento=Evento&0xef;
break;
case 0x20://Evento 5
//---------LA FUNCIÓN DEL TIMER-------------
//------------------------------------------
Evento=Evento&0xdf;
break;
case 0x40://Evento 6
//---------LA FUNCIÓN DEL TIMER-------------
//------------------------------------------
Evento=Evento&0xbf;
break;
case 0x80://Evento 7
//---------LA FUNCIÓN DEL TIMER-------------
//------------------------------------------
Evento=Evento&0x7f;
break;
}
}
}
A este proyecto debemos agregarle el archivo SWI_VEC.S provisto con KEIL, que será el encargado de generar el
vector de interrupciones de software.
Las interrupciones de software se utilizan para generar una cierta protección en el acceso desde el nivel de usuario
y el de supervisor. Su verdadera aplicación se da en los sistemas operativos embebidos, donde las funciones de
modo usuario se comportan como tareas independientes del kernel y en el momento en que alguna de ellas
necesite realizar alguna tarea que comprometa la estabilidad del sistema (según los criterios del programador del
sistema operativo) deberá hacerlo a través de una interrupción de software. Estas se comportan como una puente
entre el núcleo del sistema operativo y las tareas del usuario.
Estas interrupciones de software deberán evaluar si el llamado desde donde se requirieron cumple con todas las
condiciones para la tarea requerida y que dicha tarea no generará una inestabilidad, de lo contrario no realiza la
tarea requerida.
Las interrupciones de software se identifican con la palabra reservada __swi al final de la declaración y las tareas
lo hacen con __task.
El llamado nivel de privilegio de modo supervisor se verifica en que las excepciones no pueden interrumpir la
ejecución de una interrupción de software. Esta es la aplicación que le hemos dado en este programa.
Arrancamos en el main uno de los timers, la interrupción es la encargada de ir decrementando cada uno de los 8
timers disponibles que esten corriendo y constantemente verificamos si hay alguno que haya vencido.
Vemos también aquí el uso de la palabra reservada __inline para ejecutar el handler de la interrupción de timer. De
esta manera, desde la interrupción vemos una llamada a una función, pero en la realidad ese código se ejecuta
directamente desde ese punto (de una manera muy similar a una macro).