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, canal y resultado.
  • 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.
  • 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 resultado con nivel. Si resultado > nivel → activar PB5. Si no → apagarlo.
    • 9.4) Relanzar la conversión del ADC.
; 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 contador de 1 byte.
  • 3) Configurar PB5 como salida.
  • 4) Configurar Timer1 en modo CTC.
  • 5) Cargar OCR1A para 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 &odot;). 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)
    }
}

Entradas relacionadas: