Энкодеры — это устройства, которые превращают вращение в последовательность импульсов. Обычно мы опрашиваем их в 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;
}
}