Tutoriales

Curso Básico AVR AT90S1200

Para comenzar a estudiar este curso necesitas tener un conocimiento básico de programación a nivel ensamblador.

Paso 1
¿Qué significa AVR?

Pues según ATMEL, sólo es un nombre que se sacaron 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 Computer 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 las páginas 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
(Descarga)



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

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 pb6 pb7
; | | | |
;pb0 -> 0 1 2 3
;pb1 -> 4 5 6 7
;pb2 -> 8 9 a b
;pb3 -> c d e f
 
 
.include "1200def.inc"
 
SER R16 ;FF
OUT DDRD,R16 ;PtoD Salida
LDI R16,$0F ;00001111
OUT 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 activa
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 ;flanco 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 timer
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 tiempo 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