Curso Básico AVR AT90S1200 Escrito por Santiago Villafuerte Jueves, 19 de Marzo de 2009 20:24 Para comenzar a estudiar este curso necesitas tener un conocimiento básico de programación a nivel ensamblador. ensa mblador. Paso 1
¿Qué significa AVR? Pues según ATMEL, sólo es un nombre que se sacaron sacar on de la manga. Algunos dicen que significa Advanced Virtual RISC. Otros dicen que lleva las iniciales de los inventores de los AVR: Alf Egil Bogen and Vegard Wollan... AlfVegardRisk. Ya saben Reduced Instruction Set Comp C omputer uter es lo de RISC. Paso 2
Bajen el compilador de AVR, el AVR Studio. http://www.atmel.com/dyn/products/tools.asp?family_id=607 Si el link cambia sólo busquen el AVR Studio más reciente en la página de ATMEL. Paso 3
Para que sepan de que estamos hablando, bajen la hoja de características del AT90S1200. http://www.atmel.com/dyn/resources/prod_documents/DOC0838.PDF
Sería bueno que imprimieran imprimieran las páginas pági nas del set de instrucciones, del espacio de I/O y del sumario de bits de registros. Paso 4
Bajen un generador de subrutinas de tiempo para AVR http://www.home.unix-ag.org/tjabo/avr/AVRdelayloop.html Por ahora sólo daré una explicación básica del 1200. Compararé al ATMEL AT90S1200, que es un AVR muy popular y al MICROCHIP PIC16F84... se puede decir que son los ejemplos básicos de comparación. No. de instrucciones AVR: 89 PIC: 35 Registros AVR: 32 registros PIC: 68 RAM R AM Velocidad AVR: 12MIPS (12MHz) PIC: 20MHz en donde c/inst. toma 4 ciclos de reloj en promedio Memoria de programa AVR: 1kByte FLASH (512 líneas de programa, 16bits por inst.) PIC:1kx14
EEPROM AVR: 64B PIC: 64B Salidas AVR: 15 salidas PIC: 13 salidas TIMER AVR: 1 de 8bit (preescala desde CK hasta CK/1024) PIC: 1 de 8 bit (preescala desde 1:2 hasta 1:256) Comparador analógico (NO convertidor analógico) AVR: 1 PIC: NO Watchdog Ambos Oscilador interno Ambos, en el AVR sólo habilitable con programación paralela Niveles de pila (STACK) AVR: 3 PIC: 8 Interrupciones AVR: reset, int. externa, timer y por comparador analógico PIC: sólo me acuerdo de que son 5 jeje Antes de empezar les explico algo básico... en los AVR se tienen 3 registros para cada puerto de salida: DDRB - Sirve para decir que patitas son de entrada o salida, 0 es entrada, 1 es salida, es inverso a los pics PINB - Registro que sirve para entradas nadamás PORTB - Registro que sirve de salidas nadamás Es decir si leen, váyanse con PINB; si escriben váyanse a PORTB. Las terminales del AVR AT90S1200 son:
El PortB tiene 8 bits de datos, a diferencia del PORTD que tiene sólo 7. El bit 7 del
PORTD no sirve ; PORTD también consta de 3 registros: DDRD, PORTD y PIND. Programa de salidas en puerto B
Ahora el programa sólo será un ejemplo de cómo declarar salidas en puerto B, cada segundo se incrementa un conteo binario en portb. -----------------------------------------------------.include "1200def.inc" ;librería de definiciones de registros y demás cosas ser r16 ;SER pone a uno todos los bits del Registro 16 out ddrb,r16 ;saca R16 a DDRB, los 1 son salidas, los 0 son entradas ; que es contrario a los PIC, todo B es salida clr r16 ;pone a ceros todo r16 ciclo: inc r16 ; r16 ++ out portb,r16 ;pone r16 en portb, es decir en patitas del avr rcall retardo ;llama subrutina de 1 segundo rjmp ciclo ;salto incondicional a ciclo ; ============================= ; delay loop generator ; 4000000 cycles: es decir 1 segundo con xtal de 4MHz ; ----------------------------; delaying 3999996 cycles: retardo: ldi R17, $24 WGLOOP0: ldi R18, $BC WGLOOP1: ldi R19, $C4 WGLOOP2: dec R19 brne WGLOOP2 dec R18 brne WGLOOP1 dec R17 brne WGLOOP0 ; ----------------------------; delaying 3 cycles: ldi R17, $01 WGLOOP3: dec R17 brne WGLOOP3 ; ----------------------------; delaying 1 cycle: ret ; =============================
--------------------------------------------------------------------------------
Programa de control de una LCD 16x2
Ahora les dejo un programa más avanzado, en el que se hace uso de una LCD 16x2 y en ella se muestra un conteo de 0 a 9 usando el AVR. Antes de seguir les hago una recomendación, siempre trabajen registros desde el número 16 hasta el 31, ya que estos pueden ser trabajados con direccionamiento inmediato. Luego les diré que onda con los otros del 0 al 15. En el programa se ven instrucciones nuevas: LDI R16,$38 Esta instrucción carga de modo inmediato el valor hexa 38 en el registro 16. Si quisieran cargar el dato decimal 10, sólo pongan: LDI R16,10 MOV R16,R20 Aquí el valor del R20 pasa al R16. CPI R20,$3a Hace una comparación inmediata mediante una resta: R20 - 3A. Esto levanta banderas en el registro STATUS. BRNE loop Branch if Not Equal. Brinca si no es igual, es decir, si Z=0; en ese caso se va a loop. CBI PORTD,0 Clear Bit, pone a cero un bit de algún registro de entrada/salida. En este caso pone a cero el bit cero. Es importante que CBI y SBI sólo se usen en registros de entrada/salida que estén en el rango de 00 a 1F, ya que en los demás no funcionan. Para modificar el contenido de los demás, sería conveniente usar un LDI a algún registro y luego un OUT hacia el registro e/s. Bueno, pues les dejo el código: --------------------------------------------------------
;MigSantiago ;Contador de 0-9 en LCD de 16x2 ;RB0-RB7 son DB0-DB7 ;RD0 es RS ;RD1 es E ;R20 conteo ASCII ;R17,18,19 Subrutinas tiempo .org 0000 .include "1200def.inc" ;Establece E/S SER R16 OUT DDRB,R16 ;portb salida OUT DDRD,R16 ;portd salida RCALL unseg ;espera inicio de LCD 1seg ;Configura LCD LDI R16,$38 RCALL instru ;8-bit,2 líneas, 5x7 LDI R16,$0e RCALL instru ;D=1,C=1,B=0 LDI R16,$06 RCALL instru ;incremento, no desplaza texto LDI R16,$01 RCALL instru ;limpia LCD ;Escritura de 0-9 cero: LDI R20,$30 ;cero en ASCII loop: MOV R16,R20 ;prepara conteo ASCII a mandar RCALL dato ;manda ASCII a LCD RCALL unseg ;espera 1 seg LDI R16,$80 ;va a home RCALL instru INC R20 CPI R20,$3a ;si es 3a se va a cero BRNE loop RJMP cero ;Subrutina: instrucción a LCD instru: CBI PORTD,0 ;RS=0 RJMP envio ;Subrutina: dato a LCD dato: SBI PORTD,0 ;RS=1 RJMP envio ;Subrutina: envía dato o inst a LCD envio: SBI PORTD,1 ;E=1 OUT PORTB,R16 ;Saca R16 por B RCALL dosms ;espera 2ms escritura CBI PORTD,1 ;E=0
RCALL dosms ;espera proceso RET ;Subrutina de 2ms ; 8000 cycles: ; ----------------------------; delaying 7998 cycles: dosms: ldi R17, $1f WGLOOP0: ldi R18, $55 WGLOOP1: dec R18 brne WGLOOP1 dec R17 brne WGLOOP0 ; ----------------------------; delaying 2 cycles: nop ret ; ============================= ;Subrutina de 1seg ; 4000000 cycles: ; ----------------------------; delaying 3999996 cycles: unseg: ldi R17, $24 WGLOOP3: ldi R18, $BC WGLOOP4: ldi R19, $C4 WGLOOP5: dec R19 brne WGLOOP5 dec R18 brne WGLOOP4 dec R17 brne WGLOOP3 ; ----------------------------; delaying 3 cycles: ldi R17, $01 WGLOOP6: dec R17 brne WGLOOP6 ; ----------------------------; delaying 1 cycle: ret ; =============================
--------------------------------------------------------
Programa para uso de teclado de matriz de 4x4
Ahora les va un programa que lee la presión de una tecla en un teclado 4x4. Les pongo un poco de teoría respecto al teclado y cómo está conectado. Una buena manera de ahorrar líneas de entrada al micro es, en lugar de dedicar una línea exclusiva por cada tecla utilizada, emplear un teclado matricial. El teclado matricial se caracteriza por estar cada una de las teclas conectada a dos líneas (una columna y una fila) que la identifican. De este modo el número de teclas que pueden conectarse es el producto de filas por el de columnas.
La técnica de programación requiere tanto de entradas como de salidas. Las filas están conectadas a las patillas de salida y las columnas a las de entrada. Se comienza el testeo colocando a µ0¶ la primera fila, y a µ1¶ las restantes. Si la tecla pulsada estuviese en la columna µ0¶, ésta colocaría en su línea un µ0¶ lógico. Bastará con hacer un muestreo de las columnas, buscando el 0, para saber la tecla exacta que fue pulsada en la matriz. Si no es pulsada ninguna tecla en una fila, las entradas se encuentran en estado flotante, razón por la que son necesarias las resistencias de polarización internas, que mantienen las entradas a nivel alto. En nuestro caso, yo conecté las resistencias a tierra. Por lo tanto, lo que se busca es un UNO en las líneas de entrada. Las instrucciones nuevas que este programa tiene son: sbic pinb,5 Brinca la siguiente instrucción si el bit 5 del puerto B está en CERO. SI fuera sbis, brinca la sig. inst. si el bit 5 del port b está en UNO. Es importante decirles que los AVR at90s1200 tienen sólo 3 saltos a subrutina. Este programa que escribí no tiene ni un sólo salto a subrutina, así que si lo van a implementar en otro programa en dónde hagan uso de un teclado pues sólo llamarían a la lectura del teclado y gastarían un nivel de pila. El programa es: --------------------;programa teclado 4x4 rapid switch ;pb0, pb1, pb2, pb3 Salidas matriz ;pb4, pb5, pb6, pb7 Entradas matriz
;pd0, pd1, pd2, pd3 Salidas LEDS ;pd4 salida estado tecla presionada (E)
; pb4 pb5 ; | | | | ;pb0 -> 0 ;pb1 -> 4 ;pb2 -> 8 ;pb3 -> c
pb6 pb7 1 5 9 d
2 6 a e
3 7 b f
.include "1200def.inc" SER OUT LDI OUT
R16 ;FF DDRD,R16 ;PtoD Salida R16,$0F ;00001111 DDRB,R16 ;PtoB 4sal 4ent
clr r16 out PORTD,r16 ;borra PtoD OUT PORTB,r16 ;borra PtoB inicio: clr r16 clr r17 ;Registro mandado a ptoD clr r18 ;registro del contador de barrido clr r19 ;indicador de línea actual de barrido linea1: ldi r16,$01 out portb,r16 ;rb0=1 línea 1 activa rjmp barrido ;va a checar presión de tecla linea2: inc r19 inc r18 ;en subrutina barrido, la primer línea ;no hace incremento, por eso se ;incrementa aquí ldi r16,$02 out portb,r16 ;rb1=1 línea 2 activa rjmp barrido linea3: inc r19 ;incrementa indicador de línea inc r18 ldi r16,$04 out portb,r16 ;rb2=1 línea 3 activa rjmp barrido linea4: inc r19 ;incrementa indicador de línea inc r18 ldi r16,$08 out portb,r16 ;rb3=1 línea 4 a ctiva rjmp barrido nopresion: clr r16 ;pon E y LEDS a cero out portd,r16 ;no hubo presión
rjmp inicio barrido: sbic pinb,4 ;si rb4=0 no hay presión rjmp presion inc r18 ;siguiente tecla sbic pinb,5 rjmp presion inc r18 ;siguiente tecla sbic pinb,6 rjmp presion inc r18 ;siguiente tecla sbic pinb,7 rjmp presion rjmp quelinea ;no hubo presión en X línea presion: mov r17,r18 ;r17=r18 out portd,r17 ;saca tecla y E por portd sbi portd,4 ;pd4=1 es decir E=1 rjmp inicio ;que ya no haga barrido quelinea: cpi r19,$00 ;determina en que línea de barrido va breq linea2 ;va a línea 2 cpi r19,$01 breq linea3 cpi r19,$02 breq linea4 cpi r19,$03 breq nopresion ;se va a limpiar ptod
-----------------------------
Al final lo que hace el programa es determinar si se presionó una tecla. Si se presionó, enciende la salida E (pd4) y saca la tecla por pd0, pd1, pd2, pd3. Si se presionara el CERO sólo encendería E y pd0, pd1, pd2, pd3 estarían apagadas. Si se presionara la tecla F encendería E y todas las demás: 1111 = 15 = F. Declaración de variables
No uso la declaración de variables para los registros del AVR, pero les explico unas directivas para usarlas: Si quieres definir pines: .equ RxD =0; En este caso el pin 0 se llama RxD .equ TxD =1; En este caso el pin 1 se llama TxD Si quieres renombrar registros: .def contbit =R16 def temporal =R17 Y se usan normalmente: cbi PORTD,TxD; el pin 1 se pone a cero ldi contbit,$aa; Carga el R16 con AA Programa con interrupción externa
Ahora entremos con las interrupciones. Les presento un programa que hice muy básico sobre la interrupción externa.
La aplicación que le di fue generar un número aleatorio (random) para obtener la lectura de un dado, es decir, un número que fuera 1, 2, 3, 4, 5 ó 6 al momento en que uno aprieta un botón. Hay registros que habilitan las interrupciones: GIMSK - Tiene el bit INT0, el cuál al estar en 0 deshabilita la interrupción externa. Si está en 1 pues la habilita. MCUCR - Sólo interesan los bits ISC00 y ISC01. Sirve para decir que flanco es el que dispara la int. ext. Los otros pónganlos a cero. ISC01 ISC00 0 0 LOW 0 0 1 reservado 1 0 flanco derecho 1 1 flanco izquierdo STATUS - La bandera I habilita el llamado a interrupción en general. La instrucción SEI habilita el bit I de status. Ahora les pongo una imagen de cómo conectar los 7 leds en un arreglo que simule los puntos de un dado real:
Los AVR tienen un vector de interrupción único para cada interrupción: 0x00 RESET 0x01 Interrupción externa (GIMSK) 0x02 TIMER, OVF0 (TIMSK) 0x03 ANA_COMP Para cada interrupción el contador de programa va a caer en un vector en específico. El avr va a estar incrementando R17 hasta que llegue la presión del botón, sale del loop infinito y adecua el estado de R17 para convertirlo a una salida de 1 a 6 en el dado. Para regresar de una interrupción hay que usar RETI para que regrese al programa normal y rehabilite I en status. ;dado electrónico hecho con interrupción externa ; B0 B1 B2 B3 B4 B5 B6 B7 ; 1 2 3 4 5 6 7 NC ; ;MigSantiago ; _______ ; 1|O 4 O|5 ; 2|O O O|6
; 3|O O |7 ; ------;PD2 Push Button a tierra con r pull -up .include "1200def.inc" rjmp inicio rjmp int_ext nop nop inicio: ser r16 out ddrb,r16 ;ptob salida out gimsk,r16 ;habilita int. externa ldi r16,$03 out mcucr,r16 ;flanc o izquierdo activa interrupción sei ;activa interrupciones random: inc r17 ;inc. random r17 rjmp random int_ext: rcall rebote ;espera rebote de push button andi r17,$07 ;filtra 5 primeros bits a cero cpi r17,$00 ;si hubo cero saca 1 breq sacauno cpi r17,$01 ;si hubo 1 saca 1 breq sacauno cpi r17,$02 ;si hubo 2 saca 2 breq sacados cpi r17,$03 breq sacatres cpi r17,$04 breq sacacuatro cpi r17,$05 breq sacacinco cpi r17,$06 breq sacaseis cpi r17,$07 ;si hubo 7 saca seis breq sacaseis sacauno: ldi r16,$08 ;prende 4 sacadado: out portb,r16 reti sacados: ldi r16,$41 ;prende 1 y 7 rjmp sacadado sacatres: ldi r16,$49 ;prende 1, 4 y 7 rjmp sacadado sacacuatro: ldi r16,$55 ;prende 1,3,5,7 rjmp sacadado sacacinco: ldi r16,$5d ;prende 1,3,4,5,7 rjmp sacadado sacaseis: ldi r16,$77 ;prende 1,2,3,5,6,7
rjmp sacadado ; delay loop generator ; 800000 cycles: ; ----------------------------; delaying 799995 cycles: rebote: ldi R20, $5F WGLOOP0: ldi R18, $17 WGLOOP1: ldi R19, $79 WGLOOP2: dec R19 brne WGLOOP2 dec R18 brne WGLOOP1 dec R20 brne WGLOOP0 ; ----------------------------; delaying 3 cycles: ldi R20, $01 WGLOOP3: dec R20 brne WGLOOP3 ; ----------------------------; delaying 2 cycles: ret ; =============================
Es importante que acomoden bien los LEDs para que le encuentren forma a las salidas del dado. Programa con interrupción por timer cada segundo
Bueno, ya que están medio familiarizados con las interrupciones les dejo un programa que genera una salida binaria cada segundo usando interrupciones: -------------------------------------------------------------------------------;Programa que incrementa conteo binario ;en PtoB cada segundo usando interrupción ;por timer ;CK/64 tiempo mínimo=16us ;4ms/16us=250 conteos ;256-250=6 -> TCNT0 ;4ms x 250conteos= 1s .include "1200def.inc" rjmp main nop rjmp timer main: clr r17 ;pone a cero salida a ptoB clr r25 ;pone a cero contador 4ms ser r16
out ddrb,r16 ;ptoB de salida out timsk,r16 ;habilita int. por timer ldi r16,6 out tcnt0,r16 ;manda el 6 a time r sei ;habilita I en SREG ldi r16,$03 out tccr0,r16 ;011 en escala: CK/64 ciclo: nop ;pasa el tiempo rjmp ciclo timer: inc r25 ;inc. conteo de 4ms cpi r25,$fa ;250d = fah breq increm ;se va a inc. ptoB pon_conteo: ldi r16,$06 out tcnt0,r16 ;comienza tiem po 4ms otra vez reti ;regresa de interrupción ;habilitando I en SREG increm: inc r17 ;inc conteo en ptoB out portb,r17 ;saca conteo clr r25 ;borra conteo de 4ms rjmp pon_conteo
-------------------------------------------------------------------------------Les explico los registros del TIMER: TIMSK - Habilita la interrupción por sobreflujo del contador TCNT0 TIFR - Es la bandera que dice si hubo sobreflujo del TCNT0 TCCR0 - Aquí uno da las escalas de división de TCNT0 TCNT0 - Aquí se lleva la cuenta En TCCR0 Se tiene CS02, CS01 y CS00 los cuales indican la escala y el funcionamiento del Timer, los datos que llevan son CS02, CS01 y CS00 respectivamente: *Cristal de 4MHz 000 - stop, no cuenta 001 - CK, la escala es igual al cristal que uno le ponga (0.25us por conteo y 4MHz) 010 - CK/8 (2us) 011 - CK/64 (16us) 100 - CK/256 (64us) 101 - CK/1024 (256us) 110 - Flanco derecho en patita T0 PD4 111 - Flanco izquierdo en T0 R25 va a contar 250 veces el desbordamiento del timer. El timer se va a desbordar cada 4ms ya que se usó CK/64. R16 lleva un 6 para que el conteo se haga 250 veces.
16us x 250 (r16) = 4ms 4ms x 250 (r25) = 1000ms Trabajando
los registros 16 a 31
Antes de explicarte, por favor baja este archivo de Atmel: http://www.atmel.com/dyn/resources/prod_documents/DOC0856.PDF Este archivo trae las explicaciones de cada código de operación de la familia de 8bit de AVRs. Trae instrucciones que los AT90S1200 no incluyen, pero a esas no les hagas caso, sólo estudia las que trabaja el 1200. Ya que tengas el archivo checa la página 16 por ejemplo. Ahí viene explicada bien la instrucción ADC (Suma con acarreo) por ejemplo. Abajo dice que los registros que maneja esta instrucción son 0 < d < 31, 0 < r < 31 (menor o igual) Ahora checa la página 20. Ahí se explica la operación ANDI (AND inmediata). Los registros que ella trabaja son 16 < d < 31, 0 < K < 255 (K es el número a poner en la AND) ¿Porqué? No te puedo dar una respuesta exacta, pero tiene que ver con el modo de direccionamiento del AVR. Cada instrucción tiene límites de trabajo con los registros, siempre hay que checarlas. Te recomiendo que leas las instrucciones en las que estés interesado. Eso es todo por mi parte. Este es un curso muy breve que se basa en conocimientos que el lector ya tenga sobre lenguaje ensamblador. Si buscas un programador para este AVR te recomiendo entres a http://www.atmel.com y hagas una búsqueda. Ahí encontrarás programadores muy sencillos y el software para trabajarlos. 15 junio 2005 Última actualización el Jueves, 19 de Marzo de 2009 21:06 Si el contenido del sitio te pareció útil o interesante, no dudes en recomendarnos con tus amigos en Google+ usando el botón del menú izquierdo. ¡Gracias! ® Copyright 2001-2011 Migsantiago.com