1. Introducción
Cortos tiempo de desarrollo y requerimientos de productos electrónicos de alta calidad han hecho de los lenguajes de programación de alto nivel un requisito. La principal razón es que los lenfuajes de alto nivel hacen más facil el mantenimiento y la reutilización del código debido a la gran portabilidad y legibilidad.
La simple elección del lenguaje de programación no asegura una alta legibilidad y reusabilidad; el buen estilo de codificación sí. Así pues, los perifericos XMEGA, los archivos cabecera y los controladores estan diseñados teniendo esto en cuenta.
El lenguaje de programacíón mas ampliamente usado para microcontroladores AVR es C, y esta nota de aplicación (AVR1000) se enfoca en la programación en C. Para proporcionar soporte a muchos de los compiladores de C para AVR disponibles los ejemplos son en la medida de lomposible escritos en ANSI C. Algunos de los ejemplo son especificados para IAR Embedded Workbench, pera la idea y metodos pueden ser uados para otros compiladores con cambios menores.
Ejemplo de codificación – AVR GCC
Link: EJEMPLOS XMEGA
2. Módulos XMEGA
Un AVR XMEGA esta compuesto de varios bloques básicos. Un CPU AVR, SRAM, Flash, EEPROM, y una serie de perifericos. Esos bloques son llamados “tipos de módulo”. Un XMEGA puede tener una o más instancias de un tipo de módulo dado. Todas las instancias de un tipo de módulo tienen las mismas características y funciones.
Algunos de los módulos pueden ser un subconjunto de otros tipos de módulos. Estos módulos heredan un subconjunto de características del módulo superior, todas las características heredadas son completamente compatibles. Esto se aplica por ejemplo a los timer y puertos de entrada y salida. Un subconjunto de módulos para un timer significa que este tiene menos canales de captura y comparación que un timer completo. Similarmente, un puerto IO puede tener menos de ocho pines.
Un tipo de módulo puede ser un “USART”, mientras una instancia de este tipo puede ser “USARTC0”, donde el sufijo C0 indica que la instancia es el USART 0 en el puerto C. Cada módulo tiene un número de registros que contienen bits de control o de estado. Todos los módulos de un tipo dado contienen el mismo conjunto (o subconjunto) de registros, y todos estos registros contienen el mismo conjunto (o subconjunto) de bits de control y estado.
Cada módulo tiene una dirección base fija en el mapa de memoria I/O y todos los registros contenidos en el módulo tienen direcciones de desplazamiento fijas relativas a dirección base del módulo. De esta manera, cada registro no sólo tendrá una dirección absoluta en el espacio de memoria, sino también una dirección relativa definida por un offset. Los desplazamientos de las direcciones de registros son iguales para todas las instancias de un tipo de módulo, simplificando la tarea de escribir controladores que puedan ser usados para todos los módulos de un tipo específico.
2.1 Convención de nomenclatura de registros
Los registros están divididos en registros de control, registros de estado y registros de datos y el nombre de dichos registros refleja esto.
Un registro de control de propósito general del módulo es llamado CTRL. Sí existen múltiples registrol de control en un módulo ellos tendrán un caracter como sufijo. En este caso el registro de control será llamado CTRLA, CTRLB, CTRLC etc. Esto también aplica a los registros de estado (STATUS).
Para registros que tienen una función específica el nombre de estos refleja esta funcionalidad. Por ejemplo, un registro de control que controla los niveles de interrupción de un módulo es llamado INTCTRL.
Ya que el bus de datos AVR es de 8bits, los registros mas grandes son implementados usando varios registros de 8 bits. Para un registro de 16 bits, el alto (High) y bajo (Low) son accesados agregando una “H” o “L” respectivamente al nombre del registro. Por ejemplo, el registro de cuenta de 16 bits del Timer/Counter son llamados CNT. Los dos bytes son llamados CNTL y CNTH.
Para registros más grandes que 16 bits, los bytes son numerados desde el byte menos significativo. Por ejemplo, el registro de calibración de 32 bits del ADC es llamado CAL. Los cuatro bytes son llamados CAL0, CAL1, CAL2 y CAL3 (desde el menos al más significativo). Muchos de los compiladores de C ofrecen un manejo automatico en el acceso a multiples bytes. En el caso del timer el nombre CNT, sin sufijos “H” o “L”, puede ser usado para realizar un acceso al registro de cuenta (CNT). Esto también se aplica en el caso de los registros de 32 bits.
2.2 Convención de nomenclatura de bits
Los bits en los registros pueden tener una función individual o ser parte de un grupo de bits que tienen una función conjunta: Un bit individual puede ser un bit que habilita un módulo, ej. el USART ENABLE bit. Un grupo de bits puede consistir de dos o más bits que juntos seleccionan una configuración especifica del módulo al cual pertenecen. Un grupo de bits ofrecen hasta 2^n posibles opciones, donde n es el número de bits en el grupo. Los dos bits que controlan el nivel de interrupción de recepción completa del USART, RXINTLVL[1:0], es un ejemplo de un grupo de bits. Esos dos bits ofrecen las siguientes secciones:
Solamente los bits que forman parte de un grupo tendrán como sufijo un número. El registro de control D de un Timer/Counter tiene dos grupos de bits. EVACT y EVSEL. Los bits de esos grupos tienen un número como sufijo, mientras que el bit EVDLY, que no forma parte de un grupo, no tiene sufijo.
3. Escribiendo código en C para XMEGA
La siguiente sección se enfoca en como escribir código en C para XMEGA. Los ejemplos muestras como hacer el código altamente legible y portable entre los diferentes dispositivos XMEGA. Los ejemplos también pueden ser usados como una guía para escribir código que sea fácil de escribir y mantener.
Los módulos XMEGA se encuentran en bloques dedicados y continuos en el espacio de memoria y se pueden ver como unidades encapsuladas. Esto se refleja en la forma en que se acceden a los módulos cuando se codifica en C: Los módulos son encapsulados usando estructuras en C, en las que están contenidos todos los registros del módulo. La siguiente figura muestra una ilustración de esto.
Debemos notar que algunos registros no tienen una asociación directa con módulos. Aquellos registros no estan encapsulados en estructuras, ya que las estructuras son usadas para asociar registros con un módulo.
Para grandes proyectos, las estructuras de los modulos proporcionan una ventaja, no sólo la legibilidad, sino también porque los compiladores pueden reutilizar los driver y por lo tanto generar un código muy compacto.
3.1 Archivos cabecera XMEGA
Un archivos de cabecera dedicado esta disponible para cada dispositivo XMEGA. Si el dispositivo que se usará es especificado en la configuración del proyecto (asumiendo que usamos Atmel Studio), el compilador se encargará de incluir automáticamente el archivo cabecera correcto siempre que se incluya el archivo de dispositivo como se muestra en la soguiente figura.
La ventaja de esto es que si el dispositivo cambia, no será necesario cambiar los archivos de origen, sólo la configuración del proyecto.
3.2 Registros en los módulos
El mapa de entradas/salidas esta dispuesto de modo tal que todos los registros para un módulo periférico dado se coloquen en un bloque de memoria continua. Los registros pertenecientes a diferentes modulos no son mezclados. Esto hace posible organizar todos los módulos periféricos en estructuras. Donde la dirección de la estructura define l dirección base del módulo. Todos los registros pertenecientes a un módulo son elementos de la estructura del módulo.
Un ejemplo es módulo “USART”. El tipo estructura y la declaración para este módulo son mostradas en las siguientes figuras.
3.2.1 Registros multibytes en módulos.
Algunos registros son usados conjuntamente con otros registros para representar valores de 16 y 32 bits. Como ejemplo de esto es la declaración de la estructura ADC de la siguiente figura.
En la figura anterior, los registros de resultado de cada canal CH0RES, CH1RES, CH2RES, CH3RES, y el registro de comparación CMP, son valores de 16 bits. Ellos son declarados usando la macro _WORDREGISTER la cual es mostrada en la siguiente figura.
En la siguiente figura registro de cuenta, CNT, es un valor de 32 bits y es declarado usando la macro _DWORDREGISTER.
Como se ha visto, la macro WORDREGISTER usa los sufijos “H” y “L” para para los bytes altos y bajos respectivamente. La macro usa números como sufijos para indicar el orden de los bytes. Tanto los registros de 16 como los de 32 bits pueden ser accesados en modo 16/32 bits sin usar el sufijo como se muestra en la siguiente figura.
3.3 Direccionamiento de módulos.
Las definiciones de todos los módulos periféricos son encontradas en el archivo cabecera del dispositivo. La dirección para el módulo es especificada en ANSI C para tener compatibilidad con muchos de los compiladores disponibles. En la siguiente figura se muestra las definiciones de los ADC para los puertos A y B. Aquí se puede ver como las instancias de los módulos son desreferenciados a una dirección absoluta en la memoria, la cual coincide con la dirección base de la instancia del módulo. Los punteros al módulo son predefinidos en los archivos cabecera de cada XMEGA, por lo tanto ya no es necesario agregar estas definiciones a nuestro código.
3.4 Máscaras de bits y máscaras de grupos de bits.
Los bits en los registros pueden ser manipulados usando máscaras predefinidas, o alternativamente la posición de los bits. No se recomiendo usar la posición de los bits. Las máscaras de bits predefinidas estan relacionadas a bits individuales, denominados máscara de bits o grupo de bits.
Una máscara de bit es usada tanto cuando se setea o limpia un bit. Una máscara de grupo es principalmente usada cuando limpiamos múltiples bits en un grupo. El seteo de multiples bits que forman parte de un grupo es cubierno en la sección 3.5.
3.4.1 Mascara de bit (Bit Mask).
Considere el registro de control del Timer D, CTRLD. Los grupos de bit, los nombres de bit, las posiciones de los bits y las máscaras de bit de este registro se pueden ver en la siguiente tabla.
Ya que el nombre de los bits necesita ser único para que el compilador pueda manejarlos, todos los bits estan prefijados con el nombre del módulo al cual pertenecen. En muchos casos, el nombre del tipo del módulo esta abreviado. Para todas las definiciones de bits relacionadas con los módulos Timer/Counter, los nombres de los bits tienen un prefijo “TC_”.
Para diferenciar entre las máscaras de bit y las posiciones de los bits se agrega un sufijo. Para una máscara de bit, el sufijo es “_bm”. El nombre de la máscara de bit para el bit EVDLY es TC_EVDLY_bm. El código mostrado en la siguiente figura usa una máscara de bit. El bit EVDLY en el registro CTRLD del Timer/Counter D0 es seteado sin alterar los otros bits del registro.
3.4.2 Máscara de grupo de bits.
Muchas funciones son controladas por un grupo de bits. Los bits EVACT[2:0] y EVSEL[3:0] son grupos de bits en el registro CTRLD del Timer/Counter. Los valores de esos bits seleccionan una configuración específica.
Cuando cambiamos bits en un grupo de bits, a menudo se requiere limpiar el grupo de bits antes de asignar un nuevo valor. Para decirlo de otro modo: No es suficiente con setear los bits que deben ser seteados, también se requiere limpiar los bits que deben ser limpiados. Para que esto sea fácil se definen las máscaras de grupos de bits. Las máscaras de grupo usan el mismo nombre como los bits en el grupo de bits y un sufijo “_gm”.
El código de la siguiente figura muestra como la máscara de grupo se relaciona con las máscaras de bit. En realidad, los valores de máscara de grupo se precalculados en los archivos cabecera, entonces el compilador no necesita calcular la misma constante una y otra vez.
La máscara de grupo de bits esta principalmente destinada para borrar una configuración antigua antes de escribir la nueva. En el código de la siguiente figura de muestra como hacerlo. El código limpia el grupo de bits EVACT en el registro CTRLD del Timer/Counter D0.
3.5 Máscara de configuración de grupo de bits
A menudo se requiere consultar la hoja de datos para investigar cual patrón de bits necesita ser usado cuando seteamos un grupo de bits para una configuración especifica. Esto también se aplica a las lecturas o depuración del código. Para incrementar la legibilidad y minimizar la probabilidad de setear bits incorrectamente, es que se encuentran disponibles las máscaras de configuración de grupo.
El nombre de una máscara de configuración de grupo es concatenado al módulo tipo, al nombre del grupo de bits, a una descripción de la configuración y un sufijo “_gc”, indicando que este es un grupo de configuración. Un ejemplo de esto es mostrado en la siguiente figura.
Inspeccionando la figura anterior podemos ver que esta es usada para seleccionar una configuración para los bits RXCINTLVL en el módulo USART. Este grupo selecciona un nivel de interrupción alto (HI) .
El grupo de bits para los niveles de interrupción por recepción completa está compuesto de dos bits, RXINTLVL[1:0]. La siguiente tabla muestra las configuraciones disponibles para este grupo. Los nombres de las configuraciones son “OFF”, “LO”, “MED” y “HI”. Estos nombres hacen más fácil la escritura y el mantenimiento del código.
Para cambiar un grupo de bits a una configuración nueva, el grupo de bits de configuración (_gc) es típicamente usado junto con la máscara de grupo de bits (_gm), esto con el fin de asegurarse que la configuración vieja sea borrada primero. En la siguiente figura se aprecia un ejemplo de esto. Aquí se limpian los bits de nivel se interrupción para luego seleccionar un nuevo nivel (nivel medio).
El uso de la máscara de grupo para limpiar no es siempre requerida, el código mostrado en la siguiente figura muestra como todos los niveles de interrupción del USARTC0 pueden ser configurados a la vez. Recepción completa, transmisión completa y la interrupción por registro de datos vacío con seteadas a niveles medio, off y bajo respectivamente.
3.5.1 Enumeración de máscara de configuración de grupo
A diferencia de las máscaras de bit (_bm) y máscaras de grupo (_gm), las máscaras de configuración de grupo son definidas usando enumeradores. Un enumerador es definido para cada grupo de bits. El enumerador para el grupo USART RXCINTLVL es mostrado en la siguiente figura.
3.6 Llamadas a funciones y módulos
Cuando escribimos drivers para un módulo tipo que tiene múltiples instancias, el hecho de que todas las instancias tenga el mismo mapa de memoria puede utilizarse para hacer que el driver sea reutilizable para todas las instancias del mismo módulo. Si el driver toma un argumento tipo puntero, apuntando a la instancia del módulo, el driver se puede utilizar para todos los módulos de este tipo.
La función para inicializar y acceder a los m´dulos Timer/Counter puede ser compartida por todas las instancias del módulo. Aunque hay una pequeña sobre carga en pasar el puntero del módulo a las funciones, el tamaño total del código a menudo se reducira ya que el código es reusado para todas las instancias de cada módulo. Incluso mas importantes, el tiempo de desarrollo, el costo de mantenimiento, y la portabilidad pueden ser mejoradas usando este enfoque.
El código de la siguiente figura muestra una función que usa un puntero a un módulo para seleccionar una fuente de clock para cualquier Timer/Counter.
La función anterior toma dos argumentos: un puntero a módulo de tipo TC0_t, y un grupo de configuración de tipo TC_CLKSEL_t. El código en la función usa el puntero al módulo Timer/Counter para acceder al registro CTRLA y setear el nuevo clock.
El código de la siguiente figura muestra como la función puede ser usada para configurar diferentes módulos Timer/Counter con diferentes fuentes de clock.
4. Sumario
Para referencia, una vista general de los difentes sufijos usados se muestran en la siguiente tabla.…..escribiendo…..