Часть 18: Обработка данных с энкодеров на ассемблере

Энкодеры — это устройства, которые превращают вращение в последовательность импульсов. Обычно мы опрашиваем их в loop(), но это медленно.

Хак: Используем Pin Change Interrupts для детекции изменения состояния на порту. Это позволяет обрабатывать до 4 энкодеров одновременно без потери импульсов.

Ассемблерный код для обработки энкодера:

#define __SFR_OFFSET 0x00
#include <avr/io.h>

.global encoder_init
.global encoder_read

; Переменные в SRAM
enc_value: .word 0
last_state: .byte 1

encoder_init:
    ; Настраиваем порт C (A0-A5) как входы
    LDI R16, 0b00000000
    OUT DDRC, R16
    
    ; Включаем pull-up резисторы на A0 и A1
    LDI R16, (1 << PC0) | (1 << PC1)
    OUT PORTC, R16
    
    ; Настраиваем Pin Change Interrupts на порту C
    LDI R16, (1 << PCIE1)   ; PCINT1 (порт C)
    STS PCICR, R16
    
    LDI R16, (1 << PCINT0) | (1 << PCINT1)  ; PCINT0 = A0, PCINT1 = A1
    STS PCMSK1, R16
    
    RET

; Обработчик прерывания PCINT1_vect
.global __vector_PCINT1
__vector_PCINT1:
    PUSH R16                 ; Сохраняем регистры
    PUSH R17
    PUSH R18
    
    IN R16, PINC             ; Читаем состояние порта C
    
    ; Проверяем изменения на пинах A0 и A1
    LDS R17, last_state
    EOR R17, R16
    ANDI R17, (1 << PC0) | (1 << PC1)  ; Маска для A0 и A1
    BREQ exit                ; Если не изменились — выход
    
    ; Кодируем состояние энкодера
    LDS R18, last_state
    LDI R19, 0
    
    ; Читаем биты A0 и A1
    SBIS PINB, 0             ; Если A0 HIGH, пропускаем
    RJMP a0_low
    OR R19, 0b00000001
a0_low:
    SBIS PINB, 1
    RJMP a1_low
    OR R19, 0b00000010
a1_low:
    
    ; Сохраняем новое состояние
    STS last_state, R16
    
    ; Кодируем изменение
    LSL R18
    OR R18, R19
    
    CPI R18, 0b00001001      ; Вращение по часовой
    BREQ inc_enc
    CPI R18, 0b00000110      ; Вращение против часовой
    BREQ dec_enc
    RJMP exit

inc_enc:
    LDS R16, enc_value
    ADI R16, 1
    STS enc_value, R16
    RJMP exit

dec_enc:
    LDS R16, enc_value
    SBI R16, 1
    STS enc_value, R16

exit:
    POP R18
    POP R17
    POP R16
    RETI

encoder_read:
    LDS R16, enc_value
    RET

Использование в C++:

extern "C" {
    void encoder_init();
    uint16_t encoder_read();
}

void setup() {
    Serial.begin(115200);
    encoder_init();
    sei();  // Разрешаем прерывания
}

void loop() {
    static uint16_t oldVal = 0;
    uint16_t val = encoder_read();
    if (val != oldVal) {
        Serial.println(val);
        oldVal = val;
    }
}

Вам также может понравиться

About the Author: ардуинчиков

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *