Ensamblador AVR

November 15, 2017 | Author: averpepe | Category: Pointer (Computer Programming), Central Processing Unit, Bit, Compiler, Computer Hardware
Share Embed Donate


Short Description

Download Ensamblador AVR...

Description

Ensamblador AVR El hardware del microcontrolador El concepto detrás del ensamblador es hacer accesible los recursos de hardware del procesador. Estos recursos son todos los componentes del hardware, como la unidad central de proceso (CPU) y su servidor de matemáticas, la unidad aritmético-lógica (ALU), las unidades de almacenamiento diversas (RAM interna y externa, el almacenamiento EEPROM), los puertos y las características de los bits de control del puertos, los temporizadores, los convertidores AD y otros dispositivos. El acceso al hardware es directo y no mediante drivers u otras interfaces que proporcionan los sistemas operativos. Es el control directo de la interfaz serial o el conversor AD, no hay una capa entre usted y el hardware. Como premios a sus esfuerzos, el equipo completo está a sus órdenes, no sólo la parte que el diseñador del compilador y el programador del sistema ofrecen para usted. ¿Cómo trabaja la CPU? Lo más importante para la comprensión del ensamblador es entender como funciona la CPU. La CPU lee las instrucciones de la memoria de programa (flash), las traduce y las ejecuta en varios pasos. En los AVR, las instrucciones están escritas como números de 16 bits en la memoria flash y se leen de allí (primer paso). El número leído a continuación se traduce (2º paso) como por ejemplo llevar el contenido de los registros R0 y R1 a la ALU (tercer paso), sumarlos (4º paso) y escribir el resultado en el registro R0 (5º paso). Los registros son simples almacenes de 8 bits de ancho de los que se puede leer o escribir y llevar directamente a la ALU. Algunos ejemplos de codificación de las instrucciones: Operación de laCPU

Código binario

Cod. Hex.

Enviar a la CPU a dormir

1001.0101.1000.1000

9588

Sumar el registro R1 al registro R0

0000.1100.0000.0001

0C01

Restar el registro R1 del registro R0

0001.1000.0000.0001

1801

Escribir la constante 170 al registro R16

1110.1010.0000.1010

EA0A

Multiplique el registro R3 con el registro R2 y escribir el resultado 1001.1100.0011.0010 a los registros R1 (MSB) y R0 (LSB)

9C32

Por lo tanto, si la CPU lee en hexadecimal 9588 de la memoria flash, detiene su funcionamiento y no trae más instrucciones. No preocuparse, hay otro mecanismo necesario antes de que la CPU ejecute esto, y además, se puede despertar a la CPU. Ejecutando las instrucciones Si la CPU lee 0C01 hexadecimal, se suma el registro R1 al registro R0 y el resultado se escribe en el registro R0. Esto se ejecuta como se muestra en la imagen.

1

En primer lugar la instrucción (palabra de 16 bits) se lee de la memoria flash y es traducida al ejecutable. Eñ siguiente paso conecta los registros a las entradas de la ALU y suma su contenido. A continuación, el resultado se escribe en el registro. Si la CPU lee 9C23 hexadecimal del flash, los registros R3 y R2 son muliplicados y el resultado se escribe en R1 (los 8 bits más altos) y R0 (los 8 bits más bajos). Si la ALU no soporta la multiplicación, no está equipada con el hardware para la multiplicación (por ejemplo en un Attiny13), la instrucción 9C23 no hace nada en absoluto, ni siquiera abrir una ventana de error. En principio, la CPU puede ejecutar 65.536 (16 bits) instrucciones diferentes. No obstante, debido a que 170 deben escribirse en un registro específico, y además los valores son de entre 0 y 255 en cualquier registro de entre R16 y R31, la cantidad de instrucciones da 256*16=4.096 de los 65.536 teóricamente posibles. La instrucción de carga directa de una constante c (c7...c0) y los registros r (r3...r0, r4 son siempre 1 y no codificados), es codificado como esto: Bit

15 14 13 12 11 10

LDI R,C

1

1

1

0

9

8

c7 c6 c5 c4

7

6

5

4

3

2

1

0

r3 r2 r1 r0 c3 c2 c1 c0

Por qué esos bits se colocan así en la palabra de instrucción sigue siendo secreto de ATMEL. La suma y resta requieren 32 * 32 = 1.024 combinaciones y los registros objetivo (target) R0...R31 (t4...t0) y los registros fuente (source) R0...R31 (s4...s0) son codificados así: Bit

15

14

13

12

11

10

9

8

7

6

5

4

3

2

1

0

ADD Rt,Rs

0

0

0

0

1

1

s4

t4

t3

t2

t1

t0

s3

s2

s1

s0

SUB Rt,Rs

0

0

0

1

1

0

s4

t4

t3

t3

t1

t0

s3

s2

s1

so

Por favor, no aprendan estas colocaciones de bits, no lo necesitará más adelante. Sólo hau que entender cómo se codifica y se ejecuta una palabra de instrucción. Instrucciones en ensamblador No hay necesidad de aprender la colocación de bits en los números de 16 bits porque en ensamblador utilizará abreviaturas llamadas mnemotécnicos para ayudar a la memoria. La representación hexadecimal 9588 en ensamblador es la abreviatura “SLEEP”, que es más fácil de recordar. Sumar simplemente es “ADD”. Los dos registros que se suman están representados como parámetros. Simplemente escriba “ADD R0,R1”, que se traduce a la palabra de 16 bits 0C01. La traducción es realizada por el ensamblador. La CPU sólo entiende 0C01. El ensamblador traduce la línea a la palabra de 16 bits, que se escribe en la memoria flash. La CPU la lee de la memoria flash y la ejecuta. Cada instrucción de la CPU tiene su correspondiente mnemotécnico y viceversa. La capacidad de la CPU determina el alcance de las instrucciones que están disponibles en ensamblador. El lenguaje de la CPU es la base, la mnemotecnia sólo representa la capacidad de la propia CPU. Diferencias con los lenguajes de alto nivel En los lenguajes de alto nivel las construcciones no son dependientes del hardware o la capacidad de una CPU. Esas construcciones trabajan con procesadores diferentes si hay un compilador del lenguaje para las diversas familias de procesadores. El compilador traduce las construcciones del lenguaje al 2

lenguaje binario del procesador. Un GOTO en Basic se parece a un JMP en ensamblador, pero hay una diferencia de concepto entre los dos. Una transferencia de código de un procesador a otro, sólo funciona si el hardware es capaz de hacer lo mismo. Si un procesador de una CPU no tiene acceso a un temporizador de 16 bits, el compilador de un lenguaje de alto nivel tiene que simularlo utilizando un temporizador de 8 bits y un código de consumo-tiempo. Si hay disponibles tres temporizadores y el compilador está escrito para sólo dos o uno, el hardware disponible no se utiliza, por lo que se depende totalmente de la capacidad del compilador, no de las capacidades de la CPU. Otro ejemplo se muestra con la instrucción “MUL”. En ensamblador, el procesador de destino determina si se puede utilizar esta instrucción o se tiene que escribir una rutina de multiplicación. Si en un lenguaje de alto nivel se utiliza una multiplicación, el compilador inserta una biblioteca matemática que multiplica todo tipo de números, incluso si sólo tiene números de 8 bits y sólo con utilizar MUL le bastase. La librería le ofrece un número entero pequeño, una palabra larga y otras rutinas de multiplicación que no son necesarias. Un paquete completo de cosas que realmente no necesita, así que te quedas pronto sin memoria flash en un AVR muy pequeño y tienes que cambiar a un xmega de 35 pines con puertos no utilizados sólo por tener su gran librería con rutinas superfluas ocupando espacio en la memoria flash. Eso es lo que se obtiene a partir de un simple “ * ” sin ni siquiera pedirlo. Ensamblador no es lenguaje máquina Debido a que el ensamblador se acerca más al hardware que cualquier otro lenguaje, a veces se le llama lenguaje máquina. Esto no es correcto porque la CPU sólo entiende instrucciones de 16 bits en formato binario. La cadena “ADD R0,R1” no se puede ejecutar, y el ensamblador es mucho más simple que el lenguaje máquina. Las similitudes entre el lenguaje máquina y ensamblador son una característica, no un error. Intérprete y ensamblador Un intérprete traduce código cercano al lenguaje humano a código binario para la CPU. El intérprete tiene estas características: – – – – –

Primero lee la secuencia de texto “A = A + B” (nueve caracteres de un byte cada uno), tira los cuatro espacios en blanco del texto, localiza las variables A y B (ubicación en los registros o SRAM, la precisión, longitud, etc), identifica el signo + como operador, prepara una secuencia de máquina ejecutable que es equivalente a la formulación del texto.

En consecuencia, el código máquina resultante sería varias palabras largas, leer y escribir variables de y a SRAM, sumar enteros de 16 bits, guardar y restaurar registro de la pila, etc... La diferencia entre el intérprete y el ensamblador es que después de ensamblar la CPU obtiene sus palabras ejecutables directamente. En la interpretación, la CPU pasa la mayor parte del tiempo realizando la tarea de traducción. La traducción puede requerir de 20 ó 200 pasos de CPU, antes de que se puedan ejecutar 3 ó 4 palabras. La velocidad de ejecución es muy lenta. Esto no es problema si hay una velocidad de reloj muy rápida, pero no es apropiado en situaciones de tiempo críticas, donde se requiere una rápida respuesta a un evento. Nadie sabe en que está ocupada exactamente la CPU y cuanto tiempo requiere. No tener que pensar en problemas de tiempo conduce a la incapacidad del programador para resolver problemas de tiempo y esta ignorancia le mantiene incapaz de resolver estas cosas si es necesario.

3

Lenguajes de alto nivel y ensamblador Los lenguajes de alto nivel añaden ciertas capas de separación no transparente entre la CPU y el código fuente. Un ejemplo de concepto poco transparente son las variables. Las variables pueden almacenar un número, una cadena de texto o un simple valor booleano. En el código fuente, un nombre de variable representa un lugar donde se encuentra dicha variable y hay que declararla: el tipo, si es un número y su formato, una cadena y su longitud, etc. Para aprender ensamblador solo hay que olvidar el concepto de variable de los lenguajes de alto nivel. Ensamblador sólo sabe de bits, registros, bytes y bytes de SRAM. La expresión variable no tiene ningún significado en ensamblador. Además, los términos relacionados como “tipo” son inútiles y no tienen tampoco ningún sentido aquí. Los lenguajes de alto nivel requieren la declaración de variables antes de su primer uso en el código fuente, cómo por ejemplo, si es un byte (8 bits), palabra doble (16 bits), entero (15 bits más uno de signo), etc. Los compiladores de estos lenguajes tienen que ubicar las variables declaradas en algún lugar del espacio de almacenamiento disponible, incluyendo los 32 registros. Si esta posición se selecciona a ciegas por el compilador o si se utiliza alguna regla de prioridad, como lo hace el programador en ensamblador cuidadosamente, depende quizás del precio del compilador. El programador sólo puede tratar de entender que criterio utilizó el compilador cuando puso la variable. El poder de decisión se le ha dado al compilador que “libera” al programador de los problemas de decisión, pero lo convierte en un “esclavo” del compilador. En la instrucción “A = A + B” si A se define como un carácter y B como un número (por ejemplo 2), la formulación no es aceptada porque los caracteres no se pueden sumar con números. Los programadores de alto nivel creen que la verificación de tipos les previene de una programación sin sentido. La protección que el compilador proporciona en este caso mediante el error de tipo, es más bien inútil: la adición de 2 a la letra “F”, por supuesto, debe dar “H” como resultado. El ensamblador le permite hacer esto, pero no un compilador. El ensamblador le permite sumar y restar números como 7 o 48 a cada byte almacenado, no importa que tipo de cosas haya en el almacenamiento de bytes. Esto es decisión del programador, no de un compilador. Si cuatro registros representan un valor de 32 bits o cuatro caracteres ASCII, si esos cuatro bytes se colocan del más bajo al alto o viceversa o mezclados por completo, es sólo cuestión del programador. Él es el maestro de la colocación, nadie más. Los tipos son desconocidos, todo se compone de bits y bytes en el almacenamiento disponible. El programador no solo tiene la tarea de organizar sino también la tarea de optimizar. Igual pasa con otras reglas. Olvídese de la mayoría de las reglas en ensamblador. Para programar ensamblador es preciso contar con algunas reglas también, pero diferentes: La mayoría son creadas por el programador para ayudarse a sí mismo. Así que usted puede programar como quiera, no lo que el compilador decida por usted o lo que los profesores teóricos creen que sería buenas reglas de programación. Los programadores de alto nivel son adictos a una serie de conceptos que se interponen en el camino de aprendizaje del ensamblador: separación en diferentes niveles de acceso en el hardware, controladores y otros interfaces. En ensamblador esta separación es una tontería. La separación les condiciona a numerosas soluciones si quieren hacerlo de una manera óptima. Incluso los programadores puristas de alto nivel rompen estas reglas. En ensamblador nada le impide ser creativo, tiene acceso total al hardware, a la memoria, cualquier cosa se puede cambiar. La responsabilidad es únicamente del programador que tiene que usar su cerebro para evitar conflictos de acceso al hardware. La otra cara de la falta de mecanismos de protección es la libertad de hacer lo que quiera y como quiera, irá desarrollando sus propias reglas para evitar errores de ejecución. ¿Que es lo que es realmente más fácil en ensamblador? Todas las palabras y los conceptos que el programador de ensamblador necesita están en la hoja de datos del procesador: las instrucciones y la tabla de puertos. ¡Ya está! Con esto ya se puede construir 4

algo. No son necesarios otros documentos. ¿Como se inicia el temporizador? (está escrito en “Timer.Start)“LDI R16,0x02” y “OUT TCCR0,R16”. ¿Cómo se reinicia a cero? "CLR R16” y “OUT TCCR0,R16", todo está en la hoja de datos. No hay necesidad de consultar una documentación más o menos buena de cómo el compilador define esto o aquello. No hay palabras especiales diseñados por el compilador y los conceptos que hay que aprender están todos en la hoja de datos. Si desea utilizar un contador de tiempo en el procesador para un fin determinado puede utilizar cualquier modo de los 15 posibles modos diferentes, no hay ninguna regla en la forma de acceder al contador de tiempo, para detenerlo o reiniciarlo, etc. ¿Que en un lenguaje de alto nivel es más fácil escribir “A = A + B” que "MUL R16,R17"? No mucho. Si A y B no se definen como bytes o si el procesador es muy pequeño y no entiende MUL, el simple MUL tiene que ser intercambiado con otro código fuente tal como fue diseñado por el programador de ensamblador o copiar y pegar y adaptarlo a sus necesidades. No hay ninguna razón para importar una librería opaca, simplemente descarte la pereza en su cerebro y comience a aprender. El ensamblador le enseña directamente como funciona el procesador. Debido a que un compilador no se hace cargo de las tareas, usted es completamente el capitán del procesador. La recompensa por esto es que se le concede acceso a todo. Si lo desea puede programar una velocidad en baudios de 45,45 bps en el UART, un ajuste de velocidad que no le permite un pc con windows porque el sistema operativo sólo permite múltiplos de 75 (¿porque? Debido a que históricamente los escritores de teletipos mecanicos tenían las cajas de engranajes diseñadas para hacer una selección rápida de 75 o 300 bps). Si además, desea un registro de 1 byte y medio en lugar de uno de 1 o de 2, ¿porque no programar su propio dispositivo con ensamblador? Quien es capaz de programar en ensamblador tiene una idea de todo lo que el procesador le permite. El procesador entero está a la orden del programador. De esta manera puede adoptar soluciones de alta sensibilidad de forma fina y estética. Hardware para la programación en ensamblador AVR La interfaz ISP (programación en sistema) le permite leer y escribir el programa en la memoria flash y en la EEPROM integrada. Esta interfaz trabaja en serie y solo necesita tres lineas de señal: – – –

SCK: Una señal de reloj para desplazar los bits que se escriben en la memoria en un registro de desplazamiento interno y para desplazar los bits que se van a leer de otro registro de desplazamiento. MOSI: La señal de datos que envía los bits que se escriben en el AVR. MISO: La señal de datos que recibe los bits leídos desde al AVR. Estos tres pines se conectan internamente a la máquina de programación sólo si cambia el pin de RESET (a veces también llamado RST o restart) a cero. De lo contrario, durante el funcionamiento normal del AVR, estos pines son programables como lineas de entrada/salida como todos los demás. Si desea utilizar estos pines para otros fines durante el funcionamiento normal y en el sistema de programación, tendrá que tener cuidado de que estos dos objetivos no entren en conflicto. En general, habrá que disociarlos por medio de resistencias o un multiplexor. No es necesario pero si recomendable en el modo ISP (in-systemprogramming), que el hardware de programación suministre la tensión de alimentación. Esto requiere dos lineas adicionales entre el programador y el AVR: GND, Tierra común o polo negativo y VTG (target voltage), tensión de alimentación (por lo general +5.0 voltios). Esto suma 6 lineas entre el hardware 5

programador y el AVR, y se llama conexión ISP6 según la definición de Atmel. Los standards siempre tienen standards alternativos que se utilizaron anteriormente. Esta la base técnica que constituye la industria del adaptador. En este caso, el standard alternativo se diseñó como ISP10 y ha sido utilizado en la placa STK200, también llamada interfaz CANDA. Todavía es un standard muy extendido e incluso la placa STK500 más reciente está equipado con él. El ISP10 tiene una señal adicional para un led rojo, que indicaría que el programador estaría haciendo su trabajo. Es una buena idea, solo tiene que conectar el led con una resistencia y la tensión de alimentación positiva. Herramientas para programar en ensamblador AVR – – – –

El editor, el programa ensamblador, la interfaz de programación de chips y el simulador.

Hay dos posibles vías: 1. Todo en un paquete y 2. Cada tarea se realiza con un programa específico y los resultados se van almacenando en

archivos específicos. Generalmente se elije la primera vía, pero para comprender el mecanismo subyacente, vamos a describir la 2ª vía, que muestra la forma de un archivo de texto con palabras de instrucción en la memoria flash. Estructuración del código ensamblador La estructura básica de un programa en ensamblador AVR consta de : – – – –

comentarios, información de encabezado, código al inicio del programa y estructura general.

Comentarios El texto más útil en un programa de ensamblador son los comentarios. Si usted necesita entender código antiguo que escribió, a veces años antes, apreciará el tener algunas sugerencias y todavía mejor, lo que ocurre en cada linea de código. Si le gusta mantener sus ideas en secreto y ocultarlas contra si mismo y los demás, no use los comentarios. Un comentario empieza con un punto y coma. Todo lo que sigue por detrás en la misma linea será ignorado por el compilador. Si tiene que escribir un comentario de varias lineas, comience cada linea con un punto y coma. Cada programa en ensamblador debe comenzar así: ; ; Click.asm, Programa para conmutar un relé de abierto a cerrado cada 2 segundos ; Escrito por G. Schmidt, última modificación: 10/07/2001 ; Pon comentarios en todas las partes del programa, ya sea en una subrutina completa o una tabla. En el comentario hay que hablar de las características de la subrutina y las condiciones previas necesarias 6

para llamarla o ejecutarla. También se pueden mencionar los resultados de la subrutina en caso de que más tarde se puedan encontrar errores o para ampliarla después. Los comentarios de una sola línea se añaden mediante un punto y coma en la linea después del comando: LDI R16,0x0A ;Aquí se carga algo MOV R17,R16 ;y se copia en otro lugar

Información de encabezado En la parte superior o encabezado del programa debe ir el propósito y función del mismo, el autor, la versión y otros posibles comentarios. Después debería ir el tipo de procesador para el cual está escrito, las constantes pertinentes y una lista con los nombres de los registros. El tipo de procesador es especialmente importante, ya que los programas en ensamblador no se suelen ejecutar en diferentes tipos de chips sin cambios. Las instrucciones no son entendidas igual por todos los tipos de chips. Cada dispositivo tiene diferentes características y capacidades de EEPROM y SRAM. Todas estas características especiales se incluyen en un archivo de encabezado que se denomina xxxxdef.inc, donde xxxx es el tipo de chip como 2313, tn2323 o m8515. Estos archivos están disponibles y los proporciona Atmel. Es un buen estilo de programación incluir este archivo al comienzo de cada programa: .NOLIST ; No liste lo siguiente en el archivo de lista .INCLUDE "m8515def.inc" ; Importación del archivo de definiciones .LIST ; Cambiar a lista de nuevo La ruta de acceso, donde se puede encontrar este archivo, es necesaria si usted no trabaja con el software estudio de ATMEL. Durante el ensamble, por defecto, se genera un fichero *.lst que lista los resultados. Este fichero podría ser muy largo si se incluye el fichero de encabezado. La directiva .NOLIST omite el listado hasta que no se vuelva a activar. Veamos brevemente el archivo de encabezado. En primer lugar este archivo define el tipo de procesador: .DEVICE ATMEGA8515 ; El tipo de dispositivo de destino

La directiva .DEVICE hace que el ensamblador compruebe si están disponibles todas las instrucciones para este tipo de AVR. El resultado es un mensaje de error si se utilizan secuencias de código que no están definidas para este tipo de procesador. El archivo de encabezado define también los registros XH, XL, YH, YL, ZH y ZL. Esto es necesario si usted va a utilizar los punteros de 16 bits X, Y o Z para acceder a los bytes de mayor y menor peso (higher o lower) por separado. Todas las ubicaciones de los puertos también se definen en el archivo de encabezado. Un número hexadecimal indica donde se encuentra definido un puerto en el dispositivo. Los puertos se definen con los nombres que vienen en las hojas de daros para cada procesador. Esto también se aplica a los bits individuales de cada puerto. El acceso de lectura al bit 3 del puerto B se hace usando el nombre PINB3 tal como se define en la ficha técnica. En otras palabras, si usted olvida de incluir el archivo de encabezado se encontrará con un montón de mensajes de error durante la ejecución del ensamblaje. Los mensajes de error que se produzcan no necesariamente tienen que estar relacionados con que falta el archivo de encabezado. Otras cosas que deberían estar al principio de sus programas son las definiciones de registros con los que trabaja, por ejemplo: .DEF mpr = R16 ; Define un nuevo nombre para el registro R16

Esto tiene la ventaja de tener una lista completa de los registros y además poder ver los registros que todavía están disponibles sin utilizar. Cambiar el nombre de los registros evita conflictos en su uso y además los nombres son más fáciles de recordar. Al principio del programa y más adelante hay que definir las constantes, especialmente las que tienen un papel relevante en diferentes partes del programa. Tales constantes, por ejemplo la frecuencia Xtal a la que debe ajustarse el programa, si utiliza en la placa la interfaz serie, se definen así:

7

.EQU fq = 4000000 ; definición de frecuencia Xtal

Así, desde el principio del código fuente se puede ver rápidamente para que reloj se ha escrito el programa, mucho más fácil que buscar esta información perdida dentro de 1482 líneas de código fuente.

Lo que se debe hacer al iniciar el programa Después de haber hecho la cabecera debe comenzar el código del programa. Al principio del código se debe colocar los vectores de reset -y de interrupción- (ver su función en la sección JUMP). A medida que se produzcan saltos relativos se deben colocar detrás las respectivas subrutinas de servicio de interrupción. En el caso de dispositivos Atmega con mayor memoria flash, las instrucciones de salto se pueden colocar aquí, pero siendo cuidadoso. Hay aquí un poco de espacio de sobra para algunas subrutinas antes de colocar el programa principal, que siempre comienza con la inicialización del puntero de pila, el establecimiento de los registros con los valores por defecto y la inicialización de los componentes de hardware que se van a utilizar. El código siguiente es específico para cada programa. El Ensamblador Tenemos un archivo de texto con caracteres ASCII. El siguiente paso es traducir el código de tal forma que pueda ser comprendido por el chip AVR. Esto se llama ensamblaje, que significa “poner las palabras de instrucción de forma correcta”. El programa que lee el archivo de texto y produce otro de salida se llama Ensamblador. En su forma más simple es una aplicación de línea de comandos que cuando se le llama recibe como argumentos la ruta del archivo de texto y algunos modificadores opcionales para comenzar a ensamblar las instrucciones que encuentra en el citado archivo. Si su editor de texto permite llamar a programas externos esto es una tarea fácil. Si no, es más conveniente escribir un pequeño archivo por lotes o script (de nuevo con un editor). Este archivo por lotes debería tener una línea como esta: PathToAssembler\Assembler.exe -options PathToTextfile\Textfile.asm Al hacer click en el lugar del editor que llama al programa externo o en el archivo por lotes se incia el ensamblador de linea de comandos.

La ventana pequeña informa de proceso completo de traducción. En este caso no hay errores. Si se producen estos son notificados junto con su tipo y número de línea. El ensamblador ha producido una palabra de código resultante de la instrucción RJMP que se ha usado. El ensamblador de nuestro único archivo de texto asm ha producido otros cuatro archivos, aunque no todos se aplican aquí. Uno de estos cuatro nuevos archivos, TEST.EEP, 8

contiene lo que se va a escribir en la EEPROM del AVR. Esto no es muy interesante en este caso, ya que no se genera ningún contenido para la EEPROM, por lo que el ensamblador lo que hace es eliminar este archivo porque está vacío. El siguiente archivo, TEST.HEX, es más relevante ya que contiene las instrucciones con las que se va a programar el chip. Los números hexadecimales se escriben en forma ASCII especial, junto con la información de la dirección y una suma de comprobación, esto para cada linea. Este formato se llama intel hexadecimal, es muy antiguo y proviene del mundo de la informática. El tercer archivo, TEST.OBJ, será presentado más adelante, este archivo es necesario para simular un AVR. Su formato es hexadecimal y definido por ATMEL. El contenido de un editor hexadecimal se parece a esto. Atención, este formato de archivo no es compatible con el software de programación. El archivo obj sólo lo generan algunos ensambladores de Atmel, no espere estos archivos en todos los ensambladores. El cuarto archivo, TEST.LST, es un archivo de texto. Su contenido se puede ver con un simple editor. El archivo muestra el programa con todas sus direcciones, instrucciones y mensajes de error en un formato legible. Puede necesitar este archivo en casos en los haya errores por motivos de depuración. Estos archivos de listado solo se generan si se incluye la opción correspondiente en la linea de comandos y si la directiva .NOLIST no lo suprime. Programación del chip Para programar el código hexadecimal, tiene que disponer de él en un archivo de texto y de un software de programación AVR. Este software lee los archivos .HEX y transfiere su contenido ya sea bit a bit (programación serie) o byte a byte (programación en paralelo) a la memoria flash del AVR.

9

La imagen sería un ejemplo, pero tenga en cuenta que la ventana que aparece es del software AVR ISP.exe, un programa histórico que ya no distribuye Atmel. Ahora hay otros programas similares. El software grabará nuestro código en la memoria de programa del chip. El hardware de programación y distintas alternativas de software para diferentes sistemas operativos están disponibles en internet. Mencionamos aquí PonyProg2000 como un ejemplo para la programación en paralelo o por el puerto de comunicación serial. Simulación en el software Studio Algunas veces, aunque el ensamblado no produzca errores, el código no realiza exactamente lo que debería cuando se graba en el chip. Testear el software directamente en el chip podría ser complicado, especialmente si usted tiene un hardware mínimo sin oportunidad de mostrar los resultados provisionales o las señales de depuración. En estos casos el paquete de software Studio de Atmel ofrece oportunidades ideales para la depuración. El código del programa puede ponerse a prueba paso a paso para ver los resultados. Las imágenes que mostramos a continuación han sido tomadas de la versión 4 de Studio, que está disponible gratuitamente previo registro en el sitio web de Atmel. La aplicación Studio tiene todo lo necesario para desarrollar, depurar, simular y grabar sus programas de ensamblador en el tipo de AVR de su elección.

El primer cuadro de diálogo le pregunta si quiere abrir un proyecto ya en curso o si va a empezar uno nuevo. Después de marcar nuevo proyecto, el botón siguiente le lleva a la configuración de su nuevo proyecto. En este cuadro de diálogo selecciona “Atmel AVR Assembler” como tipo de proyecto, le asigna un nombre (en este caso test1) y después una ubicación para el proyecto donde se tiene acceso de escritura. El botón siguiente abre el cuadro de diálogo de selección de dispositivos.

10

Como plataforma de depuración seleccionar AVR simulador o AVR simulador 2. Como dispositivo, aquí fue seleccionado un Atmega8. Se cierra esta ventana con el botón finalizar y se abre una gran ventana que contiene un montón de diferentes sub-ventanas.

La ventana de la izquierda le permite ver y manipular todos los archivos del proyecto. En el centro, la ventana del editor le permite escribir el código fuente (no se preocupe por los colores, el resaltado de sintaxis es agregado por el editor). En la parte inferior izquierda está la ventana de “construcción” donde deben aparecer los mensajes de error. En el lado derecho una rara ventana de entrada/salida con la parte inferior en blanco (la veremos más adelante). Todas las ventanas se pueden redimensionar y desplazar independientemente por la pantalla. 11

Después de escribir su programa completo en código fuente, vaya al menú construir (build), pinche en el submenú construir y el resultado se mostrará en la ventana de construcción. Asegúrese de leer todo el contenido de la ventana una vez ya que le brindará mucha información. Si ha echo todo correctamente aparecerá un circulo verde con un mensaje indicándoselo, de lo contrario aparecerá un circulo rojo. Después puede ir a los menús Debug, Ver, Herramientas y Procesador, cambiar su tamaño o posición, o modificar su contenido. Debería tener este aspecto: La ventana del editor tiene ahora una flecha amarilla. Esta flecha apunta a la siguiente instrucción que se ejecutará (realmente es una simulación). La ventana de procesador muestra el valor actual del contador de programa (el programa se inicia en la dirección 0), el puntero de pila, un contador de ciclos y un cronómetro. Si pulsa en el pequeño “+” a la izquierda de la palabra “Registers” se muestra el contenido de los 32 registros (todos están vacíos al inicio de la simulación).

Ahora vamos a proceder con la primera instrucción. En el menú “Debug” y después “step into” o simplemente pulsando F11 se ejecuta la primera instrucción.

12

La instrucción “ldi rmp,0b11111111” carga el valor binario 1111.1111 en el registro R16. La flecha amarilla ya ha avanzado una instrucción hacia abajo, se encuentra ahora en la instrucción OUT. En la ventana de procesador, el contador de programa y el de ciclos han avanzado uno a su vez. El registro 16, en la lista de registros, está en rojo y muestra el valor 0xFF en hexadecimal, que en binario es 1111.1111. Avanzando un paso más se ejecuta la instrucción OUT (pulsando la tecla F11 por ejemplo) y muestra la siguiente información.

La instrucción “out DDRB,rmp” escribe 0xFF al puerto llamado DDRB. Ahora la acción se encuentra en la ventana I/O. Si se pulsa en el pequeño “+” a la izquierda de PORTB, se muestra el valor 0xFF en el puerto DDRB de dos formas diferentes: como 0xFF en la parte superior de la ventana y con 8 cuadraditos en negro en la parte inferior. Pulsamos 2 veces F11 y se escribe 0x55 en el PORTB. Como era de esperar, hay cambios en el contenido del PORTB. Hay ahora cuatro casillas en negro y las otras cuatro en blanco.

13

Otros dos F11 y se escribe 0xAA en el PORTB y se ha invertido el color en los cuadraditos. Es lo que se esperaba. Pero, ¿que ha ocurrido en PINB? Tiene los colores opuestos a PORTB, igual que los tenía el PORTB en el paso anterior. PINB es un puerto de entrada para los pines externos. Como la dirección de los puertos en DDRB están fijados para ser salidas, PINB sigue el estado de los pines en PORTB justo en ciclo más tarde. No hay nada mal aquí. Para comprobarlo, basta con pulsar F11 varias veces para comprobar que es correcto. Esta es una pequeña muestra del software simulador. El simulador es capaz de mucho más, asi que debe ser aplicado extensivamente en los casos de errores de diseño. Investigue los elementos de los menús, hay muchas cosas que se pueden mostrar. Por el momento, antes de seguir jugando con el simulador, hay que aprender algunas cosas básicas del lenguaje ensamblador, así que dejaremos el software studio por un tiempo. ¿Qué es un registro? Los registros son depósitos especiales con capacidad de 8 bits y se ven así: Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2

Bit 1

Bit 0 14

Tenga en cuenta la numeración de los bits, el menos significativo es el cero (matemáticamente 2 0 = 1). Un registro puede almacenar números del 0 al 255 sin signo, o números de -128 a 127 (números enteros con el bit 7 de signo). También puede alamacenar un valor que representa un carácter codificado en ASCII (por ejem. 'A'), o tan sólo 8 bits individuales que no tienen relación entre sí (por ejem. 8 banderas individuales, utilizadas para señalar 8 valores booleanos). Las características especiales de los registros, comparándolas con otras formas de almacenamiento, son: – están conectados directamente con la unidad central de proceso, llamada acumulador. – Pueden ser utilizados directamente en las instrucciones de ensamblador, ya sea como destino de un resultado o para leer el contenido y efectuar un cálculo o transferencia. – Las operaciones con su contenido solo requieren una palabra de instrucción. Hay 32 registros en un AVR. Originalmente su denominación va de R0 a R31, pero se les puede cambiar el nombre a otro más significativo con una directiva del ensamblador. Un ejemplo: .DEF RegistroPreferido = R16 Las directivas de ensamblador siempre empiezan con un punto, a diferencia de las instrucciones y etiquetas que NUNCA empiezan con un punto. Tenga en cuenta que las directivas sólo tienen sentido para el ensamblador, no producen ningún código ejecutable en el chip de destino. El nombre “RegistroPreferido” no se mostrará en el código hexadecimal ensamblado, por lo que no podrá ser derivado de éste. En lugar de utilizar el nombre de registro R16, ahora podremos utilizar nuestro propio nombre “RegistroPreferido” si queremos utilizar R16 en una instrucción. Tenemos que escribir un poco más de texto cada vez que usemos este registro, pero en cambio, tenemos una asociación de lo que podría ser el contenido de este registro. El uso en la linea de instrucción: LDI

RegistroPreferido, 150

que significa cargar el número 150 inmediatamente en el registro R16 (LoaD Inmediate). LDI carga un valor fijo o una constante al registro. Tras el ensamblado o traducción de este código a binario o hexadecimal, el programa almacenado en el chip luce así: 000000 E906

Esto aparecerá en el listado, el archivo *.lst producido por el ensamblador que es un simple archivo de texto. Todos los números estan en formato hexadecimal. El primero (000000) indica la dirección en la memoria flash donde se escribe y el segundo (E906) es el código de la instrucción. E906 indica al procesador tres cosas diferentes en una sola palabra, aunque no lo vea directamente: – – –

Un código básico de instrucción de carga, sinónimo de LDI, el registro de destino (R16), donde se va a escribir el valor 150, y el valor de la constante (150).

No tiene que recordar todo esto. El ensamblador sabe como traducirlo para dar finalmente E906 y el AVR lo ejecutará. 15

En una instrucción dos diferentes registros pueden desempeñar una función. Un ejemplo fácil de una instrucción de este tipo es la instrucción de copia, MOV. El nombre de esta instrucción induce a confusión porque el contenido de un registro no se puede mover (¿que quedaría en el registro si se mueve el contenido a otra parte?). Mejor debería llamarse COPY, ya que lo que hace es copiar el contenido de un registro a otro registro. De esta manera: .DEF RegistroPreferido = R16 .DEF OtroRegistro = R15 LDI RegistroPreferido, 150 MOV OtroRegistro, RegistroPreferido

Las dos primeras líneas de este pedazo de programa son directivas que definen los nuevos nombres de los registros R16 y R15. Como se mencionó antes, estas dos lineas no producen código para el AVR. Las líneas con las instrucciones LDI y MOV producen el siguiente código: 000000 E906 000001 2F01

que escribe el valor 150 en el registro R16 y copia su contenido al registro de destino R15. AVISO IMPORTANTE: El primer registro es siempre el registro de destino donde se escribe el resultado. Esto es diferente a como se lee o escribe y puede confundir al principio, pero de izquierda a derecha o de derecha a izquierda, son simples convenciones. Registros diferentes El principiante que quiera escribir las instrucciones de arriba de esta manera: .DEF OtroRegistro = R15 LDI OtroRegistro, 150

Ha perdido. Solo los registros R16 a R31 cargan una constante de inmediato con la instrucción LDI. R0 a R15 no hacen esto. Esta restricción no es muy fina, pero no pudo evitarse durante el proceso de construcción del conjunto de instrucciones de los AVR. Sin embargo, hay una excepción a esta regla: el establecimiento a cero de un registro. La instrucción: CLR RegistroPreferido es válida para todos los registros. Esta restricción, además de en la instrucción LDI, se da en las siguientes instrucciones: ANDI Rx,K ; CBR Rx,M ; CPI Rx,K ; SBCI Rx,K ; SBR Rx,M ; SER Rx ; SUBI Rx,K ;

and-bit al registro Rx con un valor constante K, Borrar todos los bits del registro Rx y establecerlos al valor de la máscara constante M, Compara el contenido del registro Rx con un valor constante K, Reste la constante K y el valor actual de la bandera de acarreo al contenido del registro Rx y almacena el resultado en este último. Establecer a 1 todos los bits del registro Rx, que son 1 en la máscara constante M, Establecer a 1, todos los bits del registro Rx (igual que LDI Rx,255), Restar la constante K del contenido del registro Rx y almacenar el resultado en este último.

En todas estas instrucciones, el registro debe ser cualquiera de entre R16 y R31. Si va a utilizar estas instrucciones, hay que seleccionar uno de estos registros para operar. Es más corto y más fácil de programar. Esta es una razón más por la que se debe utilizar la directiva para definir el nombre de un registro.

16

Registros de puntero Una función extra muy especial se define para los pares de registros: R27:R26, R29:R28 y R31:R30. El papel es tan importante que estas parejas tienen nombres cortos extra en ensamblador AVR: X, Y y Z. Estos nombres cortos son entendidos por el ensamblador. Estos pares son registros de puntero de 16 bits, capaz de apuntar a las direcciones con un máx. 16 bits de longitud, por ejemplo en lugares de SRAM (X, Y o Z) o en lugares de memoria de programa (Z). Acceder a posiciones de memoria con punteros El byte más bajo del puntero de 16 bits se encuentra en el registro inferior y el más alto en el registro superior. Ambas partes tienen su propio nombre. El byte más alto de Z se denomina ZH (=R31) y el más bajo es ZL (=R30). Estos nombres están definidos en el ensamblador. Dividir una palabra de 16 bits constante en sus dos bytes y escribir estos en un registro de puntero se hace como sigue: .EQU address = RAMEND

;RAMEND es la dirección de 16 bits más alta de la memoria SRAM, que ;está definida en el correspondiente archivo de cabecera *def.inc, LDI YH,HIGH(dirección) ;Cargar el MSB (byte más significativo) de la 'dirección' LDI YL,LOW(dirección) ;Cargar el LSB (byte menos significativo) de la 'dirección'

El acceso a través de registros de puntero se programa con instrucciones diseñadas especialmente. El acceso de lectura se denomina LD (LoaD) y el acceso de escritura ST (Store). Ejemplos con el puntero X (del mismo modo se puede usar Y y Z para tal fin):

Puntero

Secuencia

ejemplos

X

Lectura/escritura de dirección X, no cambia el puntero

LD R1,X o ST X,R1

X+

Lectura/escritura desde/hacia dirección X e incremento del puntero después en uno

LD R1,X+ o ST X+,R1

-X

Primero, disminuir el puntero en uno y lectura/escritura desde/hacia la nueva dirección, después

LD R1,-X o ST -X,R1

Lectura de la memoria de programa flash con el puntero Z Sólo hay una instrucción para el acceso de lectura en el espacio de almacenaje del programa. Se define para el puntero Z y que se denomina LPM (carga de memoria de programa). La instrucción copia el byte de la dirección del programa Flash a Z y al registro R0. Como la memoria de programa está organizada por palabras (una instrucción en una dirección se compone de 16 bits o 2 bytes o 1 palabra), el bit menos significativo selecciona el byte inferior o superior (0 = byte bajo, 1 = byte alto). Debido a esto la dirección original se debe multiplicar por 2 y el acceso está limitado a 15 bits o 32 kB de memoria de programa. De esta manera: LDI ZH,HIGH(2*address) LDI ZL,LOW(2*address) LPM

Después de esta instrucción, la dirección debe ser incrementada para apuntar al siguiente byte en la memoria de programa. Como se utiliza muy a menudo, se ha definido una instrucción especial de incremento de puntero: ADIW ZL,1 LPM

17

ADIW (Add Immediate Word). De esta manera se pueden sumar hasta un máximo de 63. Hay que tener en cuenta que le ensamblador espera la parte baja, ZL, del registro de puntero como primer parámetro. Esto es algo confuso ya que la adición se hace como una operación de 16 bits. La instrucción complementaria, que resta un valor constante entre 0 y 63 al registro de puntero de 16 bits, se llama SBIW (SuBtract Immediate Word). ADIW y SBIW son válidas para los pares de registros de punteros X, Y y Z y para los pares de registros R25:R24 en tanto que no tienen un nombre extra y no se permite el acceso a posiciones de memoria de programa o SRAM. El par R25:R24 es ideal para manejar valores de 16 bits.

Como el incremento después de la lectura es muy a menudo necesario, los nuevos tipos de AVR soportan la instrucción LPM R,Z+

Esto permite llevar el byte leído a cualquier ubicación R y auto-incrementa el puntero de registro. Listas en la memoria flash de programables Ahora que sabemos cómo leer desde la memoria flash es posible que desee colocar una lista de constantes o una cadena de texto. ¿Cómo insertar una lista de valores en la memoria de programa?

Esto se hace con las directivas del ensamblador .DB y .DW con las que se pueden insertar listas de valores por bytes o por palabras. Organizar listas por bytes tiene este aspecto: .DB 123,45,67,89 ;una lista de cuatro bytes, escritos en forma decimal .DB "This is a text." ;una lista de caracteres de un byte, escrito como texto

Siempre se debe colocar un número par de bytes en cada línea. De lo contrario el ensamblador añadirá un byte cero al final, lo que podría no ser deseado. Una lista similar de palabras se parece a esto: .DW 12345,6789 ;una lista con dos palabras constantes

En lugar de constantes también se pueden colocar etiquetas (por ejemplo, los objetivos de salto) en esa lista, así: Label1: [ ... aquí algunas instrucciones ... ] Label2: [ ... aquí algunas instrucciones ... ] Lista: .DW Label1,Label2 ;una lista de etiquetas por palabras

Las etiquetas deben comenzar en la columna 1 y tienen que terminar con ":". Tenga en cuenta que en la lectura de las etiquetas de la lista con LPM (y el incremento posterior del puntero) se obtiene primero el byte más bajo de la palabra y después el byte superior. Acceso a los registros con punteros Una aplicación muy especial para los registros de puntero es el acceso a los mismos registros. Los registros se encuentran en los primeros 32 bytes del espacio de direcciones del chip (desde la dirección 0x0000 a la 0x001F). Este acceso sólo es válido si tienes que copiar el contenido de algún registro hacia la SRAM o la EEPROM o leer estos valores desde ellas hacia algún registro. Es más común el uso de punteros para el acceso a tablas con valores fijos en el espacio de memoria de programa. Como ejemplo, una tabla con 10 valores diferentes de 16 bits, donde se carga el 5º valor a R25:R24, así :

18

MyTable: .DW 0x1234,0x2345,0x3456,0x4568,0x5678 ;Valores de la tabla organizada .DW 0x6789,0x789A,0x89AB,0x9ABC,0xABCD ;por palabras Read5: LDI ZH,HIGH(MyTable*2) ;dirección en la tabla al puntero Z LDI ZL,LOW(MyTable*2) ;multiplicado por 2 para el acceso byte a byte ADIW ZL,10 ;puntero al quinto valor de la tabla LPM ;lectura del LSB de la memoria de programa MOV R24,R0 ;copia LSB al registro de 16 bits ADIW ZL,1 ;puntero al MSB en la memoria de programa LPM ;lectura del valor MSB de la tabla MOV R25,R0 ;copia MSB al registro de 16 bits

Este es sólo un ejemplo. Se puede calcular la dirección de la tabla en Z de un cierto valor de entrada, llevando los respectivos valores de la tabla. Las tablas pueden ser organizadas byte a byte y también por caracteres. Recomendaciones de uso de los registros Las siguientes recomendaciones, si se siguen, pueden decidir si usted es un programador en ensamblador eficaz: – – – – – – – –

Definir nombres para los registros con la directiva .DEF, no utilizar nunca su nombre directo Rx. Si necesita acceso al puntero reserve los registros R26 a R31para tal fin. Un valor de 16 bits situarlo mejor en R25:R24. Si tiene que leer de la memoria del programa, por ejemplo tablas fijas, reserve Z (R31:R30) y R0 para tal fin. Si usted planea tener acceso a bits individuales dentro de ciertos registros (por ejemplo, banderas de testeo), el utilice los registros R16 a R23 para tal fin. Los registros necesarios para las matemáticas están mejor situados de R1 a R15. Si tiene registros más que suficientes, coloque las variables en los registros. Si se queda corto de registros, coloque tantas variables como sea necesario en SRAM.

Puertos ¿Qué es un puerto? Los puertos en los AVR son puertas de la unidad central de proceso a los componentes de hardware y software interno y externo. La CPU se comunica con estos componentes, lee de ellos o les escribe, como los contadores o los puertos paralelos, etc. El puerto más utilizado es el registro de banderas, donde se señalizan las operaciones y se leen las condiciones de bifurcación. Hay 64 puertos diferentes, que no están físicamente disponibles en todos los tipos de AVR. Dependiendo del espacio de almacenamiento y otros componentes internos, los diferentes puertos están disponibles y accesibles o no. Los puertos que pueden ser utilizados en un determinado tipo de avr con su correspondiente procesador están especificados en las hojas técnicas de datos. Los Atmega y ATXmega ampliados tienen más de 64 puertos, pero el acceso a los puertos más allá de #63 es diferente (veáse más adelante). Los puertos tienen una dirección fija, con la cual se comunica la CPU. La dirección es independiente del tipo de AVR. Así, por ejemplo, la dirección de puerto del puerto B es siempre 0x18 (en notación hexadecimal. 0x18 en decimal es 24). Usted no tiene que recordar estas direcciones de los puertos, ya 19

que tienen un alias conveniente. Estos nombres se definen en los archivos ' include ' (archivos de cabecera) para los diferentes tipos de AVR y que son proporcionados por el fabricante. Los archivos incluyen una línea que define la dirección del puerto B de la siguiente manera: .EQU PORTB, 0x18

Así que sólo tenemos que recordar el nombre del puerto B, no su ubicación en el espacio de entradas/salidas del chip. El archivo de inclusión 8515def.inc está involucrado en la directiva de ensamblador: .INCLUDE "C:\algúnlugar\8515def.inc"

y los registros del 8515 también son definidos en este archivo. Los puertos por lo general se organizan como números de 8 bits, pero también puede contener hasta 8 bits individuales que no tienen mucho que ver entre sí. Estos bits tienen un significado y tienen su propio nombre asociado en el archivo de inclusión, por ejemplo para habilitar la manipulación de un solo bit. La convención de nombre se da para no tener que recordar estos bits y su posición. En las hojas de datos se pueden encontrar las tablas de puertos. El acceso de escritura a los puertos A modo de ejemplo, el registro de control general del MCU, llamado MCUCR, consiste en un número de bits de control que controlan las propiedades generales del chip. He aquí los detalles del puerto MCUCR del AT90S8515, tomados de las hojas de datos del dispositivo (otros puertos son similares):

Es un puerto empacado de 8 bits de control con sus propios nombres (ISC00, ISC01, ...). Si quiere enviar a su AVR a un profundo sueño, tiene que mirar en su hoja de datos como configurar los bits implicados, Así: .DEF RegistroPreferido = R16 LDI RegistroPreferido, 0b00100000 OUT MCUCR, RegistroPreferido SLEEP La instrucción OUT trae el contenido de RegistroPreferido, con el bit SE (Sleep-Enable-Bit) activado, al puerto MCUCR. El bit SE permite al AVR irse a dormir, cada vez que la instrucción SLEEP aparezca en el código. Todos los bits del puerto MCUCR también se pueden establecer con las instrucciones anteriores. Si el bit SM (Sleep-Mode) se establece a cero, el AVR entrará en un modo denominado “duermevela”: no se ejecutarán más instrucciones pero el chip reacciona todavía al temporizador y otras interrupciones de hardware. Estos eventos interrumpen el sueño del AVR si tienen que notificar a la CPU. La formulación anterior no es muy transparente, porque "0b00100000" no es fácil de recordar, y no se ve fácilmente que se ha establecido en una de estas instrucciones. Por lo tanto, es una buena idea formular la instrucción LDI de la siguiente manera: LDI RegistroPreferido, 1
View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF