jueves, 16 de septiembre de 2010

La Pila, llamadas a subrutinas y retardos por software

En esta entrada hablaré de una muy importante como lo es el Stack o Pila, así como de las instrucciones, ya conocidas, PUSH y POP, las llamadas a subrutinas y cómo generar retardos por software.

//*****************************************************************************************************
//*****************************************************************************************************
La Pila en Atmel: Esta estructura es diferente a la de otros uC (por ejemplo los PIC) ya que no cuenta con un hardware dedicado, esto es porque tenemos la libertad de usar toda la capacidad de la RAM ( por ejemplo en un atmega324p tendríamos  como máximo 2K posiciones de pila) . Para poder usar la pila ésta debe ser definida al inicio de cada programa en el cual se use instrucciones como: (RCALL, ICALL, CALL, PUSH, POP, RET, RETI).  La declaración de la pila se hará colocando el valor de inicio de la misma en los registros SPH y SPL (Stack Pointer High y Stack Pointer Low). El valor del SP irá decrementando a medida se llena la pila, y aumentará a medida se va liberando. 
Particularmente recomiendo iniciar el SP a la posición final de la RAM, así no tendremos problemas con las posiciones de memoria que usemos como variables.




En el ejemplo mostrado arriba se puede ver cómo definir la pila a la posición final de la RAM. RAMEND ( Fin de la RAM) es una constante que se encuentra definida en .include "mxdef.inc"  y es diferente para cada uC. Por ejemplo en el atmega8 RAMEND=0x45F=1119.
1119 bytes es mayor que 1K=1024bytes, esto es ya que en la RAM están los 32 Registros de trabajo con los que cuenta este uC además de los registro de entradas/salidas  que sumados dan 0x5F=95 bytes. 32 (registros) + 63(reg i/o) + 1024(SRAM) = 1119 bytes.

LDI R17,HIGH(RAMEND) // R17 <-- 0x04
- Carga Inmediato al registro R17, la funcion HIGH nos devuelve el byte alto de RAMEND= 0x45F 
---> HIGH(RAMEND)= 0x04
LDI R16,LOW(RAMEND)   // R16<-- 0x5F
OUT SPH,R17 // Pone R17 en SPH
OUT SPL,R16               //  Pone R16 en SPL

Finalmente la pila queda definida : SP=0x45F.

RETARDOS:
~~~~~~~~~~
Crear rutinas de retardos siempre es necesario, para ello crearemos una rutina general que nos permita obtener retardos en ms.
    
La idea general es anidar rutinas de este tipo.



Por ejemplo: si tenemos f=16MHz y queremos una rutina de 1ms. 
1ms= (V)(X+4)/16MHz--> para V=250, X=60. X=60 significa que nuestra rutina tendrá 60 instrucciones NOP, para evitar esto aplicaremos el paso anterior a 60 --> 60=(V1)(X1+4) --> V1=12 y X1=1; finalmente obtenemos una rutina que para f=16MHz demora 1ms.

 
Si queremos hacer esto aún más general tendremos que anidar lo anterior en otro bucle, obteniendo lo siguiente:

Para usar esta rutina tendremos que cargar el valor del retardo en R20 y  llamar a la etiqueta RETms.

El siguiente programa hará parpadear un led con algunos retardos.



Si este ejemplo lo simulan con AVR Studio o Proteus pueden ver cómo en el final de la RAM se irán guardando los valores del PC al llamar a la rutina de retardo.

Aquí podemos ver el inicial valor de SP.


Aquí vemos PC=0x000008, en la siguiente instrucción PC=0x000009 se guardará en la última posición de la RAM o memoria de datos
 


Aquí PC saltó a la etiqueta  RETms y la dirección 0x000009 se guardó en el final de la RAM.





40 comentarios:

  1. Hola que tal, muy bueno tutoriales. Tengo una duda con la explicacion de arriba la cual va así:
    Por ejemplo: si tenemos f=16MHz y queremos una rutina de 1ms.
    1ms= (V)(X+4)/16MHz--> para V=250, X=60. X=60 significa que nuestra rutina tendrá 60 instrucciones NOP, para evitar esto aplicaremos el paso anterior a 60 --> 60=(V1)(X1+4) --> V1=12 y X1=1; finalmente obtenemos una rutina que para f=16MHz demora 1ms.

    Porque se asume v=250, que es en si V. Bueno no logro ver en el codigoV1=12 y X1=1, en que parte del codigo esta aplicada. Disculpa apenas estoy enpezando con AVR

    ResponderEliminar
    Respuestas
    1. Se supone que "X" representa la cantidad de instrucciones NOP, y yo quiero que sean la menor cantidad posible, es por eso que "V" debe ser grande y de preferencia un multiplo para que sea mas exacto.
      "v" son los valores que se cargan en los registro (como 250 y 12), y "x" son los NOP... si X=1 solamente hay un NOP.

      Eliminar
  2. Perdon ok, ya entendi porque el 12, pero no entiendo el 1. Y porque se asume que son 250

    ResponderEliminar
  3. Hola tengo algunas dudas que no me han quedado muy claras:
    Bueno estuve checando la hoja de datos del atmega8 y pues no estyo seguro si te referias a 250k del BAudrate que viene en la tabla para una frecuencia de oscilacion de 16Mhz.

    Tambien me ha entrado en duda el codigo de la subrutina

    *Porque deben ser tres RETms_1,2,3
    *Se carga los registros R21(Se decrementa y se compara con cero) y su respectivo BRNE quiere decir que va a saltar si r21 es diferente de "0"y R22 (Se decrementa y se compara con cero y BRNE si el r22 es diferente de "0") .
    * Puse un breakpoint en *BRNE RETms_1* cambie la frecuencia a 16 mhz ya que estaba en 4 orginalmente y me da 19.59us ya cuando termina y llega al breakpoint. Ando algo confundido, disculpa que sean bastante raras mis dudas



    ResponderEliminar
    Respuestas
    1. Lo del 250 ya lo explique arriba.. podrias poner 100 el lugar de 250, el calculo seria similar, pero el valor no debe pasar de 255 ya que debe ser un byte. No tiene nada que ver con lo baudios del uart.
      Los bucles se anidan para generar mas retardos, solamente por eso. Tu podrias hacer lo mismo, haciendo una rutina que demore 1ms y luego llamandola tantas veces como ms quieras.

      Saludos.

      Eliminar
  4. Hola yo solo compile la rutina de retardo y me da 256063 us, tengo una pregunta se supone que muy idependiente de que yo compile todo el programa completo con lo del encendido del led debería mostrar tu aproximación de 0.88us

    ResponderEliminar
    Respuestas
    1. perdon deberia de dar aproximadamente lo que es 1ms, si no 0.88 no me había fijado en el conteo que llevaba apenas.

      Eliminar
  5. 60=(V1)(X1+4) Disculpa tu formula el 4 es por default?

    ResponderEliminar
    Respuestas
    1. En esa formula sí, el cuatro es por defecto.
      1 por el DEC R22
      1 por el CPI R22,0x00
      2 por el BRNE LOOP_RET

      total = 4.

      Eliminar
  6. Tengo dudas por ejemplo si quisiera creear una rutina para 500ms a una frecuencia de 1mhz tendria que utilizar la formula anterior.

    ResponderEliminar
  7. Leyendo los comentarios anteriores
    Se supone que "X" representa la cantidad de instrucciones NOP, y yo quiero que sean la menor cantidad posible, es por eso que "V" debe ser grande y de preferencia un multiplo para que sea mas exacto.
    "v" son los valores que se cargan en los registro (como 250 y 12), y "x" son los NOP... si X=1 solamente hay un NOP.

    Pero dicho caso se quisiera aplciar con 1mhz para 500ms
    500ms= (V)(X+4)/1MHz
    el resultado de V sobre pasa los 250 ya que no debe sobre pasar los 255. Como podría quedar la formula ?
    Saludos

    ResponderEliminar
    Respuestas
    1. La idea no es aplicar la formula para cada retardo, tienes que hacer el calculo para 1ms, y luego hacer el llamado de esta rutina tantas veces como sea necesario. Arriba hay un ejemplo de calculo de 1ms para 16MHz, y luego se hace mas general para poderlo llamar y hacer retardos mas grandes.

      Eliminar
  8. Entonces como podría aplicar tu fragmento de coódigo y especificar que es para 500ms y aun poder modificar la frecuencia. Saludos

    ResponderEliminar
    Respuestas
    1. Realiza los calculos para 1ms usando la frecuencia de 1MHz (el ajemplo esta hecho para 16MHz). Luego cargas en r20 250 y lo llamas 2 veces... alli estarian los 500ms

      Eliminar
  9. Disculpa cual es la función del NOP solo que queda ahy. He estado checando un código

    LDI R18,2
    et2:LDI,R17,250
    et1:LDI R16,250
    et1:NOP
    DEC R16
    BRNE et1

    DEC R16
    BRNE et2

    DEC R16
    BRNE et3

    Cuando compilo el programa el r16 se carga con el 250, cuando acaba las 250, salta al registro 17 el cual empieza a decrementarse es decir de 250 a 249, 248 etc, cada vez que que termina el registro 16.
    Lo que no entiendo es para que sirve el NOP, ya que si yo decido poner de esta manera el código:

    et1: LDI CONT1,RESID1
    et3:LDI CONT2,RESID2
    et2:LDI CONT3,RESID3

    DEC CONT3
    BRNE et1

    DEC R16
    BRNE et2

    DEC R16
    BRNE et3

    El R16 solo decrementa en uno, es decir solo cambia del 249 al 250, sin afectar a los R17 y R18. Tengo entendido que el NOP es una espera de un ciclo de reloj. Pero de que manera influye en el programa.
    Disculpa las molestias

    ResponderEliminar
    Respuestas
    1. En NOP es una instruccion que demora un ciclo de reloj y no afecta a ningun registro del uC, lo unico que hace es generar un retardo.. nada mas.

      Eliminar
  10. include

    LDI R16,0xFF
    OUT DDRB,R16

    LDI R16,$04
    OUT SPH,R16
    LDI R16,$5F
    OUT SPL,R16

    BUCLE_INFINITO:
    SBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    CBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    RJMP BUCLE_INFINITO

    RETms:
    RETms_1:
    LDI R21,100
    RETms_2:
    LDI R22,1
    RETms_3:
    NOP
    DEC R22
    CPI R22,0
    BRNE RETms_3

    DEC R21
    CPI R21,0
    BRNE Retms_2

    DEC R20
    CPI R20,0
    BRNE RETms_1

    FIN: RJMP FIN


    Hola hice los calculos y pues corri el programa aproximandamente 420821us, quisiera saber si era ello a lo que te referias. Pero tengo una duda
    include

    LDI R16,0xFF
    OUT DDRB,R16

    LDI R16,$04
    OUT SPH,R16
    LDI R16,$5F
    OUT SPL,R16

    BUCLE_INFINITO:
    SBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    CBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    RJMP BUCLE_INFINITO

    RETms:
    RETms_1:
    LDI R21,100
    RETms_2:
    LDI R22,1
    RETms_3:
    NOP
    DEC R22
    CPI R22,0
    BRNE RETms_3

    DEC R21
    CPI R21,0
    BRNE Retms_2

    DEC R20
    CPI R20,0
    BRNE RETms_1

    FIN: RJMP FIN


    Hola hice los calculos y pues corri el programa aproximandamente 420821us, quisiera saber si era ello a lo que te referias.
    include

    LDI R16,0xFF
    OUT DDRB,R16

    LDI R16,$04
    OUT SPH,R16
    LDI R16,$5F
    OUT SPL,R16

    BUCLE_INFINITO:
    SBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    CBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    RJMP BUCLE_INFINITO

    RETms:
    RETms_1:
    LDI R21,100
    RETms_2:
    LDI R22,1
    RETms_3:
    NOP
    DEC R22
    CPI R22,0
    BRNE RETms_3

    DEC R21
    CPI R21,0
    BRNE Retms_2

    DEC R20
    CPI R20,0

    BRNE RETms_1

    FIN: RJMP FIN
    Hola hice los calculos y pues corri el programa aproximandamente 420821us, quisiera saber si era ello a lo que te referias.
    Pero porque cada Retms se debe comparar con Cero

    ResponderEliminar
    Respuestas
    1. No entiendi tu pregunta...
      1ms = V(X+4)/1MHz -->1000=V(X+4)---> puede ser:
      V=250 y X=0 --> no hay instrucciones NOP.
      V=200 y X=1 --> una instruccion NOP.
      V=100 y X=6 --> seis instrucciones NOP.
      V=100 y V1=1 y X1=2 ---> dos instrucciones NOP.
      Cual usaste ?.. si usate la ultima debio quedar asi:

      RETms:
      RETms_1:
      LDI R21,100 //V=100
      RETms_2:
      LDI R22,1 // V1 =1
      RETms_3:
      NOP
      NOP
      DEC R22
      CPI R22,0
      BRNE RETms_3
      DEC R21
      CPI R21,0
      BRNE Retms_2
      DEC R20
      CPI R20,0
      BRNE RETms_1

      La comparacion con 0 es para que funcione el BRNE, si quitas CPI la formula cambia.. ya no seria X+4.. seria X+3.

      Eliminar
    2. Utilize la ultima, solo que se me olvido poner una instrucción NOP, me la comí.
      Ya cheque el código y lo compile me da 496385, se hacerca bastante realmente. Como sabías en tu programa que debías poner el registro R20 con 250 y mandarlo a llamar dos veces. Gracías por ser tan atento (Y)

      Eliminar
    3. Bueno es que no se si tu programa era hacer una rutina de 240ms y pues era para probar la rutina de 1ms y cumpliera los 240ms ya que cuando la compilo y espero que termine me salen 1900411us

      Eliminar
    4. Perdon es que la he ejecutado con la frecuencia que ya tenia de 1mhz y obviamente los calculos no corresponderian XD ¡¡ Disculpa

      Eliminar
    5. .include

      LDI R16,0xFF
      OUT DDRB,R16

      LDI R16,$04
      OUT SPH,R16
      LDI R16,$5F
      OUT SPL,R16

      BUCLE_INFINITO:
      SBI PORTB,PB0
      LDI R20,250
      RCALL RETms
      CBI PORTB,PB0
      LDI R20,250
      RCALL RETms
      RJMP BUCLE_INFINITO

      RETms:
      RETms_1:
      LDI R21,100
      RETms_2:
      LDI R22,1
      RETms_3:
      NOP
      NOP
      DEC R22
      CPI R22,0
      BRNE RETms_3

      DEC R21
      CPI R21,0
      BRNE Retms_2

      DEC R20
      CPI R20,0
      BRNE RETms_1

      FIN: RJMP FIN // Poner RET

      Así deje el código al final

      Eliminar
  11. Hola , disculpa no se porque se publico muchas veces, el comentario anterior. Acabo de realizar los calculos supongo que si estoy bien con los calculos anteriores creeo que ya logre entender como se hace una rutina, lo que estube probando y moviendo alguna parte del código es en:

    DEC R20
    CPI R20,0

    *BRNE RETms_1 // Mencionas que en esta parte es para hacerlo mas general, quite esta instrucción he iba mas rapido aque se debe.

    *Quite el NOP para ver como funcionaba y lo hacia de manera normal, tal cual como antes de quitarle
    BRNE RETms_1.

    Espero que me des el visto bueno de los calculos 420821us
    Gracías de nuevo .

    ResponderEliminar
    Respuestas
    1. si tienes muchos problemas con esta rutina.. usa lo que muestro en este otro post
      http://avrperu.blogspot.com/2011/03/delaymsms-y-delayusus-para-cualquier.html

      Eliminar
  12. Hola acabo de probar este código y solamente hace 25000us al poner un breakpoint en RET o FIN:RJMP FIN

    .include

    LDI R16,0xFF
    OUT DDRB,R16

    LDI R16,$04
    OUT SPH,R16
    LDI R16,$5F
    OUT SPL,R16

    BUCLE_INFINITO:
    SBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    CBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    RJMP BUCLE_INFINITO

    RETms:
    RETms_1:
    LDI R21,100
    RETms_2:
    LDI R22,1
    RETms_3:
    NOP
    NOP
    DEC R22
    CPI R22,0
    BRNE RETms_3

    DEC R21
    CPI R21,0
    BRNE Retms_2

    DEC R20
    CPI R20,0
    BRNE RETms_1

    FIN: RJMP FIN // Poner RET

    ResponderEliminar
  13. Al igual si le quito esta parte del código:

    CBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    RJMP BUCLE_INFINITO

    cosa que no debria suceder

    ResponderEliminar
  14. Lo que hace es 25000us encendidos y 25000us apagados, pero como podría ser que hiciera media segundo encendido y medio segundo apagado

    ResponderEliminar
    Respuestas
    1. Señor... revise su configuracion , me tome la molestia de hacer la simulacion a 1MHz y funciona perfectamente. Apostaria a que tu simulacion esta a 10MHz.

      Eliminar
  15. Hola muchas gracías por la ayuda, yo puse el breakpoint en la instrucción RET, lo tengo trabajando
    1Mhz.

    http://i253.photobucket.com/albums/hh76/kapricon/blinkled_zpsd0f2b938.png

    ResponderEliminar
  16. Acabo de ver la foto, entonces así debe quedar.

    ResponderEliminar
    Respuestas
    1. Entonces si puso un breakpoint al final del código quiere decir que hace 251011us prendido y 251011us apagado, pero como podría hacer 500 us prendido y apagado? si lo maximo que se puede representar 255

      Eliminar
    2. Hola, estube buscando tambien una rutina de 500us, entonces así debe ser como la imagen que publicaron. Ya que al parecer son 251011us prendido y 251011us apagado y eso da 500000us?

      Eliminar
  17. Hola, he estado checando varios programas en internet de una rutina de 500ms y publicaron en los comentarios una, pero al igual solo hace 250000us encendido y apagado, tambien modifique el código segun los comentarios. Intente cambiar el 250 a 500, pero como lo maximo son 255 exede y no se pued eponer en el código para hacer que la rutina haga 500 000us encendido y apagado.
    He leeido los comentarios anteriores y esta trabajndo a una frecuencia de 1Mhz

    LDI R16,0xFF
    OUT DDRB,R16

    LDI R16,$04
    OUT SPH,R16
    LDI R16,$5F
    OUT SPL,R16

    BUCLE_INFINITO:
    SBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    CBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    RJMP BUCLE_INFINITO

    RETms:
    RETms_1:
    LDI R21,100
    RETms_2:
    LDI R22,1
    RETms_3:
    NOP
    NOP
    DEC R22
    CPI R22,0
    BRNE RETms_3

    DEC R21
    CPI R21,0
    BRNE Retms_2

    DEC R20
    CPI R20,0
    BRNE RETms_1

    FIN: RJMP FIN // Poner RET

    ResponderEliminar
    Respuestas
    1. Llama a la rutina de 250ms dos veces.. y tienes tus 500ms... asi de facil.

      Eliminar
  18. Hola ya logre hacer los 500ms, en lo personal me agrado bastante que se usara una formula ya que he encontrado otros porgramas en el cual lo explican y definen así 2x(250x(250x4us)) y efectivamente así salen los 500000us. Ya llame la rutina dos veces a pesar que se hace mas códgio logre entender a la perfección, me pregunto si hay una manera como en este caso que los 500us se hicieran con menos código, en parte te agradesco ya que entendí mucho.

    .include

    LDI R16,0xFF
    OUT DDRB,R16

    LDI R16,$04
    OUT SPH,R16
    LDI R16,$5F
    OUT SPL,R16

    BUCLE_INFINITO:
    SBI PORTB,PB0
    LDI R20,250
    RCALL RETms

    CBI PORTB,PB0
    LDI R20,250
    RCALL RETms
    RJMP BUCLE_INFINITO

    SBI PORTB,PB0
    LDI R20,250
    RCALL RETms1

    CBI PORTB,PB0
    LDI R20,250
    RCALL RETms1
    RJMP BUCLE_INFINITO

    RETms:
    RETms_1:
    LDI R21,100
    RETms_2:
    LDI R22,1
    RETms_3:
    NOP
    NOP
    DEC R22
    CPI R22,0
    BRNE RETms_3

    DEC R21
    CPI R21,0
    BRNE Retms_2

    DEC R20
    CPI R20,0
    BRNE RETms_1


    RETms1:
    RETms_01:
    LDI R21,100
    RETms_02:
    LDI R22,1
    RETms_03:
    NOP
    NOP
    DEC R22
    CPI R22,0
    BRNE RETms_03

    DEC R21
    CPI R21,0
    BRNE Retms_02

    DEC R20
    CPI R20,0
    BRNE RETms_01


    RET

    ResponderEliminar
    Respuestas
    1. Al igual si modifica valors como V=100 y V=1 que me habían salido, modificandolos a V=100 y V=3 quedaba tambien, pero se me hacia muy al tanteo.

      Eliminar
  19. Buenas tardes ayudar como poder hacer ejercicio
    Una empresa necesita que sea instalado un sistema de control de TEMPERATUR para una sala de servidores ,el cual recibe la temperatura del ambiente mediante mediante un sensor sala de servidores ,el cuál recibe la temperatura del ambiente mediante un sensor conectado al pin A2 de un microcontrolador ATMEGA32. Las acciones que debe realizar

    ResponderEliminar
    Respuestas
    1. Quieres un monitoreo de temperatura o control de temperatura ?

      Eliminar
    2. Creo que quiere un control de temperatura por lo que leí,aplica un PID y hacer ensayos, lo mas común es que uses un motor DC, seguro nos dará mas detalles , salds

      Eliminar