CURSO BÁSICO DEL AVR AT90S1200

 

Sitio de MigSantiago

 

Autor: Santiago Villafuerte

san.link@yahoo.com.mx

 

  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

; -----------------------------