Programación de Microcontroladores AVR: Interrupciones, ADC y Timers en Ensamblador
Clasificado en Informática
Escrito el en
español con un tamaño de 14,79 KB
Desarrollo de Código en Ensamblador para Microcontroladores
Configuración del ADC con Interrupciones
Para desarrollar un código en Ensamblador bajo condiciones específicas, se deben seguir los siguientes requerimientos:
- 1) Programa completo con interrupciones: configurar la tabla de interrupciones.
- 2) Generar las siguientes variables de 1 byte:
nivel,canalyresultado. - 3) Configurar el pin 5 del puerto B como salida.
- 4) Configurar el ADC:
- 4.1) ADC habilitado →
ADEN = 1 - 4.2) Interrupción habilitada →
ADIE = 1 - 4.3) Referencia externa →
REFS1/REFS0 = 00 - 4.4) Resultado ajustado a la izquierda →
ADLAR = 1 - 4.5) Factor de división del reloj en 32 →
ADPS2=1, ADPS1=0, ADPS0=1 - 4.6) Se puede asumir que todos los registros comienzan en 0.
- 4.1) ADC habilitado →
- 5) Almacenar un número del 0 al 6 en la variable
canal. - 6) Almacenar un número cualquiera en la variable
nivel. - 7) Configurar el canal del ADC según la variable
canal(no directamente el número) y lanzar la conversión. - 8) Bucle infinito con
NOP. - 9) Programar la interrupción del ADC:
- 9.1) Leer solo la parte alta del resultado (
ADCH). - 9.2) Almacenar el dato en
resultado. - 9.3) Comparar
resultadoconnivel. Siresultado > nivel→ activar PB5. Si no → apagarlo. - 9.4) Relanzar la conversión del ADC.
- 9.1) Leer solo la parte alta del resultado (
; MEMORIA DE DATOS
.DSEG
.ORG 0x0100 ; Inicio de la SRAM
nivel: .BYTE 1
canal: .BYTE 1
resultado: .BYTE 1
; MEMORIA DE PROGRAMA
.CSEG
; ── TABLA DE INTERRUPCIONES ──
.ORG 0x0000
rjmp reset ; Salto al inicio real del programa
.ORG 0x002A ; Dirección ADC en la tabla (tabla 32.4)
rjmp ADC_INT ; Salto a la rutina de interrupción ADC
; RESET — CONFIGURACIÓN INICIAL
reset:
; ── Configurar Stack Pointer ──
ldi R16, HIGH(RAMEND)
out SPH, R16
ldi R16, LOW(RAMEND)
out SPL, R16
; ── Configurar PB5 como salida (tabla 14.B) ──
sbi DDRB, 5
; ── Configurar ADMUX ──
; REFS=00 (referencia externa), ADLAR=1 (resultado a la izquierda)
ldi R16, (1<<ADLAR)
sts ADMUX, R16
; ── Configurar ADCSRA ──
; ADEN=1, ADIE=1, prescaler /32 (ADPS2=1, ADPS0=1)
ldi R16, (1<<ADEN)
ori R16, (1<<ADIE)
ori R16, (1<<ADPS2)
ori R16, (1<<ADPS0)
sts ADCSRA, R16
; ── Guardar canal en variable canal (condición 5) ──
ldi R16, 3 ; Canal 3 (valor entre 0 y 6)
sts canal, R16
; ── Guardar nivel en variable nivel (condición 6) ──
ldi R16, 40 ; Valor de referencia para comparar
sts nivel, R16
; ── Configurar canal ADC según variable canal (condición 7) ──
lds R16, canal ; Leer variable canal
lds R17, ADMUX ; Leer ADMUX actual
andi R17, 0b11110000 ; Limpiar bits MUX3:0
or R17, R16 ; Insertar el canal
sts ADMUX, R17 ; Guardar en ADMUX
; ── Lanzar primera conversión ──
lds R16, ADCSRA
ori R16, (1<<ADSC)
sts ADCSRA, R16
; ── Habilitar interrupciones globales ──
sei
; BUCLE PRINCIPAL (condición 8)
main:
nop
rjmp main
; INTERRUPCIÓN ADC (condición 9)
ADC_INT:
; ── Guardar registros en la Pila ──
push R16
in R16, SREG
push R16
push R17
; ── 9.1 + 9.2: Leer parte alta y guardar en resultado ──
lds R16, ADCH ; Leer solo parte alta (ajustado a izquierda)
sts resultado, R16 ; Guardar en variable resultado
; ── 9.3: Comparar resultado con nivel ──
lds R17, nivel
cp R16, R17 ; Comparar R16 (resultado) con R17 (nivel)
brlo apagar_led ; Si resultado < nivel → apagar
breq apagar_led ; Si resultado == nivel → apagar
; Resultado > nivel → encender PB5
sbi PORTB, 5
rjmp fin_adc
apagar_led:
cbi PORTB, 5
fin_adc:
; ── 9.4: Relanzar conversión ──
lds R16, ADCSRA
ori R16, (1<<ADSC)
sts ADCSRA, R16
; ── Recuperar registros de la Pila ──
pop R17
pop R16
out SREG, R16
pop R16
reti ; Volver al programa principal
Configuración de Timer1 y Control de Eventos
Enunciado del Ejercicio
- 1) Programa completo con interrupciones usando Timer1.
- 2) Crear una variable
contadorde 1 byte. - 3) Configurar PB5 como salida.
- 4) Configurar Timer1 en modo CTC.
- 5) Cargar
OCR1Apara generar una interrupción periódica. - 6) Activar la interrupción de comparación A del Timer1.
- 7) El programa principal debe ser un bucle infinito con
NOP. - 8) En cada interrupción incrementar
contador. Si contador llega a 5 → encender PB5 y reiniciar contador. Si no llega → apagar PB5.
.DSEG
.ORG 0x0100
contador: .BYTE 1 ; Variable contador de 1 byte
.CSEG
.ORG 0x0000
rjmp reset ; Salto al inicio real del programa
reti ; Vector 1 - no usado
.ORG 0x001A ; Dirección OC1A en tabla interrupciones (pág 3)
rjmp TIMER1_COMPA_int ; Salto a la rutina de interrupción Timer1
reti ; Resto de vectores no usados
reset:
ldi R16, HIGH(RAMEND) ; Cargar parte alta del fin de la SRAM
out SPH, R16 ; Configurar parte alta del Stack Pointer
ldi R16, LOW(RAMEND) ; Cargar parte baja del fin de la SRAM
out SPL, R16 ; Configurar parte baja del Stack Pointer
sbi DDRB, 5 ; Configurar PB5 como salida (condición 3)
ldi R16, 0 ; Poner contador a 0 valor conocido
sts contador, R16 ; Guardar 0 en la variable contador
ldi R16, 0 ; WGM13=0 y WGM12=0 los pone a 0
sts TCCR1A, R16 ; Guardar en TCCR1A (pág 10)
ldi R16, (1<<WGM12) ; Modo CTC → WGM12=1 (pág 9)
ori R16, (1<<CS12) ; Prescaler /256
ori R16, (1<<CS10) ; ATmega 16MHz con /256 → 62500 cuentas/sg
sts TCCR1B, R16 ; Guardar configuración en TCCR1B (pág 11)
ldi R16, HIGH(15624) ; Cargar parte alta del valor de comparación
sts OCR1AH, R16 ; Guardar parte alta en OCR1AH
ldi R16, LOW(15624) ; Cargar parte baja del valor de comparación
sts OCR1AL, R16 ; Guardar parte baja en OCR1AL
ldi R16, (1<<OCIE1A) ; Habilitar interrupción comparación A Timer1
sts TIMSK1, R16 ; Guardar en registro TIMSK1 (pág 11)
sei ; Habilitar interrupciones globales bit I=1
main:
nop ; Operación vacía
rjmp main ; Repetir bucle infinito
TIMER1_COMPA_int:
push R16 ; Guardar R16 en la Pila
in R16, SREG ; Leer registro de estado
push R16 ; Guardar SREG en la Pila
lds R16, contador ; Leer variable contador
inc R16 ; Incrementar contador en 1
cpi R16, 5 ; Comparar contador con 5
brlo no_llega ; Si contador < 5 → saltar a no_llega
; Contador llegó a 5
sbi PORTB, 5 ; Encender PB5
ldi R16, 0 ; Reiniciar contador a 0
sts contador, R16 ; Guardar nuevo valor del contador
rjmp fin_timer ; Saltar al final de la interrupción
no_llega:
cbi PORTB, 5 ; Apagar PB5
sts contador, R16 ; Guardar nuevo valor del contador
fin_timer:
pop R16 ; Recuperar SREG de la Pila
out SREG, R16 ; Restaurar registro de estado
pop R16 ; Recuperar R16 de la Pila
reti ; Volver al programa principal y habilitar bit I
Interrupción Externa INT0
Ejercicio Interrupción Externa INT0: 1) Programa completo con interrupciones externas. 2) Crear una variable contador de 1 byte. 3) Configurar PB5 como salida. 4) Configurar INT0 para dispararse por flanco de subida. 5) Habilitar INT0 y las interrupciones globales. 6) El programa principal debe ser un bucle infinito con NOP. 7) En cada interrupción externa incrementar contador. Si contador >= 3 → encender PB5. Si contador < 3 → apagar PB5.
.DSEG
.ORG 0x0100
contador: .BYTE 1 ; Variable contador de 1 byte
.CSEG
.ORG 0x0000
rjmp reset ; Salto al inicio real del programa
.ORG 0x0002 ; Dirección INT0 en tabla de interrupciones
rjmp INT0_int ; Salto a la rutina de interrupción INT0
reset:
ldi R16, HIGH(RAMEND) ; Cargar parte alta del fin de la SRAM
out SPH, R16 ; Configurar parte alta del Stack Pointer
ldi R16, LOW(RAMEND) ; Cargar parte baja del fin de la SRAM
out SPL, R16 ; Configurar parte baja del Stack Pointer
sbi DDRB, 5 ; Configurar PB5 como salida (condición 3)
ldi R16, 0 ; Poner contador a 0 valor conocido
sts contador, R16 ; Guardar 0 en la variable contador
ldi R16, (1<<ISC01) ; Flanco de subida ISC01=1 (pág 14)
ori R16, (1<<ISC00) ; Flanco de subida ISC00=1 (pág 14)
sts EICRA, R16 ; Guardar configuración INT0 en EICRA
ldi R16, (1<<INT0) ; Habilitar INT0
out EIMSK, R16 ; Guardar en registro EIMSK (pág 15)
sei ; Habilitar interrupciones globales bit I=1
main:
nop ; Operación vacía
rjmp main ; Repetir bucle infinito
INT0_int:
push R16 ; Guardar R16 en la Pila
in R16, SREG ; Leer registro de estado
push R16 ; Guardar SREG en la Pila
lds R16, contador ; Leer variable contador
inc R16 ; Incrementar contador en 1
cpi R16, 3 ; Comparar contador con 3
brlo apagar_led ; Si contador < 3 → saltar a apagar_led
encender_led:
sbi PORTB, 5 ; Encender PB5
rjmp fin_int0 ; Saltar al final de la interrupción
apagar_led:
cbi PORTB, 5 ; Apagar PB5
fin_int0:
sts contador, R16 ; Guardar nuevo valor del contador
pop R16 ; Recuperar SREG de la Pila
out SREG, R16 ; Restaurar registro de estado
pop R16 ; Recuperar R16 de la Pila
reti ; Volver al programa principal y habilitar bit I
Operaciones Aritméticas en Ensamblador
Enunciado de Operaciones
- 1) Cargar A=37 en R16 y B=7 en R17.
- 2) A+B → resultado en R18.
- 3) A-B → resultado en R19.
- 4) A·B → resultado en R1:R0 usando
MUL. - 5) Copiar resultado A·B desde R1:R0 hacia R21:R20 usando
MOVW. - 6) A·3 → cargar 3 en R22 y usar
MUL. - 7) A/2 usando
ASR→ guardar resultado en R15. - 8) Dejar programa en bucle infinito.
.CSEG
.ORG 0x0000
rjmp comenzar
comenzar:
; 1. Cargar valores A y B
ldi R16, 37 ; A = 37 en R16
ldi R17, 7 ; B = 7 en R17
; 2. A+B, resultado en R18
mov R18, R16 ; Copiar A (R16) en R18
add R18, R17 ; R18 = 37 + 7 = 44
; 3. A-B, resultado en R19
mov R19, R16 ; Copiar A (R16) en R19
sub R19, R17 ; R19 = 37 - 7 = 30
; 4. A·B, resultado en R1:R0 usando MUL
mul R16, R17 ; R1:R0 = 37 × 7 = 259
; Siempre guarda en R1:R0
; 5. Copiar A·B desde R1:R0 hacia R21:R20 usando MOVW
movw R20, R0 ; R20 = R0 (parte baja)
; R21 = R1 (parte alta)
; 6. A·3, cargar 3 en R22 y usar MUL
ldi R22, 3 ; Cargar el número 3 en R22
; Usar registro que no hayamos usado
mul R16, R22 ; R1:R0 = 37 × 3 = 111
; 7. A/2 usando ASR, guardar resultado en R15
mov R15, R16 ; Copiar A (R16) en R15
asr R15 ; Desplazamiento aritmético a la derecha
; Equivale a dividir entre 2
fin:
rjmp fin ; Bucle infinito
Nota Importante sobre Registros y Bits
- 8 bits = 1 byte = 1 registro (R16)
- 16 bits = 2 bytes = 2 registros (R17:R16) → se suma parte baja y parte alta.
- 32 bits = 4 bytes = 4 registros → se hacen en varios pasos.
Cada bit va desde el MSB (bit más significativo ⊕) hasta el LSB (bit menos significativo ⊙). Las operaciones de 16 o 32 bits deben hacerse en pasos: primero parte baja, luego parte alta.
Funciones Digitales en C++
// ════════════════════════════════════════
// digitalRead_PB5
// Lee PB5 y devuelve 1 si está alto o 0 si está bajo
// ════════════════════════════════════════
int digitalRead_PB5() {
if (PINB & (1 << 5)) { // Comprobar si el bit 5 de PINB está a 1
return 1; // Pin está alto → devolver 1
} else {
return 0; // Pin está bajo → devolver 0
}
}
// ════════════════════════════════════════
// digitalWrite_PB5
// Recibe valor → si es 0 apaga PB5, si no lo enciende
// ════════════════════════════════════════
void digitalWrite_PB5(int valor) {
if (valor == 0) {
PORTB &= ~(1 << 5); // Poner PB5 a LOW (apagar)
} else {
PORTB |= (1 << 5); // Poner PB5 a HIGH (encender)
}
}