CURSO BÁSICO DEL AVR AT90S1200
|

Autor:
Santiago Villafuerte
Volver a Inicio de Sitio.
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
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
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
;
=============================
--------------------------------------------------------------------------------

Si
lo quieren emular en PROTEUS, van a necesitar un xtal de 4MHz, capacitores de
27uf y poner a reset a 5V.
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
;
=============================
--------------------------------------------------------

Los
archivos de PROTEUS para que lo emulen están en:
http://miarroba.com/foros/ver.php?foroid=348538&temaid=2468801
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
;
-----------------------------