Tabla de contenido:

Tutorial de ensamblador AVR 3: 9 pasos
Tutorial de ensamblador AVR 3: 9 pasos

Video: Tutorial de ensamblador AVR 3: 9 pasos

Video: Tutorial de ensamblador AVR 3: 9 pasos
Video: Tutorial Avr ensamblador Tercer porgrama 2024, Mes de julio
Anonim
Tutorial de ensamblador AVR 3
Tutorial de ensamblador AVR 3

¡Bienvenido al tutorial número 3!

Antes de comenzar, quiero hacer un punto filosófico. No tenga miedo de experimentar con los circuitos y el código que estamos construyendo en estos tutoriales. Cambie los cables, agregue nuevos componentes, elimine componentes, cambie líneas de código, agregue nuevas líneas, elimine líneas y vea qué sucede. Es muy difícil romper algo y si lo haces, ¿a quién le importa? Nada de lo que estamos usando, incluido el microcontrolador, es muy caro y siempre es educativo ver cómo pueden fallar las cosas. No solo sabrá qué no hacer la próxima vez, sino que, lo que es más importante, sabrá por qué no hacerlo. Si eres como yo, cuando eras un niño y compraste un juguete nuevo, no pasó mucho tiempo antes de que lo tuvieras en pedazos para ver qué lo hacía funcionar, ¿verdad? A veces, el juguete terminaba irreparablemente dañado, pero no era gran cosa. Permitir que un niño explore su curiosidad incluso hasta el punto de los juguetes rotos es lo que lo convierte en un científico o un ingeniero en lugar de un lavaplatos.

Hoy vamos a cablear un circuito muy simple y luego profundizar un poco en la teoría. Lo siento, pero necesitamos las herramientas. Prometo que lo compensaremos en el tutorial 4, donde haremos una construcción de circuitos más seria y el resultado será bastante bueno. Sin embargo, la forma en que necesita hacer todos estos tutoriales es de una manera muy lenta y contemplativa. Si simplemente avanza, construye el circuito, copia y pega el código y ejecútalo, seguro que funcionará, pero no aprenderás nada. Debes pensar en cada línea. Pausa. Experimentar. Inventar. Si lo hace de esa manera, al final del quinto tutorial, estará construyendo cosas interesantes y no necesitará más tutoría. De lo contrario, simplemente está observando en lugar de aprender y crear.

En cualquier caso, basta de filosofía, ¡empecemos!

En este tutorial necesitará:

  1. tu tablero de prototipos
  2. un LED
  3. cables de conexión
  4. una resistencia alrededor de 220 a 330 ohmios
  5. El manual del conjunto de instrucciones: www.atmel.com/images/atmel-0856-avr-instruction-se…
  6. La hoja de datos: www.atmel.com/images/Atmel-8271-8-bit-AVR-Microco…
  7. un oscilador de cristal diferente (opcional)

Aquí hay un enlace a la colección completa de tutoriales:

Paso 1: construcción del circuito

Construyendo el circuito
Construyendo el circuito

El circuito de este tutorial es extremadamente simple. Básicamente, vamos a escribir el programa "blink", por lo que todo lo que necesitamos es lo siguiente.

Conecte un LED a PD4, luego a una resistencia de 330 ohmios, luego a tierra. es decir.

PD4 - LED - R (330) - TIERRA

¡y eso es todo!

Sin embargo, la teoría va a ser difícil …

Paso 2: ¿Por qué necesitamos los comentarios y el archivo M328Pdef.inc?

Creo que deberíamos comenzar mostrando por qué el archivo de inclusión y los comentarios son útiles. Ninguno de ellos es realmente necesario y puede escribir, ensamblar y cargar código de la misma manera sin ellos y se ejecutará perfectamente (aunque sin el archivo de inclusión puede recibir algunas quejas del ensamblador, pero sin errores)

Aquí está el código que vamos a escribir hoy, excepto que eliminé los comentarios y el archivo de inclusión:

.dispositivo ATmega328P

.org 0x0000 jmp a.org 0x0020 jmp ea: ldi r16, 0x05 out 0x25, r16 ldi r16, 0x01 sts 0x6e, r16 sei clr r16 out 0x26, r16 sbi 0x0a, 0x04 sbi 0x0b, 0x04 b: sbi 0x0bcall, 0x04 b: sbi 0x4 rcall, 0x04 b: sbi 0x0bcall cbi 0x0b, 0x04 rcall c rjmp bc: clr r17 d: cpi r17, 0x1e brne d ret e: inc r17 cpi r17, 0x3d brne PC + 2 clr r17 reti

bastante simple ¿verdad? Ja ja. Si ensambló y cargó este archivo, hará que el LED parpadee a una velocidad de 1 parpadeo por segundo con el parpadeo de 1/2 segundo y la pausa entre parpadeos de 1/2 segundo.

Sin embargo, mirar este código es poco esclarecedor. Si escribiera un código como este y quisiera modificarlo o reutilizarlo en el futuro, lo pasaría mal.

Así que pongamos los comentarios e incluyamos el archivo de nuevo para que podamos entenderlo.

Paso 3: Blink.asm

Aquí está el código que discutiremos hoy:

;************************************

; escrito por: 1o_o7; fecha:; versión: 1.0; archivo guardado como: blink.asm; para AVR: atmega328p; frecuencia de reloj: 16 MHz (opcional); **********************************; Función del programa: ---------------------; cuenta los segundos parpadeando un LED;; PD4 - LED - R (330 ohmios) - GND;; --------------------------------------.nolist.include "./m328Pdef.inc".list; ==============; Declaraciones:.def temp = r16.def overflows = r17.org 0x0000; ubicación de la memoria (PC) del controlador de reinicio rjmp Reset; jmp cuesta 2 ciclos de CPU y rjmp cuesta solo 1; así que a menos que necesite saltar más de 8k bytes; solo necesitas rjmp. Por lo tanto, solo algunos microcontroladores; tener rjmp y no jmp.org 0x0020; ubicación de la memoria del controlador de desbordamiento de Timer0 rjmp overflow_handler; vaya aquí si se produce una interrupción de desbordamiento del timer0; ============ Reset: ldi temp, 0b00000101 out TCCR0B, temp; configure los Bits selectores de reloj CS00, CS01, CS02 en 101; esto pone Timer Counter0, TCNT0 en modo FCPU / 1024; por lo que marca en la CPU freq / 1024 ldi temp, 0b00000001 sts TIMSK0, temp; establezca el bit de habilitación de interrupción de desbordamiento del temporizador (TOIE0); del registro de máscara de interrupción del temporizador (TIMSK0) sei; habilitar interrupciones globales - equivalente a "sbi SREG, I" clr temp out TCNT0, temp; inicialice el temporizador / contador a 0 sbi DDRD, 4; establecer PD4 en salida; ======================; Cuerpo principal del programa: parpadeo: sbi PORTD, 4; encienda el LED en PD4 rcall delay; el retraso será de 1/2 segundo cbi PORTD, 4; apague el LED en el retardo de llamada del PD4; el retraso será de 1/2 segundo parpadeo rjmp; bucle de regreso al retraso de inicio: clr se desborda; establecer overflows en 0 sec_count: cpi overflows, 30; comparar el número de desbordamientos y 30 brne sec_count; bifurca para volver a sec_count si no es igual a ret; si se han producido 30 desbordamientos, vuelva a parpadear overflow_handler: inc overflows; agregue 1 a la variable overflows cpi overflows, 61; compárese con 61 brne PC + 2; Programe el contador + 2 (salte la siguiente línea) si no es igual, se desborda de clr; si ocurrieron 61 desbordamientos, reinicie el contador a cero reti; volver de la interrupción

Como puede ver, mis comentarios ahora son un poco más breves. Una vez que sabemos qué hacen los comandos en el conjunto de instrucciones, no es necesario que lo expliquemos en los comentarios. Solo necesitamos explicar lo que está sucediendo desde el punto de vista del programa.

Discutiremos qué hace todo esto pieza por pieza, pero primero intentemos tener una perspectiva global. El cuerpo principal del programa funciona de la siguiente manera.

Primero establecemos el bit 4 de PORTD con "sbi PORTD, 4", esto envía un 1 a PD4 que pone el voltaje a 5V en ese pin. Esto encenderá el LED. Luego saltamos a la subrutina "delay" que cuenta 1/2 segundo (explicaremos cómo lo hace más adelante). Luego volvemos a parpadear y borramos el bit 4 en PORTD, lo que establece PD4 en 0V y, por lo tanto, apaga el LED. Luego lo retrasamos por otro 1/2 segundo, y luego saltamos de nuevo al comienzo de blink con "rjmp blink".

Debería ejecutar este código y ver que hace lo que debería.

¡Y ahí lo tienes! Eso es todo lo que hace este código físicamente. La mecánica interna de lo que está haciendo el microcontrolador es un poco más complicada y es por eso que estamos haciendo este tutorial. Así que analicemos cada sección por turno.

Paso 4: Directivas del ensamblador.org

Ya sabemos lo que hacen las directivas de ensamblador.nolist,.list,.include y.def de nuestros tutoriales anteriores, así que primero echemos un vistazo a las 4 líneas de código que vienen después de eso:

.org 0x0000

jmp Restablecer.org 0x0020 jmp overflow_handler

La instrucción.org le dice al ensamblador en qué parte de la "Memoria del programa" colocar la siguiente instrucción. A medida que se ejecuta su programa, el "Contador de programa" (abreviado como PC) contiene la dirección de la línea actual que se está ejecutando. Entonces, en este caso, cuando la PC esté en 0x0000, verá el comando "jmp Reset" que reside en esa ubicación de memoria. La razón por la que queremos poner jmp Reset en esa ubicación es porque cuando el programa comienza, o el chip se reinicia, la PC comienza a ejecutar el código en este lugar. Entonces, como podemos ver, acabamos de decirle que "salte" inmediatamente a la sección etiquetada "Restablecer". ¿Por qué hicimos eso? ¡Eso significa que las dos últimas líneas anteriores simplemente se están saltando! ¿Por qué?

Bueno, ahí es donde las cosas se ponen interesantes. Ahora tendrá que abrir un visor de pdf con la hoja de datos completa de ATmega328p que señalé en la primera página de este tutorial (por eso es el elemento 4 en la sección "necesitará"). Si tu pantalla es demasiado pequeña o ya tienes demasiadas ventanas abiertas (como es mi caso), puedes hacer lo que yo hago y ponerlo en un Ereader o en tu teléfono Android. Lo usará todo el tiempo si planea escribir código ensamblador. Lo bueno es que todos los microcontroladores están organizados de manera muy similar, por lo que una vez que se acostumbre a leer hojas de datos y codificarlas, le resultará casi trivial hacer lo mismo para un microcontrolador diferente. Así que en realidad estamos aprendiendo a usar todos los microcontroladores en cierto sentido y no solo el atmega328p.

Bien, pase a la página 18 en la hoja de datos y observe la Figura 8-2.

Así es como se configura la Memoria de Programa en el microcontrolador. Puede ver que comienza con la dirección 0x0000 y está dividida en dos secciones; una sección de flash de aplicación y una sección de flash de arranque. Si consulta brevemente la tabla 27-14 de la página 277, verá que la sección de flash de la aplicación ocupa las ubicaciones de 0x0000 a 0x37FF y la sección de flash de arranque ocupa las ubicaciones restantes de 0x3800 a 0x3FFF.

Ejercicio 1: ¿Cuántas ubicaciones hay en la memoria del programa? Es decir. convierta 3FFF a decimal y agregue 1 ya que comenzamos a contar en 0. Dado que cada ubicación de memoria tiene 16 bits (o 2 bytes) de ancho, ¿cuál es el número total de bytes de memoria? Ahora convierta esto a kilobytes, recordando que hay 2 ^ 10 = 1024 bytes en un kilobyte. La sección de flash de arranque va de 0x3800 a 0x37FF, ¿cuántos kilobytes es esto? ¿Cuántos kilobytes de memoria nos quedan para usar para almacenar nuestro programa? En otras palabras, ¿qué tan grande puede ser nuestro programa? Finalmente, ¿cuántas líneas de código podemos tener?

Muy bien, ahora que sabemos todo sobre la organización de la memoria del programa flash, continuemos con nuestra discusión de las declaraciones.org. Vemos que la primera ubicación de memoria 0x0000 contiene nuestra instrucción para saltar a nuestra sección que etiquetamos Reset. Ahora vemos lo que hace la instrucción ".org 0x0020". Dice que queremos que la instrucción de la siguiente línea se coloque en la ubicación de memoria 0x0020. La instrucción que hemos colocado allí es un salto a una sección de nuestro código que hemos etiquetado como "overflow_handler" … ahora, ¿por qué diablos exigiríamos que este salto se coloque en la ubicación de memoria 0x0020? Para averiguarlo, vamos a la página 65 en la hoja de datos y echamos un vistazo a la Tabla 12-6.

La Tabla 12-6 es una tabla de "Vectores de reinicio e interrupción" y muestra exactamente a dónde irá la PC cuando reciba una "interrupción". Por ejemplo, si observa el vector número 1. La "fuente" de la interrupción es "RESET", que se define como "Pin externo, reinicio de encendido, reinicio de caída de voltaje y reinicio del sistema de vigilancia", es decir, si alguno de esas cosas le suceden a nuestro microcontrolador, la PC comenzará a ejecutar nuestro programa en la ubicación de memoria del programa 0x0000. ¿Qué pasa con nuestra directiva.org entonces? Bueno, colocamos un comando en la ubicación de memoria 0x0020 y si miras hacia abajo en la tabla, verás que si ocurre un desbordamiento del Timer / Contador0 (proveniente de TIMER0 OVF), ejecutará lo que sea que esté en la ubicación 0x0020. Entonces, siempre que eso suceda, la PC saltará al lugar que etiquetamos "overflow_handler". ¿Guay, verdad? Verá en un minuto por qué hicimos esto, pero primero terminemos este paso del tutorial con un aparte.

Si queremos hacer nuestro código más limpio y ordenado, realmente deberíamos reemplazar las 4 líneas que estamos discutiendo actualmente con lo siguiente (ver página 66):

.org 0x0000

rjmp Reset; PC = 0x0000 retirado; PC = 0x0002 retirado; PC = 0x0004 retirado; PC = 0x0006 retirado; PC = 0x0008 retirado; PC = 0x000A… retirado; PC = 0x001E jmp overflow_handler: PC = 0x0020 reti: PC = 0x0022… reti; PC = 0x0030 retirado; PC = 0x0032

De modo que si ocurre una interrupción dada, simplemente "retirará" lo que significa "volver de la interrupción" y no ocurrirá nada más. Pero si nunca "habilitamos" estas diversas interrupciones, entonces no se utilizarán y podemos poner el código del programa en estos lugares. En nuestro programa actual "blink.asm" solo vamos a habilitar la interrupción de desbordamiento del timer0 (y por supuesto la interrupción de reinicio que siempre está habilitada) y así no nos molestaremos con las demás.

Entonces, ¿cómo "habilitamos" la interrupción de desbordamiento del timer0? … Ese es el tema de nuestro próximo paso en este tutorial.

Paso 5: Temporizador / Contador 0

Temporizador / Contador 0
Temporizador / Contador 0

Eche un vistazo a la imagen de arriba. Este es el proceso de toma de decisiones del "PC" cuando alguna influencia externa "interrumpe" el flujo de nuestro programa. Lo primero que hace cuando recibe una señal del exterior de que ha ocurrido una interrupción es comprobar si hemos establecido el bit de "habilitación de interrupciones" para ese tipo de interrupción. Si no lo hemos hecho, simplemente continúa ejecutando nuestra siguiente línea de código. Si hemos establecido ese bit de habilitación de interrupciones en particular (de modo que haya un 1 en esa ubicación de bit en lugar de un 0), entonces verificará si hemos habilitado o no las "interrupciones globales", de lo contrario, volverá a pasar a la siguiente línea. de código y continuar. Si también hemos habilitado las interrupciones globales, entonces irá a la ubicación de la memoria del programa de ese tipo de interrupción (como se muestra en la Tabla 12-6) y ejecutará cualquier comando que hayamos colocado allí. Entonces veamos cómo hemos implementado todo esto en nuestro código.

La sección de reinicio etiquetada de nuestro código comienza con las siguientes dos líneas:

Reiniciar:

ldi temp, 0b00000101 out TCCR0B, temp

Como ya sabemos, esto carga en temp (es decir, R16) el número inmediatamente siguiente, que es 0b00000101. Luego escribe este número en el registro llamado TCCR0B usando el comando "out". ¿Qué es este registro? Bueno, vayamos a la página 614 de la hoja de datos. Esto está en el medio de una tabla que resume todos los registros. En la dirección 0x25 encontrará TCCR0B. (Ahora sabe de dónde vino la línea "out 0x25, r16" en mi versión sin comentarios del código). Vemos por el segmento de código anterior que hemos establecido el bit 0 y el segundo bit y hemos borrado el resto. Al mirar la tabla, puede ver que esto significa que hemos configurado CS00 y CS02. Ahora vayamos al capítulo de la hoja de datos llamado "Temporizador / Contador0 de 8 bits con PWM". En particular, vaya a la página 107 de ese capítulo. Verá la misma descripción del registro "Timer / Counter Control Register B" (TCCR0B) que acabamos de ver en la tabla de resumen de registros (por lo que podríamos haber venido directamente aquí, pero quería que vieras cómo usar las tablas de resumen Para futura referencia). La hoja de datos continúa dando una descripción de cada uno de los bits en ese registro y lo que hacen. Saltaremos todo eso por ahora y pasaremos la página a la Tabla 15-9. Esta tabla muestra la "Descripción del bit de selección de reloj". Ahora mire hacia abajo en esa tabla hasta que encuentre la línea que corresponde a los bits que acabamos de establecer en ese registro. La línea dice "clk / 1024 (de prescaler)". Lo que esto significa es que queremos que Timer / Counter0 (TCNT0) marque a lo largo de una tasa que es la frecuencia de la CPU dividida por 1024. Como tenemos nuestro microcontrolador alimentado por un oscilador de cristal de 16MHz, significa que la tasa a la que nuestra CPU ejecuta las instrucciones es 16 millones de instrucciones por segundo. Entonces, la velocidad a la que nuestro contador TCNT0 marcará es 16 millones / 1024 = 15625 veces por segundo (pruébelo con diferentes bits de selección de reloj y vea qué sucede, ¿recuerda nuestra filosofía?). Mantengamos el número 15625 en el fondo de nuestra mente para más adelante y pasemos a las siguientes dos líneas de código:

temperatura del ldi, 0b00000001

pts TIMSK0, temp

Esto establece el bit 0 de un registro llamado TIMSK0 y borra el resto. Si echa un vistazo a la página 109 en la hoja de datos, verá que TIMSK0 significa "Timer / Counter Interrupt Mask Register 0" y nuestro código ha establecido el bit 0 que se llama TOIE0 que significa "Timer / Counter0 Overflow Interrupt Enable" … ¡Allí! Ahora ves de qué se trata todo esto. Ahora tenemos el "conjunto de bits de habilitación de interrupciones" como queríamos desde la primera decisión en nuestra imagen en la parte superior. Así que ahora todo lo que tenemos que hacer es habilitar las "interrupciones globales" y nuestro programa podrá responder a este tipo de interrupciones. Habilitaremos las interrupciones globales en breve, pero antes de hacerlo, es posible que haya algo confundido … ¿por qué diablos usé el comando "sts" para copiar en el registro TIMSK0 en lugar del habitual "out"?

Siempre que me vea usar una instrucción que no haya visto antes, lo primero que debe hacer es pasar a la página 616 en la hoja de datos. Este es el "Resumen del conjunto de instrucciones". Ahora busque la instrucción "STS" que es la que utilicé. Dice que toma un número de un registro R (usamos R16) y la ubicación k "Almacenar directamente en SRAM" (en nuestro caso dada por TIMSK0). Entonces, ¿por qué tuvimos que usar "sts" que toma 2 ciclos de reloj (ver la última columna en la tabla) para almacenar en TIMSK0 y solo necesitábamos "out", que toma solo un ciclo de reloj, para almacenar en TCCR0B antes? Para responder a esta pregunta, debemos volver a nuestra tabla de resumen de registros en la página 614. ¿Ve que el registro TCCR0B está en la dirección 0x25 pero también en (0x45), verdad? Esto significa que es un registro en SRAM, pero también es un cierto tipo de registro llamado "puerto" (o registro de E / S). Si observa la tabla de resumen de instrucciones al lado del comando "out", verá que toma valores de los "registros de trabajo" como R16 y los envía a un PUERTO. Entonces podemos usar "out" al escribir en TCCR0B y ahorrarnos un ciclo de reloj. Pero ahora busque TIMSK0 en la tabla de registros. Ves que tiene la dirección 0x6e. Esto está fuera del rango de puertos (que son solo las primeras ubicaciones 0x3F de SRAM), por lo que debe volver a usar el comando sts y tomar dos ciclos de reloj de CPU para hacerlo. Lea la Nota 4 al final de la tabla de resumen de instrucciones en la página 615 ahora mismo. También observe que todos nuestros puertos de entrada y salida, como PORTD, se encuentran en la parte inferior de la tabla. Por ejemplo, PD4 es el bit 4 en la dirección 0x0b (¡ahora ves de dónde vienen todas las cosas 0x0b en mi código no comentado!).. está bien, pregunta rápida: ¿cambiaste "sts" a "out" y ver qué sucede? ¡Recuerda nuestra filosofía! ¡romperlo! no se limite a creer en mi palabra.

Bien, antes de continuar, pase a la página 19 en la hoja de datos por un minuto. Verá una imagen de la memoria de datos (SRAM). Los primeros 32 registros en SRAM (de 0x0000 a 0x001F) son los "registros de trabajo de propósito general" R0 a R31 que usamos todo el tiempo como variables en nuestro código. Los siguientes 64 registros son los puertos de E / S hasta 0x005f (es decir, aquellos de los que estábamos hablando que tienen esas direcciones sin corchetes junto a ellos en la tabla de registros que podemos usar el comando "out" en lugar de "sts"). la siguiente sección de SRAM contiene todos los demás registros de la tabla de resumen hasta la dirección 0x00FF y, por último, el resto es SRAM interno. Ahora, pasemos rápidamente a la página 12 por un segundo. Allí verá una tabla de los "registros de trabajo de propósito general" que siempre usamos como nuestras variables. ¿Ve la línea gruesa entre los números R0 a R15 y luego R16 a R31? Esa línea es la razón por la que siempre usamos R16 como el más pequeño y entraré en él un poco más en el próximo tutorial donde también necesitaremos los tres registros de direcciones indirectas de 16 bits, X, Y y Z. No lo haré Sin embargo, entre en eso todavía, ya que no lo necesitamos ahora y nos estamos empantanando lo suficiente aquí.

Retroceda una página a la página 11 de la hoja de datos. ¿Verá un diagrama del registro SREG en la parte superior derecha? Verá que el bit 7 de ese registro se llama "I". Ahora baje la página y lea la descripción del Bit 7…. ¡Hurra! Es el bit de habilitación de interrupción global. Eso es lo que necesitamos configurar para pasar por la segunda decisión en nuestro diagrama anterior y permitir interrupciones de desbordamiento del temporizador / contador en nuestro programa. Entonces, la siguiente línea de nuestro programa debería leer:

sbi SREG, yo

que establece el bit llamado "I" en el registro SREG. Sin embargo, en lugar de esto, hemos utilizado la instrucción

sei

en lugar de. Este bit se establece con tanta frecuencia en los programas que simplemente crearon una forma más sencilla de hacerlo.

¡Okey! Ahora tenemos las interrupciones de desbordamiento listas para funcionar, de modo que nuestro "jmp overflow_handler" se ejecutará siempre que ocurra una.

Antes de continuar, eche un vistazo rápido al registro SREG (Registro de estado) porque es muy importante. Lea lo que representa cada una de las banderas. En particular, muchas de las instrucciones que usamos establecerán y verificarán estos indicadores todo el tiempo. Por ejemplo, más adelante usaremos el comando "CPI" que significa "comparar inmediatamente". Eche un vistazo a la tabla de resumen de instrucciones para esta instrucción y observe cuántos indicadores coloca en la columna "indicadores". Todos estos son indicadores en SREG y nuestro código los establecerá y comprobará constantemente. Verá ejemplos en breve. Finalmente, el último bit de esta sección de código es:

temp de clr

fuera TCNT0, temp sbi DDRD, 4

La última línea aquí es bastante obvia. Simplemente establece el cuarto bit del Registro de dirección de datos para PortD, lo que hace que PD4 sea OUTPUT.

El primero establece la variable temp en cero y luego la copia en el registro TCNT0. TCNT0 es nuestro Timer / Counter0. Esto lo pone a cero. Tan pronto como la PC ejecute esta línea, el timer0 comenzará en cero y contará a una velocidad de 15625 veces por segundo. El problema es este: TCNT0 es un registro de "8 bits", ¿verdad? Entonces, ¿cuál es el número más grande que puede contener un registro de 8 bits? Bueno, 0b11111111 es. Este es el número 0xFF. Que es 255. Entonces, ¿ves lo que pasa? El temporizador se acelera aumentando 15625 veces por segundo y cada vez que llega a 255 se "desborda" y vuelve a 0 nuevamente. Al mismo tiempo que vuelve a cero, envía una señal de interrupción de desbordamiento del temporizador. La PC recibe esto y ya sabes lo que hace, ¿verdad? Sí. Va a la ubicación 0x0020 de la memoria del programa y ejecuta la instrucción que encuentra allí.

¡Excelente! Si todavía estás conmigo, ¡eres un superhéroe incansable! Sigamos…

Paso 6: Controlador de desbordamiento

Así que supongamos que el registro del temporizador / contador0 acaba de desbordarse. Ahora sabemos que el programa recibe una señal de interrupción y ejecuta 0x0020 que le dice al Contador del Programa, PC que salte a la etiqueta "overflow_handler", el siguiente es el código que escribimos después de esa etiqueta:

overflow_handler:

inc overflows cpi overflows, 61 brne PC + 2 clr overflows reti

Lo primero que hace es incrementar la variable "overflows" (que es nuestro nombre para el registro de trabajo de propósito general R17) luego "compara" el contenido de overflows con el número 61. La forma en que funciona la instrucción cpi es que simplemente resta los dos números y si el resultado es cero, establece la bandera Z en el registro SREG (le dije que estaríamos viendo este registro todo el tiempo). Si los dos números son iguales, entonces la bandera Z será un 1, si los dos números no son iguales, entonces será un 0.

La siguiente línea dice "brne PC + 2" que significa "rama si no es igual". Esencialmente, verifica la bandera Z en SREG y si NO es uno (es decir, los dos números no son iguales, si fueran iguales, se establecería la bandera cero) la PC se ramifica a PC + 2, lo que significa que se salta el siguiente line y va directamente a "reti" que regresa de la interrupción al lugar que estaba en el código cuando llegó la interrupción. Si la instrucción brne encontrara un 1 en el bit de bandera cero, no se ramificaría y, en su lugar, continuaría a la siguiente línea, lo que haría que se produjeran desbordamientos y lo restablecería a 0.

¿Cuál es el resultado neto de todo esto?

Bueno, vemos que cada vez que hay un desbordamiento del temporizador, este controlador aumenta el valor de "desbordamientos" en uno. Entonces, la variable "desbordamientos" está contando el número de desbordamientos a medida que ocurren. Siempre que el número llega a 61 lo ponemos a cero.

Ahora, ¿por qué demonios haríamos eso?

Vamos a ver. Recuerde que nuestra velocidad de reloj para nuestra CPU es de 16MHz y la "preescalamos" usando TCCR0B para que el temporizador solo cuente a una velocidad de 15625 conteos por segundo, ¿verdad? Y cada vez que el temporizador llega a 255, se desborda. Eso significa que se desborda 15625/256 = 61.04 veces por segundo. Estamos realizando un seguimiento del número de desbordamientos con nuestra variable "desbordamientos" y estamos comparando ese número con 61. ¡Así que vemos que los "desbordamientos" serán igual a 61 una vez por segundo! Entonces, nuestro controlador restablecerá los "desbordamientos" a cero una vez por segundo. Entonces, si simplemente monitoreáramos la variable "overflows" y tomáramos nota de cada vez que se restablece a cero, estaríamos contando segundo a segundo en tiempo real (tenga en cuenta que en el siguiente tutorial mostraremos cómo obtener una delay en milisegundos de la misma manera que funciona la rutina de "delay" de Arduino).

Ahora hemos "manejado" las interrupciones de desbordamiento del temporizador. Asegúrese de comprender cómo funciona esto y luego continúe con el siguiente paso en el que hacemos uso de este hecho.

Paso 7: Retraso

Ahora que hemos visto que nuestra rutina "overflow_handler" del controlador de interrupciones de desbordamiento del temporizador establecerá la variable "overflows" en cero una vez por segundo, podemos usar este hecho para diseñar una subrutina de "retraso".

Eche un vistazo al siguiente código debajo de nuestro retraso: etiqueta

demora:

clr desborda sec_count: cpi desborda, 30 brne sec_count ret

Vamos a llamar a esta subrutina cada vez que necesitemos un retraso en nuestro programa. La forma en que funciona es que primero establece la variable "overflows" en cero. Luego ingresa a un área etiquetada "sec_count" y compara los desbordamientos con 30, si no son iguales, se bifurca de nuevo a la etiqueta sec_count y compara una y otra vez, etc. hasta que finalmente son iguales (recuerde que todo el tiempo esto va en nuestro controlador de interrupciones del temporizador continúa incrementando los desbordamientos de variables y, por lo tanto, cambia cada vez que damos la vuelta aquí. Cuando desbordamientos finalmente es igual a 30, sale del bucle y regresa a donde llamamos delay: from. El resultado neto es un retraso de 1/2 segundo

Ejercicio 2: cambie la rutina overflow_handler a lo siguiente:

overflow_handler:

inc overflows reti

y ejecuta el programa. ¿Hay algo diferente? ¿Por qué o por qué no?

Paso 8: ¡Parpadea

Finalmente, veamos la rutina de parpadeo:

parpadear:

sbi PORTD, 4 retardo de llamada cbi PORTD, 4 retardo de llamada rjmp parpadeo

Primero encendemos PD4, luego llamamos a nuestra subrutina de retardo. Usamos rcall para que cuando la PC llegue a una instrucción "ret" regrese a la línea que sigue a rcall. Luego, la rutina de demora demora 30 conteos en la variable de desbordamiento como hemos visto y esto es casi exactamente 1/2 segundo, luego apagamos PD4, demoramos otro 1/2 segundo y luego volvemos al principio nuevamente.

¡El resultado neto es un LED parpadeante!

Creo que ahora estará de acuerdo en que "blink" probablemente no sea el mejor programa de "hola mundo" en lenguaje ensamblador.

Ejercicio 3: Cambie los distintos parámetros del programa para que el LED parpadee a diferentes velocidades, como un segundo o 4 veces por segundo, etc. Ejercicio 4: Cambie de modo que el LED esté encendido y apagado durante diferentes períodos de tiempo. Por ejemplo, encendido durante 1/4 de segundo y luego apagado durante 2 segundos o algo así. Ejercicio 5: Cambie los bits de selección del reloj TCCR0B a 100 y luego continúe subiendo la tabla. ¿En qué punto se vuelve indistinguible de nuestro programa "hello.asm" del tutorial 1? Ejercicio 6 (opcional): Si tiene un oscilador de cristal diferente, como 4 MHz o 13,5 MHz o lo que sea, cambie su oscilador de 16 MHz en su tablero para el nuevo y vea cómo eso afecta la velocidad de parpadeo del LED. Ahora debería poder realizar el cálculo preciso y predecir exactamente cómo afectará la tasa.

Paso 9: Conclusión

Para aquellos de ustedes, fanáticos que llegaron tan lejos, ¡Felicitaciones!

Me doy cuenta de que es bastante difícil trabajar duro cuando estás leyendo y mirando hacia arriba de lo que estás cableando y experimentando, pero espero que hayas aprendido las siguientes cosas importantes:

  1. Cómo funciona la memoria de programa
  2. Cómo funciona SRAM
  3. Cómo buscar registros
  4. Cómo buscar instrucciones y saber qué hacen
  5. Cómo implementar interrupciones
  6. Cómo el CP ejecuta el código, cómo funciona el SREG y qué sucede durante las interrupciones
  7. Cómo hacer bucles, saltos y rebotar en el código
  8. ¡Qué importante es leer la hoja de datos!
  9. Una vez que sepa cómo hacer todo esto para el microcontrolador Atmega328p, será relativamente fácil aprender cualquier controlador nuevo que le interese.
  10. Cómo cambiar el tiempo de la CPU a tiempo real y usarlo en rutinas de retardo.

Ahora que tenemos mucha teoría fuera del camino, podemos escribir mejor código y controlar cosas más complicadas. Así que el próximo tutorial lo haremos. Construiremos un circuito más complicado, más interesante y lo controlaremos de forma divertida.

Ejercicio 7: "Romper" el código de varias formas y ver qué pasa. ¡Bebé de curiosidad científica! Alguien más puede lavar los platos, ¿verdad? Ejercicio 8: Ensamble el código usando la opción "-l" para generar un archivo de lista. Es decir. "avra -l blink.lst blink.asm" y eche un vistazo al archivo de lista. Crédito adicional: ¡El código no comentado que di al principio y el código comentado que discutimos más adelante difieren! Hay una línea de código que es diferente. ¿Puedes encontrarlo? ¿Por qué no importa esa diferencia?

¡Espero que te hayas divertido! Nos vemos la próxima vez …

Recomendado: